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:

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}.