Lab 05 - Application Security

Objectives

  • Call conventions & stack structure
  • Buffer overflow vulnerabilities
  • Using pwndbg & pwntools to facilitate exploit development

Resources

Setup

If you're not using the OpenStack VM:

  • Install the 32-bit libc and gcc-multilib packages:
    sudo apt install libc6-dev-i386 gcc-multilib
  • Install the PwnDbg plugin:
    git clone https://github.com/pwndbg/pwndbg
    cd pwndbg
    ./setup.sh

To check if everything is OK, run the command gdb with no arguments. The prompt should be similar to this:

➜ gdb
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
...
pwndbg>

Enter q to exit GDB. We are using 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!

Also check out one of the resources linked on top ^^ !

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 website.

GDB primer

Please check out 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 breakpoints & 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 GDB tutorial if you're missing any of the above knowledge.

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:

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
─────────────────────────────────────────────────────────────────────────────────────────────────────────

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 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!

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:

set variable *(int *)&myvar = value

  • 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;

In case of emergency, expand

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 :(

[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);

You can use run args < <(python3 -c 'import sys; sys.stdout.buffer.write(b"A" * N)') for stdin redirection directly within GDB! ;)

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

  • Next, try to answer this question: how many bytes do I need to overflow until I get to the EIP saved by the caller (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?

[30p] 03. Format string leak + pwntools

  • Run the following command to open a docker container:
    docker run -d -p 31337:1337 --rm --name leakme -it ghcr.io/cs-pub-ro/isc-lab-buff-leakme
    • 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 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!

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 ;) ).

  • 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!

Feedback

Please take a minute to fill in the feedback form for this lab.

isc/labs/05.txt · Last modified: 2024/11/04 10:43 by florin.stancu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0