SekaiCTF 2023 - Sahuang Flag Checker
Short write-up on Sahuang Flag Checker - a reverse challenge in the CTF.
Overview
Our team solved all reverse challenges from this year’s SekaiCTF edition!
In this blog, I will only share my write-up for Sahuang Flag Checker challenge. For other challenges if needed, DM me via my Twitter .
Sahuang Flag Checker
- Given file: Get it here!
- Description: Can you figure out what master sahuang flag checker is doing to satisfy the 🐸? Note: Your CPU needs to support AVX-512 to run the binary.
- Category: Reverse Engineering
We are given a binary filled with bunch of AVX-512
instructions to work with. Using IDA to analyze the binary, we get this code.
|
|
So we are prompted for an input, and it will be extended using X
to make the length divisible by 16
.
|
|
The big do-while makes it clear to see that our input is encrypted by chunks of length 16
each.
|
|
I used IDA debugger to analyze this, and I actually guessed how rc()
works by inputting ABCD...
.
First thing we have to observe is rc()
changes the value in eax
register. By inputting ABCD
, which is 0x41424344
, gives 0x20
, 0x28
, 0x21
, 0x29
in eax
register, respectively.
From that I got the hang of this function. It basically just does some manglings on input[i] - 33
, by performing rotate left 3 times on the lower 4 bits of input[i] - 33
. Refer to the script below to get how the function works.
|
|
To reverse it is a fairly easy task, just do rotate right 3 times on the lower 4 bits of the encrypted value, then add 33
to the result.
|
|
Continue on this part, specifically this line: matmult_SSE4((__int64)v29, (__int64)A, (__int64)v28);
, we can see that v29 = A * v28
, with A
is a 16 * 16
matrix and v28
is a vector of size 16
that contains our mangled input chunk.
Since A
’s elements are treated as doubles from qwords in little endian, matrix A
can easily be extracted by using Python struct, like below.
|
|
|
|
First, the program sets rbx = v29
, means that now the rbx
points to the vector that was calculated from the operation v29 = A * v28
.
We encounter 2
constants here, as shown below.
|
|
The first constant is loaded into xmm1
using this instruction: vmovss xmm1, cs:constant1; float
, which we can easily retrieve using Python struct using this line of code.
|
|
The second constant is loaded into xmm0
using this instruction: vmovss xmm0, cs:constant2; float
. We will use the same approach to get the floating point value.
|
|
The mul()
function simply sets XMM0 = XMM0 * XMM1
. From that I was able to summarize the algorithm flow, using this script.
|
|
So, clearly, it is just maths. To be able to solve this, refer to the note below.
The elements are all in range [0, 93]
, so we can work on stuffs in Zmod(94)
.
From this, we can just do a simple solve_right on AX = B
in Zmod(94)
with A being our matrix in Zmod(94)
and B being our target vector, with the elements all subtracted by 33
, since we have this line *((_BYTE *)ct + v14++) = add(v24, 33u);
.
|
|
And, combine this with our very first script, we have our flag for the challenge!
|
|
Flag is: SEKAI{1_I_i_|_H0oOo@p3eEe_Y0Uu_/Didn’t_BruT3F0rCe_GuYy5}