[go: up one dir, main page]

0% found this document useful (0 votes)
14 views14 pages

Building A House Full Writeup

The document details the analysis of a web application requiring a passcode, focusing on the JavaScript and WebAssembly components involved in validating the passcode. It outlines various constraints derived from function calls and memory operations that must be satisfied to derive the correct passcode and flag. The final passcode and flag are provided, along with insights into anti-debugging techniques used in the application.

Uploaded by

michao0923
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views14 pages

Building A House Full Writeup

The document details the analysis of a web application requiring a passcode, focusing on the JavaScript and WebAssembly components involved in validating the passcode. It outlines various constraints derived from function calls and memory operations that must be satisfied to derive the correct passcode and flag. The final passcode and flag are provided, along with insights into anti-debugging techniques used in the application.

Uploaded by

michao0923
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 14

First let’s open the html file, we can see it asks for a passcode.

Fig1

Let’s check the DevTools to see what this button does.

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

This function takes 3 arguments and passes them to “emscripten_asm_const_int”.

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

Well, looks like “console.log” is hooked (anti-debugging trick).


A work around for this is to replace “console.log” with another similar function, here I will use
“console.dir”.

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

Here is the result.

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

This function looks a little bit tricky.

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

That’s definitely better.


# Constraint13: (S+2880+8-0) - (S+2879+8-0) == 0x09
# Constraint14: (S+2880+8-1) - (S+2879+8-1) == 0x36
# Constraint15: (S+2880+8-2) - (S+2879+8-2) == 0x13
# Constraint16: (S+2880+8-3) - (S+2879+8-3) == 0xD7
# Constraint17: (S+2880+8-4) - (S+2879+8-4) == 0x12
Let’s keep moving, the next function is “f7”.

Fig27

It takes 3 arguments, adds them to the passcode stack offset then passes them to
“emscripten_asm_const_int”.
Fig28

The javascript function at index 2521 is quite simple.

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

# Constraint21: ((S+2896) & (S+2897)) == 53


# Constraint22: ((S+2897) - (S+2909)) == -15
# Constraint23: ((S+2909) | (S+2910)) == 116
# Constraint24: ((S+2896) + (S+2910)) == 107
The last checking function is “f8”.

Fig31

This function looks a lot like “f6”.

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

# Constraint25: ((S+2880+19) ^ 0xDE) + 1 == 0xB8


# Constraint26: ((S+2880+20) ^ 0xAD) + 1 == 0xE9
# Constraint27: ((S+2880+21) ^ 0xBE) + 1 == 0x9C
# Constraint28: ((S+2880+22) ^ 0xEF) + 1 == 0x9C
# Constraint29: ((S+2880+23) ^ 0xDE) + 1 == 0x96
# Constraint30: ((S+2880+24) ^ 0xAD) + 1 == 0xCF
# Constraint31: ((S+2880+25) ^ 0xBE) + 1 == 0xEB
# Constraint32: ((S+2880+26) ^ 0xEF) + 1 == 0xE0
And with that we are done with “__syscall12”, now let’s check “__syscall15” to see if there’s
anything left.

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

It has a misleading name “emscripten_heap_init”.

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)

You might also like