Monday 8 October 2018

mast3r - InCTF 2018

Hope all of you had fun playing InCTF 2018!

We had a variety of challenges this year. I made the challenge mast3r, it's based on unicorn engine, the platform emulation framework. Sometime back I was trying to write a binary analysis tool using unicorn engine and had found the framework to be awesome. So this time I thought of making a challenge using unicorn. You can find the challenge files here.

There are 2 files given in this challenge. The file mast3r is the challenge file, if you dont have unicorn engine installed, you will need to use the second file libunicorn.so.1 to run the challenge, just set the environment variable LD_LIBRARY_PATH to the path of libunicorn.so.1 and after that you will be able to run the challenge.


Briefly speaking, there are 2 stages for this challenge. The checks in the first stage happens in emulated ARM code and the checks in second stage happens in x64 and emulated MIPS code. The user needs to pass both the stages to get the flag.

If you are not familiar with unicorn I suggest you to take a look at this tutorial. Just get a basic idea on how the emulation happens and continue reading.

The main function clearly shows that there are two stages, check1 takes input1 as parameter whereas check2 takes input2 as patameter, if both checks are passed then the printflag function is called, which prints out the flag by wrapping input1 and input2 between inctf{}.

While running the challenge, we saw that the program prints out invalid length. So, lets see what happens inside the first check. Below is the check1 function.



Check1: In this check, first of all, the length of input is checked, if the length is not equal to 20, the program exits by printing invalid length. If the length is correct then an instance of unicorn engine is created using the function uc_open, it takes architecture, mode as first, second arguments respectively. In the compiled binary you can see only the enum constant and not the string. So, you will have to refer the source to find it. You can find all the details related to architecture and mode there.

Now, we know that 1 is for ARM and 0 is for little endian mode. If the uc_open function was successful then memory is mapped and some data is written at addresses 0x10000, 0x11000 and 0x12000. From the arguments of uc_emu_start function we know that code being emulated starts at address 0x10000 and ends at address 0x100044. Your input gets copied to the address 0x11000 and at address 0x12000 a const string "xt=aok}x=~as}a~<<xw}" is written. Then the emulation starts, if it was successful then register r11 (enum const 77) is read and the value is copied to v6. The same value is returned as result and from the main function we know that the return value must not be equal to zero. Let's fire up radare2 and dump the code being emulated.


The offsets in the above image does not start from 0x10000, you can use rasm2 to make the base address 0x10000.


We can write a small python script to pass the above check.



Let's give this input to the program and see what happens.


Yay! We passed the first stage. So the correct input was th3_mast3r_is_r00tus. But we have reached half way only, need to pass the next stage as well to get the flag.

Check2: Diving into check2 we see lot more code. Following the same procedure as check1, we can find that MIPS code is being emulated here. The input must be of length 24. It is divided into several parts and is copied into few registers and other memory locations which are used during code emulation. All details regarding MIPS registers in unicorn can be found here. Emulated code starts at address 0x10000. After emulation some register values are being checked. Try to understand the following pseudocode to get a better idea about the check.


We can see a hook function in the above code. Let's take a look at that as well. We can understand that instructions are getting hooked and a number of registers are being read. At addresses 0x10010, 0x10020, 0x10030, 0x100D0 few more check are happening and we need to pass those as well.


Let's dump the code being emulated and try to understand it. I dumped the code (at address 0x00401758, size 0xf8) using rasm2 and beautified it a bit to view the addresses.

There are different types of checks happening in this stage. Some checks happen before emulation, some during and some after emulation.

Remember the length of input in this stage is 24. The following check happens before emulation. The last 8 characters of the input must be "un1c0rn!", found during the begining of check2, compared using strcmp. Next, two different types of checks happen during emulation. As I said before, few checks happen by instruction hooking see the hook function to find at which addresses these checks happen, other checks happen in the MIPS code itself, some character by character comparisons. The final check, in the MIPS code, some characters of the input are incremented and stored into registers then checked after the emulation. See the final part of check2 function. You can solve all these checks without any scripts.

We need to find an input which will satisfy all these checks and that input is 3r_and_h3_l0v3s_un1c0rn!.

Once we pass this stage the printflag function is called.


Lets run the program and see what happens.


Finally, the flag is printed out:
inctf{th3_mast3r_is_r00tus3r_and_h3_l0v3s_un1c0rn!}


