About The Project
Pwndbg and Pwntools are Python frameworks for automating different parts of exploit development. These frameworks are highly popular amongst CTF players as they simplify and accelerate the creation of Proof of Concept (PoC) scripts for memory corruption exploits. I’m not proficient in using pwntools, and pwndbg, but this marks the beginning of a series of blogs aimed at improving my skills with pwntools for memory corruption CTF challenges.
Simple Stack Smash - Virgina Tech Summit CTF 2023
Virginia Tech’s Summit CTF was this past April, and I joined promptly about an hour or two before it ended. Taking a look at the Reverse Engineering challenges, an introductory 32-bit ELF binary challenge called ‘simple-stack-smash’ was available and the perfect buffer-overflow (BoF) candidate to explore Pwntools.
1) Let’s leverage checksec to identify protections on the binary.
The image below shows there’s no stack canary, but there is a non-executable stack (NX). What does this mean?
- No Stack Canary: there’s no additional check on the stack for buffer overflows.
- Non-executable stack: We cannot execute code onto the stack (start thinking ROP)
Now lets explore the binary a little bit, by starting at
If you’re new to Reverse Engineering, and looking at how to identify
main, checkout this YouTube video made by Arch Cloud Labs a year ago on covering this very subject. The
main function within Ghidra shows taking user input via
fgets of up to
0x400 bytes and storing it in a buffer of only 16 bytes. Ultimately, if an end user enters more than 16 bytes, the fixed length-array will overflow.
This is a classic buffer overflow attack. Often, telltale signs in CTFs of these types of vulnerabilities are the use of
gets, which performs no bounds checking, or the use of an insecure copy command such as
memcpy. However, here we see an example of where
fgets, and by extension, other copy functions where the number of bytes copied is specified by the user, can be used incorrectly.
2) Identify the CTF Goal
Immediately, it’s visible to the competitor the buffer can be overwritten and crash the program.
However, crashes are not the desired end goal. Ultimately, the goal is to overwrite the EIP register to influence control flow of the binary.
But what does one do once control flow is gained since the NX bit prevents executing arbitrary code on the stack? Enter, the “
With non-executable stacks in entry-level CTF BoFs, a competitor usually end up in a “ret2win” scenario. A ret2win challenge’s goal is to influence execution to a “win method”.
This “win method” usually returns a shell or gives the user a flag. Sure enough, our non-stripped binary has said
win method at offset
In order to complete this challenge, EIP will have to contain the address of the win method.
Let’s analyze this further.
The disassembly below shows a string loaded into
0x0804922f), pushed onto the stack, and then executed via the
Cross-referencing the strings output (using
axt in radare2), the following command is executed:
cat /src/flag.txt. Obviously
/src/flag.txt does not exist on the local machine, but rather the target machine the competition is expecting the competitor to connect to.
[0x08049216]> s sym.win [0x08049216]> pdf ┌ 41: sym.win (); │ 0x08049216 f30f1efb endbr32 │ 0x0804921a 55 push ebp │ 0x0804921b 89e5 mov ebp, esp │ 0x0804921d 53 push ebx │ 0x0804921e e82dffffff call sym.__x86.get_pc_thunk.bx │ 0x08049223 81c3dd2d0000 add ebx, 0x2ddd │ 0x08049229 8d8308e0ffff lea eax, [ebx - 0x1ff8] │ 0x0804922f 50 push eax ; const char *string │ 0x08049230 e88bfeffff call sym.imp.system ; int system(const char *string) │ 0x08049235 83c404 add esp, 4 │ 0x08049238 6a00 push 0 ; int status └ 0x0804923a e891feffff call sym.imp.exit ; void exit(int status) ............................................................................................................ [0x08049216]> iz [Strings] nth paddr vaddr len size section type string ――――――――――――――――――――――――――――――――――――――――――――――――――――――― 0 0x00002008 0x0804a008 17 18 .rodata ascii cat /src/flag.txt 1 0x0000201a 0x0804a01a 24 25 .rodata ascii Please enter your name: 2 0x00002033 0x0804a033 11 12 .rodata ascii Hello, %s!\n
With a target function identified, and the vulnerability clear as day, now one must generate a payload to overwrite EIP and then influence code execution to the
3) Payload Offset Generation
Calculating the offset and generating the payload for buffer overflows overwrite EIP is often tedious.
This is where pwndbg comes into the picture. Pwndbg is a GDB plugin with handy helper functions for Reverse Engineering tasks.
A particular function of interest to us is
cyclic function generates unique patterns to be fed to EIP to trigger a crash.
After the crash, depending on the bytes within EIP, the appropriate offset can be calculated to idenitfy how many bytes need to be generated until the attacker can control what’s in the EIP register. The image below shows EIP with “
cyclic command can then be used to find the offset neded to control
EIP which is 24 bytes.
Now, pwntools can be used to generate a simple template via
$> pwn template.
This template is pretty awesome. It can easily be used for remote and local exploits. The image below shows copying data from pwndbg to pwntools for building our proof-of-concept exploit. Line 38 shows
p32(0x08049216). This function “packs” the bytes in little-endian at runtime, making it easier to read and construct our payload while writing the “pwn” script.
4) Putting it All Togeter
With output from pwndbg, and pwntools the target directory of
/src/flag.txt can be created locally to simply show the successful overwrite and code execution
Beyond The Blog
This was a straight forward buffer overflow walkthrough with pwntools and pwndbg. It’s handy to keep these templates around for future competitions, and building more complicated scripts. Hopefully you found it helpful! More tutorials for can be found on pwntools official documentation here.
Note, this article as gramatically edited by ChatGPT