Intro

This year I solved all the challenges of Flare-On 2024. Due to my unemployment, I had time to focus on those challenges. (If you’re hiring please email me mustafa at mustafadur.com) I spend so much time and money(challenge 9) to solve those challenges. I wrote a very short blog post for my solutions. My solutions are not very straightforward or pretty. You may check the official site for both the solutions and binaries.

I uploaded my notes and solution scripts to my GitHub repo

1 frog

I actually never ran this challenge to solve it. I opened the frog.py file and saw a function named GenerateFlagText. I searched how this function is used inside the file and found the following reference:

if player.x == victory_tile.x and player.y == victory_tile.y:
 victory_mode = True
 flag_text = GenerateFlagText(player.x, player.y)

So our x and y positions must be equal to victorytile coordinates. When we search this reference we find it as `victorytile = pygame.Vector2(10, 10)In order to invokeGenerateFlagText` coordinates must be 10,10 So running the below scrip shows us the flag

def GenerateFlagText(x, y):
 key = x + y*20
 encoded = "\xa5\xb7\xbe\xb1\xbd\xbf\xb7\x8d\xa6\xbd\x8d\xe3\xe3\x92\xb4\xbe\xb3\xa0\xb7\xff\xbd\xbc\xfc\xb1\xbd\xbf"
    return ''.join([chr(ord(c) ^ key) for c in encoded])

flag = GenerateFlagText(10,10)

print(flag)
welcome_to_11@flare-on.com

2 checksum

When the application starts it creates a random number between 3 and 8. This is the number of questions it is going to ask. For each iteration, two random numbers are generated and it asks for the result of the equation. Example:

Check sum: 7594 + 2828 = 10422
Good math!!!
------------------------------
Check sum: 9020 + 199 = 9219
Good math!!!
------------------------------
Check sum: 7741 + 5813 = 13554
Good math!!!
------------------------------
Check sum: 3657 + 5065 = 8722
Good math!!!
------------------------------
Check sum: 3201 + 1808 = 5009
Good math!!!
------------------------------
Checksum:

At this point it expects us to write a hex string since we see encoding_hex_Decode function. Writing a random hex string gives us Maybe it's time to analyze the binary! ;) error message.

We also see that this hex string must be 32 bytes when it was decoded. It then initializes chacha20poly1305_xchacha20poly1305 class. Then tries to decrypt main_encryptedFlagData with checksum as both key and nonce. The size of this buffer is 0x2C52C.

After decoding, it calculates the SHA256 of the decoded main_encryptedFlagData Then it compares this hash to our checksum. We have a dilemma here. Without the correct key we can’t decrypt the main_encryptedFlagData we can’t brute-force the SHA256 hash too. However, when we analyze further we find another function named main_a we see that it xors our checksum value with FlareOn2024 and creates a base64 encoded string, and then compares this with cQoFRQErX1YAVw1zVQdFUSxfAQNRBXUNAxBSe15QCVRVJ1pQEwd/WFBUAlElCFBFUnlaB1ULByRdBEFdfVtWVA== To find our correct checksum following script will be enough

import base64

def decode_xor(encoded, key):
 decoded = base64.b64decode(encoded)
    return bytes([b ^ key[i % len(key)] for i, b in enumerate(decoded)])

encoded_string = 'cQoFRQErX1YAVw1zVQdFUSxfAQNRBXUNAxBSe15QCVRVJ1pQEwd/WFBUAlElCFBFUnlaB1ULByRdBEFdfVtWVA=='
key = b"FlareOn2024"
result = decode_xor(encoded_string, key)
print(result.decode('utf-8', errors='replace'))

Our checksum must be 7fd7dd1d0e959f74c133c13abb740b9faa61ab06bd0ecd177645e93b1e3825dd When we use this value, the program extracts the flag and saves it as REAL_FLAREON_FLAG.JPG to the path which is the result of os_UserCacheDir When we open this picture we get our flag

Th3_M4tH_Do_b3_mAth1ng@flare-on.com

3 aray

We only get a single file named aray.yara file. We need to create a file that should trigger this YARA rule. I didn’t solve this in a pretty way lol. I wrote a very basic parser that extracts the bytes of the flag for basic math operations. Such as

