Linux Shellcode “Hello, World!”

后端 未结 2 820
执笔经年
执笔经年 2020-11-30 22:41

I have the following working NASM code:

global _start

section .text

_start:
    mov eax, 0x4
    mov ebx, 0x1
    mov ecx, message
    mov edx, 0xF
    int         


        
相关标签:
2条回答
  • 2020-11-30 22:59

    As BSH mentioned, your shellcode does not contain the message bytes. Jumping to the MESSAGE label and calling the GOBACK routine just before defining the msg byte was a good move as the address of msg would be on the top of the stack as return address which could be popped to ecx, where the address of msg is stored.

    But both yours and BSH's code has a slight limitation. It contains NULL bytes ( \x00 ) which would be considered as end of string when dereferenced by the function pointer.

    There is a smart way around this. The values you store into eax, ebx and edx are small enough to be directly written into the lower nibbles of the respective registers in one go by accessing al, bl and dl respectively. The upper nibble may contain junk value so it can be xored.

    b8 04 00 00 00 ------ mov $0x4,%eax
    


    becomes

    b0 04          ------ mov $0x4,%al
    31 c0          ------ xor    %eax,%eax
    


    Unlike the prior instruction set, the new instruction set does not contain any NULL byte.

    So, the final program looks like this :

    global _start
    
    section .text
    
    _start:
    jmp message
    
    proc:
        xor eax, eax
        mov al, 0x04
        xor ebx, ebx
        mov bl, 0x01
        pop ecx
        xor edx, edx
        mov dl, 0x16
        int 0x80
    
        xor eax, eax
        mov al, 0x01
        xor ebx, ebx
        mov bl, 0x01   ; return 1
        int 0x80
    
    message:
        call proc
        msg db " y0u sp34k 1337 ? "
    
    section .data
    

    Assembling and linking :

    $ nasm -f elf hello.asm -o hello.o
    $ ld -s -m elf_i386 hello.o -o hello
    $ ./hello
     y0u sp34k 1337 ? $ 
    

    Now extract the shellcode from the hello binary :

    $ for i in `objdump -d hello | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\\x$i" ; done
    

    output:

    \xeb\x19\x31\xc0\xb0\x04\x31\xdb\xb3\x01\x59\x31\xd2\xb2\x12\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xb3\x01\xcd\x80\xe8\xe2\xff\xff\xff\x20\x79\x30\x75\x20\x73\x70\x33\x34\x6b\x20\x31\x33\x33\x37\x20\x3f\x20
    

    Now we can have our driver program to launch the shellcode.

    #include <stdio.h>
    
    char shellcode[] = "\xeb\x19\x31\xc0\xb0\x04\x31\xdb"
                       "\xb3\x01\x59\x31\xd2\xb2\x12\xcd"
                       "\x80\x31\xc0\xb0\x01\x31\xdb\xb3"
                       "\x01\xcd\x80\xe8\xe2\xff\xff\xff"
                       "\x20\x79\x30\x75\x20\x73\x70\x33"
                       "\x34\x6b\x20\x31\x33\x33\x37\x20"
                       "\x3f\x20";
    
    
    int main(int argc, char **argv) {
        (*(void(*)())shellcode)();
        return 0;
    }
    

    There are certain security features in modern compilers like NX protection which prevents execution of code in data segment or stack. So we should explicitly specify the compiler to disable these.

    $ gcc -g -Wall -fno-stack-protector -z execstack launcher.c -o launcher
    

    Now the launcher can be invoked to launch the shellcode.

    $ ./launcher
     y0u sp34k 1337 ? $ 
    

    For more complex shellcodes, there would be another hurdle. Modern Linux kernels have ASLR or Address Space Layout Randomization You may need to disable this before your inject the shellcode, especially when it is through buffer overflows.

    root@localhost:~# echo 0 > /proc/sys/kernel/randomize_va_space 
    
    0 讨论(0)
  • 2020-11-30 23:22

    When you inject this shellcode, you don't know what is at message:

    mov ecx, message
    

    in the injected process, it can be anything but it will not be "Hello world!\r\n" since it is in the data section while you are dumping only the text section. You can see that your shellcode doesn't have "Hello world!\r\n":

    "\xb8\x04\x00\x00\x00"
    "\xbb\x01\x00\x00\x00"
    "\xb9\x00\x00\x00\x00"
    "\xba\x0f\x00\x00\x00"
    "\xcd\x80\xb8\x01\x00"
    "\x00\x00\xbb\x00\x00"
    "\x00\x00\xcd\x80";
    

    This is common problem in shellcode development, the way to work around it is this way:

    global _start
    
    section .text
    
    _start:
        jmp MESSAGE      ; 1) lets jump to MESSAGE
    
    GOBACK:
        mov eax, 0x4
        mov ebx, 0x1
        pop ecx          ; 3) we are poping into `ecx`, now we have the
                         ; address of "Hello, World!\r\n" 
        mov edx, 0xF
        int 0x80
    
        mov eax, 0x1
        mov ebx, 0x0
        int 0x80
    
    MESSAGE:
        call GOBACK       ; 2) we are going back, since we used `call`, that means
                          ; the return address, which is in this case the address 
                          ; of "Hello, World!\r\n", is pushed into the stack.
        db "Hello, World!", 0dh, 0ah
    
    section .data
    

    Now dump the text section:

    $ nasm -f elf shellcode.asm
    $ ld shellcode.o -o shellcode
    $ ./shellcode 
    Hello, World!
    $ objdump -d shellcode
    
    shellcode:     file format elf32-i386
    
    
    Disassembly of section .text:
    
    08048060 <_start>:
     8048060:   e9 1e 00 00 00   jmp    8048083 <MESSAGE>
    
    08048065 <GOBACK>:
     8048065:   b8 04 00 00 00   mov    $0x4,%eax
     804806a:   bb 01 00 00 00   mov    $0x1,%ebx
     804806f:   59               pop    %ecx
     8048070:   ba 0f 00 00 00   mov    $0xf,%edx
     8048075:   cd 80            int    $0x80
     8048077:   b8 01 00 00 00   mov    $0x1,%eax
     804807c:   bb 00 00 00 00   mov    $0x0,%ebx
     8048081:   cd 80            int    $0x80
    
    08048083 <MESSAGE>:
     8048083:   e8 dd ff ff ff   call   8048065 <GOBACK>
     8048088:   48               dec    %eax                    <-+
     8048089:   65               gs                               |
     804808a:   6c               insb   (%dx),%es:(%edi)          |
     804808b:   6c               insb   (%dx),%es:(%edi)          |
     804808c:   6f               outsl  %ds:(%esi),(%dx)          |
     804808d:   2c 20            sub    $0x20,%al                 |
     804808f:   57               push   %edi                      |
     8048090:   6f               outsl  %ds:(%esi),(%dx)          |
     8048091:   72 6c            jb     80480ff <MESSAGE+0x7c>    |
     8048093:   64               fs                               |
     8048094:   21               .byte 0x21                       |
     8048095:   0d               .byte 0xd                        |
     8048096:   0a               .byte 0xa                      <-+
    
    $
    

    The lines I marked are our "Hello, World!\r\n" string:

    $ printf "\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21\x0d\x0a"
    Hello, World!
    
    $ 
    

    So our C wrapper will be:

    char code[] = 
    
        "\xe9\x1e\x00\x00\x00"  //          jmp    (relative) <MESSAGE>
        "\xb8\x04\x00\x00\x00"  //          mov    $0x4,%eax
        "\xbb\x01\x00\x00\x00"  //          mov    $0x1,%ebx
        "\x59"                  //          pop    %ecx
        "\xba\x0f\x00\x00\x00"  //          mov    $0xf,%edx
        "\xcd\x80"              //          int    $0x80
        "\xb8\x01\x00\x00\x00"  //          mov    $0x1,%eax
        "\xbb\x00\x00\x00\x00"  //          mov    $0x0,%ebx
        "\xcd\x80"              //          int    $0x80
        "\xe8\xdd\xff\xff\xff"  //          call   (relative) <GOBACK>
        "Hello wolrd!\r\n";     // OR       "\x48\x65\x6c\x6c\x6f\x2c\x20\x57"
                                //          "\x6f\x72\x6c\x64\x21\x0d\x0a"
    
    
    int main(int argc, char **argv)
    {
        (*(void(*)())code)();
    
        return 0;
    }
    

    Lets test it, using -z execstack to enable read-implies-exec (process-wide, despite "stack" in the name) so we can executed code in the .data or .rodata sections:

    $ gcc -m32 test.c -z execstack -o test
    $ ./test 
    Hello wolrd!
    

    It works. (-m32 is necessary, too, on 64-bit systems. The int $0x80 32-bit ABI doesn't work with 64-bit addresses like .rodata in a PIE executable. Also, the machine code was assembled for 32-bit. It happens that the same sequence of bytes would decode to equivalent instructions in 64-bit mode but that's not always the case.)

    Modern GNU ld puts .rodata in a separate segment from .text, so it can be non-executable. It used to be sufficient to use const char code[] to put executable code in a page of read-only data. At least for shellcode that doesn't want to modify itself.

    0 讨论(0)
提交回复
热议问题