Init. Commit
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
<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}`
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
from pathlib import Path
|
||||
import argparse, base64, hashlib, json, lzma, marshal, os, plistlib, re, struct, subprocess, sys, zipfile, zlib
|
||||
|
||||
def xor(data, key):
|
||||
return bytes(b ^ key[i % len(key)] for i, b in enumerate(data))
|
||||
|
||||
def find_after(data, marker):
|
||||
off = data.index(marker) + len(marker)
|
||||
ln = struct.unpack_from('<I', data, off)[0]
|
||||
off += 4
|
||||
return data[off:off+ln]
|
||||
|
||||
def sections(w):
|
||||
assert w[:8] == b'\x00asm\x01\x00\x00\x00'
|
||||
i = 8
|
||||
def read_uleb():
|
||||
nonlocal i
|
||||
shift = n = 0
|
||||
while True:
|
||||
b = w[i]; i += 1
|
||||
n |= (b & 0x7f) << shift
|
||||
if not b & 0x80: return n
|
||||
shift += 7
|
||||
while i < len(w):
|
||||
sid = w[i]; i += 1
|
||||
ln = read_uleb(); end = i + ln
|
||||
if sid == 0:
|
||||
nln = read_uleb(); name = w[i:i+nln].decode(); i += nln
|
||||
yield name, w[i:end]
|
||||
i = end
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(); ap.add_argument('apk', nargs='?', default='../public/WasmCoatScanner.apk')
|
||||
args = ap.parse_args()
|
||||
with zipfile.ZipFile(args.apk) as z:
|
||||
dex = z.read('classes.dex'); w = z.read('assets/guard.wasm')
|
||||
key = re.search(rb'wasmcoat-warehouse', dex).group(0)
|
||||
sec = dict(sections(w))
|
||||
cfg = json.loads(xor(sec['wasmcoat.guard'], key))
|
||||
print(cfg['target'])
|
||||
if __name__ == '__main__': main()
|
||||
Reference in New Issue
Block a user