uint32 ( 52 ) ^ 425706662 == 1495724241 I combined those values to partially solve the string as

1R####DayK33p$Malw4r##w4y@##are-o##com Then I manually tested the rest of the chars to extract the flag as

1RuleADayK33p$Malw4r3Aw4y@flare-on.com

4 Meme Maker 3000

There is a single html file which has obfuscated JavaScript file. You can use any tool such as obfuscator-io-deobfuscator to deobfuscate the JavaScript file. However, I prefer manually decoding the script using jsdom. You can check my solutions repo to see how I load the raw javascript and just call the functions manually.

wh0a_it5_4_cru3l_j4va5cr1p7@flare-on.com

5 sshd

The challenge has only one file called ssh_container.tar When we list the files inside this tar file according to the last modified date we see the following list

./root/flag.txt - Last Modified: 2024-09-11 23:55:59
./var/lib/systemd/coredump/sshd.core.93794.0.0.11.1725917676 - Last Modified: 2024-09-10 00:34:36
./usr/lib/x86_64-linux-gnu/liblzma.so.5.4.1 - Last Modified: 2024-09-10 00:34:33

Of course flag.txt doesn’t have the flag. We need to check the next file which is a coredump of sshd process.

We load the coredump via gdb.

root@6a86c0a426d0:/# gdb sshd /var/lib/systemd/coredump/sshd.core.93794.0.0.11.1725917676

When we issue the bt command we can see the backtrace as follows.

#0  0x0000000000000000 in ?? ()
(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x00007f4a18c8f88f in ?? () from /lib/x86_64-linux-gnu/liblzma.so.5
#2  0x000055b46c7867c0 in ?? ()
#3  0x000055b46c73f9d7 in ?? ()
#4  0x000055b46c73ff80 in ?? ()
#5  0x000055b46c71376b in ?? ()
#6  0x000055b46c715f36 in ?? ()
#7  0x000055b46c7199e0 in ?? ()
#8  0x000055b46c6ec10c in ?? ()
#9  0x00007f4a18e5824a in __libc_start_call_main (main=main@entry=0x55b46c6e7d50, argc=argc@entry=4,
 argv=argv@entry=0x7ffcc6602eb8) at ../sysdeps/nptl/libc_start_call_main.h:58
#10 0x00007f4a18e58305 in __libc_start_main_impl (main=0x55b46c6e7d50, argc=4, argv=0x7ffcc6602eb8,
 init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7ffcc6602ea8)
 at ../csu/libc-start.c:360
#11 0x000055b46c6ec621 in ?? ()

It seems the sshd process crashed due to a function at 0x00007f4a18c8f88f inside liblzma.so.5 file. This file is symlinked to liblzma.so.5.4.1 Let’s check where it crashed

(gdb) info sharedlibrary
0x00007f4a18c8ad40  0x00007f4a18ca8d26  Yes (*)     /lib/x86_64-linux-gnu/liblzma.so.5

The above address is somewhere inside the text segment. In order to find out the base address of this we can check the mappings

(gdb) info proc mappings
 0x7f4a18c86000     0x7f4a18c8a000     0x4000        0x0 / (deleted)
 0x7f4a18c8a000     0x7f4a18ca9000    0x1f000     0x4000 / (deleted)
 0x7f4a18ca9000     0x7f4a18cb7000     0xe000    0x23000 / (deleted)

We can’t see the module name but since the 0x00007f4a18c8ad40 address is inside the 0x7f4a18c86000 0x7f4a18c8a000 range we can conclude that our base address is 0x7f4a18c86000

We can open the file in Ghidra and set the base address to 0x7f4a18c86000 via Window -> Memory map -> Set Image Base.

After we load our file to disassembler rebase the binary and jump to our crash address 0x00007f4a18c8f88f we see that it is crashing due to the dsylm function can’t find a function named RSA_public_decrypt with extra space. If we check the crossreference, we can see that this function is used like below

void _INIT_1(void)

