Flare-On 5 CTF WriteUp (Part 3)

Flare-On 5 CTF WriteUp (Part 3)

. 6 min read

This is part 3 of the Flare-On 5 CTF writeup series.

5 - Web 2.0

The future of the internet is here, again. This next challenge will showcase some the exciting new technologies paving the information super-highway for the next generation.

Similar to the fourth, the fifth challenge is also based on Web technologies - HTML, Javascript and ... Web Assembly (WASM). Web Assembly is quite new and its usage is fairly limited but increasing nevertheless. As its name suggests, WASM allows to execute assembly code on the browser, but it doesn't mean traditional assembly languages like x86, ARM and MIPS we're familiar with. Instead, WASM defines an entirely new binary format with its own instruction set to be run on a stack-based virtual machine in the browser. WASM comes with the promise that it will provide near-native performance as a desktop app running on the user's system.

Let's go back to the challenge. We've three files index.html, main.js and test.wasm. Before WASM can be executed on the browser there are some tasks that must be done beforehand like setting up an interface so that the WASM code can interact with the JS code, setting up arguments for function calls, converting JS values such that WASM can operate on that etc. All of this is done by main.js.

5-1

It reads a URL parameter q and converts it to a Uint8Array of bytes. It allocates two 0x200 bytes memory block within the WASM instance. One of them is filled with an array of byte values declared at the start. The other is filled with the bytes of the URL parameter q. Next, it calls a function named Match exported from the WASM code with the proper arguments. It's evident the URL parameter q is the flag. If the function returns 1, our flag is correct.

Analyzing the WASM code in JEB decompiler

JEB decompiler from PNF Software can analyze WASM binaries. The demo version is good enough at least for a basic analysis.

5-2

Apart from Match, JEB identified nine other functions from f1 to f9. Besides, there's another function writev_c. Let's have a look at Match.

5-3

The signature specifies it takes four i32 parameters and returns an i32 value as output. We already had found out this information by analyzing the JS code. Unlike regular native executables, the WASM binary format also describes the signature and number of local variables of each and every function. For example, Match has 24 locals inclusive of the four parameters. Have a look at this document to know in-depth about the WASM format.

WASM is a stack-based virtual machine like CPython. There are no registers. All operations must be done by pushing and popping values off the stack. For example, adding two numbers is achieved by pushing the values; the i32.add instruction pops off the topmost two values, adds them and pushes the result back on the stack.

For experimenting with Web Assembly, try out WasmExplorer and WebAssembly Studio.

Match calls f9 and based on its return value either returns 1 or 0. So essentially the check is performed in f9.

5-4

Using the WebAssembly Binary Toolkit

The WebAssembly Binary Toolkit (WABT) provides several useful tools to work on WebAssembly. Among them is wasm2c which aims to convert a wasm binary to equivalent C source with a header. It is not a true decompiler but rather converts the WASM instructions to equivalent C code.

After running wasm2c on test.wasm, the C code for Match looks like.

static u32 Match(u32 p0, u32 p1, u32 p2, u32 p3) {
  u32 l0 = 0, l1 = 0, l2 = 0, l3 = 0, l4 = 0, l5 = 0, l6 = 0, l7 = 0, 
      l8 = 0, l9 = 0, l10 = 0, l11 = 0, l12 = 0, l13 = 0, l14 = 0, l15 = 0, 
      l16 = 0, l17 = 0, l18 = 0, l19 = 0, l20 = 0, l21 = 0, l22 = 0, l23 = 0;
  FUNC_PROLOGUE;
  u32 i0, i1, i2, i3, i4;
  i0 = g0;
  l0 = i0;
  i0 = 32u;
  l1 = i0;
....
snip
....

  i0 = l2;
  i1 = p1;
  i32_store((&memory), (u64)(i0 + 20), i1);
  i0 = l2;
  i1 = p2;
  i32_store((&memory), (u64)(i0 + 16), i1);
....
snip
....  
  i0 = i32_load8_u((&memory), (u64)(i0 + 11));
  l18 = i0;
  i0 = 1u;
  l19 = i0;
  i0 = l18;
  i1 = l19;
  i0 &= i1;
  l20 = i0;
  i0 = l2;
  i1 = l20;
  i32_store((&memory), (u64)(i0 + 28), i1);
....
snip
....
  i0 += i1;
  l23 = i0;
  i0 = l23;
  g0 = i0;
  i0 = l21;
  goto Bfunc;
  Bfunc:;
  FUNC_EPILOGUE;
  return i0;
}

All assignment and arithmetic operations have been converted to their C equivalent. Additionally, wasm2c introduced some helper functions like i32_load and i32_store since they no C equivalent. However, the code is still far away from being fit for analysis. It would be nice if we can optimize the code.

We don't want to optimize by hand. That's the work of a compiler. However, we need to provide a main function before we can run gcc.

5-5

Compiling with we get an x86 ELF. Decompiling the file using HexRays in IDA, f9 looks like

5-6

This is a lot better than what we obtained with wasm2c. If we analyze the WASM code of f9 we can see it there's a CALL_INDIRECT instruction. This translates to the following C snippet.

5-7

Immediately below there's a comparison between a character of our input to the value calculated by the above indirect function call.

5-8

This means if set a breakpoint at this location we may be able to retrieve the flag character by character. However, we need to do this in the WASM code and not in the ELF as the decompiled wasm2c code may not have the exact behaviour as the WASM binary.

Debugging WASM in Firefox

Firefox offers the ability to debug WASM code. First, we need to set up a local web server which can be done using $ python -m SimpleHTTPServer or something like https://github.com/m3ng9i/ran.

With Firefox and the web server setup let's identify the proper location in the WASM code which implements the comparison of the correct byte with that of the flag.

The CALL_INDIRECT instruction is located at B7F.

5-9

The comparison instruction must be located after this. Indeed, a few lines below we do find an i32.eq instruction which performs a sign agnostic equality check.

5-10

Let's set a breakpoint here and rerun with a known flag as input like http://127.0.0.1:8080/?q=abcdefghijklmnopqrst.

5-11

When the breakpoint hits we can see the instruction is essentially comparing the values of var82 and var83 which are loaded on the stack just before. var83 contains 97 (a) the first character of our input. This is being compared to 119 (w). This suggests the first character of the flag is w. Proceeding in the same fashion we can retrieve the entire flag character by character.

5-12

FLAG: wasm_rulez_js_droolz@flare-on.com

Continue to the next part: Flare-on 5 CTF Write-up (Part 4)