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

image

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

image

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)

image

image

image

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…”.

image

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! ;)”

image

image

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

image

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

image

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.

image

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.

image

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”

image

Take a look into logic javascript code, it’s so long and fully obfuscated.

image

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

image

 

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.

image

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

image

image

Using bt command, we can find the return address, and from that, we can identify where it crashed

image

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

image

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

image

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 :))

image

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

image

The frame contain info is inside the main_backdoor

image

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.

image

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.

image

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

image

==> nonce:

image

==> file_name:

image

==> encrypted data

image

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/

326 lượt xem