Quack Quack
Personal Rating: Hard
Last updated
Personal Rating: Hard
Last updated
This challenge was very hard for me, especially since it was the first binary exploitation challenge and was marked with "easy". We start off with a binary file called "quack_quack".
We see that Full RELRO, NX and a Canary are detected, but PIE was not. This suggests that we have to bypass a stack canary and use ROP to solve the challenge. Strings did not return anything interesting.
The duckling and duck_attack functions catch the eye.
Stepping through with si, we see that the duckling function appears at 0x401625
. When duckling is executed, user input can be made and this indicates that the overflow has to be done there.
We find out the addresses of the relevant functions:
duckling: 0x40162a
duck_attack: 0x40137f
Loading the program in Ghidra, we notice that local_10 seems to be a stack canary that has to remain unchanged throughout execution. An explanatory screenshot of Ghidra will be provided later.
So this is the payload layout from what we know so far:
Quack Quack <filler until rbp-8><canaryvalue><filler until 0x401604><0x40137f>
Let' us determine the right offset to reach RIP.
0x66 (102) is the input size of the first read, 0x6a (106) the size of the second read. This statement has a format string vulnerability. If we can make sure that pcVar1 + 0x20 is the address of the canary, it will be leaked:
32 Bytes, or 0x20 is skipped, which is where the printf function starts outputting 9 characters. pcVar1+0x20 needs to be aligned with rbp-0x8, which is where the canary resides.
This means that the input buffer starts at rbp-0x80
Since the input buffer starts at rbp-0x80 and the canary is at rbp-0x8, the distance is 0x80 - 0x8 = 0x78 bytes. Since 0x20 bytes are filled by the printf function anways, we need to fill 0x78 - 0x20 = 0x58 (88) bytes to get to the canary.
pcVar1 Points to the Start of "Quack Quack ": After the strstr() function identifies "Quack Quack " in the input, pcVar1 points to the beginning of the substring "Quack Quack " within the buffer.
The +0x20 Offset Is Relative to pcVar1: When you add +0x20 to pcVar1, the pointer skips exactly 32 bytes from where "Quack Quack " starts. Therefore, the content of "Quack Quack " (11 bytes) is irrelevant to the calculation of the 88-byte filler needed to reach the stack canary.
Since testing was all not really working and my calculations did not seem correct, I tried to bruteforce the offset:
After this also failed, friend told what the issue was. I have to insert the filler before the "Quack Quack " and my calculations from before were correct.
At the fillersize of 89, the output could very well be the canary. I confirmed that in gdb. From the disassembly, we can determine a good point to break for inspecting the potential canary.
I set a breakpoint at just when then registers are set for the print command to inspect if the canary is output.
Comparing this, first the canary, then the leaked valued, we can see that the canary is indeed leaked, but the null byte at the end is missing and one byte is prepended. With a filler of 88, the exact canary is leaked.
We now know that the initial payload has to be this to leak the canary:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQuack Quack
I was told that the canary is always 8 bytes after the base pointer, above the stack. The return value comes after that. This value after the canary states where the program should go after the function was executed.
Usually, we would need 32 bytes less of a filler, since the local_68, that we write to with the second input, is 32 byte later on the stack than local_88. However, since the first print statement adds 32 bytes (0x20), the second input can use the same offsets for the canary location as the first one.
duckling: 0x40162a duck_attack: 0x40137f
The canary and the replacement for the return pointer have to be input in reverse. Since the "40" in "0x40137f" is already in place at the regular return value, it is enough to write 0x137f
Upon reiterating, a friend noted that we could have a newline that gets in the way. So we should write more of our return value replacement to offset the newline character
Payload1:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQuack Quack
Payload2:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<reversed_canary>\x69\x69\x69\x69\x69\x69\x69\x69\x7f\x13\x00\x00\x00\x00\x00\x00\x00
I wrote this script to send the final payloads: