Web - Ding-o-Tron - USCG 2024
A WASM web challenge developed by tsuto for Season IV of the U.S. Cyber Open.
Description
What came first? The ding…or the flag?
Solution
Summary:
- Every time the button is clicked,
clickWrapper.func1
is called inding.wasm
. - Stepping through
clickWrapper
, the memory address storing the current number of clicks can be found. - Modifying the value to be over 9000 causes a taunting message to be printed to the console.
- Digging through the wasm code, the functions
flagWrapper.func1
andactualFlagWrapper.func1
are found. - Breaking on
flagWrapper.func1
and viewing the callstack shows an indirect call fromhandleEvent
. - Indirect calls use the function table to jump to the correct function. Modifying the function table to instead point to
actualFlagWrapper.func1
will cause it to be called, printing the flag.
Finding and Overwriting the Click Count
Looking at the HTML source shows that when the bell is clicked it calls window.ding()
.
1
<img src="/static/assets/img/bell.png" role="button" onclick="window.ding()">
I couldn’t find a function for ding in the JavaScript source so I turned to ding.wasm
. Inside ding.wasm
I found the functions dingWrapper
and dingWrapper.func1
. Setting a breakpoint on both, I clicked the bell and started to step through the function until I found the value of the current number of clicks being loaded and incremented by 1.
1
2
3
4
5
6
7
8
i64.const 1259744
i32.wrap_i64
i64.const 1259744
i32.wrap_i64
i64.load
i64.const 1
i64.add
i64.store
The code performs the following actions:
- Push
1259744
onto the stack - Push
1259744
onto the stack - Load data memory address
1259744
- This replaces the second instance of
1259744
on the stack with the current number of clicks.
- This replaces the second instance of
- Increment the number of clicks by one.
- Store the new value back at
1259744
.
Using this location information, I can overwrite the number of clicks in memory to any value. To modify the value, I take advantage of the function storeValue
in wasm_exec.js
. To execute this function, it must be in scope, so I set a breakpoint in wasm_exec.js
and ran storeValue(1259744, 9000)
in the console.
The memory address is now storing 9000 clicks which means I should get the flag now right?
[LOL] Did you think it would be that easy? Can you find my secret hidden function?
Wrong.
Overwriting the Function Table Pointer for flagWrapper.func1
Knowing that there was supposed to be a hidden function, I started looking through the wasm code again for any mention of flag
, and came across two interesting functions, flagWrapper.func1
and actualFlagWrapper.func1
. Obviously, actualFlagWrapper.func1
is what I want to be called, so to figure out how flagWrapper.func1
is called, I set a breakpoint on it and viewed the callstack.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$main.flagWrapper.func1
$syscall_js.handleEvent
$runtime.handleEvent
$wasm_export_resume
_resume
(anonymous)
syscall/js.valueCall
$syscall_js.valueCall
$syscall_js.value.Call
$main.dingWrapper.func1
$syscall_js.handleEvent
$runtime.handleEvent
$wasm_export_resume
_resume
(anonymous)
onClick
The callstack shows that flagWrapper
was called by $syscall_js.handleEvent
. Viewing the code where flagWrapper
was called reveals a call_indirect
instruction. Now, I had no idea how this works, so I started to research and came across this writeup which has a small blurb about function tables and indirect function calls.
https://medium.com/tenable-techblog/coding-a-webassembly-ctf-challenge-5560576e9cb7#bd80
Using this new information, I found the function table in the debug menu under the module
dropdown. To find the index of flagWrapper
in the function table, I set a breakpoint on the indirect call and took note of the value at the top of stack; 5503
. With this information, I looked at the function table around index 5503 and found actualFlagWrapper
at index 5502
.
Knowing this, I assumed that overwriting the flagWrapper
at index 5503
with actualFlagWrapper
would print the flag. Running the following command in the console successfully changed the function pointer which printed the flag the next time I clicked the bell.
1
2
3
4
> tables.$table0.set(5503,tables.$table0.get(5502))
undefined
> tables.$table0.get(5502)
ƒ $main.actualFlagWrapper.func1() { [native code] }
Flag: SIVUSCG{d1ng_d1ng_d1ng_d1ng}
WTF Did I Just Read?!
After I solved the challenge a friend and I were discussing the solve, and come to find out, there is a reason this challenge has the most solves in the web category.
1
if you go to console and type window. it'll come up with a list of functions you can call, I just scrolled thru it and there was a function that gave the flag lol
Discord message from friend
The Intended Solve
- Notice that there is a commented line
// giveFlag()
inmain.js
. - Run that in the console and get the taunting message.
- List all of the functions in the current context by running
window
. - Find the function
superSecretFunction_d81d7981b32efd3d()
and run it to get the flag.
my solve is cooler