Challenge Info
This program is not impressed by cheap parlor tricks like reading arbitrary data off the stack. To impress this program you must change data on the stack!
Downloads the binary here.
Downloads the source here.
Additional details will be available after launching your challenge instance.
hint 1: pwntools are very useful for this problem!
author: SKRUBLAWD
Understanding vuln.c
The code for your convenience:
1 |
|
Below are the important parts summarized:
int sus = 0x21737573;
- Global integer variable
sus
is initialized with the hex value0x21737573
. - The value it’s initialized with doesn’t really matter, we’re going to have to change it to get the flag anyways.
if (sus == 0x67616c66), printf("I have NO clue how you did that, you must be a wizard. Here you go...\n");
- Checks if
sus
is equal to0x67616c66
- If
sus
is equal to0x67616c66
, we get the flag
So all we need to do is change the variable sus
to equal 0x67616c66
Finding sus’s address
Before changing the sus
variable, we need to understand where it’s being stored. To find that, I decided to use obj, but realistically any decompiler can work. The command I ran was:
1 | ~/Downloadss > objdump -t ./vuln | grep sus |
From this, we know that the memory address where sus
is being stored is 0x404060
(the 0x
is there because all memory addresses start with this).
From here, I recalled the challenge’s hint- that pwntools would be crucial to beating this.
Experimenting
Firstly, we can try to read stack values:
1 | from pwn import * |
- The numbers (1, 2, 3 ,4 ,5 ,6), specify the offset on the stack
- This use the
llx
modifier is so that we print 8 bytes rather than 4
After running this python script, we receive this output:
1 | > python3 solve.py |
This doesn’t give us anything though. If we put each individual hex through Cyberchef, it just returns gibberish.
From here, we can modify the specific offsets we want to read, instead of doing %1$llx,%2$llx,%3$llx
and so forth, we can try to do %17$llx,%18$llx,%19$llx
etc.
Now, our script looks like this:
1 | from pwn import * |
- This read and print the value at the 17th, 18th, and 19th position (offset) ont he stack using the
llx
modifier, to ensure we read all 8 bytes, rather than 4 WEAREHERE
serves as a marker so that we can identify where our input is on the stack.
After running this, our output looks like this:
1 | > python3 solve.py |
When we put this into Cyberchef, we notice that we’ve successfully located where we are in the stack.
Writing to the stack
Now, what we can try to do is move to the address 0x404060
(the address where sus is located), and then 0x404060
(2 forward). This way we can try to write half a number at the time.
Keep in mind: the bytes in the memory are stored in little endian order. In little-endian systems, the least significant byte (LSB) of a multi-byte value is stored first (at the lowest memory address), and the most significant byte (MSB) is stored last (at the highest memory address). This is why we’re writing the addresses “backwards”.
1 | from pwn import * |
Our output should look something like this:
1 | > python3 solve.py |
The 404060,404062
part indicates we were successful with moving to our desired part in the stack.
But remember: our goal isn’t to just overwrite sus, it’s to overwrite it to specifically 0x67616c66
. But because it’s in little endian order, we need to write the first half, AKA the low-order bytes, 0x6761
and then the second half, AKA the high-order bytes, 6c66
. If we convert 0x6761
from hexadecimal to decimal, we get 26465
. Meaning we need to push a value of 26465
to get to 0x6761
. The script below accomplishes this.
1 | from pwn import * |
Let’s break down this script step-by-step:
%26464d,
: This ensures our output string has 26465 characters (notice the ,
also counts as a character)
%20$hn
: This specifier writes 2 bytes (half a word) to the memory address that’s stored on the 20th argument on the stack. This use the %hn
modifier to ensure that we only modify the lower-order 2 bytes.
Our output:
1 | sus = 0x67617573 |
Notice the first 4 numbers of sus has changed?
Now we just need to write the high order bytes, 0x6c66
, which is 27750
when converted to decimal. Since we’ve already written 26465
for the low order bytes, we just need to write 1285 more characters:
1 | from pwn import * |
One final time, let’s break this down:
1281d
- This specifies that 1281 characters should be printedAAAA
- Placeholder padding (4 characters)%19$hn
- Writes 2-byte value (or half-word) to the 19th stack. Thehn
modifier will write the high-order bytes.
Output:
1 | I have NO clue how you did that, you must be a wizard. Here you go... |
This have our flag!: picoCTF{f0rm47_57r?_f0rm47_m3m_f43e6ccc}
Bonus: the automatic way
If you don’t really care to learn how to manually execute a format string vulnerability, I’ve left a pwntools script below:
1 | from pwn import * |