Tags: aes wasm 

Rating: 4.5

Solved by: krystalgamer

WASM(nasm) Fans Only

Requirements

  • wabt - (wasm2c) converts wasm to c
  • c compiler
  • chromium based browser (couldn't get it to work on firefox)
  • disable any script/ad-blocker since it may block the wasm file
  • decompiler (not necessary but helpful)

verifyFlag.js

Analyzing the file there's two interesting functions: lose and win.

Set a breakpoint on lose and press login. According to call-stack it was called from the wasm module, more precisely the verify_flag function.

If the call to function23 returns 0 then it doesn't even go into the big loop.

Deep diving

Download the wasm module run: wasm2c verifyFlag.was -o converted.c Converted module dependecy: https://github.com/WebAssembly/wabt/blob/master/wasm2c/wasm-rt.h cc -c converted.c

function23

Calls function25 and checks whether the result is 24. After that goes into a loop and checks for character '}'.

function25 (strlen)

Starts by checking memory alignment of the passed ptr and if it finds any zero byte, quits immiediatelly returning 0. Then it goes into a loop, incrementing a pointer, and performing this check: v - UINT64_C(0x0101010101010101)) & ~(v) & UINT64_C(0x8080808080808080);, which is a zero byte checker, used as a strlen performance enhancement. It then returns ptr-argument. It's a strlen.

Conjugating this knowledge, this function must be checking the structure of the data passed. 24 characters and ending with a '}' -> utflag{16_chars_here}.

Backtracking lose calls

Besides the one mentioned above there's another one, that is called in a tight loop check.

while(..)
    if(x!=y)
        return lose()
win()

Two buffers are being compared, the result of function9 and another that was already in memory in position 5245584(the encrypted flag!).

function9

The real name of this function is aes_encrypt_block and takes three arguments. (buffer_to_encrypt, key, output_buffer). The buffer_to_encrypt is a ptr to the password field, starting after the '{', the key is nasmfans.ga and the output_buffer is 5245568 (contiguous to the encrypted flag!).

Encrypted flag

Having the key nasmfans.ga and the encrypted key [15, 174, 248, 89, 132, 177, 40, 103, 40, 24, 136, 23, 100, 211, 37, 42]. We can just decrypt it.

from Crypto.Cipher import AES

encrypted = [15, 174, 248, 89, 132, 177, 40, 103, 40, 24, 136, 23, 100, 211, 37, 42]

key = bytearray()

for c in 'nasmfans.ga':
    key.append(ord(c))

while len(key) != 16:
    key.append(0)

cipher = AES.new(bytes(key), AES.MODE_ECB)

enc_buffer = bytearray()
for e in encrypted:
    enc_buffer.append(e)
print(cipher.decrypt(bytes(encrypted)))

Which yields fPRv38aICAz31Ix7. The flag is utflag{fPRv38aICAz31Ix7}

Feel free to reach out to me for any questions.

Final notes on function10

I can't leave without talking about this piece of shit, that took me a lot of time to understand. It just converts an array to a matrix. For some reason I thought the name aes was just to troll...

aaSSfxxxMarch 9, 2020, 6:04 p.m.

Hi,

I used a very similar approach, except I compiled with `-m32 -fno-PIC -O2 -c -fno-reorder-functions -fno-inline-functions-called-once -fno-inline-small-functions` in order to simplify most of useless variables (and tweaked the get_intXX/set_intXX to have direct x-refs to data in IDA instead of computing offsets manually).

Nice writeup :)