Barbhack 2026: Contrôle d'intégrité
Recon
The challenge starts with a webpage that loads a WebAssembly module. On the page we have two buttons:
- The button on the right should show us the time needed to fetch one valid byte from the password.
- The button on the left gets us the flag directly through a
reveal()function.
Also, the webpage gives us the length of the flag: 28 bytes.
The challenge would be too easy if the push of a button reveals the flag. When we click it, the website seems to compute the password.. and never ends.
So, we click the button on the right to see how much time it would take to get one valid byte from the password. The webpage computes something for some time (can be long), and then it finally outputs that, for my machine at least, the time needed is 386.8 seconds per byte.
This means that the reveal() function would take over 180 minutes to fetch the whole password. So we could technically resolve the challenge this way, but a) it’s not fun and b) it’s not convenient.
Therefore we have to find a way to speed up that byte fetch process so we can use the reveal() function easily.
WebAssembly reversing
This was my first time with WebAssembly, so the syntax was kind of weird. We can see from the Javascript file that the server gave us that it calls functions from the WASM module to get its results. One interesting function is func19:
(func (;19;) (type 9) (result i64)
(local i64 i64 i64 i64 i64 i64 i32)
i64.const 2611923443488327891
local.set 1
i64.const 1
local.set 2
loop ;; label = @1
local.get 0
i64.const -7046029254386353131
i64.mul
local.set 5
i32.const -28
local.set 6
local.get 0
local.set 3
local.get 2
local.set 4
loop ;; label = @2
local.get 6
i32.const 1048604
i32.add
i64.load8_u
local.get 1
i64.const 1099511628211
i64.mul
i64.add
local.get 4
local.get 3
i64.const 63
i64.div_u
i64.add
i64.rotl
local.get 5
i64.xor
local.set 1
local.get 3
i64.const 1
i64.add
local.set 3
local.get 4
i64.const 1
i64.add
local.set 4
local.get 6
i32.const 1
i32.add
local.tee 6
br_if 0 (;@2;)
end
local.get 2
i64.const 1
i64.add
local.set 2
local.get 0
i64.const 1
i64.add
local.tee 0
i64.const 200000000
i64.ne
br_if 0 (;@1;)
end
local.get 1)
It seems that we have two nested loops in this code, and also a couple of constant definitions. Most of these constants look like random numbers, but one sticks out of the pile: 200000000. It also is an interesting number because it’s placed at the end of the outer loop and it seems like it’s the loop’s stop condition, such as:
for (int i=0; i<200000000; i++) {
for (whatever condition) {
// inner loop code...
}
}
I wanted to modify the code right away but it didn’t seem possible. By looking at some Chromium documentation, it is possible to replace some source files with local files with what they call “Local overrides”, in the “Sources” developer tools tab.
So I downloaded the WebAssembly source file and opened it…
␀asm␁␀␀␀␁B␌`␂⑿⑿␀`␃⑿⑿⑿␁⑿`␂⑿⑿␁⑿`␃⑿⑿⑿␀`␁⑿␀`␀␀`␁⑿␁⑿`␀␂⑿⑿`␅⑿⑿⑿⑿⑿␀`␀␁~`␄⑿⑿⑿⑿␁⑿`␀␁⑿␂9␁␕./bh2026_chall2_bg.js␟__wbindgen_init_externref_table␀␅␃0/␆␄␁␀␀␂␀␁␂␀␀␃␃␀␈␀␃␂ ␀␁␀␀
␀␇␄␄␀␁␃␂␁␂␀␀␀
It actually seems like a binary file, already assembled. And it indeed is. In fact, the Chromium developer tools automatically display WebAssembly .wasm binary files as their text counterpart, .wat (WebAssembly Text Format).
To go from a .wasm binary to .wat on a Linux-based machine, the WASM Toolbox is available:
sudo dnf install wabt
Then we can use one of the tools in the toolbox to do that operation:
wasm2wat bh2026_chall2_bg.wasm -o output.wat
Now we can edit the func19 end condition, and set it to something low like 20000, diminishing it ten thousand times. The code should be way faster!
Then, we convert back the WebAssembly text file to a binary with the wat2wasm tool from the tooklit.
Now when we force reload the webpage with our new .wasm source file, we can check the function runs way faster using the button on the right, and indeed it does. We use the button on the left to call the reveal() function with a reasonable (very quick) execution time, and we have the flag: brb{REDACTED}.