{
  int iVar1;
  long in_FS_OFFSET;
  void *local_18;
  long local_10;
  
 local_10 = *(long *)(in_FS_OFFSET + 0x28);
 local_18 = (void *)0x0;
 iVar1 = FUN_7f4a18c8eb10(&local_18,_strlen,_strlen + 0x10);
  if (iVar1 == 0) {
    FUN_7f4a18c8f1b0(local_18,"RSA_public_decrypt",FUN_7f4a18c8f820,0);
    if (local_18 != (void *)0x0) {
      free(local_18);
 }
 }
  if (local_10 == *(long *)(in_FS_OFFSET + 0x28)) {
    return;
 }
 /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

If we check the strings referenced by FUN_7f4a18c8eb10 such as failed to find DT_STRTAB, we can find out that this function is used by plthook_elf.c to hook functions. So our function at the 0x7f4a18c8f820 address is a hooked function of RSA_public_decrypt. If we apply the function signature we get the following disassembly.

int RSA_public_decrypt_hook(int flen,void *from,uchar *to,RSA *rsa,int padding)

{
  __uid_t _Var1;
  int iVar2;
 code *pcVar3;
  void *__dest;
  char *pcVar4;
  long in_FS_OFFSET;
 undefined local_108 [200];
  long local_40;
  
 local_40 = *(long *)(in_FS_OFFSET + 0x28);
 _Var1 = getuid();
 pcVar4 = "RSA_public_decrypt";
  if (_Var1 == 0) {
 /* WARNING: Load size is inaccurate */
    if (*from == -0x3abf85b8) {
      FUN_7f4a18c8f3f0(local_108,(long)from + 4,(long)from + 0x24,0);
 __dest = mmap((void *)0x0,(long)DAT_7f4a18cb8360,7,0x22,-1,0);
 pcVar3 = (code *)memcpy(__dest,&DAT_7f4a18ca9960,(long)DAT_7f4a18cb8360);
      FUN_7f4a18c8f520(local_108,pcVar3,(long)DAT_7f4a18cb8360);
 (*pcVar3)();
      FUN_7f4a18c8f3f0(local_108,(long)from + 4,(long)from + 0x24,0);
      FUN_7f4a18c8f520(local_108,pcVar3,(long)DAT_7f4a18cb8360);
 }
 pcVar4 = "RSA_public_decrypt ";
 }
 pcVar3 = (code *)dlsym(0,pcVar4);
 iVar2 = (*pcVar3)(flen,from,to,rsa,padding);
  if (local_40 == *(long *)(in_FS_OFFSET + 0x28)) {
    return iVar2;
 }
 /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

This hook is only applied if the from parameter is equal to 0xc5407a48 I don’t know why ghidra is showing as -0x3abf85b8

We must search these magic bytes 0x48, 0x7A, 0x40, 0xC5 inside the coredump to find the parameters of this function. This magic byte is shown in 4 places and like below

487a40c5943df638a81813e2d.....

FUN_7f4a18c8f3f0(local_108,(long)from + 4,(long)from + 0x24,0);

From the above disassembly, we can see that the first 4 bytes are skipped and the next 0x20(32) bytes are used as key for ChaCha and after the key, at 0x24 we have the nonce.

key = “943df638a81813e2de6318a507f9a0ba2dbb8a7ba63666d08d11a65ec914d66f” nonce = “f236839f4dcd711a52862955”

I used Unicorn to decrypt the buffer. After decryption, I created new binary and changed the bytes at 0x7f4a18ca9960 with decrypted ones. When we disassemble it, we see that this connects to 10.0.2.15 at port 13337 and receives 4 things from the server. Which are;

  • Key 0x20 bytes
  • Nonce 0xC bytes
  • File name size
  • Filename

Then it reads these 0x80 bytes from this file, encrypts it, and sends it to this server. We need to go back to our coredump file and find a suspicious file that could be used in this operation. We find the following data in the memory

00007FFCC6600BE8                 db 8Dh, 0ECh, 91h, 12h, 0EBh, 76h, 0Eh, 0DAh, 7Ch, 7Dh
00007FFCC6600BF2                 db 87h, 0A4h, 43h, 27h, 1Ch, 35h, 0D9h, 0E0h, 0CBh, 87h
00007FFCC6600BFC                 db 89h, 93h, 0B4h, 0D9h, 4, 0AEh, 0F9h, 34h, 0FAh, 21h
00007FFCC6600C06                 db 66h, 0D7h
00007FFCC6600C08                 db 11h, 11h, 11h, 11h, 11h, 11h, 11h, 11h, 11h, 11h, 11h
00007FFCC6600C13                 db 11h
00007FFCC6600C14                 dd 20h
00007FFCC6600C18                 db '/root/certificate_authority_signing_key.txt',0

According to this, our key is 8DEC9112EB760EDA7C7D87A443271C35D9E0CB878993B4D904AEF934FA2166D7 and our nonce is 111111111111111111111111

However, encryption is not the same as before. Therefore, we need to again emulate this function. At this point, I lifted the functions from the binary and ran it manually which gave me the following flag.

supp1y_cha1n_sund4y@flare-on.com

6 bloke2

This challenge was easier than I thought. When we check the files we see a following line

 localparam TEST_VAL = 512'h3c9cf0addf2e45ef548b011f736cc99144bdfee0d69df4090c8a39c520e18ec3bdc1277aad1706f756affca41178dac066e4beb8ab7dd2d1402c4d624aaabe40;

This looks like part of the flag. It is being used like below.

h <= h_in ^ (TEST_VAL & {(W*16){tst}});

The result h depends on the value of tst. We see that it is wired as tst <= finish;

If we change tst as 1 tst <= 1'b1; and run make test we get our flag

please_send_help_i_am_trapped_in_a_ctf_flag_factory@flare-on.com

7 fullspeed

This file is .NET file compiled as AOT. It is hard to disassemble this file without the signatures. I saw some references to Bouncy Castle and ECC(Elliptic Curve Cryptography) in the binary. However, it was hard to understand what was going on.

I used this blog post to create a signature and applied to binary.

This program uses ECC to share information. Parameters of ECC are being xored with 133713371337133713371337133713371337133713371337133713371337133713371337133713371337133713371337 during key exchange. We have the following parameters for the ECC.

p = 0xc90102faa48f18b5eac1f76bb40a1b9fb0d841712bbe3e5576a7a56976c2baeca47809765283aa078583e1e65172a3fd
a = 0xa079db08ea2470350c182487b50f7707dd46a58a1d160ff79297dcc9bfad6cfc96a81c4a97564118a40331fe0fc1327f
b = 0x9f939c02a7bd7fc263a4cce416f4c575f28d0c1315c4f0c282fca6709a5f9f7f9c251c9eede9eb1baa31602167fa5380
gx = 0x087b5fe3ae6dcfb0e074b40f6208c8f6de4f4f0679d6933796d3b9bd659704fb85452f041fff14cf0e9aa7e45544f9d8
gy = 0x127425c1d330ed537663e87459eaa1b1b53edfe305f6a79b184b3180033aab190eb9aa003e02e9dbf6d593c5e3b08182
px = 0x195b46a760ed5a425dadcab37945867056d3e1a50124fffab78651193cea7758d4d590bed4f5f62d4a291270f1dcf499
py = 0x357731edebf0745d081033a668b58aaa51fa0b4fc02cd64c7e8668a016f0ec1317fcac24d8ec9f3e75167077561e2a15

The order is almost smooth.

35809 * 46027 * 56369 * 57301 * 65063 * 111659 * 113111 *
7072010737074051173701300310820071551428959987622994965153676442076542799542912293

We can leave out the last big prime factor and apply Pohlig-Hellman algorithm to recover the private key. I used the sage script to recover the private key.

The script calculates the shared secret as

3C54F90F4D2CC9C0B62DF2866C2B4F0C5AFAE8136D2A1E76D2694999624325F5609C50B4677EFA21A37664B50CEC92C0

The server and client then calculate the SHA256 of this secret.

b48f8fa4c856d496acdecd16d9c94cc6b01aa1c0065b023be97afdd12156f3dc3fd480978485d8183c090203b6d384c20e853e1f20f88d1c5e0f86f16e6ca5b2

The server and client use part of this hash for key and nonce.

key =  bytes.fromhex("B48F8FA4C856D496ACDECD16D9C94CC6B01AA1C0065B023BE97AFDD12156F3DC")
nonce = bytes.fromhex("3FD480978485D818")

If we decrypt the traffic as a single stream we get our flag as base64 encoded string. Decodindg this string gives us the flag.

D0nt_U5e_y0ur_Own_CuRv3s@flare-on.com

8 clearlyfake

This challenge was mostly a guesswork. After you decrypt the javascript, we get the address of the smart contract 0x9223f0630c598a200f99c5d4746531d10319a569 We need to analyze the bytecode. I used the decompiler at dedaub.com to further analyze it. If the parameter of the function is giV3_M3_p4yL04d! we get the next address which is 0x5324EAB94b236D4d1456Edc574363B113CEbf09d By using the block number 43152014 and smart contract address, we can further analyze the state of the contract at address https://testnet.bscscan.com/tx/0x05660d13d9d92bc1fc54fb44c738b7c9892841efc9df4b295e2b7fda79756c47#statechange Base64 decoding this string gives us another PowerShell script. I decoded this script with PowerDecode Decoded script looks like below

$testnet_endpoint = "ENDPOINT"
$_body = '{"method":"eth_call","params":[{"to":"$address","data":"0x5c880fcb"},
BLOCK],"id":1,"jsonrpc":"2.0"}'
$resp = (Invoke-RestMethod -Uri $testnet_endpoint -Method 'Post' -Body $_body -ContentType
"application/json").result

