CyCTF 2024 Finals
Write up for CyCTF 2024 Finals Reverse Engineering Challenge.
Introduction
I’ll be sharing the solutions for the reverse engineering challenges, hope you like it.
1n7ern4ls
challenge link | https://drive.google.com/file/d/16d3jeKxpGTeV7j57mMmYK9pqCv7QbvIp/view?usp=sharing |
password | cyctf2024 |
I suggest downloading the files so you can follow up with me
Description
unfortunately I don’t have the challenge description :( , but we are provided with a copy of victim’s “AppData” folder that he suspect something weird is happening on his device
First Look
The first thing I tried was navigating though files trying to find anything that might be interesting, but couldn’t find anything from the first look, so I started searching more logically by looking for important files that might be related to logs
while hoping between the files, I ran into AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
, it had the following logs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cd .\Desktop\CTF2024\
.\leakage.exe .\ape.txt
.\enc-leakage.exe .\ape.txt
.\leakage.exe .\ape.txt
.\enc-leakage.exe .\ape.txt
.\leakage.exe .\ape.txt
.\encleakage.exe .\ape.txt
.\leakage.exe .\ape.txt
.\encleakage.exe .\ape.txt
.\leakage.exe .\ape.txt
.\encleakage.exe .\ape.txt
.\leakage.exe .\ape.txt
.\encleakage.exe .\ape.txt
.\leakage.exe .\ape.txt
.\encleakage.exe .\ape.txt
.\leakage.exe .\ape.txt
.\encleakage.exe .\ape.txt
.\leakage.exe .\ape.txt
.\vscode.exe .\ape.txt
searched for all these exe files, and only found vscode.exe at AppData\Local\Microsoft\Microsoft VS Code
, you might think it’s a legitimate path, but no :)
Analysis Start
doing basic analysis on that exe, it’s packed using upx
ok, so I couldn’t unpack it automatically even with unpack.me, IDK if I’m the problem or this intended but it looks like we need to do it the hard way
the author told me later that the sections names is changed and in order to just fix the automatic unpacking I need to rename it with UPX’s default
Unpacking
For manual unpacking UPX you can refer to “Practical Malware Analysis Book CH:18”, as summary, you need to find a tail jump through the unpacking stub
After some stepping in x32dbg, found this far jump
a single step, and we are at the OEP.
use Scylla -> Get Imports -> IAT Autosearch -> Dump -> Fix Dump and we got an unpacked exe with imports
Solving Statically
First of all as we saw in the cmdhistory, it was ran with a txt file as argument
fast look at strings there is a string CYCTF2024 let’s start with the function that uses it
- Open a file
(CreateFileA)
. - Acquire a cryptographic context
(CryptAcquireContextA)
. - Either generate a cryptographic key or use an existing one
(CryptGenKey)
. - Read the file in chunks and encrypt its contents
(CryptEncrypt)
. - Write the encrypted content back to the file.
For the CryptGenKey
function it takes 0x6610 as an identifier parameter for the crypto algorithm which is AES-256
Now that we can tell it takes a file as an argument and encrypts it using AES-256, our next step is to find two key pieces of information: the encryption key and the encrypted file.
If we dig deeper and take a look at sub_403F80
, we will find that it sets a file path for some purpose, and then in sub_404020
, it will save the generated encryption key to that file.
\Microsoft\d36586d6acac55752b3d57d91b78b803_44e26773-283f-424b-828c-3ebe299bf94c
Collecting Parts
searching for the location of the file d36586d6acac55752b3d57d91b78b803_44e26773-283f-424b-828c-3ebe299bf94c
we found it here AppData\Roaming\Microsoft\Crypto\Keys
1
2
3
08 02 00 00 10 66 00 00 20 00 00 00 21 92 85 E6
E1 8B 3F 2D C1 5D B0 D5 78 51 0E A7 C1 58 D1 0E
8B BC 37 0A 73 B5 B1 E4 24 63 43 B5
while 08 02 00 00 10 66 00 00 20 00 00 00
is the Key Blob Header, the rest is our AES-256 key
1
21 92 85 E6 E1 8B 3F 2D C1 5D B0 D5 78 51 0E A7 C1 58 D1 0E 8B BC 37 0A 73 B5 B1 E4 24 63 43 B5
now we need the encrypted file and for this task, I did some guessing cause I didn’t find that ape.txt, but found a settings.config in the same folder with vscode.exe and it looked encrypted enough to me XD
1
CB 44 4C D0 D3 37 6B 69 6B BC 5F 78 2A 75 40 64 B6 51 1A 73 D9 06 FE 9C EF AC 90 F5 66 44 94 C7 75 C5 A8 77 E2 8D EA 81 58 34 F3 81 9D 1B 60 DA 6B E7 9F 3F 50 E5 33 88 32 47 65 D3 F5 3E 35 38 7A 33 14 EE 6A 43 67 85 0C 13 05 E2 85 02 BE 95 C1 24 03 EE A2 1F 36 D7 79 B9 6C 7A 6C 04 DF CA
Placing Everything Together
Let’s craft our script now, and test our decryption
1
2
3
4
5
6
7
8
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import binascii
key = binascii.unhexlify("219285e6e18b3f2dc15db0d578510ea7c158d10e8bbc370a73b5b1e4246343b5")
data = binascii.unhexlify("cb444cd0d3376b696bbc5f782a754064b6511a73d906fe9cefac90f5664494c775c5a877e28dea815834f3819d1b60da6be79f3f50e53388324765d3f53e35387a3314ee6a4367850c1305e28502be95c12403eea21f36d779b96c7a6c04dfca")
cipher = AES.new(key, AES.MODE_CBC, iv=b'\x00'*16)
plaintext = unpad(cipher.decrypt(data), AES.block_size)
print(plaintext.decode('utf-8'))
and here is our encrypted data :)
1
2
3
4
5
H4ck
Eat
Sleep
Repeat
Here's your gift: CyCTF{h0w_c0nfus1n9_w1nd0w$_@P1s__xXxXx}
IH8PeterPan
First look
For this challenge, we got a 64-bit DLL that have only 1 export other than entry point
and it basically does almost nothing 🤷♂️
deeper look in the DLL, I found some strange strings
Taking a look on XREFS, I ended up in pdata section with a lot of runtime function
Each part will do some operations and then returns a specific number, which can be presented as an ascii letter
and we can confirm that easily by debugging the DLL in x64dbg and setting the RIP on the first part directly and step until return
If we continue stepping through the code, skipping the return instructions, we’ll notice that these characters eventually form meaningful words.
However, manually doing that for over 800 parts is a daunting task.
Fortunately, as we observed earlier in IDA, all these parts are contiguous, with the only obstacle being the ret
instructions that prevent us from executing them sequentially.
To overcome this, we need to find a workaround.
Lazy mentality
The approach I thought of was patching all these ret
instruction with nop
instructions
because the pattern before each ret
is identical, we can get a unique array of bytes for all ret
instructions and replace the opcode of ret
with 0x90
directly
Note: there are 2 different sequences of bytes for
ret
so you need to patch both of them
1
2
0F B6 00 48 83 C4 30 5D C3 -> 0F B6 00 48 83 C4 30 5D 90
0F B6 00 48 83 C4 10 5D C3 -> 0F B6 00 48 83 C4 10 5D 90
I won’t overkill this by doing a script or something, I’ll just replace the desired bytes with HxD
and here’s our binary patched
Tracing
Now, we need a way to log all the values of rax
at this nop instruction
Fortunately, xdbg offers a very powerful feature called tracing, we can trace the execution while logging the the values we need of any register.
The first idea I had was making a condition to only log value of rax
if the current instruction is a nop
, but couldn’t craft it’s syntax properly.
so my other idea was to log all instructions with the value of rax
and then filter it with notepad++
So the steps will be as the following
- setting the instruction pointer on the start of part1 function
- use trace into option with the following settings
- Log Text:
{i:cip} | rax : {rax}
# i = instruction as text, cip = current instruction pointer, rax = value of rax xdbg string formatting
- Log Text:
- specify a log file
- might need to increase maximum trace count to something like 500000
after it finished tracing, and we got our log file, I’ll filter only line with nop
instruction
copy all of them and filter only for the value of rax
, head to cyberchef and get your gift
1
CyCTF{b!n@ry_1n$trum3nt@t10n_!S_4W350M3!}
OG
For this challenge, we have flag.enc and an ELF file,
First Look
The first thing I did was running the ELF file, it asked for argument so I gave it the flag.enc file but it gave me output “bad file”
IDA Time
Ok, it’s time to start analyzing this, I opened IDA and the binary was really big, with a lot of code and functions at the point where you don’t even know where to start
so, I started with searching for the string “bad file” as a first step, and found it inside another big function with a lot of switch cases
We notice here that it’s trigged on the case 38, so let’s see what leads to this
nothing looks relevant here, let’s see what leads to this 32
this might be useful, as there is a function call here, let’s examine it
Inside there, there was another function call, and by following like 2 calls, I ended up in a function that looked intersting enough sub_451B20
at this point, I started debugging to see if I can notice anything that might be useful.
And after some stepping in this function I ended up in a variable being set to my flag.enc
file magic bytes
what I noticed here, was that v7
and v8
takes holds the first 8 bytes of a1 and a2, and then v6
is calculated by subtracting v8
from v7
and if v6
is not equal to 0, it will return from this function and eventually leading to the “bad file” output
But I noticed the magic bytes that being compared to our file is actually the PNG magic bytes!
Let’s change the magic bytes of the flag.enc file and give it as argument and hope that maybe this will be some progress?
After doing that, It outputted out.enc
file, when I opened it in hex editor, the whole flag.enc was actually changed but with an unknown magic bytes again, but as the file is relatively big and according to our case here, I suspected that is has to be some kind of Image so I changed the magic bytes of the file to PNG again, and voila, it’s our flag!
1
CyCTF{M@zl0um_F3_H0b3k_Ya_M@5r}
babypwn
I’m not doing a write up for this one 😡
fs0c137y
Stay tuned