Thread: How to map machine instctions in memory and execute them? (like a loader does)

  1. #1
    Registered User
    Join Date
    Oct 2021
    Posts
    138

    How to map machine instctions in memory and execute them? (like a loader does)

    I tried to find anything that will show code but I wasn't able to find anything expect for an answer on stackoverflow. I would find a lot of theory but no practical code that works. What I want to do is allocate memory (with execution mapping), add the machine instructions and then allocate another memory block for the data and finally, execute the block of memory that contains the code. So something like what the OS loader does when reading an executable. I have come with the following code:

    Code:
    #include "stdio.h"
    #include "string.h"
    #include "stdlib.h"
    #include "sys/mman.h"
    
    #define null (void*)0
    
    int main() {
      char* data = mmap(null, 15, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
      memset(data, 0x0, 15); // Default value
    
      *data = 'H';
      data[1] = 'e';
      data[2] = 'l';
      data[3] = 'l';
      data[4] = 'o';
      data[5] = ' ';
    
      data[6] = 'w';
      data[7] = 'o';
      data[8] = 'r';
      data[9] = 'l';
      data[10] = 'd';
      data[11] = '!';
    
      void* code = mmap(null, 500, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0);
      char* code_byte = (char*)code;
      long* code_long = (long*)code;
      memset(code, 0xc3, 500); // Default value
    
      /* Call the "write" and "exit" system calls*/
      // mov rax, 0x04
      *code_byte = 0x48;
      code_byte[1] = 0xC7;
      code_byte[2] = 0xC0;
      code_byte[3] = 0x04;
      code_byte[4] = 0x00;
      code_byte[5] = 0x00;
      code_byte[6] = 0x00;
    
      // mov rbx, 0x01
      code_byte[7]  = 0x48;
      code_byte[8]  = 0xC7;
      code_byte[9]  = 0xC3;
      code_byte[10] = 0x01;
      code_byte[11] = 0x00;
      code_byte[12] = 0x00;
      code_byte[13] = 0x00;
    
      // mov rdx, 12 (hardcoded)
      code_byte[14] = 0x48;
      code_byte[15] = 0xC7;
      code_byte[16] = 0xC2;
      code_byte[17] = 12;
      code_byte[18] = 0x00;
      code_byte[19] = 0x00;
      code_byte[20] = 0x00;
    
      // mov rdx, 
      code_byte[21] = 0x48;
      code_byte[22] = 0xC7;
      code_byte[23] = 0xC1;
      code_long[24] = (long)data;
      code_byte[32] = 0x00;
    
      // int 0x80 (which calls the "write" system call on linux)
      code_byte[33] = 0xcd;
      code_byte[34] = 0x80;
    
      /* Execute the code */
      ((void(*)(void))code)();
    }
    When I compile and execute the program, nothing happens! I'm 100% sure that the instructions work as I have tested them with another example that creates an ELF executable file and it was able to execute correctly. So unless I copy-pasted them wrong, the instructions are not the problem. The only thing that may be wrong is when I'm getting the location of the "data" "segment". In my eyes, this uses 8 bytes for the memory address (I'm in a 64bit machine) and it takes the memory address the "data" variable holds so I would expect it to work....

    Any ideas?

  2. #2
    Registered User
    Join Date
    Dec 2017
    Posts
    1,628
    I'm not sure what you expect this to do, but it certainly won't load 8 consecutive bytes starting at code_long+24:
    Code:
      code_long[24] = (long)data;
    Try something like this (untested) :
    Code:
    #include "stdio.h"
    #include "string.h"
    #include "stdlib.h"
    #include "sys/mman.h"
     
    #define null (void*)0
     
    int main() {
      char* data = mmap(null, 15, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
      memset(data, 0x0, 15); // Default value
     
      memcpy(data, "Hello world!", 12);
     
      void* code = mmap(null, 500, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0);
      char* code_byte = (char*)code;
      long* code_long = (long*)code;
     
      memset(code, 0xc3, 500); // Default value
     
      /* Call the "write" and "exit" system calls*/
     
      // mov rax, 0x04
      memcpy(code_byte, "\x48\xC7\xC0\x04\x00\x00\x00", 7);
     
      // mov rbx, 0x01
      memcpy(code_byte+7, "\x48\xC7\xC3\x01\x00\x00\x00", 7);
     
      // mov rdx, 12 (hardcoded)
      memcpy(code_byte+14, "\x48\xC7\xC2\x0C\x00\x00\x00", 7);
     
      // mov rdx, 
      memcpy(code_byte+21, "\x48\xC7\xC1", 3);
      memcpy(code+24, &data, 8);
      code_byte[32] = 0;
     
      // int 0x80 (which calls the "write" system call on linux)
      code_byte[33] = 0xcd;
      code_byte[34] = 0x80;
     
      /* Execute the code */
      ((void(*)(void))code)();
     
      return 0;
    }
    A little inaccuracy saves tons of explanation. - H.H. Munro

  3. #3
    Registered User
    Join Date
    Oct 2021
    Posts
    138
    Quote Originally Posted by john.c View Post
    I'm not sure what you expect this to do, but it certainly won't load 8 consecutive bytes starting at code_long+24:
    Code:
      code_long[24] = (long)data;
    Try something like this (untested) :
    Code:
    #include "stdio.h"
    #include "string.h"
    #include "stdlib.h"
    #include "sys/mman.h"
     
    #define null (void*)0
     
    int main() {
      char* data = mmap(null, 15, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
      memset(data, 0x0, 15); // Default value
     
      memcpy(data, "Hello world!", 12);
     
      void* code = mmap(null, 500, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0);
      char* code_byte = (char*)code;
      long* code_long = (long*)code;
     
      memset(code, 0xc3, 500); // Default value
     
      /* Call the "write" and "exit" system calls*/
     
      // mov rax, 0x04
      memcpy(code_byte, "\x48\xC7\xC0\x04\x00\x00\x00", 7);
     
      // mov rbx, 0x01
      memcpy(code_byte+7, "\x48\xC7\xC3\x01\x00\x00\x00", 7);
     
      // mov rdx, 12 (hardcoded)
      memcpy(code_byte+14, "\x48\xC7\xC2\x0C\x00\x00\x00", 7);
     
      // mov rdx, 
      memcpy(code_byte+21, "\x48\xC7\xC1", 3);
      memcpy(code+24, &data, 8);
      code_byte[32] = 0;
     
      // int 0x80 (which calls the "write" system call on linux)
      code_byte[33] = 0xcd;
      code_byte[34] = 0x80;
     
      /* Execute the code */
      ((void(*)(void))code)();
     
      return 0;
    }
    Thanks for trying to help but it still doesn't work. I posted this in another forum (technically not for C so I don't duplicate) and I was also informed from the W^X[1] policy that most operating systems have so I removed the "PROT_WRITE" flag from the "code" segment and it still doesn't work. I have some other sources, I'll see if I can find anything.


    [1] W^X - Wikipedia

  4. #4
    Registered User
    Join Date
    Dec 2017
    Posts
    1,628
    Thanks for trying to help but it still doesn't work.
    That doesn't surprise me.
    However, the error I pointed out still needed to be fixed.
    A little inaccuracy saves tons of explanation. - H.H. Munro

  5. #5
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,659
    > ((void(*)(void))code)();
    Put a breakpoint here, then use the debugger to disassemble code to see if it's what you expected.

    Then maybe even single step it.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  6. #6
    Registered User
    Join Date
    Oct 2021
    Posts
    138
    Quote Originally Posted by john.c View Post
    That doesn't surprise me.
    However, the error I pointed out still needed to be fixed.
    Yeah, thank you!

  7. #7
    Registered User
    Join Date
    Oct 2021
    Posts
    138
    Quote Originally Posted by Salem View Post
    > ((void(*)(void))code)();
    Put a breakpoint here, then use the debugger to disassemble code to see if it's what you expected.

    Then maybe even single step it.
    Thank you! I thought about using a debbuger too but I thought that It will not be useful as my program is getting a segmentation error
    and there is no way to find how this is happening because it is thrown from the OS probably (tho forgive me if that's not true, I'm just guessing).
    I used it and indeed, it wasn't useful. When the program crashes, I'm getting a windows dialog saying:
    Program received signal SIGSEGV
    Also, I tried only using doing the "exit" system call and it still doesn't work so something goes wrong with the memory and the execution and
    I cannot figure out what....

  8. #8
    Registered User
    Join Date
    Dec 2017
    Posts
    1,628
    I assume you mean you got a seggy when you tried to single step the code after the breakpoint you created.
    Did you disassemble the code you created in memory? Did it look correct?
    Also, in single stepping the code past your breakpoint, you need to single step the individual machine language instructions, not the C program lines!

    This doesn't segfault (or give an illegal instruction or anything).
    Code:
    #include "stdio.h"
    #include "string.h"
    #include "stdlib.h"
    #include "sys/mman.h"
     
    #define null (void*)0
     
    int main() {
      void* code = mmap(null, 500, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0);
      char* code_byte = (char*)code;
     
      // mov rax, 0x01  (code for sys_exit)
      memcpy(code_byte, "\x48\xC7\xC0\x01\x00\x00\x00", 7);
     
      // int 0x80  (execute system call)
      memcpy(code_byte+7, "\xCD\x80", 2);
     
      // Execute the code
      ((void(*)(void))code)();
     
      return 0;
    }
    Disassembling the generated code looks correct.
    On your original code, it shows a bad instruction after the one that loads the address.

    Here's a new version of your program that might be easier to work with.
    To show the disassembly of the code in memory:
    gcc program.c -g
    gdb a.out
    l (to list the program, keep hitting return until you see the line that runs the code)
    b 58 (assuming it is line 58)
    r (to run up to the breakpoint)
    x/i code (shows first disassembled line; hit enter for the next, etc.)
    Code:
    #include "stdio.h"
    #include "string.h"
    #include "stdlib.h"
    #include "sys/mman.h"
     
    #define null (void*)0
     
    void putbytes(void *code, int *pos, const void *bytes, int len) {
      memcpy((char*)code + *pos, (const char*)bytes, len);
      *pos += len;
    }
     
    int main() {
      char* data = mmap(null, 15, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
      memset(data, 0x0, 15); // Default value
      memcpy(data, "Hello world!", 12);
     
      void* code = mmap(null, 500, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0);
     
      memset(code, 0xc3, 500); // Default value
     
      int pos = 0;
     
      // Call the "write" and "exit" system calls
     
      // mov rax, 0x04   (sys_write)
      putbytes(code, &pos, "\x48\xC7\xC0\x04\x00\x00\x00", 7);
     
      // mov rbx, 0x01   (stdout)
      putbytes(code, &pos, "\x48\xC7\xC3\x01\x00\x00\x00", 7);
     
      // mov rdx, 12     (string length)
      putbytes(code, &pos, "\x48\xC7\xC2\x0C\x00\x00\x00", 7);
     
      // mov rcx, data   (string address)
      putbytes(code, &pos, "\x48\xC7\xC1", 3);
      putbytes(code, &pos, &data, 8);
      putbytes(code, &pos, "\x00", 1);
     
      // int 0x80
      putbytes(code, &pos, "\xCD\x80", 2);
     
      // mov rax, 0x01
      putbytes(code, &pos, "\x48\xC7\xC0\x01\x00\x00\x00", 7);
     
      // int 0x80
      putbytes(code, &pos, "\xCD\x80", 2);
     
      // Execute the code
      ((void(*)(void))code)();
     
      return 0;
    }
    Last edited by john.c; 06-06-2022 at 02:13 PM.
    A little inaccuracy saves tons of explanation. - H.H. Munro

  9. #9
    Registered User
    Join Date
    Dec 2017
    Posts
    1,628
    It looks like your machine code to load the string address is only for 4 bytes of the address, which leaves the next 4 bytes to be interpreted as an (illegal) instruction. You need to find the correct machine code to load an 8 byte address.

    Somewhat more simplified version of the program. Still not working since I do not know the machine instructions for loading an 8-byte address.
    Code:
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/mman.h> 
     
    #define null (void*)0
     
    void putbytes(char **code, const char *bytes) {
      unsigned byte;
      for (int i = 0, n; sscanf(bytes + i, "%x%n", &byte, &n) == 1; i += n)
          *(*code)++ = byte;
    }
     
    void putdata(char **code, const void *data, int len) {
        memcpy(*code, data, len);
        *code += len;
    }
     
    int main() {
      char *data = mmap(null, 15, PROT_READ|PROT_WRITE,
                        MAP_PRIVATE|MAP_ANON, -1, 0);
      memcpy(data, "Hello world!", 12);
     
      char *code = mmap(null, 500, PROT_READ|PROT_WRITE|PROT_EXEC,
                        MAP_PRIVATE|MAP_ANON, -1, 0);
     
      char *pos = code;
      
      // Call the "write" and "exit" system calls
     
      // mov rax, 0x04   (sys_write)
      putbytes(&pos, "48 C7 C0 04 00 00 00");
     
      // mov rbx, 0x01   (stdout)
      putbytes(&pos, "48 C7 C3 01 00 00 00");
     
      // mov rdx, 12     (string length)
      putbytes(&pos, "48 C7 C2 0C 00 00 00");
     
      // mov rcx, data   (string address)
      putbytes(&pos, "48 C7 C1");
      putdata(&pos, &data, sizeof data);
     
      // int 0x80
      putbytes(&pos, "CD 80");
     
      // mov rax, 0x01
      putbytes(&pos, "48 C7 C0 01 00 00 00");
     
      // int 0x80
      putbytes(&pos, "CD 80");
     
      // Execute the code
      ((void(*)(void))code)();
     
      return 0;
    }
    Last edited by john.c; 06-06-2022 at 03:04 PM.
    A little inaccuracy saves tons of explanation. - H.H. Munro

  10. #10
    Registered User
    Join Date
    Dec 2017
    Posts
    1,628
    This works. Note that I had to change to the modern syscall interface (instead of the old 32-bit int 0x80).
    Code:
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/mman.h> 
     
    #define null (void*)0
     
    void putbytes(char **code, const char *bytes) {
      unsigned byte;
      for (int i = 0, n; sscanf(bytes + i, "%x%n", &byte, &n) == 1; i += n)
          *(*code)++ = byte;
    }
     
    #define putdata(pos, data) putdata2(pos, &data, sizeof data);
     
    void putdata2(char **code, const void *data, int len) {
        memcpy(*code, data, len);
        *code += len;
    }
     
    int main() {
      char *data = mmap(null, 15, PROT_READ|PROT_WRITE,
                        MAP_PRIVATE|MAP_ANON, -1, 0);
      strcpy(data, "Hello world!\n");
      char *code = mmap(null, 500, PROT_READ|PROT_WRITE|PROT_EXEC,
                        MAP_PRIVATE|MAP_ANON, -1, 0);
      char *pos = code;
     
      // Call the "write" and "exit" system calls
      putbytes(&pos, "48 C7 C0 1 0 0 0");  // mov rax, 0x01     (write syscall)
      putbytes(&pos, "48 C7 C7 1 0 0 0");  // mov rbx, 0x01     (stdout)
      putbytes(&pos, "48 C7 C2 D 0 0 0");  // mov rdx, 13       (string length)
      putbytes(&pos, "48 BE");             // movabs rcx, data  (string address)
      putdata(&pos, data);
      putbytes(&pos, "0F 05");             // syscall
      putbytes(&pos, "48 C7 C0 3C 0 0 0"); // mov rax, 0x3C     (exit syscall)
      putbytes(&pos, "0F 05");             // syscall 
      
      // Execute the code
      ((void(*)(void))code)();
     
      return 0;
    }
    Last edited by john.c; 06-06-2022 at 04:49 PM.
    A little inaccuracy saves tons of explanation. - H.H. Munro

  11. #11
    Registered User
    Join Date
    Dec 2017
    Posts
    1,628
    The comments showing the assembly code above have some incorrect registers.
    For the syscall interface, rbx should be rdi and rcx should be rsi.
    The machine language is correct.
    A little inaccuracy saves tons of explanation. - H.H. Munro

  12. #12
    Registered User
    Join Date
    Dec 2017
    Posts
    1,628
    I'm 100% sure that the instructions work
    Apparently you were running either in 32-bit mode or in some kind of small memory model that was limited to 32-bit addresses.

    Your original code only stored one byte of the 8 bytes, so that didn't work.
    My correction stored 8 bytes but your opcode was only using 4 bytes of it so the next 4 bytes were interpreted as an instruction.
    Changing the opcode to use all 8 bytes and switching to the 64-bit syscall fixed it.
    A little inaccuracy saves tons of explanation. - H.H. Munro

  13. #13
    Registered User
    Join Date
    Oct 2021
    Posts
    138
    @john.c

    Thank you! Yes the final code works now! It seems that you were right and it didn't worked because it was code for 32bit machines. Tbh, it was also weird to me that
    the 32bit version would work in a 64bit machine but I didn't said anything because the code from the original source[1] worked in my machine without any problems.
    However, the original version creates and ELF executable and then your run it through the Linux loader (aka "./") and maybe, the loader does some magic to allow 32bit
    apps to run in a 64bit system (maybe translates the opcode and uses the equivalent 32bit ones?) but when we mapped the op codes in the memory ourselves and run it,
    it didn't worked...

    However, it seems weird to me that it works as from what I thought, modern OSes should not allow a memory region to be both writable and executable at the same time
    and I would expect that we must remove the "PROT_WRITE" flag from the "code" memory "segment". But it seems that either something weird happens or Linux does not
    follow that...

    Thank you because I wasn't able to find any sources only (at least not without dipping into books) about this and it seems that forums are the best source for any topic that
    is uncommon and very low level as I have been helped countless times (especially from this one).

    [1] GitHub - vishen/go-x64-executable: Generate ELF Linux 64-bit (x86-64) executable manually

  14. #14
    Registered User
    Join Date
    Feb 2019
    Posts
    1,078
    Quote Originally Posted by rempas View Post
    ...It seems that you were right and it didn't worked because it was code for 32bit machines. Tbh, it was also weird to me that
    the 32bit version would work in a 64bit machine...
    There are some differences between 32 bits code and 64 bits code... For example:
    Code:
    0x40           - inc eax     ; i386
    0xFF 0xC0      - inc eax     ; x86_64
    In x86-64 mode 0x4x is a REX prefix, so INC/DEC instructions are different.
    There are more differences...

    []s
    Fred

  15. #15
    Registered User
    Join Date
    Oct 2021
    Posts
    138
    Quote Originally Posted by flp1969 View Post
    There are some differences between 32 bits code and 64 bits code... For example:
    Code:
    0x40           - inc eax     ; i386
    0xFF 0xC0      - inc eax     ; x86_64
    In x86-64 mode 0x4x is a REX prefix, so INC/DEC instructions are different.
    There are more differences...

    []s
    Fred
    I don't know why my reply didn't uploaded...

    So, yeah I didn't knew about that! I want to make a compiler and my own file format
    so I have to learn machine instruction op codes and how they work. But still I do wonder
    why the 32bit instructions worked in my 64bit machine and the 64bit ELF file format (the EI_CLASS
    field set to 2 which means 64bit machine)....

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 3
    Last Post: 09-29-2021, 04:31 AM
  2. Replies: 4
    Last Post: 01-18-2008, 07:05 PM
  3. look at my messed up md3 loader
    By Shadow12345 in forum Game Programming
    Replies: 0
    Last Post: 12-10-2002, 07:10 PM
  4. multiple os loader
    By codefx in forum C Programming
    Replies: 1
    Last Post: 11-23-2002, 09:45 PM
  5. IDEA: A Slot Machine (aka a fruit machine)
    By ygfperson in forum Contests Board
    Replies: 0
    Last Post: 08-12-2002, 11:13 PM

Tags for this Thread