Flareon 11 Writeup Part 1

Published By: BLUE TEAM

Published On: 11/11/2024

Published in:

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/
1374 lượt xem