Description

Welcome Reversers! Let’s start with this. Can u break the algorithm and give me the flag ?
Author : KERRO
File: here

Write-Up

This program took me a bit long to reverse, although it was not difficult. If we execute it, this is what we get:

Welcome to securinets quals CTF :)
PASSCODE:test
NOPE :(

As strings doesn’t display anything relevant, this program probably reads a simple input which must verify some conditions in order to get the flag.

My general procedure is using ghidra to understand the program structure and its behaviour and using gdb or any other debugger (I personally like edb debugger) to get a more accurate and deep understanding of what I need.
So let’s start opening it with ghidra and see what we get. When I did it the first time, ghidra didn’t detect the function main. The solution was easy, we go to the function entry and rename the first parameter of __libc_start_main to main. Let’s open that function and analyse the code.

First, after all the variable declarations, we can see those calls to puts, printf and fgets, which print some output and read our input. Our input is saved into the global DAT_003020e0, which I will rename to input. The next loop of code seems to create an array with each character of our input interpreted as integer. After that, the function FUN_0010089a is called, and a malloc is done using as size the return value of that function plus one. Next, it calls FUN_001008ec using as parameters the array of ints, the integer 100 and the dynamic variable allocated before. Finally, it copies the content of the dynamic variable into a global variable until it founds a ‘C’. It seems that the function FUN_001008ec writes something in the dynamic variable according to our input. We could reverse it, or we could see what’s the content of the dynamic variable after calling the function.

In gdb, I ran the code until the execution of the desired function, and read the content of the variable in the stack:

Same in edb debugger:

It looks like a base64 string. If we decode dGVzdHBhc3Njb2Rl (using ‘C’ as terminator, because it only copies until that character), we get testpasscode, which was the input I entered. Now we know that this function simply encodes our input with base64. After renaming some functions and variables in ghidra, the code should look like this:

First block of code in main

After this block of code, we see something very interesting. The program null-terminates the string, and then calls a lot of functions passing the input in base64 as argument, saves the return values and sums all of them. If the result equals 0x19, which is 25, the program prints that we already know what to submit, so probably the input that satisfies every function is the flag. As there are 25 function calls, we know that each function must return 1 in order to get the flag. So yes, we must reverse them all. This is the most tedious part.

Once we get into the first function, we notice that it calls FUN_00100b1e several times. Let’s see what it does. It takes an element of our input in base64 as parameter. As our input is a string, the parameter is a char. The function seems to iterate a global string, which ghidra calls s_ABCDEF..., comparing each character to the character it received as argument. Once those two character are the same, it returns the index. So this function basicly returns the position of the character in the global variable.

FUN_00100b1e

Now, reversing every of the 25 functions is easy. The first one, for example, tells us that the first character of our input in base64 must be the one in the position 0x1c of the global ABC, which is a ‘c’. It also tells us another condition, which we can see here:

First of the 25 functions

The rest of the functions are very similar, and not difficult to reverse. I only had problems with the 7th, 14th and 20th. In those functions, I decided to use the debugger to see which characters and indexes were being compared which can be easier than reversing all the code. After repeating the same process for every function, this is the string we get:

c2VjdXJpbmV0c3tsM3RzA3c0cm1fMBRfdCB9

We don’t know A, B and C, but thanks to the 20th function we know that applying XOR to these three characters must result in 0x58. So I made a simple python script to list every possible option next to the possible flag (applying base64):

import base64
import itertools

ABC = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
options = itertools.product(ABC, repeat=3)

for i in options:
    x = i[0]
    y = i[1]
    z = i[2]
    if ord(x) ^ ord(y) ^ ord(z) == 0x58:
        flag = "c2VjdXJpbmV0c3tsM3Rz" + x + "3c0cm1fM" + y +"Rfd" + z +"B9"
        print(flag, base64.b64decode(flag).decode("utf-8", "ignore"))

Output:

c2VjdXJpbmV0c3tsM3RzA3c0cm1fMARfdXB9 securinets{l3tsw4rm_0_up}
c2VjdXJpbmV0c3tsM3RzA3c0cm1fMCRfdZB9 securinets{l3tsw4rm_0$_u}
c2VjdXJpbmV0c3tsM3RzA3c0cm1fMHRfdQB9 securinets{l3tsw4rm_0t_u}
c2VjdXJpbmV0c3tsM3RzA3c0cm1fMIRfdPB9 securinets{l3tsw4rm_0_t}
c2VjdXJpbmV0c3tsM3RzA3c0cm1fMJRfdSB9 securinets{l3tsw4rm_0_u }
c2VjdXJpbmV0c3tsM3RzA3c0cm1fMKRfdRB9 securinets{l3tsw4rm_0_u}
c2VjdXJpbmV0c3tsM3RzA3c0cm1fMLRfdUB9 securinets{l3tsw4rm_0_u@}
c2VjdXJpbmV0c3tsM3RzA3c0cm1fMMRfdTB9 securinets{l3tsw4rm_0_u0}
...

There are 2356 possibilities, but if we look at the flags we can easily guess that the correct flag is:

securinets{l3ts_w4rm_1t_up}

Leave a comment

Your email address will not be published. Required fields are marked *