I hope you had fun while reversing it. Please let me know if you have any doubts.

Happy hacking!

Sunday 11 February 2018

Quad Math - HackIM 2018

This time HackIM had a variety of RE challenges. There was one LLVM IR, one ARM, one Mach-O, one Linux x64 and one more(didn't unlock it, so don't know about that).

I solved the ARM challenge, named Quad math. It was a 150 pointer challenge. I couldn't run it. So I fired up radare to analyse it statically. The main function looked something like this:

       

From the disassembly, it is clear that the binary takes one string as input, then it goes through a series of checks, and finally the result gets printed out. There were a total of 65 check functions which checked the input, character by character. But the checks turned out to be a little short as multiple correct answers were being accepted by the binary. It will become clear in a little while. And each of the checking functions looked something like this:

 

It is actually checking the first and third character of the input(i.e. flag).

        void func_6b8()
        {
             fin = input[0] * input[0] - 203 * input[0] != -10296 || input[2] * input[2] - 203 * input[2] != -10296;
        }

Rest of the characters were being checked similarly(i.e. two alternate characters at a time). So, what I did is, I wrote a z3 script to find the correct input.


As I earlier said, the constraints for checking the flag were a little less, therefore the script printed out many possible answers. So I sent few of them having only printable characters to the admin and he was kind enough to send me the correct flag which will be accepted by the server.

The correct flag was:

hackim18{'W0W_wow_w0w_WoW_y0u_h4v3_m4th_sk1ll5_W0oW_w0ow_wo0w_Wo0W'}

Tuesday 6 February 2018

Codegate CTF 2018 Preliminary - Boom

Even though I didn't solve any challenge during the CTF, Codegate was fun. There were a variety of RE challenges this time. Half of the day I tried the challenge Easy Serial. I understood that it was a haskell program. I haven't used it before and debugging in GDB did not make much sense as haskell programs do not use the conventional stack. I read about haskell program and it's stack structure then left it midway to look at the challenge Boom. I guess this challenge can be solved in many ways. I approached it in the following way:

Boom was written in rust. I tried running the program.


From the output it is evident that there are 5 stages in this challenge.

Let's figure out what is the first one. Inside the main::main function there is a function called main::func1 which looks interesting. The first input and check happens in that function itself.
We can see an if statement and a string compare in it. Our input is compared with the string "Know what? It's a new day~". If it is correct then main::func13 checks if the file /tmp/files/1 is present or not. If that is present then only we move on to the second check.
The second check happens in the function main::func2. First of all it checks if the input has 7 characters. In this case, the characters are space separated integers which will become clear with little debugging.

This is a predefined array in the program:
f7*zq5$ase0t6ui#^yd2owgb_n8pu4!k&vc@lrj19mx3h

The input is 7 integers which after few calculations must match with the position of characters of the string pand0ra in the above array. With a little trial and error along with one of my teammates, I was able to find the correct input as 57 14 23 92 50 7 14. If it is correct then the check for file /tmp/files/6 happens. If that is present then we move on to the next check.

The third input is also a string of integers i.e. 25 space separated integer values. Put a breakpoint in GDB at the address of _ZN4main5func717h1a0f9200721da090E+2791 and we can observe that the value at rdx (our input, one character at a time) is compared with QWORD PTR [rax+rsi*8]. To get all the values at that memory location, I wrote a small GDB script.
Running the script at that address gives the correct input and that is:
17 24 1 8 15 23 5 7 14 16 4 6 13 20 22 10 12 19 21 3 11 18 25 2 9

Now make the file named 10 at the same location as other files. So now we have passed the first 3 checks. Moving onto the 4th check. 

The 4th input is just an integer. The most important check for it happens in the function _ZN4main6func1217h2c7664a1999a9c74E. The integer should be such that the return value of the function must be 0x6b.

I was too lazy to reverse the function, so I wrote a GDB script to bruteforce the correct input. The correct input is 188. Also we need to have a file named 13 at the location /tmp/files.
The final input is a bit confusing because many values are possible (that might be the reason why they hosted the challenge on a server). We need to enter 4 indexes as input. I gave the input 1 1 1 1 and it worked.

The complete output of the binary:

This is just a brief summary of how to solve the challenge. There are many checks and exception handling in the program. I hope giving these inputs on the remote server will print the flag, can't confirm as the server is down after the CTF.