## Challenge 1: frog
Reading the source code, it is easy to see that when the character jumps to the cell with coordinates (10, 10), the code to generate the flag is called:
```python=
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])
```
Solve script:
```python=
key = 210
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"
flag = ''.join([chr(ord(c) ^ key) for c in encoded])
print(flag)
```
Flag: `welcome_to_11@flare-on.com`
## Challenge 2: checksum
Running the exe file, we see that the challenge requires solving a series of calculations. If the answer is wrong, it will exit; if correct, it will ask for an input, which is the key of the challenge.
Using IDA, observing the `main_main` function, we see the code to generate the flag:

Analyzing the program flow, we see that this code is called based on the condition of the `main_a` function, which takes a parameter as the string we need to input. Thus, `main_a` is the "check key" function we are looking for:

Checking the `main_a` function, we see that the input is XORed with the string `FlareOn2024`, then encoded with base64:

Solve script:
```python=
import base64
import binascii
key = b"FlareOn2024"
encode_str = b'cQoFRQErX1YAVw1zVQdFUSxfAQNRBXUNAxBSe15QCVRVJ1pQEwd
/WFBUAlElCFBFUnlaB1ULByRdBEFdfVtWVA=='
flag = b""
decode_str = base64.b64decode(encode_str)
for i in range(len(decode_str)):
c = key[i % 11] ^ decode_str[i]
flag += bytes([c])
print(flag)
# 7fd7dd1d0e959f74c133c13abb740b9faa61ab06bd0ecd177645e93b1e3825dd
```
Result:

But where is the flag?? Going back to the code in the `main_main` function, we see that right above the code to generate the flag is the `os_UserCacheDir()` function, which gets the destination path to save the flag => C:\Users\user_name\AppData\Local.
Flag:

