Search code examples
assemblyx86-64nasmshellcode

How can I reprogram my shellcode snippet to avoid null bytes?


I have programmed a piece of x64 linux assembly. All it does is just prints a line "Hello world", that's all. However what I want to do is copy the bytes from it's object file by objdump, so that I can make my own shellcode for my buffer overflow attacks.

The problem I a facing is that the shellcode contains lots of null bytes and that will terminate the execution of my shellcode.

root@kali:~/C scripts/shellcode/Assembly Based Shellcode# cat print.asm
section .text
 
global _start
 
_start:
 
    mov rax, 1
    mov rdi, 1
    mov rsi, message
    mov rdx, 12
    syscall
 
    mov rax, 60
    xor rdi, rdi
    syscall
 
message:
    db "Hello world", 10
root@kali:~/C scripts/shellcode/Assembly Based Shellcode# nasm -f elf64 print.asm && ld print.o -o print && ./print
Hello world
root@kali:~/C scripts/shellcode/Assembly Based Shellcode# objdump -D print.o
 
print.o:     file format elf64-x86-64
 
 
Disassembly of section .text:
 
0000000000000000 <_start>:
   0:   b8 01 00 00 00          mov    $0x1,%eax
   5:   bf 01 00 00 00          mov    $0x1,%edi
   a:   48 be 00 00 00 00 00    movabs $0x0,%rsi
  11:   00 00 00
  14:   ba 0c 00 00 00          mov    $0xc,%edx
  19:   0f 05                   syscall
  1b:   b8 3c 00 00 00          mov    $0x3c,%eax
  20:   48 31 ff                xor    %rdi,%rdi
  23:   0f 05                   syscall
 
0000000000000025 <message>:
  25:   48                      rex.W
  26:   65 6c                   gs insb (%dx),%es:(%rdi)
  28:   6c                      insb   (%dx),%es:(%rdi)
  29:   6f                      outsl  %ds:(%rsi),(%dx)
  2a:   20 77 6f                and    %dh,0x6f(%rdi)
  2d:   72 6c                   jb     9b <message+0x76>
  2f:   64                      fs
  30:   0a                      .byte 0xa
root@kali:~/C scripts/shellcode/Assembly Based Shellcode#

I hoped the shellcode would be free from null bytes. However it is not. Can someone help me and correct my code?


Solution

  • You seem to be confused in assembly and buffer overflows.

    I reprogrammed the assembly file like this :

    section .text
    
    GLOBAL _start
    
    _start:
    
        xor rax, rax                  ; Clear the RAX register
        push rax                      ; Push the NULL byte [ string terminator ]
        add al, 0x1                   ; RAX = 1, to put the system in sys_write mode
        mov rdi, rax                  ; RDI = 1, to setup the fist parameter for write ( file descriptor to write to ). The integral value for 'stdout' is 1.
        lea rsi, [rel msg+0x41414141] ; Move the relative RIP address of msg to RSI to prepare the string buffer for writing to the stdout. Also add a large 4-byte offset to evade NULL bytes.
        sub rsi, 0x41414141           ; Subtract that large offset to make the RSI point correctly.
        xor rdx, rdx                  ; Empty the 3rd argument for write
        mov dl, 0xc                   ; RDX = 12, 12 ==> string length of msg
        syscall                       ; system call
    
    msg db "Hello world", 0xa
    

    EDIT : As per the discussion in the comment section I have removed the terminating NULL byte

    section .text
    
    GLOBAL _start
    
    _start:
    
        push 0x1
        pop rax
        mov rdi, rax
        mov rbx, 'AAAAArld'
        shr rbx, 0x28
        push rbx
        mov rbx, 'Hello wo'
        push rbx
        mov rsi, rsp
        push 0xc
        pop rdx
        syscall
    

    and then I compiled the program like this :

    root@kali:~/Desktop/assembly# nasm -f elf64 main.asm; ld main.o -o main.elf; ./main.elf 
    Hello world
    Segmentation fault
    

    The seg fault really doesn't matter because you want to use this as shellcode for a buffer overflow attack so there is anyway a seg fault there.

    Now, extract the bytes from the object code :

    root@kali:~/Desktop/assembly# for i in $(objdump -D main.o | grep "^ " | cut -f2); do echo -n "\x$i"; done; echo
    \x48\x31\xc0\x50\x04\x01\x48\x89\xc7\x48\x8d\x35\x4f\x41\x41\x41\x48\x81\xee\x41\x41\x41\x41\x48\x31\xd2\xb2\x0c\x0f\x05\x48\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x0a
    

    [ OPTION ] : You can test your shellcode before executing it in a memory corruption exploit

    For that just copy the bytes from the above command and make a new C file [ it seems you are confused in the C script as well ]

    #include <stdio.h>
    
    int main(void) {
    
        char shellcode[] = "\x48\x31\xc0\x50\x04\x01\x48\x89\xc7\x48\x8d\x35\x4f\x41\x41\x41\x48\x81\xee\x41\x41\x41\x41\x48\x31\xd2\xb2\x0c\x0f\x05\x48\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x0a";
        int (*ret)() = (int (*)())shellcode;
        // The above line will create an integer pointer ret make it point to a function which doesn't require parameter [ indicated by the () ]. Then it will type casting to cast the shellcode to a function pointer of the same type.
        // So this will essentially cast your shellcode array address to a function pointer which you can later use to call it as a function and execute the code.
        ret(); // Execute the shellcode
    
    }
    
    

    Then compile the program and make sure to make the stack executable otherwise you will end up getting a seg fault here itself and the shellcode will not execute.

    root@kali:~/Desktop/assembly# gcc -z execstack test.c; ./a.out
    Hello world
    Segmentation fault
    

    From the above code it seems like the shellcode seems to work just fine!

    I have tried this on a basic application and it works so your problem should be solved.

    Another point to mention is that, only if your executable application [ the one you are going to be exploiting ] uses input methods which stop on a NULL bytes like strcpy(), only then you have to remove NULL bytes.

    If your executable uses input functions like gets() and fgets() then you don't need to worry about NULL bytes [ unless you are looking forward for format string vulnerability as well ] This came from the man page of fgets:

    fgets() reads in at most one less than size characters from stream and stores them into the buffer pointed to by s. Reading stops after an EOF or a newline. If a newline is read, it is stored into the buffer. A terminating null byte ('\0') is stored after the last character in the buffer.

    this clearly means that the NULL bytes shouldn't bother your exploit.

    I hope your doubts get cleared!