How to write and execute PURE machine code manually without containers like EXE or ELF?

后端 未结 10 1669
天涯浪人
天涯浪人 2020-11-30 18:38

I just need a hello world demo to see how machine code actually works.

Though windows\' EXE and linux\' ELF is near machine code,bu

10条回答
  •  生来不讨喜
    2020-11-30 19:34

    Real Machine Code

    What you need to run the test: Linux x86 or x64 (in my case I am using Ubuntu x64)

    Let's Start

    This Assembly (x86) moves the value 666 into the eax register:

    movl $666, %eax
    ret
    

    Let's make the binary representation of it:

    Opcode movl (movl is a mov with operand size 32) in binary is = 1011

    Instruction width in binary is = 1

    Register eax in binary is = 000

    Number 666 in signed 32 bits binary is = 00000000 00000000 00000010 10011010

    666 converted to little endian is = 10011010 00000010 00000000 00000000

    Instruction ret (return) in binary is = 11000011

    So finally our pure binary instructions will look like this:

    1011(movl)1(width)000(eax)10011010000000100000000000000000(666) 11000011(ret)

    Putting it all together:

    1011100010011010000000100000000000000000
    11000011
    

    For executing it the binary code has to be placed in a memory page with execution privileges, we can do that using the following C code:

    #include 
    #include 
    #include 
    #include 
    
    /* Allocate size bytes of executable memory. */
    unsigned char *alloc_exec_mem(size_t size)
    {
        void *ptr;
    
        ptr = mmap(0, size, PROT_READ | PROT_WRITE | PROT_EXEC,
                   MAP_PRIVATE | MAP_ANON, -1, 0);
    
        if (ptr == MAP_FAILED) {
                perror("mmap");
                exit(1);
        }
    
        return ptr;
    }
    
    /* Read up to buffer_size bytes, encoded as 1's and 0's, into buffer. */
    void read_ones_and_zeros(unsigned char *buffer, size_t buffer_size)
    {
        unsigned char byte = 0;
        int bit_index = 0;
        int c;
    
        while ((c = getchar()) != EOF) {
                if (isspace(c)) {
                        continue;
                } else if (c != '0' && c != '1') {
                        fprintf(stderr, "error: expected 1 or 0!\n");
                        exit(1);
                }
    
                byte = (byte << 1) | (c == '1');
                bit_index++;
    
                if (bit_index == 8) {
                        if (buffer_size == 0) {
                                fprintf(stderr, "error: buffer full!\n");
                                exit(1);
                        }
                        *buffer++ = byte;
                        --buffer_size;
                        byte = 0;
                        bit_index = 0;
                }
        }
    
        if (bit_index != 0) {
                fprintf(stderr, "error: left-over bits!\n");
                exit(1);
        }
    }
    
    int main()
    {
        typedef int (*func_ptr_t)(void);
    
        func_ptr_t func;
        unsigned char *mem;
        int x;
    
        mem = alloc_exec_mem(1024);
        func = (func_ptr_t) mem;
    
        read_ones_and_zeros(mem, 1024);
    
        x = (*func)();
    
        printf("function returned %d\n", x);
    
        return 0;
    }
    

    Source: https://www.hanshq.net/files/ones-and-zeros_42.c

    We can compile it using:

    gcc source.c -o binaryexec

    To execute it:

    ./binaryexec

    Then we pass the first sets of instructions:

    1011100010011010000000100000000000000000

    press enter

    and pass the return instruction:

    11000011

    press enter

    finally ctrl+d to end the program and get the output:

    function returned 666

提交回复
热议问题