72 lines
2.9 KiB
Markdown
72 lines
2.9 KiB
Markdown
<h1 align="center">WasmCoat</h1>
|
|
|
|
<p align="center">
|
|
<img src="https://img.shields.io/badge/category-Reverse-blueviolet" alt="Reverse"/>
|
|
<img src="https://img.shields.io/badge/difficulty-medium-orange" alt="medium"/>
|
|
</p>
|
|
|
|
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}`
|