Figure out this missive from COViD to his intern! (re)

We’ve intercepted this communication between COViD and his intern, but the program doesn’t seem to be working properly! Help us solve this to move on to the next stage!

“Hey, my previous intern did this program but got it wrong. Make this program work ASAP. Don’t forget, the file should be named after my favourite potato. You should already know this, don’t bother me with trifles. Source code? It’s all in the file. Get it done.”

ctf.exe

• You can use the organisation’s website from “Who are the possible kidnappers”. Some information from the organisation’s website is useful in solving this challenge. There is no longer a need to complete that OSINT challenge to unlock this challenge.
PS S:\RE Challenges\re-challenge-5> .\ctf.exe
You forgot the colour of my favourite potato! >:E


I loaded the program into Ghidra for analysis, and it took quite long (a few minutes) to complete. The file size is 12.6MB. Normally when a binary that doesn’t even do much is so large, it is because it is either statically-compiled, or some runtime in embedded in the binary.

For example, some commonly known ones are Go or Rust programs.

Looking at the strings in the binary, we can identify that this is a racket-lang program. For example, we can find the following strings:

racket [<option> ...] <argument> ...
http://docs.racket-lang.org/local-redirect/index.htmlZget-doc-open-url
http://planet.racket-lang.org/servlets/planet-servlet.ss"


The next step is to watch some Racket lang tutorials:

Where is the program?

I tried searching for the program entry point in the binary, by searching for references to the string "You forgot the colour of my favourite potato!", but could not find anything. However, from searching for references to printf, I found a function that prints the following message.

racket [<option> ...] <argument> ...
File and expression options:
-e <exprs>, --eval <exprs> : Evaluate <exprs>, prints results
-t <file>, --require <file> : Like -e '(require (file "<file>"))' [*]
-l <path>, --lib <path> : Like -e '(require (lib "<path>"))' [*]
-p <package> : Like -e '(require (planet "<package>")' [*]
-r <file>, --script <file> : Same as -f <file> -N <file> --
-u <file>, --require-script <file> : Same as -t <file> -N <file> --
-k <n> <m> <p> : Load executable-embedded code from offset <n> to <p>
-m, --main : Call main' with command-line arguments, print results
...


I think it is possible that the Racket lang runtime is embedded in the binary, and the program bytecode is loaded by the runtime, in order to run the actual program.

I verified my claims with the documentation for raco exe.

Compiled code produced by raco make relies on Racket executables to provide run-time support to the compiled code. However, raco exe can package code together with its run-time support to form an executable…

I also found that raco decompile could decompile bytecode.

The raco decompile command takes the path of a bytecode file (which usually has the file extension “.zo”) or a source file with an associated bytecode file (usually created with raco make) and converts the bytecode file’s content back to an approximation of Racket code.

This is great. Now, my goal is clear. Find the bytecode embedded in the binary, then decompile the bytecode to understand what the program does.

Finding the bytecode

But the question is, where is the bytecode? To find out, I installed Racket on my Windows 10 VM, and tried out some commands with a simple Racket program.

#lang racket

(define (extract str)
(substring str 4 7))

(extract "the cat out of the bag")


First, I tried to compile the bytecode from source, then decompile it back to source. Just to make sure that things are working as how I expect them to.

PS S:\RE Challenges\re-challenge-5> &"C:\Program Files\Racket\raco.exe" make -v hello.rkt
"hello.rkt":
making #<path:S:\RE Challenges\re-challenge-5\hello.rkt>
[output to "compiled\hello_rkt.zo"]