## Challenge 3: aray
The challenge provides a yara rule file, in which the size of the file to be found is only 85 bytes, so it can be predicted that the file to be found is a text containing the flag. Noticing the `==` conditions, we can accurately find the character at that offset, so we will write a script to find these characters first (then we can brute force because the size has been reduced and we have the file's hash):
```python=
import string
import hashlib
import zlib
from Cryptodome.Util.number import *
with open("aray.yara","r") as f:
data = f.read().split("and")[2:]
hash = []
xor = []
sub = []
add = []
mod = []
flag = ["_"]*85
check = []
def brute_md5(s):
for i in range(256):
for j in range(256):
byte = bytes([i,j])
if md5_hash(byte) == s:
return byte
def brute_sha256(s):
for i in range(256):
for j in range(256):
byte = bytes([i,j])
if sha256_hash(byte) == s:
return byte
def brute_crc32(s):
for i in range(256):
for j in range(256):
byte = bytes([i,j])
if crc32_hash(byte) == s:
return byte
def md5_hash(data):
return hashlib.md5(data).hexdigest()
def sha256_hash(data):
return hashlib.sha256(data).hexdigest()
def crc32_hash(data):
return zlib.crc32(data) & 0xffffffff
def solve_hash(s):
global flag
index = s[s.find("(")+ 1:s.find(")")].split(",")[0]
h = s[6:s.find("(")]
s = s.split()
if h == "md5":
d = s[-1][1:-1]
f = brute_md5(d)
elif h == "sha256":
d = s[-1][1:-1]
f = brute_sha256(d)
elif h == "crc32":
d = s[-1][2:]
f = brute_crc32(int(d,16))
for i in range(int(index),int(index)+len(f)):
flag[i] = long_to_bytes(f[i-int(index)]).decode()
def solve_xor(s):
global flag
index = s[s.find("(")+ 1:s.find(")")]
s = s.split()
s = long_to_bytes(int(s[2])^int(s[4]))
if len(s.decode()) == 4: s = s[::-1]
for i in range(int(index),int(index)+len(s)):
flag[i] = long_to_bytes(s[i-int(index)]).decode()
def solve_sub(s):
global flag
index = s[s.find("(")+ 1:s.find(")")]
s = s.split()
s = long_to_bytes(int(s[2])+int(s[4]))
if len(s.decode()) == 4: s = s[::-1]
for i in range(int(index),int(index)+len(s)):
flag[i] = long_to_bytes(s[i-int(index)]).decode()
def solve_add(s):
global flag
index = s[s.find("(")+ 1:s.find(")")]
s = s.split()
s = long_to_bytes(int(s[4])-int(s[2]))
if len(s.decode()) == 4: s = s[::-1]
for i in range(int(index),int(index)+len(s)):
flag[i] = long_to_bytes(s[i-int(index)]).decode()
for i in data:
if i.find("^") != -1 and i.find("==")!= -1:
solve_xor(i)
if i.find("hash")!= -1 and i.find("==")!= -1:
#print(i)
solve_hash(i)
if i.find("-") != -1 and i.find("==")!= -1:
solve_sub(i)
if i.find("+") != -1 and i.find("==")!= -1:
solve_add(i)
flag = "".join(flag)
print(flag)
```
A lucky thing in this challenge is that we can find the flag immediately:
`rule flareon { strings: $f = "1RuleADayK33p$Malw4r3Aw4y@flare-on.com" condition: $f }`
## Challenge 4: FLARE Meme Maker 3000
Export the javascript code in the HTML file of the challenge, then use [this tool](https://deobfuscate.relative.im/) to deobfuscate:
```javascript=
function a0f() {
document.getElementById('caption1').hidden = true
document.getElementById('caption2').hidden = true
document.getElementById('caption3').hidden = true
const a = document.getElementById('meme-template')
var b = a.value.split('.')[0]
a0d[b].forEach(function (c, d) {
var e = document.getElementById('caption' + (d + 1))
e.hidden = false
e.style.top = a0d[b][d][0]
e.style.left = a0d[b][d][1]
e.textContent = a0c[Math.floor(Math.random() * (a0c.length - 1))]
})
}
a0f()
const a0g = document.getElementById('meme-image'),
a0h = document.getElementById('meme-container'),
a0i = document.getElementById('remake'),
a0j = document.getElementById('meme-template')
a0g.src = a0e[a0j.value]
a0j.addEventListener('change', () => {
a0g.src = a0e[a0j.value]
a0g.alt = a0j.value
a0f()
})
a0i.addEventListener('click', () => {
a0f()
})
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) {}
// a = boy_friend0.jpg
// b = FLARE On
// c = Security Expert
// d = Malware
var f =
d[3] + // w
'h' +
a[10] + // 0
b[2] + // A
a[3] + // _
c[5] + // i
c[c.length - 1] + // t
'5' +
a[3] + // _
'4' +
a[3] + // _
c[2] + // c
c[4] + // r
c[3] + // u
'3' +
d[2] + // l
a[3] + // _
'j4' +
a0c[1][2] + // v
d[4] + // a
'5' +
c[2] + // c
d[5] + // r
'1' +
c[11] + // p
'7' +
a0c[21][1] + // @
b.replace(' ', '-') + // flare-on
a[11] + // .
a0c[4].substring(12, 15) // com
f = f.toLowerCase()
alert(atob('Q29uZ3JhdHVsYXRpb25zISBIZXJlIHlvdSBnbzog') + f)
}
}
const a0l = document.getElementById('caption1'),
a0m = document.getElementById('caption2'),
a0n = document.getElementById('caption3')
a0l.addEventListener('keyup', () => {
a0k()
})
a0m.addEventListener('keyup', () => {
a0k()
})
a0n.addEventListener('keyup', () => {
a0k()
})
```
Flag: `wh0a_it5_4_cru3l_j4va5cr1p7@flare-on.com`
## Challenge 5: sshd
First, I build an docker image from tar file provided by challenge. Follow the decription, the server has crashed so I look for a sshd core dump file. Its name is `sshd.core.93794.0.0.11.1725917676` and located in `/var/lib/systemd/coredump`.
Using gdb to debug sshd with this core dump file:

This process crashed since calling to a null pointer. Saved rip is `0x7f4a18c8f88f`, this indicates that before this address is a `call` instruction. To examine where this address belongs to, I use `info sharedlibrary`:

Here it is, `/lib/x86_64-linux-gnu/liblzma.so.5`. Now I calculate offset from above address and got the result `988f`, so I find this function in IDA:
```clike=
__int64 __fastcall sub_9820(unsigned int a1, _DWORD *a2,
__int64 a3, __int64 a4, unsigned int a5)
{
const char *v9; // rsi
void *v10; // rax
void *v12; // rax
void (*v13)(void); // [rsp+8h] [rbp-120h]
char v14[200]; // [rsp+20h] [rbp-108h] BYREF
unsigned __int64 v15; // [rsp+E8h] [rbp-40h]
v15 = __readfsqword(0x28u);
v9 = "RSA_public_decrypt";
if ( !getuid() )
{
if ( *a2 == 0xC5407A48 )
{
sub_93F0(v14, a2 + 1, a2 + 9, 0LL);
v12 = mmap(0LL, dword_32360, 7, 0x22, 0xFFFFFFFF, 0LL);
v13 = memcpy(v12, &unk_23960, dword_32360);
sub_9520(v14, v13, dword_32360);
v13();
sub_93F0(v14, a2 + 1, a2 + 9, 0LL);
sub_9520(v14, v13, dword_32360);
}
v9 = "RSA_public_decrypt ";
}
v10 = dlsym(0LL, v9);
return (v10)(a1, a2, a3, a4, a5);
}
```
This function is trying hook `RSA_public_decrypt` by another patch (with space at the end of the name). It examine uid and 4 bytes header pointed by `a2` before run the suspicios code. I look in `sub_93F0` and `sub_9520` but their logic is so confusing, so I try to search and use ChatGPT to analyze them. Finally, two function are parts of ChaCha20 stream cipher, so what to do now is extract this cipher's key and nonce.
As you can see, the key and nonce are pointed by `a2`, which argument used for `call` instruction that caused this crash. Compare psudecode with assembly, I realize that register `rsi` save `a2`, so I can print it by command in gdb:

4 bytes header is `0xC5407A48`, 32 bytes follow is key, and 12 bytes end is nonce (48=4+32+12). With cipher text at address 0x23960 and its size is 0xF96, I use CyberChef to decrypt it and replace these encrypted bytes in `liblzma.so.5.4.1` by output:
```clike=
__int64 __fastcall sub_24722(__int64 a1, __int64 a2, __int64 a3)
{
unsigned int v3; // ebx
signed __int64 v4; // rax
signed __int64 v5; // rax
signed __int64 v6; // rax
signed __int64 v7; // rax
signed __int64 v8; // rax
signed __int64 v9; // rax
unsigned __int64 v10; // kr08_8
signed __int64 v11; // rax
signed __int64 v12; // rax
char ubuf[32]; // [rsp+410h] [rbp-1278h] BYREF
char v15[16]; // [rsp+430h] [rbp-1258h] BYREF
char filename[256]; // [rsp+440h] [rbp-1248h] BYREF
char buf[4224]; // [rsp+540h] [rbp-1148h] BYREF
unsigned int size; // [rsp+15C0h] [rbp-C8h] BYREF
unsigned int size_4; // [rsp+15C4h] [rbp-C4h] BYREF
LOWORD(a3) = 1337;
v3 = (sub_2397A)(a1, a2, a3);
v4 = sys_recvfrom(v3, ubuf, 32uLL, 0, 0LL, 0LL);
v5 = sys_recvfrom(v3, v15, 12uLL, 0, 0LL, 0LL);
v6 = sys_recvfrom(v3, &size, 4uLL, 0, 0LL, 0LL);
v7 = sys_recvfrom(v3, filename, size, 0, 0LL, 0LL);
filename[v7] = 0;
v8 = sys_open(filename, 0, 0);
v9 = sys_read(v8, buf, 128uLL);
v10 = strlen(buf) + 1;
size_4 = v10 - 1;
sub_24632(&buf[v10], buf, ubuf, v15, 0LL);
sub_246A9(&buf[v10], buf, buf, size_4);
v11 = sys_sendto(v3, &size_4, 4uLL, 0, 0LL, 0);
v12 = sys_sendto(v3, buf, size_4, 0, 0LL, 0);
sub_2396B();
sub_239EF(v3, buf, 0LL);
return 0LL;
}
```
Realize that `sub_2397A` is function create socket, with the appearances of `recv_from`, `open`, `read`, `send_to` so we can guest this shellcode is responsible for receive data from remote, do something with this and send output to remote.
Analyze `sub_24632` and `sub_246A0` I realize that these instructions are similar to ChaCha20 stream cipher mentioned above, but these function use 128-bit const `expand 32-byte K` instead of `expand 32-byte k`. So I think I must rewrite script to decrypt this custom ChaCha20, but first I must find key and nonce.
Let's look at `sub_24722`, we can see that:
- Shellcode store 32 bytes in `ubuf` (`rbp - 0x1278`) => this is key
- Shellcode store 12 bytes in `v15` (`rbp - 0x1258`) => this is nonce
- Shellcode store 4 bytes in `&size` (`rbp - 0xC8`) => this is size of data
- Shellcode store `size` bytes in `filename` (`rbp - 0x1248`) => this is file's name
- Shellcode read data from `filename` and store in `buf` (`rbp - 0x1148`)=> this is encrypted data
And now, we know that we need to calculate `rbp` when `sub_24722` called to get key, nonce and encrypted data. But how? Go back a bit, I know when program crashed the return address (saved rip) is saved at `0x7ffcc6601e98`, so before the `call` instruction execute `rsp = 0x7ffcc6601e98 + 8 = 0x7ffcc6601ea0`. This value of `rsp` is also the value of `rsp` right before the shellcode is called. After disassembly, I noticed that 2 `call` instructions and 6 `push` instruction are executed before `mov rbp, rsp` in `sub_24722` => so now the value of `rsp` is `rsp = 0x7ffcc6601ea0 -(6 + 2) * 8 = 0x7ffcc6601e60`. On the other hand, `rbp = rsp` so `rbp = 0x7ffcc6601e60`.



I know some way to get flag, but because of limited level I choose the easiest way - reimplement ChaCha20 with custom constant. You will get source code [here](https://github.com/pts/chacha20/blob/master/chacha20_python3.py).
```python=
import struct
import binascii
def yield_chacha20_xor_stream(key, iv, position=0):
"""Generate the xor stream with the ChaCha20 cipher."""
if not isinstance(position, int):
raise TypeError
if position & ~0xffffffff:
raise ValueError('Position is not uint32.')
if not isinstance(key, bytes):
raise TypeError
if not isinstance(iv, bytes):
raise TypeError
if len(key) != 32:
raise ValueError
if len(iv) != 12:
raise ValueError
def rotate(v, c):
return ((v << c) & 0xffffffff) | v >> (32 - c)
def quarter_round(x, a, b, c, d):
x[a] = (x[a] + x[b]) & 0xffffffff
x[d] = rotate(x[d] ^ x[a], 16)
x[c] = (x[c] + x[d]) & 0xffffffff
x[b] = rotate(x[b] ^ x[c], 12)
x[a] = (x[a] + x[b]) & 0xffffffff
x[d] = rotate(x[d] ^ x[a], 8)
x[c] = (x[c] + x[d]) & 0xffffffff
x[b] = rotate(x[b] ^ x[c], 7)
ctx = [0] * 16
ctx[:4] = (1634760805, 857760878, 2036477234, 1260414324)
ctx[4 : 12] = struct.unpack('<8L', key)
ctx[12] = position
ctx[13 : 16] = struct.unpack('<3L', iv)
while 1:
x = list(ctx)
for i in range(10):
quarter_round(x, 0, 4, 8, 12)
quarter_round(x, 1, 5, 9, 13)
quarter_round(x, 2, 6, 10, 14)
quarter_round(x, 3, 7, 11, 15)
quarter_round(x, 0, 5, 10, 15)
quarter_round(x, 1, 6, 11, 12)
quarter_round(x, 2, 7, 8, 13)
quarter_round(x, 3, 4, 9, 14)
for c in struct.pack('<16L', *(
(x[i] + ctx[i]) & 0xffffffff for i in range(16))):
yield c
ctx[12] = (ctx[12] + 1) & 0xffffffff
if ctx[12] == 0:
ctx[13] = (ctx[13] + 1) & 0xffffffff
def chacha20_encrypt(data, key, iv, position=0):
"""Encrypt (or decrypt) with the ChaCha20 cipher."""
if not isinstance(data, bytes):
raise TypeError
if iv is None:
iv = b'\0' * 12
if isinstance(key, bytes):
if not key:
raise ValueError('Key is empty.')
if len(key) < 32:
# TODO(pts): Do key derivation with PBKDF2 or something similar.
key = (key * (32 // len(key) + 1))[:32]
if len(key) > 32:
raise ValueError('Key too long.')
return bytes(a ^ b for a, b in
zip(data, yield_chacha20_xor_stream(key, iv, position)))
uh = lambda x: binascii.unhexlify(bytes(x, 'ascii'))
print(chacha20_encrypt(uh('a9f63408422a9e1c0c03a8089470bb8daadc6d7b24ff7f247cda839e92f7071d0263902ec1580000d0b4586db455000020ea78194a7f0000d0b4586db4550000'), uh('8dec9112eb760eda7c7d87a443271c35d9e0cb878993b4d904aef934fa2166d7'), uh('111111111111111111111111')))
#b'supp1y_cha1n_sund4y@flare-on.com\n\x86Xm\xb4U5G\xa5\xfc\xfb\xdfz\xb8z\xaa\xa0\xc1\r\xa9\xf0\x01)\xb3\x94:*s\x03\x97\x83V'
```
Flag: `supp1y_cha1n_sund4y@flare-on.com`