Building A House Full Writeup
Building A House Full Writeup
Fig1
Fig2
It calls “checkPasscode” function. We can beautify the JS code using chrome’s “PrettyPrint” to
make it easier to read.
Fig3
So the passcode length must be 32 chars. If that’s the case it will call “__syscall12” with passcode
as its argument, and then pass the result to “__syscall15”.
If we take a look at the page sources we can see 3 files, one of them is “chal.wasm” which is a web
assembly file.
The way web assembly works is by exporting functions to be imported and called in javascript.
Fig4
We can completely depend on chrome DevTool for the analysis, but here I’m gonna use JEB
Decompiler to statically analyze this wasm file.
NOTE: function parameters are reversed in JEB because of the wasm way to push parameters to
the stack (first parameter is pushed first unlike x86 calling convention).
Fig5
JEB did a great job detecting all the functions, so let’s start by looking at “__syscall12”.
Fig6
This function takes one argument (our passcode) and assigns it to (v0+8), then it loops from 0 to 32
and assigns the stack values starting from address 2912 to that passcode.
From now on I will refer to stack values as “S+offset”.
So the passcode starts from “S+2912” to “S+2943”.
Next it checks the first 4 chars and last char for the flag format characters.
# Constraint1: S+2912 == 'G'
# Constraint2: S+2913 == 'F'
# Constraint3: S+2914 == '$'
# Constraint4: S+2915 == '8'
# Constraint5: S+2943 == 'R'
Note that (v0+12) is used here as the return value.
After these checks we see 2 function calls to “f5”.
Fig7
Fig8
Emscripten is a compiler that compiles C and C++ code to web assembly. It provides methods to
connect and interact between JavaScript and compiled C or C++ code.
One of these methods is using “EM_ASM” which allows us to call javascript code inside C code.
The function “emscripten_asm_const_int” takes a pointer to arguments as the first parameter, and
an index to the javascript function as the third parameter.
These indexes are mapped to javascript functions in a variable called “ASM_CONSTS” inside
“chal.js” file.
Fig9
Now let’s take a closer look at our desired function at index 1795.
Fig10
It initializes a long byte array then creates a new instance of WebAssembly Module with this array
as its argument. This is used to compile and execute byte arrays of WASM at runtime.
To analyze these raw bytes, we can save them as a wasm file and use JEB for the analysis.
Fig11
That’s much better, we just need to look back at the parameter of “f5” function to see what offsets
of the passcode gets passed to the function above (see Fig7).
# Constraint6: ((S+2881) + 32) == (((S+2889) + (S+2907)) / 2)
# Constraint7: ((S+2911) + 32) == (((S+2894) + (S+2898)) / 2)
We have 2 more constraints at the end of Fig7.
# Constraint8: ((S+2889) - (S+2898)) == -12
# Constraint9: ((S+2894) + (S+2907)) == 216
Moving on, we have 3 function calls to “f4”.
Fig11
This function takes 3 arguments and passes them along with other 2 arguments to
“emscripten_asm_const_int”.
Fig12
The first of these 2 arguments is (v0+48) which seems to be a byte array by looking at the while
loop at the start of the function. It loops from 0 to 107 and xors the contents of (v0+48) with the
key 61.
And the last argument is 107 which is the length of (v0+48) byte array.
We can use the debugger to get the final value of (v0+48). The best place to set a breakpoint would
be after the xor operation.
Fig13
The xor result will be stored in “$var29”, but we can’t just stop after every hit of the breakpoint
and print that value (107 times).
What we need is a “Logpoint” instead of the regular breakpoint, this allows us to log a specific
value every time the breakpoint is hit.
We can double click on the breakpoint and make it log “$var29.value”.
Fig14
We also need to set another breakpoint at the end of the loop (as “f4” function is called 3 times
and we just need to print the array once).
Fig15
If we entered a random passcode and run the debugger, we will get this message.
Fig16
Fig17
Now let’s clear the console and run the debugger again.
Fig18
Nice! Here is our lovely array, we can disable similar messages grouping in console to get the full
list of values.
Fig19
Looking back to Fig12, we see that the arguments are passed to the function with index 1532
(remember the ASM_CONSTS map).
Fig20
This function treats the third argument (the array we dumped earlier) as wasm instructions, so we
just save that array as a wasm file and throw it into JEB.
We can use CyberChef to handle the negative values for us.
Fig21
Fig22
This is actually the implementation of “Rotate Left” operation, so it’s rotating “param0” to left by
“param1” steps.
# Constraint10: RotateLeft((S+2908), 5) == 233
# Constraint11: RotateLeft((S+2895), 3) == 178
# Constraint12: RotateLeft((S+2890), 7) == 155
The next function is “f6” which takes 2 arguments.
Fig23
Fig24
First it sets (v0+24) to param0 and (v0+20) to param1, then creates a pointer “ptr0” which points to
(v0+15) and copies 5 bytes from address 0x55B to the pointer memory.
Fig25
We can rewrite this function in C and rename those (v0+offset) variables to make it more clear.
Fig26
Fig27
It takes 3 arguments, adds them to the passcode stack offset then passes them to
“emscripten_asm_const_int”.
Fig28
Fig29
It concatenates the 3 arguments then uses “btoa” function to base64-encode them and compares
the result with the string “OU9u”.
So we just need to base64-decode this string to get the correct values.
# Constraint18: (S+2880+11) == '9'
# Constraint19: (S+2880+12) == 'O'
# Constraint20: (S+2880+13) == 'n'
Next we have some bits operations.
Fig30
Fig31
Fig32
First it sets (v0+24) to param0, stores from bytes from address 0x400 in (v0+19) and stores 8 bytes
from address 0x560 tin (v0+11).
Let’s rewrite it in C as we did with “f6”.
Fig33
Fig34
Cool, it just checks If param0 (the return value of “__syscall12”) is equal to 1 (should be the correct
passcode) and decrypts the flag using our passcode.
The javascript function at index 2656 takes one argument and calls “alert”, while the function at
index 2759 also takes one argument but calls “console.log” (remember that “console.log” is
hooked as an anti-debugging trick).
To get the correct passcode using these constraints, we can use Z3 SMT Solver and give it our
constraints.
BONUS:
The function responsible for the anti-debugging trick (hooking “console.log”) is called right after
the page is loaded.
Fig35
Fig36
It contains a large base64-encoded strings that gets decoded and passed to “eval” function (this is
implemented in the javascript function at index 1424).
This is how it looks after decoding.
Fig37
Passcode: GF$8J!4jsf79OnrV75riE%tKcT0fOD4R
Flag: S3D{w3b_4ss3mb1y_1s_c00l_r1ght?}
Abdallah Elshinbary (@NightW0o0lf) from The Crafters (@CTFCreators)