Yesterday, Me and my classmate decided to do a group hacking study of the past CTF challenges. One of them suggests we play the Greek hacking challenge - Finals by Deloitte greece
I say "challenge accepted"
So I decided in this post I will talk about how to solve one of the RE challenges
Description:
easy peasy challenge 200pts (desc : Hint: There's a flag in that binary Flag format: CTF{32-hex})
first, let's determine what kind of file that is given to us. Okay, its a 64 bit ELF file
The program doesn't give us any prompt or further information (that's kinda depressing tho)
let's load it in radare2 and try to list all the functions
There is only one function that caught my attention which is main and the rest of the function is just loaded from C++ library.
move to the main function:
~# s main
~# VV (enter a visual mode so we have a much nicer representation of the program flow)
let's try to make sense of the first stages of the code.
tips: to all of the reader who just starting RE, I suggest you guys don't try to understand each of the assembly lines one by one because eventually you will burn out yourself. It is better if you try to focus on the big pictures of the program by study the call function and the logic of the program
there are four functions used in this stage:
- call sym std::__cxx11::basic_string
- call sym std::basic_istream
- call sym std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::length()
- call sym std::vector<char, std::allocator<char> >::size() const
after the program called all the functions and do some operation with our input it comparing the register rbx and rax and if it's wrong it will exit code
I assume that one if this register is the result operation that happens in our input. It probably has a connection with the length() function.
lets put a breakpoint at the comparison:
aha! so out input length is compared with the value 37 so all we have to do is to create a string with length of 37 to pass the first stage
I'm gonna use this string as the input, for now:
CTF{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA} (since this is the format of the flag)
now we are going to stage 2:
in this phase, we greet with the length check for our string and after that, it compares again the register rbx and rax. As you can see, register rbx is come from var_28h
let's find out what is inside this two registers:
hmmm, looks like the register is compared again with the length of the expected input and since the value is still 0 it will jump to the assembly code below
and as you can see before the jump condition it comparing the 1-byte value from (var_21h)
if it's not the same it will go to the left statement(false) which is going to jump to the exit function whereas if we get it right it will increment the value of the previous variable var_28h by one and jump again to the beginning of stage 2.
so what is the value of var_21h that is compared to al register? let's find out by putting a breakpoint at the comparison
let's examine the var_21h memory, to list all of the local variable type "afvd"
and type "ps" to examine the memory, hmmm take a look at the first char it looks similar to our input try to continue couple of time and examine the memory of var_21h in each break point
ahhhh interesting it looks like our input is compared with the flag character one by one.
you can loop the radare2 again-again after it covers all 37 characters of the flag but that's now what hacker's right?
a better approach is to create a python script that will loop every char
luckily, radare is equipped with r2pipe library that enables us to create a python script.
ok so let me explain the code one by one
1. r = r2pipe.open(filename = 'chall',flags=["-d","-e","dbg.profile=chall.rr2"])
this is basically just telling us to create an object of r2pipe to load the binary with debugging mode and we need to pass the "-e dbg.profile=chall.rr2" since our value will be put in cstream right? so in order for radare to automatically input into stdin we have to create a rr2 file, that looks like this.
2.
r.cmd("aaa")
r.cmd("s main")
#put breakpoint at the comparison
print r.cmd("db 0x55555555536d")
the following code is pretty explanatory is just let you analyze the function and put a breakpoint (at the comparison)
3.
for x in range(37):
r.cmd("dc")
print r.cmd("afvd")
data = r.cmdj("psj @0x7fffffffdacf")
char_flag = data['string'][0]
flag += char_flag.encode()
r.cmd("dr rax=\'" + char_flag.encode()+ "\'")
print flag
after that all you have to do is just to create a loop with the iteration of 37 and dump the memory at the var_21h(0x7fffffffdacf). cmdj means that I return the result of the memory in json so I have a better flexibility to get the first char.
data = r.cmdj("psj @0x7fffffffdacf") will return
{"string":"C\u00e0[UUUU\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0080QUUUU\u0000\u0000`\u00db\u00ff\u00ff\u00ff\u007f\u0000\u0000\u00e0[UUUU\u0000\u0000\u0097k\u000c\u00f7\u00ff\u007f\u0000\u0000\u0090\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u00ffh\u00db\u00ff\u00ff\u00ff\u007f\u0000\u0000\u0090\u00ff\u00ff\u00ff\u0001\u0000\u0000\u0000eRUUUU\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0083\u00de\u0018$\u00fb\u008a\u00c9\u001e\u0080QUUUU\u0000\u0000`\u00db\u00ff\u00ff\u00ff\u007f\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0083\u00de\u00f8&\u00ae\u00df\u009cK\u0083\u00deFEI\u00ce\u009cK\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u00003W\u00de\u00f7\u00ff\u007f\u0000\u0000\u00b8R\u00dc\u00f7\u00ff\u007f\u0000\u0000\u00b6\u00d7R\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0080QUUUU\u0000\u0000`\u00db\u00ff\u00ff\u00ff\u007f\u0000\u0000\u00aaQUUUU\u0000\u0000X\u00db\u00ff\u00ff\u00ff\u007f\u0000\u0000\u001c\u0000\u0000\u0000\u0000\u0000\u0000","offset":140737488345695,"section":"[stack]","length":256,"type":"ascii"}
so we only need the string index and get the first char
r.cmd("dr rax=\'" + char_flag.encode()+ "\'")
after we got the character I assign the first char to the rax register which is our input so we simply change register to contain the right char so the script don't have to start from all over again.
Note:
you may be wondering why I have to put this piece of code
print r.cmd("afvd") it's because the var_21h memory might move somewhere when we load our script it is better to check it and change the memory inside the "psj" code:
data = r.cmdj("psj @0x7fffffffdacf")
and also remember to check the breakpoint value of this code before you run the program:
print r.cmd("db 0x55555555536d")
okay so it already set its time run the code and we got the flag
the following is the full code of the program:
import r2pipe
import json
#open the binary and turn off the ASLR mode so the memory stay the same
r = r2pipe.open(filename = 'chall',flags=["-d","-e","dbg.profile=chall.rr2"])
flag = ""
r.cmd("aaa")
r.cmd("s main")
#put breakpoint at the comparison, it is better to check the breakpoint before you run it
print r.cmd("db 0x55555555536d")
for x in range(37):
r.cmd("dc")
print r.cmd("afvd")
data = r.cmdj("psj @0x7fffffffdacf") #make sure you have the right memory location
char_flag = data['string'][0]
flag += char_flag.encode()
r.cmd("dr rax=\'" + char_flag.encode()+ "\'")
print flag
for chall.rr2:
#!/usr/bin/rarun2
stdin="CTF{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}"
aslr=no
Kudos to Cristos Servos
thanks for giving me a chance to finish this CTF challenge I have a lot of fun

Comments
Post a Comment