WasmCoat

Reverse medium

Android-клиент склада проверяет штрихкоды через WebAssembly. В раздатке есть `WasmCoatScanner.apk`, в котором нужно найти защитную конфигурацию. Ключ и данные разнесены между DEX и wasm, поэтому смотрим оба файла. ## Решение Из APK достаём `classes.dex` и `assets/guard.wasm`. В DEX-коде загрузчика находим строку `wasmcoat-warehouse`, которая используется как ключ. Wasm начинается стандартной сигнатурой `\0asm\x01\0\0\0`. Дальше идёт последовательность секций: ```text id: u8 | размер: ULEB128 | содержимое секции ``` Нужны кастомные секции с `id = 0`. Внутри каждой сначала записаны длина имени в ULEB128 и само UTF-8-имя. Находим `wasmcoat.guard` и берём байты после её имени. > Размеры секций закодированы числами ULEB128, которые могут занимать разное количество байт. Если прочитать ULEB128 как обычный `u32`, границы секций съедут ещё до расшифровки. Полезная нагрузка секции скрыта повторяющимся XOR: ```python import json import zipfile with zipfile.ZipFile("public/WasmCoatScanner.apk") as apk: wasm = apk.read("assets/guard.wasm") def uleb(data, pos): value = shift = 0 while True: byte = data[pos] pos += 1 value |= (byte & 0x7f) << shift if not byte & 0x80: return value, pos shift += 7 assert wasm[:8] == b"\0asm\x01\0\0\0" pos = 8 while pos < len(wasm): section_id = wasm[pos] size, pos = uleb(wasm, pos + 1) end = pos + size if section_id == 0: name_size, pos = uleb(wasm, pos) name = wasm[pos:pos + name_size].decode() if name == "wasmcoat.guard": blob = wasm[pos + name_size:end] break pos = end else: raise ValueError("wasmcoat.guard не найдена") key = b"wasmcoat-warehouse" plain = bytes(b ^ key[i % len(key)] for i, b in enumerate(blob)) config = json.loads(plain) print(config["target"]) ``` С правильной границей секции получаем JSON. Значение `target` — искомый флаг. Запуск WebAssembly для его извлечения не нужен. [Солвер](solve/solve.py). ## Флаг `caplag{android_wasm_guard_table_unwrapped}`