BambooFox CTF 2021

January 2021

Flag Checker

The task involved some Verilog code. This is not a standard reversing challenge per-se, since code is already available and nothing needs to be disassembled or decompiled. The only thing needed is to understand Verilog syntax. A generally good tutorial for Verilog is www.asic-world.com.

The entry point so to say is in the t_chall.v file due to the initial begin block. This block is associated with test benches. The test bench copies one byte of the flag array into inp and one byte of a target array into tmp.

for (idx = 0; idx < 32; idx++) begin
inp = flag[idx];
tmp = target[idx];
#4;
end

At every clock cycle, the chall module applies some transformation to the byte using a magic module.

magic m0(.clk(clk), .rst(rst), .inp(inp), .val(val0), .res(res0));
magic m1(.clk(clk), .rst(rst), .inp(res0), .val(val1), .res(res1));
magic m2(.clk(clk), .rst(rst), .inp(res1), .val(val2), .res(res2));
magic m3(.clk(clk), .rst(rst), .inp(res2), .val(val3), .res(res3));
always @(posedge clk) begin
if (rst) begin
assign res = inp;
end else begin
assign res = res3;
end
end

The magic module is a simple switch case statement.

case (val)
2'b00: res = (inp >> 3) | (inp << 5);
2'b01: res = (inp << 2) | (inp >> 6);
2'b10: res = inp + 8'b110111;
2'b11: res = inp ^ 8'd55;
endcase

Lastly, the test bench verifies the result of the transformation on the flag byte against the tmp byte from the target array.

always @(posedge clk) begin
#1 ok = ok & (out == tmp);
end

My solution was to reimplement the basic parts in python and bruteforce each byte value to find matches with the bytes from the target array. The python implementation was rather straightforward. One thing that I learned was the fact that there were collisions, where multiple flag byte candidates would be transformed to the same target bytes. At this point I simply made some educated guesses and finally obtained the flag flag{v3ry_v3r1log_f14g_ch3ck3r!}

Ransomware

This challenge required me to reverse engineer a python .pyc file, which is a compiled version of a .py source file.

I flopped this one and apparently took the long difficult route. I have learned from others that there is a pretty good python decompiler called uncompyle6. With that being said, let me tell you my solution.

I expected to see some differences in the python bytecode between versions 2 and 3, and maybe even between 3.x versions. After reading some blogs, I learned that the .pyc format is generated and used by the marshal module which is not supposed to retain backwards or forward compatibility always.

My first goal was to figure out if I was working with a python 2 or 3 file. Using some google fu, I figured out that each file has an initial 4 bytes magic which can be used to identify the exact version of python. The magic in the CTF file was 0x0d55, or 3413 in decimal, which meant the version used to compile the .pyc file is 3.8.

I used this dissassembler script as a starting point. I modified it briefly to fix some indentation issues, skip an unknown field in newer versions of .pyc binaries and to add vim folding markers (of course). My version of the script is here. The dissassembly output is here.

I learned that the python virtual machine is stack based, where opcodes either push values to the stack or consume/pop them. Some of these opcodes are pretty obvious, but others not so much. I found this overview of opcodes which came in very handy. For the rest I decompiled the binary manually.

The code simply makes an HTTP GET request to the https://ctf.bamboofox.tw/rules URL and uses part of the content as key and IV to encrypt a PNG image using AES in CBC mode. I made a script to do the decryption and obtained the plaintext version of the PNG, which did not contain the flag, but a subliminal message instead.

After some minutes, I ran binwalk on the PNG and found out that a second PNG image was located in the binary. I recovered this second PNG and obtained the flag for the challenge.

Better Than ASM

The challenge provided a .ll file containing LLVM. While I do have some familiarity with this format, I decided to simply compile this file to a binary and use IDA instead.

clang task.ll -o task

At this point, reversing the binary became trivial. It expects an input of a certain length, which it passes to a check function to verify byte by byte. If that check passes, the input is further used in combination with a secret array to generate the printable flag.

