Description
EN: These crazy twins are driving me crazy! They know something I don`t. They are simply playing with me!
Resolution
In this challenge we are getting not one but TWO binaries to reverse.
That was a first time for us in a CTF.
Those binaries were almost identical,
you need to input 2 arguments, “twin” is the first for both binaries, “#1st” and “#2nd” is the second one. it clearly explain who you should be running first. (detail explanation is just a few line under)
the aim was to make them talk to each other, threw a shared file (‘BiTwins.bi’), where each binary would write one letter at a time, turn by turn.
There was a lots of ways to solve this challenge, we’ll give you three.
– The dumb way
– The easy way
– The “lets finally learn IDAPython” way
you could also have tried to decode both hardcoded string inside the binaries as it was a simple reverse-lookup table in the function at 0x400CF9
As we’ll make this WriteUp about IDA Python, we’ll try to explain this in a “tutorial” way.
Let’s begin :
Part 1 – initial dissas & getting the args right
If you’ve never used IDA before, one of the great advantage of IDA is that you can run it from almost any evironment (Win,linux,osx) and debug/dissas any kind of binary (PE,ELF,Mach-O).
thanks to the “remote servers” you’ll be able to connect to another machine running the right OS, and debug it from there.
We’ll be using IDA under windows for this matter, with the linux_serverx64 to remotly connect to the elf binaries.
IDA is a great tool, please support the devs buy buying their software : https://www.hex-rays.com/index.shtml
the config is pretty basic, you need to run linux_serverx64 on your linux machine, that will open a socket (default port is 23946. you can add a password with -P)
under IDA, “Debbuger” menu>”Process Options”
the first two lines should be the full path to the binary on your “remote” machine
the third is the directory
the rest is pretty self explanatory.
one important thing, if your path doesn’t point to the binary, IDA will ask if you want to upload the binary currently being used in IDA.
now that we are able to run the binary, let’s start the dissas.
openning the “main” function, we can quickly see that :
– 0x400A75 is comparing the number of arguments to 2, and will terminate if it is lower or equal (JLE 0x400D0A). So we need to provide 3 arguments, the first beeing to binary name, we have to give it 2 more.
– 0x400A92 will compare”twin” with our Arg1
– 0x400AEE will check the size of arg2 and will terminate if it is “lower or equal to 3”
-0x400AEC is were the binary will check our arg2 against a hardcoded value, byte by byte. if it is a match is will finally run the main part of the challenge.
Part 2 – Dissas of TwinIsGonnaRunHisLifeCycle
So, what we have here is some kind of a conversation between the 2 binaries using a shared file “BiTwins.bi”
it first try to open and read the file (0x400B40),
if it succeed it will read a char from it (0x400BEC) and compare it to the last char he had read.
if it is the same char, he will sleep and retry, if it’s a new char he’ll go on and write his next char.
That is a very crappy way to do things, because that means that we can’t have to two binaries “talk” the same char or they will both ends up in an infinite loop. that also means that we will not have the same char twice, wich will be good for our “crappy solution” :p
Let’s solve this : The easy way
we now know how to run both binaries (with the good arguments), and we know that they will write there char inside “BiTwin.bi” and then sleep 1 sec each time.
we can make a simple reader and store those values with a one liner :
while true;do cat BiTwin.bi>>flag;sleep 1;done
you would have some char that are read twice in your final sentence, so to clean it up :
f=open('flag','rb') myflag=f.read() f.close() last_char='' full_flag='h4ckit{' for char in flag: if char!=last_char: full_flag+=char last_char=char
We are lucky that the way they implemented it, we can have the same char twice. without it the cleaning part would have been more difficult.
that’s why we looked for an other way
Let’s solve this : The dumb way
As you can see, on 0x400C29, you’ll have passed the “it’s still the same char” conditions. so the value in EAX will be the Char that was sent by the other binary
then again, on 0x400C4D, you’ll have generated your own next char and will be ready to send it. EAX will also contain your next char value.
so by putting breakpoints to those 2 adresses, and letting the second binary run freely, you’ll break at each char and can manually collect those chars.
that’s the manually part that is dumb, and the creator made sur that it would be a pain in the ass for us if we were to chose this path, as the output is more than 50 char long.
Let’s solve this : The “lets finally learn IDAPython” way
Clearly, we already had the flag threw our easy way, but we’ve been looking at malware reverse tutorial for quite some times now and those guys use a lot of IDAPython.
we always wanted to try, but never took the time. this was the perfect opportunity.
IDAPython allow us to use python inside IDA (what a shocker ;p)
https://github.com/idapython
just download the right binary for you (right IDA and Python version – https://github.com/idapython/bin)
Copy the whole “python” directory to %IDADIR%
Copy the contents of the “plugins” directory to the %IDADIR%\plugins\
Copy “python.cfg” to %IDADIR%\cfg
After that, is was still unable to make it works. i had to add some path to the PATH environnement variable:
C:\Python27;C:\Python27\Scripts;C:\Python27\Library\bin;
now restart IDA, and in “File”>”Script Command” you should now be able to change the scripting language from IDA’s IDC to Python
what we will do here is pretty similar to the “dumb way”, but we will capture the EAX value and transforme it into our flag automatically.
import idc import idautils import idaapi final='' def BreakpointHandlerPrintEAX(): global final target = GetRegValue("EAX") final+=chr(target) print final def main(): func_input = 0x00400c29 #Get Char from Binary1 func_output = 0x00400c4d #Get Char from Binary2 #Lets us use python functions for breakpoint conditions RunPlugin("python", 3) AddBpt(func_input) AddBpt(func_output) SetBptCnd(func_input, "BreakpointHandlerPrintEAX()") print("Breakpoint at: %x" % func_input) SetBptCnd(func_output, "BreakpointHandlerPrintEAX()") print("Breakpoint at: %x" % func_output) if __name__ == '__main__': main()
instead of breaking at our breakpoints, our Handler will be called, and he will add the char to our final string.
Flag was h4ckit{R3v3R53_T4k35_2_MvCh_T1m3_Wh3N_U_DrUnK}