# Remove the '0x' prefix from the response
Set-Variable -Name hexNumber -Value ($resp -replace '0x', '')

# Convert from hex to bytes
Set-Variable -Name bytes0 -Value (0..($hexNumber.Length / 2 - 1) | ForEach-Object {
    Set-Variable -Name startIndex -Value ($_ * 2)
 [Convert]::ToByte($hexNumber.Substring($startIndex, 2), 16)
})

# Convert bytes to string and extract part of it
Set-Variable -Name bytes1 -Value ([System.Text.Encoding]::UTF8.GetString($bytes0))
Set-Variable -Name bytes2 -Value ($bytes1.Substring(64, 188))

# Convert extracted string from base64 to bytes
Set-Variable -Name bytesFromBase64 -Value ([Convert]::FromBase64String($bytes2))
Set-Variable -Name resultAscii -Value ([System.Text.Encoding]::UTF8.GetString($bytesFromBase64))

# Convert ASCII result to hex bytes
Set-Variable -Name hexBytes -Value ($resultAscii | ForEach-Object {
    '{0:X2}' -f $_  # Format each byte as two-digit hex
})

# Join hex bytes into a single string
Set-Variable -Name hexString -Value ($hexBytes -join ' ')

# Remove spaces from hex string
Set-Variable -Name hexBytes -Value ($hexBytes -replace " ", "")