My solution simply brute forces byte values based on the check function, such that the input would be made of printable ascii characters only. For every such candidate string I find, I apply the same transformations using the secret array, and finally print the result.

In the possible candidates printed by the script, one stands out which is also the flag 7h15_f14g_15_v3ry_v3ry_l0ng_4nd_1_h0p3_th3r3_4r3_n0_7yp0.

Flag Checker Revenge

This was by far the best challenge I encountered in the CTF. The challenge provided an ELF which asked for a flag. The flag was then passed to a series of functions (500 in total), each performing a simple check on individual characters of the string.

The 500 functions were very similar in structure as can be seen in both IDA and Binary Ninja.

IDA

Binary Ninja

I immediately thought of using a SAT solver like z3 to find an input that would meet all these constraints. The first step was to use the Binary Ninja API to extract the constraints into a format that I could easily work with. The script iterates through all the 500 functions, gets the text associated with the HLIL (i.e. the decompiled code) and applies some regular expressions to extract the relevant information. I will no go through how the script works because, except for the Binary Ninja API, it is nothing special.

The output of the script is a list of constraints for characters of the flag. Using some vim fu, I created a second z3 script, which prints out integers that fit the constraints. After making a string out of these integers, I got the flag flag{4ll_7h3_w4y_70_7h3_d33p357_v4l1d4710n}.

Super solid. Super nice.

The Vault

This challenge provides a simple website using a wasm binary. The website asks for a PIN to be inserted, which the wasm module then somehow verifies. The purpose of the challenge was to find the correct PIN. I am not sure whether I solved this challenge in the correct fashion, but the flag is what counts.

The first step was to take the main.js file and run it through a formatter. I noticed that the file was not super obfuscated, and that many variables had their original names. The resulting prettified script was rather easy to read.

I quickly found that I was supposed to trigger a call to the win function on line 572. This function is somehow referenced on line 609 by an asmLibraryArg dictionary. This dictionary actually contains a set of functions that are somehow passed to the wasm module. The one function that stood out was get_password, which I suspected was used by the wasm module to retrieve the PIN code from the HTML form.

My first attempt was to load some code in the devtools console to iterate over all PINs and see which would trigger a different message from the wasm module. That however did not work. I then decided to look into the wasm binary itself.

Unlike the previous Ransomware challenge, I was more inspired to search for a wasm decompiler. Lucky for me, I found WebAssembly Binary Toolkit, or simply wabt. This is a collection of various tools which aid in analyzing wasm binaries. One of the tools is called wasm-decompile, which was able to decompile the binary into a semi-readable format. The output of the tools is here.

I quickly noticed the some function declarations at the top

import function a_a(a:int):int;
import function a_b();
import function a_c(a:int);
import function a_d():int;
import function a_e();

which looked very similar to the ones from the aforementioned asmLibraryArg dictionary

var asmLibraryArg = {
"e": banner,
"a": _emscripten_resize_heap,
"b": fail,
"d": get_password,
"c": win
};

At this point, I simply looked for references to the a_d function which I hoped would somehow link to the get_password function from main.js. I found a specific code snippet

var b:{ a:ubyte, b:ubyte, c:ubyte, d:ubyte } = a_d();
a.d = d__WD_l4GoR[12]:ushort;
a.c = d__WD_l4GoR[2]:long;
a.b = d__WD_l4GoR[1]:long;
a.a = d__WD_l4GoR[0]:long;
if (f_h(b) != 4) goto B_b;
if (b.a != 112) goto B_b;
if (b.b != 51) goto B_b;
if (b.c != 107) goto B_b;
if (b.d != 48) goto B_b;

which seemed to verify 4 bytes (chars?) against something that was in the ascii printable range, specifically the string p3k0. I gave it a shot and ran the following code in the devtools console

document.getElementById('password').value = 'p3k0';
"p3k0"

After pressing the check button, I got the flag flag{w45m_w4sm_wa5m_wasm}. Sweet.