Flare-On 2024: checksum
checksum presents a binary compiled from Golang. I’ll have to answer a series of math addition problems, and then give it the valid SHA256 hash that matches a static value stored in the binary. On success, it writes an image to my AppData Local directory that has the flag.
Challenge
The challenge prompt reads:
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!
The download has a single 64-bit Windows executable:
oxdf@hacky$ file checksum.exe
checksum.exe: PE32+ executable (console) x86-64, for MS Windows, 15 sections
Run It
Running the .exe
on Windows pops a terminal window with a math prompt:
Answering correctly provides another:
Answering incorrectly prints “Try again! ;)” and closes the window. Entering a non-integer value prints “Not a valid answer…” and exits the window.
If I manually do enough math (the number changes on each execution), it asks for a checksum:
...[snip]...
Good math!!!
------------------------------
Check sum: 9097 + 6900 = 15997
Good math!!!
------------------------------
Checksum: asdfasdf
Not a valid checksum...
Later I’ll see that this is likely a SHA256 hash, and sending it 64 bytes of hex generates a new reply (and exit):
Good math!!!
------------------------------
Checksum: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Maybe it's time to analyze the binary! ;)
Without knowing what it’s calculating, not much I can do here.
RE
Strings
There are lots of string in the binary that suggest it’s written in Golang. For example:
golang.org/x/sys/cpu..inittask
golang.org/x/sys/cpu.Initialized
golang.org/x/sys/cpu.X86
golang.org/x/sys/cpu.options
golang.org/x/sys/cpu.getAuxvFn
There’s also a lot of really long string blogs. For example, there are 24 strings longer than 500 characters:
oxdf@hacky$ strings -n 500 checksum.exe | wc -l
24
That’s because of how Go stores strings, not null terminated, but all jammed together and referenced by offsets and lengths.
main.main
Given that this is a Golang binary, I’ll find the main
function under Namespaces in Ghidra:
There’s a bunch of junk in here, but it’s not too hard to get a high level look at what it’s doing:
It starts by getting a random number that’s 0-4 [1], and initializes a count of correct answers to 0 [2]. Then it enters a while loop as long as the number of correct answers is less than 3 + the random number [3]. It generates two more random numbers less than 10,000 [4], and gets their sum [5].
There’s a bunch of difficult to read code for printing this and reading in the user response, which then leads to:
main.b
is called to handle errors coming from converting the user input to an int, printing the “Not a valid answer…” message and exiting [1]. If the user input is incorrect, then it prints “Try again! ;)” and returns [2]. Otherwise, it prints the “Good math!!!” message [3], increments the number correct count [4], and continues the while loop.
There’s some stuff handling input, as well as initializing a Chacha20 encryption object, and references to a SHA256:
This section isn’t too important, but it’s where I learn that the checksum is likely a SHA256, and thus 64 bytes of hex.
The important part to note in the next bit is that it is calling main.a
to determine if the user-entered checksum is correct:
After that, it does something that involves openubg os.UserCacheDir()
+ \\REAL_FLAREON_FLAG.JPG
:
According to Golang docs, this returns %LocalAppData%
on Windows.
I could dive deeper into this, but it seems like if I can figure out what the checksum should be, I can then look for that flag in that image.
main.a
main.a
decompiles (at least by Ghidra) a bit oddly:
It starts a counter [1] and loops infinitely, but then has an if
in the loop checking to see if it’s looped over the entire string [2]. If so, it does stuff and returns.
At the bottom of the loop [3], it loops over the input XORing it by characters from a global static string, “FlareOn2024”:
Once the entire input has been XORed, then it goes into the if
and base64-encodes the result. That must be 0x58 bytes long, and equal to a static string for main.a
to return true.
Solve
Find Checksum
To find the correct checksum input, I’ll drop to Python starting with the resulting base64-encoded string:
>>> encoded = "cQoFRQErX1YAVw1zVQdFUSxfAQNRBXUNAxBSe15QCVRVJ1pQEwd/WFBUAlElCFBFUnlaB1ULByRdBEFdfVtWVA=="
I’ll decode it to get the raw encrptyed key:
>>> from base64 import b64decode
>>> enc_key = b64decode(encoded)
>>> enc_key
b"q\n\x05E\x01+_V\x00W\rsU\x07EQ,_\x01\x03Q\x05u\r\x03\x10R{^P\tTU'ZP\x13\x07\x7fXPT\x02Q%\x08PERyZ\x07U\x0b\x07$]\x04A]}[VT"
I’ll use the cycle
function to pair each byte of the encrypted string with the right byte of the XOR key, and get the decrypted checksum:
>>> from itertools import cycle
>>> ''.join(chr(x^y) for x,y in zip(enc_key, cycle(b'FlareOn2024')))
'7fd7dd1d0e959f74c133c13abb740b9faa61ab06bd0ecd177645e93b1e3825dd'
Solve
Now I’ll solve the math again and enter this as the checksum:
PS > .\checksum.exe
Check sum: 2355 + 6787 = 9142
Good math!!!
------------------------------
Check sum: 47 + 4874 = 4921
Good math!!!
------------------------------
Check sum: 2630 + 2777 = 5407
Good math!!!
------------------------------
Check sum: 6771 + 2654 = 9425
Good math!!!
------------------------------
Check sum: 3856 + 2597 = 6453
Good math!!!
------------------------------
Check sum: 8759 + 5563 = 14322
Good math!!!
------------------------------
Check sum: 8943 + 4821 = 13764
Good math!!!
------------------------------
Checksum: 7fd7dd1d0e959f74c133c13abb740b9faa61ab06bd0ecd177645e93b1e3825dd
Noice!!
Recover Flag
I know from the RE that something may be writing a JPG to%LOCALAPPDATA%
, and there is:
PS > ls $env:LOCALAPPDATA\*.JPG
Directory: C:\Users\0xdf\AppData\Local
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 11/1/2024 4:49 PM 181532 REAL_FLAREON_FLAG.JPG
It’s got the flag: