
28
WebAssembly (WASM) – это не просто очередная игрушка для фронтенда. Это полноценный бинарник, который крутится в браузере и на серверах, и его можно эксплуатировать так же, как классические PE/ELF файлы. Только вот большинство исследователей безопасности до сих пор смотрят на WASM как на что-то экзотическое. А зря – уязвимости там есть, и они реальные.
Что такое WASM с точки зрения реверсера
WASM – это байткод, который компилируется из C/C++/Rust и других языков. У него два представления: бинарное (.wasm) и текстовое (.wat). Важный момент: WASM работает со своей линейной памятью, которая изолирована от основной памяти хоста, но это не значит, что её нельзя скоррапить.
Основные особенности, которые нужно знать:
Инструментарий для декомпиляции
WABT – твой основной арсенал
WABT (WebAssembly Binary Toolkit) – это швейцарский нож для работы с WASM. Вот что тебе понадобится:
wasm2wat – конвертирует бинарник в текстовое представление:
|
1 |
wasm2wat input.wasm -o output.wat |
wasm-decompile – декомпилирует WASM в псевдокод, похожий на C/JS. Это твой лучший друг для быстрого анализа:
|
1 |
wasm-decompile input.wasm -o output.dcmp |
Пример того, что получишь на выходе вместо ассемблерной каши:
|
1 2 3 4 5 |
function dot(a:int, b:int):float { return f32_load(a) * f32_load(b) + f32_load(a + 4) * f32_load(b + 4) + f32_load(a + 8) * f32_load(b + 8); } |
wasm2c – декомпилирует WASM в валидный C-код, который можно скомпилировать обратно:
|
1 |
wasm2c input.wasm -o output.c |
wasm-objdump – дизассемблер с подробной информацией о секциях:
|
1 2 3 |
wasm-objdump -d input.wasm # дизассемблирование wasm-objdump -x input.wasm # все секции и импорты wasm-objdump -s input.wasm # дамп сырых данных |
Ghidra и IDA Pro
Ghidra поддерживает WASM из коробки (с плагинами), а IDA Pro тоже можно прокачать под анализ WebAssembly. Это полезно, когда нужен граф потока управления (CFG) и глубокий статический анализ.
Техники реверсинга WASM
Шаг 1: Извлечение модуля
WASM может быть встроен прямо в JS или загружаться отдельным файлом. Открой DevTools → Network → фильтр по “wasm”. Или ищи WebAssembly.instantiate() в JS-коде.
Шаг 2: Базовый анализ структуры
|
1 |
wasm-objdump -x target.wasm |
Смотри на:
Шаг 3: Декомпиляция
|
1 |
wasm-decompile target.wasm -o analysis.dcmp |
Ищи интересные функции – аутентификацию, криптографию, валидацию входных данных. В псевдокоде будет видно логику намного лучше, чем в .wat.
Шаг 4: Анализ потока данных
WASM использует линейную память. Обращения к памяти выглядят так:
|
1 2 |
(i32.load offset=1024 (local.get 0)) ;; читаем int32 по адресу local[0]+1024 (i32.store offset=2048 (local.get 1)) ;; пишем по адресу local[1]+2048 |
Прослеживай, откуда берутся значения для offset и base pointer – это ключ к поиску багов.
Типичные уязвимости в WASM
Buffer Overflow
Да, в WASM тоже есть переполнения буфера! Хотя WASM изолирован, скоррапленная память внутри модуля может привести к:
Пример уязвимого кода (C → WASM):
|
1 2 3 4 |
char buffer[64]; void vulnerable_func(char *input) { strcpy(buffer, input); // нет проверки длины! } |
При компиляции в WASM это превращается в запись в линейную память без bounds check. Эксплуатация:
wasm-objdumpОтсутствие Stack Smashing Protection (SSP)
В отличие от нативных бинарников, WASM часто компилируется без SSP. Это значит, что stack overflow эксплуатировать легче – нет канареек, которые надо обходить.
Format String
Если WASM использует printf-подобные функции, format string bugs могут дать arbitrary read/write:
|
1 2 3 |
void vuln(char *user_input) { printf(user_input); // классика } |
В WASM это можно использовать для чтения/записи в линейную память.
Use-After-Free и Integer Overflow
Работают так же, как в нативном коде. UAF особенно опасен, если WASM управляет объектами JS через импортированные функции.
Improper Validation of Array Index
|
1 2 3 4 |
int array[10]; int get_value(int index) { return array[index]; // нет проверки границ } |
Можно читать/писать за пределами массива в линейной памяти.
Redirecting Indirect Calls
WASM использует таблицы функций для непрямых вызовов. Если можешь контролировать индекс в таблице, получаешь call hijacking:
|
1 |
(call_indirect (type $sig) (local.get $func_index)) |
Перепиши $func_index и вызовешь любую функцию из таблицы.
Практика: эксплуатация BOF в WASM
Реальный сценарий из исследований:
|
1 |
wasm2wat app.wasm | grep -A 20 "func.*login" |
strcpy без проверки длины, пароль хранится в памяти по offset 1024|
1 2 3 4 5 6 7 8 |
# Payload: переполняем буфер и перезаписываем пароль payload = b"A" * 128 + b"\x00" * 16 + b"admin_pass\x00" # Отправляем через JS API js_code = f""" const buffer = new Uint8Array({list(payload)}); wasmInstance.exports.process_input(buffer); """ |
Продвинутые техники
Символьное выполнение с Manticore
Manticore поддерживает WASM для автоматического поиска уязвимостей:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
from manticore.wasm import ManticoreWASM m = ManticoreWASM("target.wasm") @m.hook(None) def hook(state): # Проверяем условия для уязвимости if state.can_be_true(state.constraints[-1]): print(f"Potential vuln at {state.pc}") # Запускаем символьное выполнение m.run() |
Fuzzing WASM модулей
Используй wasm-interp из WABT для запуска модуля с фаззинговыми входами:
|
1 2 3 4 5 6 |
# Создай wrapper для fuzzing echo "(module (import \"\" \"func\" (func $test)))" > harness.wat wat2wasm harness.wat # Fuzz с AFL++ или libFuzzer afl-fuzz -i inputs/ -o findings/ -- wasm-interp target.wasm @@ |
Динамическая инструментация
Патчи WASM байткод на лету для логирования или изменения поведения:
|
1 2 3 4 5 6 7 8 9 10 |
import wasmtime # Загружаем модуль module = wasmtime.Module.from_file("target.wasm") # Создаём хуки на memory access def mem_hook(caller, offset, length): print(f"Memory access: offset={offset}, len={length}") # Инструментируем runtime |
Deobfuscation
Обфусцированный WASM часто использует:
Лайфхак: используй wasm-decompile и затем прогони через оптимизатор:
Лайфхаки и советы
|
1 |
wasm-objdump -s -j Data target.wasm | strings |
3. Function table mapping: Экспортируй таблицу функций и сопоставь с именами из debug info (если есть):
|
1 |
wasm-objdump -x target.wasm | grep -A 100 "Table\[" |
wasm-opt или патчинга байткода напрямую.|
1 2 3 4 |
const wasm_func = Module.getExportByName(null, "WebAssembly_Function"); Interceptor.attach(wasm_func, { onEnter: function(args) { console.log("WASM call!"); } }); |
Bottom Line
WASM – это полноценная цель для реверс-инжиниринга и эксплуатации. Инструменты есть, уязвимости реальные, а исследователей мало. WABT даёт всё необходимое для статического анализа, Manticore – для символьного, а комбинация декомпиляции + динамического анализа позволяет находить баги, которые пропускают стандартные инструменты.
Главное – не забывай, что линейная память WASM – это просто буфер, а баги работы с памятью никуда не делись. Ищи overflow, format strings, UAF – и найдёшь. Удачной охоты!