This shows you the differences between two versions of the page.
isc:labs:05 [2024/11/03 11:50] florin.stancu |
isc:labs:05 [2024/11/04 10:43] (current) florin.stancu |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== Lab 05 - Application Security ===== | + | ====== Lab 05 - Application Security ====== |
+ | |||
+ | ===== Objectives ===== | ||
+ | |||
+ | * Call conventions & stack structure | ||
+ | * Buffer overflow vulnerabilities | ||
+ | * Using pwndbg & pwntools to facilitate exploit development | ||
+ | |||
+ | ===== Resources ===== | ||
+ | |||
+ | * [[https://dhavalkapil.com/blogs/Buffer-Overflow-Exploit/|Buffer overflow explained]] | ||
+ | * [[https://dhavalkapil.com/blogs/Shellcode-Injection/|Shellcode explained]] | ||
+ | * [[https://chatgpt.com/share/67279837-b05c-800e-a60a-6629ef3dd7f7|ChatGPT's record for stack structure & buffer overflow]] //(same length, but why bother read the opinion of some anonymous industry expert when you got the popular AI kid parroting the same stuff, right?)// | ||
+ | |||
+ | ===== Setup ===== | ||
+ | |||
+ | * [[:isc:info:virtualmachine|Open a lab VM instance]] on [[https://cloud.grid.pub.ro|OpenStack]], use the **m1.medium** flavor for 1.5GB of RAM (required by ''pwndbg'' :(( ). | ||
+ | |||
+ | **If you're not using the OpenStack VM**: | ||
+ | |||
+ | * Install the 32-bit **libc** and **gcc-multilib** packages: <code> | ||
+ | sudo apt install libc6-dev-i386 gcc-multilib | ||
+ | </code> | ||
+ | * Install the PwnDbg plugin: <code> | ||
+ | git clone https://github.com/pwndbg/pwndbg | ||
+ | cd pwndbg | ||
+ | ./setup.sh | ||
+ | </code> | ||
+ | |||
+ | To check if everything is OK, run the command ''gdb'' with no arguments. The prompt should be similar to this: | ||
+ | |||
+ | <code> | ||
+ | ➜ gdb | ||
+ | GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1 | ||
+ | ... | ||
+ | pwndbg> | ||
+ | </code> | ||
+ | |||
+ | Enter ''q'' to exit GDB. We are using [[https://github.com/pwndbg/pwndbg|PwnDbg]] instead of the classic GDB because it is much more user friendly. Hope you'll like it ;) | ||
+ | |||
+ | ===== Overview ===== | ||
+ | |||
+ | A buffer overflow occurs when data written to a buffer overruns its boundary and overwrites adjacent memory locations, due to insufficient bounds checking. Thus, an exploiting this will be able to alter the execution flow of the program by overriding the previous instruction pointer register (prior to issuing the current function call) saved on stack and execute its own malicious code! | ||
+ | |||
+ | {{:isc:labs:stack_layout.png?700}} | ||
+ | |||
+ | Also check out one of the resources linked on top ^^ ! | ||
+ | |||
+ | <note tip> | ||
+ | This representation of the stack is valid for 32 bit programs. The calling convention is to save the parameters on the stack. | ||
+ | |||
+ | To find out what's different for a 64 bit program check this [[https://www.systutorials.com/x86-64-calling-convention-by-gcc/|website]]. | ||
+ | </note> | ||
+ | |||
+ | |||
+ | ===== GDB primer ===== | ||
+ | |||
+ | Please check out [[https://users.ece.utexas.edu/~adnan/gdb-refcard.pdf|a GDB cheatsheet]] for the most common operations. | ||
+ | |||
+ | You should see how to: | ||
+ | |||
+ | * run a binary inside GDB, pass arguments (and, for advanced cases: programmatic standard input -- which ''pwntools'' will later prove very useful for!); | ||
+ | * add ''break''points & execution control (''n'' / ''s'' / ''ni'' / ''si'' / ''c''); | ||
+ | * display variables and memory contents (''p'' / ''x'' + C expressions + various formats); | ||
+ | * backtrace / frame printing & navigation; | ||
+ | * use ''info'' to see breakpoints / symbols; | ||
+ | |||
+ | Please read [[:isc:labs:05:extra:gdb-tutorial]] if you're missing any of the above knowledge. | ||
+ | |||
+ | [[https://github.com/pwndbg/pwndbg|PwnDbg]] is a very nice GDB plugin (written in Python) which displays a plethora of information at each breakpoint and provides many useful commands (some which may prove to be useful when writing exploits!); here's an example output: | ||
+ | |||
+ | <code> | ||
+ | 22 if (argc == 1) { | ||
+ | LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA | ||
+ | ─────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────────────── | ||
+ | *EAX 0xffffd880 ◂— 0x2 | ||
+ | *EBX 0x56558fcc (_GLOBAL_OFFSET_TABLE_) ◂— 0x3ed4 | ||
+ | *ECX 0xffffd880 ◂— 0x2 | ||
+ | *EDX 0xffffd8a0 —▸ 0xf7fa9000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x229dac | ||
+ | *EDI 0xf7ffcb80 (_rtld_global_ro) ◂— 0x0 | ||
+ | *ESI 0xffffd934 —▸ 0xffffdabd ◂— '/home/student/appsec/buggy' | ||
+ | *EBP 0xffffd868 —▸ 0xf7ffd020 (_rtld_global) —▸ 0xf7ffda40 —▸ 0x56555000 ◂— 0x464c457f | ||
+ | *ESP 0xffffd850 —▸ 0xffffd890 —▸ 0xf7fa9000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x229dac | ||
+ | *EIP 0x56556281 (main+31) ◂— cmp dword ptr [eax], 1 | ||
+ | ───────────────────────────────────[ DISASM / i386 / set emulate on ]──────────────────────────────────── | ||
+ | ► 0x56556281 <main+31> cmp dword ptr [eax], 1 | ||
+ | 0x56556284 <main+34> jne main+61 <main+61> | ||
+ | ↓ | ||
+ | 0x5655629f <main+61> mov dword ptr [ebp - 0xc], 0x796568 | ||
+ | 0x565562a6 <main+68> mov eax, dword ptr [eax + 4] | ||
+ | 0x565562a9 <main+71> add eax, 4 | ||
+ | 0x565562ac <main+74> mov eax, dword ptr [eax] | ||
+ | 0x565562ae <main+76> sub esp, 4 | ||
+ | 0x565562b1 <main+79> push eax | ||
+ | 0x565562b2 <main+80> lea eax, [ebp - 0xc] | ||
+ | 0x565562b5 <main+83> push eax | ||
+ | 0x565562b6 <main+84> lea eax, [ebx - 0x1f6e] | ||
+ | ────────────────────────────────────────────[ SOURCE (CODE) ]──────────────────────────────────────────── | ||
+ | In file: /home/student/appsec/buggy.c | ||
+ | 17 gets(name); | ||
+ | 18 printf("bye\n"); | ||
+ | 19 } | ||
+ | 20 | ||
+ | 21 int main(int argc, char **argv) { | ||
+ | ► 22 if (argc == 1) { | ||
+ | 23 puts("Usage: %s <name>\n"); | ||
+ | 24 return 1; | ||
+ | 25 } | ||
+ | 26 char buf[] = "hey"; | ||
+ | 27 | ||
+ | ────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────── | ||
+ | 00:0000│ esp 0xffffd850 —▸ 0xffffd890 —▸ 0xf7fa9000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x229dac | ||
+ | 01:0004│-014 0xffffd854 —▸ 0xf7fbe66c —▸ 0xf7ffdba0 —▸ 0xf7fbe780 —▸ 0xf7ffda40 ◂— ... | ||
+ | 02:0008│-010 0xffffd858 —▸ 0xf7fbeb20 —▸ 0xf7d99cc6 ◂— 'GLIBC_PRIVATE' | ||
+ | 03:000c│-00c 0xffffd85c ◂— 0x1 | ||
+ | 04:0010│-008 0xffffd860 —▸ 0xffffd880 ◂— 0x2 | ||
+ | 05:0014│-004 0xffffd864 —▸ 0xf7fa9000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x229dac | ||
+ | 06:0018│ ebp 0xffffd868 —▸ 0xf7ffd020 (_rtld_global) —▸ 0xf7ffda40 —▸ 0x56555000 ◂— 0x464c457f | ||
+ | 07:001c│+004 0xffffd86c —▸ 0xf7da0519 (__libc_start_call_main+121) ◂— add esp, 0x10 | ||
+ | ──────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────── | ||
+ | ► 0 0x56556281 main+31 | ||
+ | 1 0xf7da0519 __libc_start_call_main+121 | ||
+ | 2 0xf7da05f3 __libc_start_main+147 | ||
+ | 3 0x565560cb _start+43 | ||
+ | ───────────────────────────────────────────────────────────────────────────────────────────────────────── | ||
+ | </code> | ||
+ | |||
+ | Let's take a look at the previous output that PwnDbg prints. You can see it is seprated into 5 sections: REGISTERS, DISASM, SOURCE, STACK and TRACE. | ||
+ | With the original GDB you would have to manually print registers, disassemble code and inspect the stack. Thanks, God, for PwnDbg! | ||
+ | |||
+ | ==== Exercises ==== | ||
+ | |||
+ | === 00. Preparation === | ||
+ | |||
+ | Download the {{ :isc:labs:lab05-code.zip | lab archive }} and unpack it somewhere in your home. | ||
+ | |||
+ | Run ''make''. | ||
+ | |||
+ | === [20p] 01. Obfuscated Code === | ||
+ | |||
+ | * Run the ''obfusflag'' binary; you may study the code, but your task is clear: find out the obfuscated flag! | ||
+ | * Use ''gdb'', ofc! | ||
+ | * Trouble navigating through the runtime code? see the tutorials / cheatsheets above! | ||
+ | |||
+ | <note tip> | ||
+ | To change a variable without typing info built-in (not compiled using ''-g''), take the variable's address, cast to pointer of desired type then dereference:<code> | ||
+ | set variable *(int *)&myvar = value | ||
+ | </code> | ||
+ | </note> | ||
+ | |||
+ | * Hint: you're on 64-bit, check the links above for the calling convention... | ||
+ | * Hint 2: you also don't have debugging info compiled-in, so you must use disassembly to find the RBP offset of the ''buf'' variable; | ||
+ | <spoiler In case of emergency, expand> | ||
+ | If this seems too difficult or you wasted too much time, just add ''-g'' to the ''gcc'' rule inside the Makefile, recompile and try it this way :( | ||
+ | </spoiler> | ||
+ | |||
+ | <solution -hidden> | ||
+ | <code> | ||
+ | # gdb obfusflag | ||
+ | gdb> break check_fl0gz0rx | ||
+ | gdb> run 12345678901234567890 | ||
+ | gdb> set var *(int*)&deez = 1 | ||
+ | gdb> tbreak *check_fl0gz0rx + 159 # before strcmp | ||
+ | gdb> continue | ||
+ | gdb> x/10s $rbp - 0x30 # find out buf's RBP offset from disass | ||
+ | </code> | ||
+ | </solution> | ||
+ | |||
+ | === [50p] 02. Stack overflow (EZ) === | ||
+ | |||
+ | * Run & study the ''buffovf'' binary. There is a vulnerability in there, can you see it? | ||
+ | * Yep, you **must** use stack overflow to get this flag! | ||
+ | * First, try to crash the program. Use programmatically generated input (e.g., from Python3); | ||
+ | |||
+ | <note tip> | ||
+ | You can use ''%%run args < <(python3 -c 'import sys; sys.stdout.buffer.write(b"A" * N)')%%'' for stdin redirection directly within GDB! ;) | ||
+ | </note> | ||
+ | <note warning> | ||
+ | Do not use ''print'' in Python for this purpose as some installations (especially on Ubuntu) use a default UTF-8 encoding and auto-correct any unknown binary string to a valid sequence. This is why ''sys.stdout.buffer.write()'' was used in the snippet above, but beware: it requires a ''bytes'' object as argument! | ||
+ | You can test the binary output using **xxd**: ''python3 -c '... write here ...' | xxd -g 1'' | ||
+ | </note> | ||
+ | |||
+ | * Next, try to answer this question: how many bytes do I need to overflow until I get to the EIP saved by the ''call''er (also see the stack diagram above)? | ||
+ | * Use either ''objdump -S'' or ''pwndbg> disass <name>'' on the //vulnerable function// to figure out the offset of the buffer variable from the EBP register using the assembly code; | ||
+ | * In order to check if the answer is right, try to use an input of the following form: ''%%b"A" * N + b"\xEF\xCD\xAB\x98"%%''; this should make the program segfault with the end instruction pointer at ''0x98ABCDEF'' (readily visible in pwndbg's automatic registers printing); | ||
+ | |||
+ | * Things start to become easy; call the ''for_the_win'' function (simply replace the address above with the function's virtual address)! | ||
+ | * Do not forget: x86 uses little endian encoding for multi-byte integers! | ||
+ | * As bonus, can you further chain calls to make the exploited program gracefully exit? | ||
+ | |||
+ | <solution -hidden> | ||
+ | <code> | ||
+ | pwndbg> run "Salam" < <(python3 -c 'import sys; sys.stdout.buffer.write(b"A" * 0x19 +b"\xb6\x91\x04\x08" + b"\x12\x93\x04\x08" + b"\xbe\xba\xfe\xca")') | ||
+ | </code> | ||
+ | </solution> | ||
+ | |||
+ | === [30p] 03. Format string leak + pwntools === | ||
+ | |||
+ | * Run the following command to open a docker container: <code> | ||
+ | docker run -d -p 31337:1337 --rm --name leakme -it ghcr.io/cs-pub-ro/isc-lab-buff-leakme | ||
+ | </code> | ||
+ | * try if the server is up using the ''netcat'' utility: ''nc localhost 31337''; after the test succeeds, simply quit... read on! | ||
+ | |||
+ | * Open (locally, ignore the container server for now) the ''leakme'' code & binary. Try to overflow its input buffer! | ||
+ | * For this task, you must use [[https://docs.pwntools.com/en/stable/|pwntools]]. Check the included ''exploit_skel.py'' code for further instructions ;) | ||
+ | * Note: ASLR is running (and don't just disable it :P); you'll need to leak some ''.TEXT'' (code segment) addresses using a format string attack and figure out the final ''call_me_maybe()'' function's virtual address! | ||
+ | * For practice, you could also (trivially) implement the exploit for the previous task in ''pwntools''! | ||
+ | <note warning> | ||
+ | Beware of this behavior: if you overwrite just 1-2 extra bytes after the saved EIP, you will invalidate the ''get_user_info'' function's arguments, thus your program will crash earlier on one of those ''memcpy'' lines and won't get to return properly! Use the attached GDB and set breakpoints before these lines to debug (or just ''reverse-*'' your execution to discover the crash reason ;) ). | ||
+ | </note> | ||
+ | |||
+ | * After successfully exploiting it, note that there's no flag file available locally! But you should have the exploit ready, so simply set the ''REMOTE = True'' to get the real one! | ||
+ | |||
+ | <solution -hidden> | ||
+ | [[https://github.com/cs-pub-ro/ISC-labs/blob/master/lab05-appsec/resources/exploit_solved.py|View solution code (GitHub, requires login!)]] | ||
+ | </solution> | ||
+ | |||
+ | === Feedback === | ||
+ | |||
+ | Please take a minute to fill in the [[https://forms.gle/5Lu1mFa63zptk2ox9|feedback form]] for this lab. | ||
- | TODO |