WASM (rev)
We are given an encryption script which contains the following code:
function strToBytes(str) {
const bytes = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++)
bytes[i] = str.charCodeAt(i);
return bytes;
}
const wasmBuffer = new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01, 0x60, 0x01, 0x7f, 0x00, 0x02, 0x09, 0x01, 0x00, 0x03, 0x6d, 0x65, 0x6d, 0x02, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, 0x07, 0x0a, 0x01, 0x06, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x00, 0x00, 0x0a, 0x64, 0x01, 0x62, 0x02, 0x04, 0x7f, 0x01, 0x7c, 0x02, 0x40, 0x41, 0x00, 0x21, 0x01, 0x03, 0x40, 0x20, 0x01, 0x20, 0x00, 0x41, 0x01, 0x6b, 0x4e, 0x0d, 0x01, 0x02, 0x40, 0x20, 0x01, 0x21, 0x02, 0x03, 0x40, 0x20, 0x02, 0x20, 0x00, 0x41, 0x01, 0x6b, 0x4e, 0x0d, 0x01, 0x20, 0x02, 0x2d, 0x00, 0x00, 0x21, 0x03, 0x20, 0x02, 0x41, 0x01, 0x6a, 0x2d, 0x00, 0x00, 0x21, 0x04, 0x20, 0x03, 0x20, 0x04, 0x73, 0x21, 0x04, 0x20, 0x02, 0x41, 0x01, 0x6a, 0x20, 0x04, 0x3a, 0x00, 0x00, 0x20, 0x02, 0x41, 0x01, 0x6a, 0x21, 0x02, 0x0c, 0x00, 0x0b, 0x0b, 0x20, 0x01, 0x41, 0x01, 0x6a, 0x21, 0x01, 0x0c, 0x00, 0x0b, 0x0b, 0x0b]).buffer;
const importObject = {
"": {
mem: new WebAssembly.Memory({ initial: 1 }),
}
};
WebAssembly.instantiate(wasmBuffer, importObject)
.then(({ instance }) => {
let memArr = new Uint8Array(importObject[""].mem.buffer);
let flagArr = strToBytes('THIS_IS_A_FAKE_FLAG');
for (let i = 0; i < flagArr.length; i++)
memArr[i] = flagArr[i];
instance.exports.encode(flagArr.length);
let result = "";
for (let i = 0; i < flagArr.length; i++) {
result += ("00" + memArr[i].toString(0x10)).slice(-2);
}
console.log(result); # 571653080c6e350c6b0f01196d06436075756365
});
It uses WebAssembly.instantiate
to create a WebAssembly instance. Then, each encoded character is appending to result
. We then receive the result
.
After some googling, I realised that the bytes given as wasmBuffer
is actually a WebAssembly payload which exports a function called encode
. After using the wasm2wat
tool, I get the following web assembly:
(module
(type $t0 (func (param i32)))
(import "" "mem" (memory $.mem 0))
(func $encode (export "encode") (type $t0) (param $p0 i32)
(local $l1 i32) (local $l2 i32) (local $l3 i32) (local $l4 i32) (local $l5 f64)
(block $B0
(local.set $l1
(i32.const 0))
(loop $L1
(br_if $B0
(i32.ge_s
(local.get $l1)
(i32.sub
(local.get $p0)
(i32.const 1))))
(block $B2
(local.set $l2
(local.get $l1))
(loop $L3
(br_if $B2
(i32.ge_s
(local.get $l2)
(i32.sub
(local.get $p0)
(i32.const 1))))
(local.set $l3
(i32.load8_u
(local.get $l2)))
(local.set $l4
(i32.load8_u
(i32.add
(local.get $l2)
(i32.const 1))))
(local.set $l4
(i32.xor
(local.get $l3)
(local.get $l4)))
(i32.store8
(i32.add
(local.get $l2)
(i32.const 1))
(local.get $l4))
(local.set $l2
(i32.add
(local.get $l2)
(i32.const 1)))
(br $L3)))
(local.set $l1
(i32.add
(local.get $l1)
(i32.const 1)))
(br $L1)))))
I asked chatGPT to convert this into Python and got the following encoding function:
def encode(data):
for i in range(len(data) - 1):
for j in range(i, len(data) - 1):
data[j + 1] = data[j] ^ data[j + 1]
return data
The encoding function iterates i
from from the 0
to n - 1
, and for each i
it iterates j
from i
to n - 1
. For each j
value, the element at index j + 1
is XOR'd with the element at index j
. To decode this function we can do the XOR operations in the reverse order.
x = "571653080c6e350c6b0f01196d06436075756365"
ct = [x[i:i+2] for i in range(0, len(x), 2)] # create an array of hex strings
n = len(ct)
for i in range(0, n):
ct[i] = int(ct[i], 16) # convert hex values to ints
for i in range(n - 2, -1, -1):
for j in range(n - 2, i - 1, -1):
ct[j + 1] ^= ct[j] # decode
for i in range(0, n):
ct[i] = chr(ct[i])
print(ct) # CDDC24{WASM_15_R34LY_C00L!!}
Last updated