# Convert hex back to bytes
Set-Variable -Name bytes3 -Value (0..($hexBytes.Length / 2 - 1) | ForEach-Object {
    Set-Variable -Name startIndex -Value ($_ * 2)
 [Convert]::ToByte($hexBytes.Substring($startIndex, 2), 16)
})

# Convert the resulting bytes back to a string
Set-Variable -Name bytes5 -Value ([System.Text.Encoding]::UTF8.GetString($bytes3))

# Convert the key "FLAREON24" to bytes
Set-Variable -Name keyBytes -Value ([System.Text.Encoding]::ASCII.GetBytes("FLAREON24"))

# Perform the XOR operation
Set-Variable -Name resultBytes -Value (@())
for (Set-Variable -Name i -Value (0); $i -lt $bytes5.Length; $i++) {
    Set-Variable -Name resultBytes -Value ($resultBytes + ($bytes5[$i] -bxor $keyBytes[$i % $keyBytes.Length]))
}

# Convert the result back to an ASCII string
Set-Variable -Name resultString -Value ([System.Text.Encoding]::ASCII.GetString($resultBytes))

# Prepare the command for extracting a tar file
Set-Variable -Name command -Value ("tar -x --use-compress-program 'cmd /c echo $resultString > C:\\flag' -f C:\\flag")

