The Flare-On Challenge is the FLARE team‘s annual Capture-the-Flag (CTF) contest. It is a single-player series of Reverse Engineering puzzles that runs for 6 weeks every fall. #flareon11 is launching Sept. 27th 2024 at 8pm EST with 10 challenges.
In this blog we share our solution to the challenges
Challenge 1 – frog
Welcome to Flare-On 11! Download this 7zip package, unzip it with the password ‘flare’, and read the README.txt file for launching instructions. It is written in PyGame so it may be runnable under many architectures, but also includes a pyinstaller created EXE file for easy execution on Windows.
Your mission is get the frog to the “11” statue, and the game will display the flag. Enter the flag on this page to advance to the next stage. All flags in this event are formatted as email addresses ending with the @flare-on.com domain.
Given an exe and src python code of that exe. Read the src, we see the flag is generated by the function GenerateFlagText, which decrypts the encrypted flag with a simple xor
They key decrypt is x + y * 20 where x and y are the params passed into this function
Follow where this function called, we conclude that x, y will be the 10 for both
Flag: welcom_to_11@flare-on.com
Challenge 2 – checksum
We recently came across a silly executable that appears benign. It just asks us to do some math… From the strings found in the sample, we suspect there are more to the sample than what we are seeing. Please investigate and let us know what you find!
We’re given an executed code in Go, and we’re lucky that it’s not stripped at all. For the first part, it requires the user to do some math a random number of times (at most will be 8)
After finishing this first part, a checksum is required. This checksum is then decoded hex. If the input checksum is not a valid checksum (not in hex string format), it prints out “Not a valid checksum…”.
Then the decode input will be checked length, if its length is 32, then it will be passed as the key for chacha20poly1305 decrypt, else, it prints out “Maybe it’s time to analyze the binary! ;)”
If the key is wrong, it also prints out the same message as above.
Take a deeper look into binary, the flow of code is kinda weird, cause it passes input checksum as a decrypt key but does not yet check it, and the check appears after that calls the chacha_open(), maybe it’s intense of the author.
At first, it converts the hex byte input back to hex string
Then it’s passed into main_a, in this function, it xor input with key “FlareOn2024” then encodes base64 the output, later compare with a hardcode base64 string
Decrypt it, we get the checksum: 7fd7dd1d0e959f74c133c13abb740b9faa61ab06bd0ecd177645e93b1e3825dd
After decrypting the flag, flag content will be stored in UserCacheDir in the form of a JPG image.
Flag: Th3_M4tH_Do_b3_mAthng@flare-on.com
Challenge 3 – aray
And now for something completely different. I’m pretty sure you know how to write Yara rules, but can you reverse them?
Given a Yara file, we have to find a file that matches that Yara rule.
Take a look into the Yara rule, there are a bunch of rules, but just some of them are required to get the flag.
After for a while, i found that only conditions where equal condition appears and the operation can be convert back like (add, sub, mul, xor) that actual help us find the flag. Wickly remove all reduntdant condition, all the left is just kind easy to find.
uint8(58) + 25 == 122
uint32(52) ^ 425706662 == 1495724241
uint32(17) - 323157430 == 1412131772
hash.crc32(8, 2) == 0x61089c5c
hash.crc32(34, 2) == 0x5888fc1b
uint8(36) + 4 == 72
uint8(27) ^ 21 == 40
uint32(59) ^ 512952669 == 1908304943
uint8(65) - 29 == 70
uint8(45) ^ 9 == 104
uint32(28) - 419186860 == 959764852
uint8(74) + 11 == 116
hash.crc32(63, 2) == 0x66715919
hash.sha256(14, 2) == "403d5f23d149670348b147a15eeb7010914701a7e99aad2e43f90cfa0325c76f"
hash.sha256(56, 2) == "593f2d04aab251f60c9e4b8bbc1e05a34e920980ec08351a18459b2bc7dbf2f6"
uint8(75) - 30 == 86
uint32(66) ^ 310886682 == 849718389
uint32(10) + 383041523 == 2448764514
uint32(37) + 367943707 == 1228527996
uint32(22) ^ 372102464 == 1879700858
uint8(2) + 11 == 119
hash.md5(0, 2) == "89484b14b36a8d5329426a3d944d2983"
uint32(46) - 412326611 == 1503714457
hash.crc32(78, 2) == 0x7cab8d64
uint8(7) - 15 == 82
uint32(70) + 349203301 == 2034162376
hash.md5(76, 2) == "f98ed07a4d5f50f7de1410d905f1477f"
uint32(80) - 473886976 == 69677856
uint32(3) ^ 298697263 == 2108416586
uint8(21) - 21 == 94
uint8(16) ^ 7 == 115
uint32(41) + 404880684 == 1699114335
hash.md5(50, 2) == "657dae0913ee12be6fb2a6f687aae1c7"
uint8(26) - 7 == 25
hash.md5(32, 2) == "738a656e8e8ec272ca17cd51e12f558b"
uint8(84) + 3 == 128
For hash check, because it’s just two characters for each check, it can be easy to crack by brute force
The left it just recover back to from add/sub/xor
Flag: 1RuleADayK33p$Malw4r3Aw4y@flare-on.com
Challenge 4 – Meme Maker 3000
You’ve made it very far, I’m proud of you even if noone else is. You’ve earned yourself a break with some nice HTML and JavaScript before we get into challenges that may require you to be very good at computers.
Given an HTML file, open it in the browser, it’s exactly “mememaker”
Take a look into logic javascript code, it’s so long and fully obfuscated.
Deobfuscate this code can be done by many tools, for me, I use this
The deobfuscated code is pretty simple, in that, the a0k function is likely to have the flag.
function a0k() {
const a = a0g.alt.split('/').pop()
if (a !== Object.keys(a0e)[5]) {
return
}
const b = a0l.textContent,
c = a0m.textContent,
d = a0n.textContent
if (
a0c.indexOf(b) == 14 &&
a0c.indexOf(c) == a0c.length - 1 &&
a0c.indexOf(d) == 22
) {
var e = new Date().getTime()
while (new Date().getTime() < e + 3000) {}
var f =
d[3] +
'h' +
a[10] +
b[2] +
a[3] +
c[5] +
c[c.length - 1] +
'5' +
a[3] +
'4' +
a[3] +
c[2] +
c[4] +
c[3] +
'3' +
d[2] +
a[3] +
'j4' +
a0c[1][2] +
d[4] +
'5' +
c[2] +
d[5] +
'1' +
c[11] +
'7' +
a0c[21][1] +
b.replace(' ', '-') +
a[11] +
a0c[4].substring(12, 15)
f = f.toLowerCase()
alert(atob('Q29uZ3JhdHVsYXRpb25zISBIZXJlIHlvdSBnbzog') + f)
}
}
The decode base64 in alert (“Congratulations! Here you go: “) suggests that f is the flag we have to find.
f is constructed from a, b, c, d, a0c variable where:
- a0c: hardcode array of string
- a: a0e[5] ==> ‘boy_friend0.jpg’
- b: a0c[14] ==> ‘FLARE On’
- c: a0c[len(a0c) – 1] ==> ‘Security Expert’
- d: a0c[22] ==> ‘Malware’
==> f: wh0A_it5_4_cru3l_j4va5cr1p7@FLARE-On.com
the flag is in the lower form: wh0a_it5_4_cru3l_j4va5cr1p7@flare-on.com
Flag: wh0a_it5_4_cru3l_j4va5cr1p7@flare-on.com
We can also change the js code that always match the condition so that the browser shows the flag for us
Challenge 5 – sshd
Our server in the FLARE Intergalactic HQ has crashed! Now criminals are trying to sell me my own data!!! Do your part, random internet hacker, to help FLARE out and tell us what data they stole! We used the best forensic preservation technique of just copying all the files on the system for you.
Flareon team gave us a copy of a system, with a hint that the server crashed, and it may be related to sshd. As it hints that the server is crashed, searching for the core-dump file, we found that it’s exactly sshd coredump.
Using gdb, we can find where the system has crashed. To make gdb recognize all shared lib that crashed program load, we need to let gdb know the right library folder, not on your own system but the /usr/lib and /usr/lib64 of the given one.
set set solib-absolute-prefix
file ./usr/sbin/sshd
core-file ./var/lib/systemd/coredump/sshd.core.93794.0.0.11.1725917676
using two commands “info shared library” and “info proc mappings” we can find where libraries loaded on mem. Typically, a lib appears as deleted, but combines the result of two commands, we can conclude that it’s liblzma.so
Using bt
command, we can find the return address, and from that, we can identify where it crashed
The return address is 0x0007f4a18c8f88f which is in the range of liblzma.so.5
sshd and liblzma, which lead us to the famous CVE-2024-3094
According to the blog, it hooks the RSA_public_decrypt function. Take a look into binary liblzma.so.5, we can easily find where the backdoor located
To find the key and nonce, the code reveals that it starts with ‘\x48\x7A\x40\xC5’, search the sequence bytes in coredump, and we can quickly find the key and nonce that
key: 94 3d f6 38 a8 18 13 e2 de 63 18 a5 7 f9 a0 ba 2d bb 8a 7b a6 36 66 d0 8d 11 a6 5e c9 14 d6 6f
nonce: f2 36 83 9f 4d cd 71 1a 52 86 29 55
Decrypt shellcode, and we get beautiful code that can F5 easily :))
The logic is kinda straightforward, connect socket, receive key, nonce for “salcha20”, filename to read, read file, then encrypt “salcha20”, send back to server.
All the info may be still on the stack, so to find the info, we have to find the stackframe of this backdoor function.
Start of shellcode
The frame contain info is inside the main_backdoor
RBP of main_backdoor = RBP sub_0 – (5(push) + 1(call)) * 8
RBP of sub_0 = (RSP before call backdoor) – (1(push) + 1(call)) * 8
For now, finding the key, nonce, and encrypted data is not as easy as before, cause no hint or signature to find. We need to lean on the current debug status, find exactly where the server crashed, and trace back.
RIP = 0, which leads to a crash, it may be due to a call/jmp to 0, from the return address found before, we can find the RVA of this address, which is 0x988F (0x0007f4a18c8f88f – 0x7f4a18c86000). Following the return RVA, the call above is called RAX, and RAX in context is exactly 0.
Read the assembly code, we can see that, the RSP register value we want to find will be plus 8 compared to the current RSP, due to call instruction.
from all above info, RBP of main_backdoor = current_rsp + 8 – 8 * 8 = 0x7ffcc6601e98 – 0x38 = 0x7ffcc6601e60
==> key
==> nonce:
==> file_name:
==> encrypted data
For encrypted data, i take until it meets the null byte (actually only 0x40 bytes from the start).
Try decrypting using Cyberchef, but it doesn’t work 🙂 After a while, I found that salcha20 is customized a little bit, not “expand 32-byte k” as normal, but “expand 32-byte K” 🙂
To decrypt, I chose to write a program that run that shellcode, with key, nonce, and enc as above.
Flag: supp1y_cha1n_sund4y@flare-on.com
Read more:
Part 2: https://sec.vnpt.vn/2024/11/flareon-11-writeup-part-2/
Part 3: https://sec.vnpt.vn/2024/11/flareon-11-writeup-part-3/