PS S:\RE Challenges\re-challenge-5> &"C:\Program Files\Racket\raco.exe" decompile compiled/hello_rkt.zo
(module hello ....
(require (lib "racket/main.rkt"))
(provide)
(define-values
(extract)
(begin
'%%inline-variant%%
(#%closed
extract50
(lambda (arg0-51)
'#(extract
#<path:S:\RE Challenges\re-challenge-5\compiled/hello.rkt>
...


Looks good. Now, to find the location of the bytecode in a binary.

1. Run raco make to build the bytecode.
2. Run raco exe to build an executable with the runtime and bytecode embedded in it.
3. Search for the bytecode generated in Step 1 inside the executable generated in Step 2.

With hello_rkt.zo generated in Step 1, and hello.exe generated in Step 2, I first look at the contents in hello_rkt.zo.

➜ xxd hello_rkt.zo | head
00000000: 237e 0337 2e38 0672 6163 6b65 7444 0200  #~.7.8.racketD..
00000010: 0000 1200 0000 1163 6f6e 6669 6775 7265  .......configure
00000020: 2d72 756e 7469 6d65 c203 0000 b502 0000  -runtime........
00000030: 3800 0000 0000 0000 0000 0000 4c00 0000  8...........L...
00000040: 7603 0000 0000 0000 0000 0000 237e 0337  v...........#~.7
00000050: 2e38 0672 6163 6b65 7442 4fce c851 b06a  .8.racketBO..Q.j
00000060: c35c 0a5e 8054 9eb5 e7ba eecf 3be0 0d00  .\.^.T......;...
00000070: 0000 0100 0008 0016 001b 0027 0048 0068  ...........'.H.h
00000080: 0082 008d 0099 009e 00a5 00ac 0000 002f  .............../
00000090: 0300 0051 6578 7472 6163 741c 005d 0509  ...Qextract..]..


It seems to start with some magic values. Perhaps I could search for them in the executable.

➜ python
Python 3.9.0 (default, Oct 27 2020, 14:15:17)
[Clang 12.0.0 (clang-1200.0.32.21)] on darwin
>>> hex(exe.index(bytecode[:10]))
'0x1a87e'


The bytecode probably is located at offset 0x1a87e of the executable. Navigating to 0x41a87e in Ghidra, I landed in the .rsrc section of the executable. This means that the bytecode is embedded as a resource in the executable. I used Resource Hacker to extract the bytecode into extracted.zo.

Then, I ran raco decompile to decompile the extracted bytecode.

PS S:\RE Challenges\re-challenge-5> &"C:\Program Files\Racket\raco.exe" decompile extracted.zo
(namespace-require '(only '#%kernel module))


Hmm? That’s all? The decompiled code only has one line? What happened to my program?

extracted.zo is 7.3MB large. So there must be more content in this file. Maybe, there are many bytecode segments in this file.

➜ xxd extracted.zo | head
00000000: 237e 0337 2e38 0672 6163 6b65 7444 0100  #~.7.8.racketD..
00000010: 0000 0000 0000 2600 0000 a301 0000 0000  ......&.........
00000020: 0000 0000 0000 237e 0337 2e38 0672 6163  ......#~.7.8.rac
00000030: 6b65 7442 0000 0000 0000 0000 0000 0000  ketB............
00000040: 0000 0000 0000 0000 0300 0000 0100 0007  ................
00000050: 000d 0000 0070 0100 0050 6d6f 6475 6c65  .....p...Pmodule
00000060: 4f71 756f 7465 1100 042f 1300 6602 010b  Oquote.../..f...
00000070: 312f 2f10 01f8 2201 0101 145f 4e6f 6e6c  1//..."...._Nonl
00000080: 795e 0202 5223 256b 6572 6e65 6c02 0110  y^..R#%kernel...
00000090: 0010 0010 0410 0010 0010 0010 015b 6e61  .............[na

➜ xxd hello_rkt.zo | head
00000000: 237e 0337 2e38 0672 6163 6b65 7444 0200  #~.7.8.racketD..
00000010: 0000 1200 0000 1163 6f6e 6669 6775 7265  .......configure
00000020: 2d72 756e 7469 6d65 c203 0000 b502 0000  -runtime........
00000030: 3800 0000 0000 0000 0000 0000 4c00 0000  8...........L...
00000040: 7603 0000 0000 0000 0000 0000 237e 0337  v...........#~.7
00000050: 2e38 0672 6163 6b65 7442 4fce c851 b06a  .8.racketBO..Q.j
00000060: c35c 0a5e 8054 9eb5 e7ba eecf 3be0 0d00  .\.^.T......;...
00000070: 0000 0100 0008 0016 001b 0027 0048 0068  ...........'.H.h
00000080: 0082 008d 0099 009e 00a5 00ac 0000 002f  .............../
00000090: 0300 0051 6578 7472 6163 741c 005d 0509  ...Qextract..]..


There seems to be some small differences in the file header between extracted.zo and hello_rkt.zo. I’m hypothesizing that there are multiple bytecode files in extracted.zo. To get more ideas, I looked for the string used in my program to see where it is located in the executable.

➜ strings -t x extracted.zo | grep "cat out of the bag"
740a47 the cat out of the bag36


Oh, it is located quite far inside the file (at offset 0x740a47). I decided to see whether there is any file header (#~.7.8.racketD) located near this string.

Python 3.9.0 (default, Oct 27 2020, 14:15:17)
[Clang 12.0.0 (clang-1200.0.32.21)] on darwin
>>> from binascii import unhexlify
>>> hex(extracted.index(unhexlify("237e03372e38067261636b657444"), 0x740a47))
'0x740c87'


Turns out there is another file header after the string. How about before?

>>> hex(extracted[:0x740a47].rfind(unhexlify("237e03372e38067261636b657444")))
'0x7408ba'


Not bad. There is another file header not far away before the string. I extracted the contents between the 2 file headers found above into extracted-smaller.zo. Then, I ran raco decompile on the file.

>>> open("extracted-smaller.zo", "wb").write(extracted[0x7408ba:0x740c87])

PS S:\RE Challenges\re-challenge-5> &"C:\Program Files\Racket\raco.exe" decompile extracted-smaller.zo
(module hello ....
(require (lib "racket/main.rkt"))
(provide)
(define-values
(extract)
(begin
'%%inline-variant%%
(#%closed
extract50
(lambda (arg0-51)
'#(extract
#<path:s:\re challenges\re-challenge-5\hello.rkt>
3
0
15
44
#f)
'(flags: preserves-marks single-result)
(substring arg0-51 '4 '7)))
(#%closed
extract49
(lambda (arg0-55)
'#(extract
#<path:s:\re challenges\re-challenge-5\hello.rkt>
3
0
15
44
#f)
'(flags: preserves-marks single-result)
(substring arg0-55 '4 '7)))))
(#%apply-values print-values (substring '"the cat out of the bag" '4 '7))
(void))


It works! I managed to

1. Extract the bytecode from an executable
2. Find the part of the bytecode that represents the actual program
3. Decompile the bytecode to recover the program source

Reversing the challenge program

Now, I just need to replicate the steps above to get the Racket source code of the program given in this challenge. Doing so, I obtained the following code, which is quite long, with 840 lines of code.

(module purple ....
(require (lib "racket/main.rkt"))
(provide)
(define-values
(trololol)
'"1410130404451d141118050b155918051f1119161750000011120715400f1f1e170240021f1108190009115916020d021f1b10110009500f5e03030b191652040400501d17140b00181a155c0e0402171b15564500071b1e0f00030652110813151b06051e005c191d0208451f135204040050071b1e0b165c1c1c130915041c1d1e")
(define-values
(i-dunno-what-this-is)
'"060e041600130d430212151901400b1a3e1842173a1c5518523e133d0240550a3e12110a561d5613")
(define-values
(omnonon)
(string->bytes/utf-8
(car
(let ((local56
(path->string (file-name-from-path (find-system-path 'run-file)))))
(string-split.1 '#f '#t (#%sfs-clear local56) '".")))))
(define-values
(do-something-to-input)
(#%closed
do-something-to-input52
(lambda (arg0-64)
'#(do-something-to-input
9
0
544
290
#f)
'(flags: preserves-marks single-result)
...


After watching this tutorial on conditionals, loops and recursive functions in Racket lang, it is not too hard to understand what the program is doing. Also, about 50% of the code is repeated, so there is not too much to actually reverse. Perhaps code was duplicated by the compiler, or decompiler, or maybe just badly written by the program author?

Anyways, some examples of variables/functions include:

(define-values
(omnonon)
(string->bytes/utf-8
(car
(let ((local56
(path->string (file-name-from-path (find-system-path 'run-file)))))
(string-split.1 '#f '#t (#%sfs-clear local56) '".")))))


omnonon is the program file name.

(define-values
(?lifted.0)
(lambda (arg0-163)
16
7
860
43
#t)
'(captures: (val/ref #%globals) (string->number))
(string->number (#%sfs-clear arg0-163) '16)))


?lifted.0 is equivalent to int(x, 16) in Python.

Since the other functions are quite long, I shall omit them in this writeup. It is really not too difficult to understand anyways.

To summarize:

1. The program file name is used as the key to xor decrypt a constant string.
• The decrypted string is expected to contain 9 comma-separated values, i.e. the string should contain 8 commas (e.g. a,b,c,d,e,1,2,3,4)
• If the decrypted string does not fulfil the requirement above, the program complains that the file name is wrong and terminates.
2. If the file name is correct, a key containing 8 bytes is constructed from some of the values obtained from Step 1.
3. The key is used to xor decrypt another constant string, and the result is printed.
• The constant string is unhexlify("060e041600130d430212151901400b1a3e1842173a1c5518523e133d0240550a3e12110a561d5613")

It seems that the first step is to change the program file name from ctf.exe to something that satisfies the requirement in Step 1. The program seems to be asking for the author’s favourite potato colour. This information could be obtained from another OSINT challenge in this CTF. Changing the program file name to purple.exe gives the following output.

PS S:\RE Challenges\re-challenge-5> .\purple.exe
MMMMMMMMMMMMMMMMNdhhhhhhdmNMMMMMMMMMMMMM
MMMMMMMMMMMMmho/:::::::::::/oymMMMMMMMMM
MMMMMMMMMMms/::::::::::::::::::+dMMMMMMM
MMMMMMMMNy+/:::::::::::::::::::::sNMMMMM
MMMMMMMmo//:::::::::::::::::::::::oNMMMM
MMMMMMm+///::::::::::::::::::::::::hMMMM
MMMMMNo///:::::::::::::::::::::::::+MMMM
MMMMMy////::::::::::::::::::::::::::NMMM
MMMMm+///::::sddo::::::::::sddo:::::dMMM
MMMMs////::::hNNy::::::::::hNNy:::::dMMM
MMMm+///:::::://:::shssh+::://::::::hMMM
MMMs////:::::::::::/+//+/:::::::::::sMMM
MMN+////::::::::::::::::::::::::::::/NMM
MMh////::::::::::::::::::::::::::::::yMM
MMs////::::::::::::::::::::::::::::::/NM
MN+////:::::::::::::::::::::::::::::::yM
Mh/////:::::::::::::::::::::::::::::::oM
Ms/////:::::::::::::::::::::::::::::::+M
Mo/////:::::::::::::::::::::::::::::::+M
My//////::::::::::::::::::::::::::::::oM
Mm+//////:::::::::::::::::::::::::::::hM
MMh+///////::::::::::::::::::::::::::+NM
MMMh+////////:::::::::::::::::::::::/mMM
MMMMms+//////////::::::::::::::::::oNMMM
MMMMMMmy++//////////////////////+ymMMMMM
MMMMMMMMMmhso++/////////++++syhmMMMMMMMM
MMMMMMMMMMMMMNmddhhhhddmmNMMMMMMMMMMMMMM

I am the purple potato
Key length looks okay... will this work?
#"s{wbAQN\awgfm@\2H^Km1c{^\26\\'KIC\2\26NKgb~\27_\25W"


Some unreadable text is printed at the end of the program output. This is the string printed in Step 3.

With purple.exe as the file name, the key generated in Step 2 is uustABCD. Decrypting the ciphertext with this key gives the following string, which is the same as the output above.

>>> xor(unhexlify("060e041600130d430212151901400b1a3e1842173a1c5518523e133d0240550a3e12110a561d5613"), "uustABCD")
b"s{wbAQN\x07wgfm@\x02H^Km1c{^\x16\\'KIC\x02\x16NKgb~\x17_\x15W"


Most likely, the key that was constructed in Step 2 is incorrect, and I need to find the correct key to get the flag.

I know that the flag starts with govtech-csg. I also know that the key is 8-bytes long. So, I can xor govtech- with the encrypted flag to get the key.

>>> from pwn import xor
>>> from binascii import unhexlify
>>> xor(unhexlify("060e041600130d430212151901400b1a3e1842173a1c5518523e133d0240550a3e12110a561d5613"), "govtech-")[:8]
b'aarbepen'


The key is aarbepen, and the flag is

>>> xor(unhexlify("060e041600130d430212151901400b1a3e1842173a1c5518523e133d0240550a3e12110a561d5613"), "aarbepen")
b'govtech-csg{d0nt_y0u_l0v3_a_g00d_sch3m3}'


govtech-csg{d0nt_y0u_l0v3_a_g00d_sch3m3}`