# Execute the command
Invoke-Expression $command

This is basically base64 decodes a string and then converts the result from hex to string and finally xor the result with FLAREON24 key. By combining the state values at https://testnet.bscscan.com/tx/0xae4711c6e9d6d8f5d00a88e1adb35595bc7d7a73130e87356e3e71e65e17f337#statechange we get the following string NDEgM2EgN2EgN2IgM2MgN2MgM2QgNGEgNTAgNGUgNWUgNzYgNDQgNTUgNjcgMTEgNTAgNWUgNjYgMTUgM2EgNTUgM2YgMTcgM2MgM2QgNTEgMTUgNjEgNTUgNTkgNDEgNmQgMzkgNGUgNDIgNjMgNmIgN2MgNDEgMjIgNjUgNjAgMGEgNmMgNjUgNjM= Base64 decoding and xoring with FLAREON24 gives us the flag as

N0t_3v3n_DPRK_i5_Th15_1337_1n_Web3@flare-on.com

9 serpentine

This is it. This was the hardest challenge of Flareon-2024. I spent almost a month and only partially solved it and brute-forced a couple of bytes. I even bought a mini PC since I couldn’t trace the binary on my Macbook M3 pro machine via VM. This challenge cost me both time and money lol. Since I didn’t solve this one fully, I wouldn’t write my partial solution here. However, you can find the unicorn scripts I used to emulate famous UNWIND_INFO and other stuff.

10 CatbertRansomware

After spending so much time on serpentine, I had only 2 days left to solve this challenge. This challenge is a UEFI challenge. You can run the challenge via the below command:

qemu-system-x86_64 -drive format=raw,file=disk.img -biosbios.bin

We see a cute cat logo which says that our files are encrypted. To analyze this firmware, we need to find the module responsible for decryption. You can use UEFITool to extract the module. Make sure you click Show all assets link and download UEFITool. After you download the tool open the bios.bin and click Action/Search/Text and type decrypt Click one of the search results and right click PE32 Image and select extract body. The address of the decrypt function is 31BC4. If checks whether the file header has C4TB and tries to decrypt the files with the given password. The length of the password is 16 bytes(32 in Unicode). Before using the password, it checks the validity of a password by using the file as input and custom VM handler at address 31274. The structure of the file is something like below

 magic db "C4TB"
 len   dd 0
 start dd 0
 decrypt_len dd 0

If the key passes the checks of this VM, qword_168ED0 is set to 1. At this point, I didn’t have much time to write a decompiler for this custom VM. Instead, I lifted the VM logic to custom binary and wrote a script to solve it via angr. The first two files were decrypted very quickly but the last one took 1.5 hours. Running my script gives us the cute result

🐾🔍 Investigating meowstery catmeme1.jpg.c4tb
    🐈 Found a paw-sible solution: DaCubicleLife101
🐾🔍 Investigating meowstery catmeme2.jpg.c4tb
    🐈 Found a paw-sible solution: G3tDaJ0bD0neM4te
🐾🔍 Investigating meowstery catmeme3.jpg.c4tb
    🐈 Found a paw-sible solution: VerYDumBpassword

Decrypting all those files one by one will give us the following partial flag.

th3_ro4d_t0_succ3ss_1s_alw4ys_

You need to decrypt files one by one in a single session. I made a mistake not once but twice to close the VM lol. After decrypting the files, it says:

0x10 bytes successfully written to your_mind.jpg.c4tb.
You thought you were clever, huh?
Thought you'd find your precious answer here? Well, tough luck.
You were almost right, but not quite.
I've left another little surprise for you on disk.
Your reward is the password: 'BrainNumbFromVm!'. Enjoy your headache, human.
And remember, I'm always watching.

When we decrypt the file via

decrupt_file your_mind. jpg.c4tb BrainNumbFromVm!

We get an ASCII string _und3r_c0nstructi0n as the remaining part of the flag. Combining them gives us the final flag.

th3_ro4d_t0_succ3ss_1s_alw4ys_und3r_c0nstructi0n@flare-on.com
I am actively job-hunting and available
Interested? Feel free to reach