I’ve started seeing WebAssemly (WASM) stuff popping up in a few places, most notably CloudFlare’s recent anti-container isolated v8 workload stuff and I wanted to understand it a little better, but from a hacker's perspective.
Essentially, WebAssembly is a way to compile stuff to a browser-native binary format .wasm, which you can then load with JavaScript and interact with.
Simplest C
Since this is binary, I wanted to start with a C program. Since it’s C, to avoid includes or C<->JS string handling, I’m just going to return 42 like other tutorials start with :)
int main() { return 42; }
If we compile and run it as usual:
> gcc -o 42 -O1 42.c > ./42 > echo $? 42
If we disassemble 42, we get:
push rbp mov rbp, rsp mov eax, 0x2a pop rbp ret
Now as WASM
Right, now let’s see what it looks like as WASM. The easiest way to get started is to use an online fiddle tool such as:
https://mbebenita.github.io/WasmExplorer/
or
https://wasdk.github.io/WasmFiddle/?q1rr6
There is a human readable intermedia form wasm can be represented as (a .wat). For our 42 program this looks like:
(module (table 0 anyfunc) (memory $0 1) (export "memory" (memory $0)) (export "main" (func $main)) (func $main (; 0 ;) (result i32) (i32.const 42) ) )
If we look at WasmExplorer, it also shows the asm of the resulting binary .wasm:
sub rsp, 8 ; 0x000000 48 83 ec 08 mov eax, 0x2a ; 0x000004 b8 2a 00 00 00 nop ; 0x000009 66 90 add rsp, 8 ; 0x00000b 48 83 c4 08 ret
I’ve no idea why that nop is in there.
Compiling and Running it Yourself
Online tools are nice, but what if we wanted to compile and host it oursleves?
First you need emscripten. Hopefully your OS has a nice package. On macOS the homebrew version broke badly, so I followed the manual installation instructions which were super easy.
Once you’ve got it installed, you can compile a “hello world” to wasm with:
emcc hello.c -o hello.html -s WASM=1
This will generate three files, the .wasm binary, a .js loader, and a .html emscripten front-end. Put them up on a webserver of your choice and access the .html, and you’ll see ‘hello world’ in the console. Alternativley, you can have emscripten host a webserver and run it for you with:
emrun --browser firefox --port 8080 .
Or try it at https://sensepost.github.io/wasm-demos/emscripten/hello.html
Going manual
It’s nice that emscripten automates a bunch of stuff for us, like the JS, but I wanted to see what the simplest calls are. So let’s make our own. Mozilla documents this here.
The .wasm emcc compiles is rather large, so I used the .wasm from the fiddler above (click the download icon next to “Wasm”).
The simplest loader for our 42 program that I can come up with is this:
<html> <body> <script> var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]); var m = new WebAssembly.Instance(new WebAssembly.Module(wasmCode)); console.log(m.exports.main()) </script> </body> </html>
The buffer is simply a decimal representation of the .wasm file’s bytes. WebFiddle can do it for you if you change from “Text Format” to “Code Buffer” in the dropdown. You can also generate it with this horrible one liner:
out="";for x in $(xxd -ps -c1 42.wasm); do out="$out,$(( 16#$x ))"; done; echo $out|sed "s/^,\(.*\)$/var wasmCode = new Uint8Array([\1]);/"
or expanded to a script:
#!/bin/sh # Usage: ./wasm2cb.sh <filename>.wasm out="" for x in $(xxd -ps -c1 $1) do out="$out,$(( 16#$x ))" done echo $out|sed "s/^,\(.*\)$/var wasmCode = new Uint8Array([\1]);/"
Simple IO & Function Calling
Just running binaries and logging to the console isn’t very interesting. The good news is that passing parameters in and out is very simple.
Here’s the binary code I’m going to use, note that it doesn’t *need* a main():
int foo(int x) { return x+1; }
Throwing the resulting code buffer into some HTML looks like:
<html> <body> <script> function calc(num) { var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,134,128,128,128,0,1,96,1,127,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,144,128,128,128,0,2,6,109,101,109,111,114,121,2,0,3,102,111,111,0,0,10,141,128,128,128,0,1,135,128,128,128,0,0,32,0,65,1,106,11]); var m = new WebAssembly.Instance(new WebAssembly.Module(wasmCode)); document.getElementById('out').innerHTML = m.exports.foo(num); } </script> <input id="in" /> <button onclick="calc(document.getElementById('in').value)" >Go</button> <div id="out"></div> </body> </html>
And voila, we can now pass input to our binary and get a response.
Including Remote .wasm’s
You don’t need to use a code buffer each time, browsers provide WebAssembly.instantiateStreaming() to do it on the fly for you. Here’s the calc() function from above rewritten to call an external .wasm file with fancy Promise style code I don’t really grok:
function calc(num) { WebAssembly.instantiateStreaming(fetch('io-simple.wasm')).then(obj => obj.instance.exports.foo(num) ).then(res => document.getElementById('out').innerHTML = res ); }
Although this doesn’t work on Safari.
Calling JS Functions
You can also call JavaScript functions from inside your binary! You do that with imports. For example, given the following C:
int foo(int x) { bar(x); return x+1; }
You can define a function bar() in the JavaScript and import it to the WebAssembly like this (building on from the calc() example earlier):
function calc(num) { var importObj = { env: { bar: arg => console.log('Got it: '+arg) } }; WebAssembly.instantiateStreaming(fetch('io-adv.wasm'),importObj).then(obj => obj.instance.exports.foo(num) ).then(res => document.getElementById('out').innerHTML = res ); }
The importObj dictionary’s “env” and “bar” entries were from the resulting .wat, which included the line:
(import "env" "bar" (func (;0;) (type 1)))
So I knew how to build the import.
Disassembling
We’re hackers, and we’re probably going to need to reverse this at some point. This article from the Flare-On challenge pointed me to the WebAssembly Binary Toolkit (wabt pronounced wabbit). It includes the wasm-objdump and wasm2wat tools. wasm2wat will convert the binary to the human readable .wat stack language and is probably the most useful dissasembly. wasm-objdump will give you much the same info, but in more of a typical disasm format. To get actual asm, IDA does some magic with SpiderMonkey that I haven’t looked into yet.
Conclusion
I hope this was useful to you and helped give you a hacker rather than dev intro to wasm.