Reverse Engineering
F is for Flag
Description
-
Solution
Given ELF file, open it using IDA. Through dynamic analysis we able to recover some of the function names.

Above code basically doing validation for the result from function std::function<std::variant<unsigned int,std::string,std::shared_ptr<Cons>> ()(void)>::function<main::{lambda(void)#1},void>(. To take a look on the actual code from lambda function we can follow below pattern
Click lambda function
Click function next to (a1 + 3) or _M_invoke
Click
__invoke__functionClick
__invoke__functionClick operator()
We will see below code as the main process

lambda_1_op1
This function do conversion from input string to dword.
lambda_1_op2
Click function with the most argument
lambda_op2_wrapper(a1, a2, v4, v8, v9);Choose function that processed function_if_false

Click function next to (a1 + 3) or _M_invoke
Then do the same flow like the first lambda function
lambda_1_op1 actual code will be look like below

To take a look on the actual code for each wrapper we can do the same approach. During the competition we analyze it dynamically and make conclusion for each wrapper
lambda_op_2_false_wrapper1
mapping 4 bit
lambda_op_2_false_wrapper2
multiplication with 0x4e6a44b9
lambda_op_2_false_wrapper3
process input with xor and rol
lambda_op_2_false_wrapper4
should be looping
lambda_1_op3
Do validation for processed input and static values

After getting all the algorithm we reconstruct the whole process in python.
Last, reverse the algorithm
create res variable
recover block one by one, because there are 3 blocks unmodified
brute the original value for multiplication (because the result was wrapped to 32 bit)
unmap the value
do 8 times

Flag: SECCON{fUnCt10n4l_pRoGr4mM1n6_1s_pR4c7iC4lLy_a_pUr3_0bfu5c4T1oN}
Jump
Description
Solution
Given ELF file with architecture aarch64. Open it using IDA.

Looking at start function, we will see sub_4004f4 which will act as main function. Going deeper. we will found the argument length check and if it is not 2 it will printout incorrect string.

So basically this program is a flag checker, through analyzing each function we found interesting function that do validation.

All of the functions has the same pattern which is it uses check_val (global variable) to store the result. So we can get all of the check function by looking at its xref.
Below is the output
Set breakpoint on all logic/arithmetic operation on that function except the last one (0x400c48). Then trigger the input, check the input validated, and bypass the validation.
From trial and error we found that there are only 3 breakpoints hitted.

Each value compared it also from our input (except the validation that use 1 constant and 1 variable)

Now we know the pattern used, which is block[i] block[i-1] (which is same like in the code). But the problem is we dont know what is the value of i used on each function. So i decided to map each function manually.
So for validation that use one block, we can get the valid input by reversing the algorithm. But for the two blocks validation we cannot directly get the valid input but we can use the previous known input and do a little bruteforce recursively.

Flag: SECCON{5h4k3_1t_up_5h-5h-5h5hk3}
Reaction
Description
-
Solution
Given ELF, open using IDA. Look at main function.

Code above basically do 3 things
Generate seed using flag and use Mersenne Twister to generate random
Call Environment::update which consist almost all of the algorithm in the code
Validate flag_counter (which processed in Environment::update), if flag_counter > 13 it will produce flag
So lets take a look on Environment::update

Our input received at Environment::set, each input consist of 2 bytes value.

From code above we can see that the second byte can be only 0,1,2, or 3. And the first value is limited to < 0xe (which is the same size like maximum index of array initialized in main function). After having this information we tried to debug it and take a look on each data processed.
At first i though it was a maze direction, so i tried to findout where is the random generated. The generated random actually in the same function which is Environment::set, below is the code to generate the random number.

After found it, i convert the whole random number generation process.
Output:
The output doesnt look like a value in maze. So i continue to analyze the algorithm that will process above values. Our objective is to findout a way to make the flag_counter > 13 and i found that Environment::react will affect the flag_counter.

From analysis we found that Environment::react process information based on column and row. The total of column and row is 14x14 and it is the same address like we got from the Environment::set. So in this step we tried to dump the table to know the mapping for our input.

I dont know what kind of puzzle is this, so i just shared it to the discord and my teammate found that it was a puyo puyo. So the next step i did is trying to find out how to get the block and then i realize that the block was leaked from the response. To get the block i just send the most possible combinations that can be made if there is no block explodes.
After that my teammate tried to solve it manually, he has experience with this kind of puzzle/game.
To solve, I (zafirr) played puyo puyo manually on google sheets π and recorded the moves. This is the state of the board before the last move. It results in a 15 chain

During the solving process, after he found the solution there is a little step missing, so i decide to do a modification on my gdb script to help him to find out the actual map of the block. To do that, basically i change the value written to the map with the value from the server. Below is the updated gdb script.
Below is the example of the output to debug the puyo puyo

Last step, just send the moves and solved:

FLAG: SECCON{puyoyo_mo_yoyo_mo_yoyo_no_uchi}
Last updated