2.9 KiB
WasmCoat
Android-клиент склада проверяет штрихкоды через WebAssembly. В раздатке есть WasmCoatScanner.apk, в котором нужно найти защитную конфигурацию. Ключ и данные разнесены между DEX и wasm, поэтому смотрим оба файла.
Решение
Из APK достаём classes.dex и assets/guard.wasm. В DEX-коде загрузчика находим строку wasmcoat-warehouse, которая используется как ключ.
Wasm начинается стандартной сигнатурой \0asm\x01\0\0\0. Дальше идёт последовательность секций:
id: u8 | размер: ULEB128 | содержимое секции
Нужны кастомные секции с id = 0. Внутри каждой сначала записаны длина имени в ULEB128 и само UTF-8-имя. Находим wasmcoat.guard и берём байты после её имени.
Размеры секций закодированы числами ULEB128, которые могут занимать разное количество байт. Если прочитать ULEB128 как обычный
u32, границы секций съедут ещё до расшифровки.
Полезная нагрузка секции скрыта повторяющимся XOR:
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 для его извлечения не нужен.
Флаг
caplag{android_wasm_guard_table_unwrapped}