163 lines
7.5 KiB
Markdown
163 lines
7.5 KiB
Markdown
# Tesseract
|
||
|
||
Забудьте про статику: истина доступна только в динамике.
|
||
|
||
## Решение
|
||
|
||
Подсказка в задании сразу задает направление: **"забудь про статику, смотри на динамику"**.
|
||
|
||

|
||
|
||

|
||
|
||
Первым делом загружаем сэмпл в **Detect It Easy**. Видим **VMProtect**, anti-debug. Т.к. это *.NET*, открываем в **DnSpy**, понимаем что смылсла в статике нет. Везде сильная обфускация, Control-Flow + Сильная анти-дебаг защита. Поэтому лучше смотреть, что происходит во время работы программы: какие строки появляются в памяти и через какие WinAPI проходят данные.
|
||
|
||
Ниже разберем решение пошагово, тем же путем, которым обычно идет участник.
|
||
|
||
## Что понадобится
|
||
|
||
- `frida` и `frida-tools` на Windows.
|
||
- PowerShell
|
||
- Process Hacker / Process Explorer - посмотреть TCP-соединения.
|
||
- WinAPI Monitor - понять, какие WinAPI реально вызываются (SSPI/Schannel, Winsock и т.п.).
|
||
|
||
Готовые скрипты решения:
|
||
|
||
- стадия 1 (получение пароля): `stage_1_findpassword.js`
|
||
- стадия 2 (получение флага): `stage_2_decryptmessage.js`
|
||
|
||
## Что видно при запуске
|
||
|
||
После старта видим простое окно:
|
||
|
||

|
||
|
||
- поле ввода
|
||
- кнопка `Check`
|
||
- подпись `status:`
|
||
|
||
Если вводить случайные строки и нажимать `Check`, появляется сообщение **`Invalid Password`**.
|
||
|
||
Из этого делаем два практических вывода:
|
||
|
||
1. внутри есть сравнение с правильным паролем;
|
||
2. правильная строка где-то в процессе все-таки появляется, хотя бы на короткое время.
|
||
|
||
## Стадия 1: достаем пароль динамически
|
||
|
||
### Почему не `strings` и не статический реверс
|
||
|
||
Если мы запустим программу, укажем любой пароль и посмотрим какие строки лежат в памяти процесса, то ничего полезного не увидем. Сам верный пароль может лежать в памяти, но не долго.
|
||
|
||
В задачах такого типа пароль часто:
|
||
|
||
- вычисляется на лету;
|
||
- расшифровывается в память на долю секунды;
|
||
- сразу затирается;
|
||
- скрывается за виртуализацией/обфускацией.
|
||
|
||
Поэтому здесь быстрее работать через рантайм.
|
||
|
||
### Идея
|
||
|
||
Мы не знаем пароль, но знаем строку, которая точно рисуется на экране: `Invalid Password`.
|
||
|
||
План такой:
|
||
|
||
1. поймать момент, когда приложение рисует `Invalid Password`;
|
||
2. получить указатель на эту строку;
|
||
3. найти memory range, где лежит этот указатель;
|
||
4. просканировать соседнюю область памяти на строки-кандидаты;
|
||
5. выбрать наиболее похожую на пароль.
|
||
|
||
В этом таске строка рисуется через `user32!DrawTextExW`, поэтому это удобная точка для хука.
|
||
|
||
### Как автоматизировать клики
|
||
|
||
Чтобы не нажимать кнопку вручную, скрипт:
|
||
|
||
- находит `EDIT` и кнопку `Check`;
|
||
- шлет `WM_SETTEXT` в поле;
|
||
- шлет `BM_CLICK` в кнопку;
|
||
- повторяет это циклом.
|
||
|
||
Поиск контролов сделан через `EnumWindows` и `EnumChildWindows`.
|
||
|
||
### Что делает `stage_1_findpassword.js`
|
||
|
||
По сути в нем четыре шага:
|
||
|
||
1. Минимально отключает антидебаг (`IsDebuggerPresent`, `CheckRemoteDebuggerPresent`).
|
||
2. Автоматически гоняет ввод и нажатие `Check`.
|
||
3. Хукает `DrawTextExW` и ловит вызов с текстом `Invalid Password`.
|
||
4. От найденного адреса сканирует память, собирает строки-кандидаты и ранжирует их по простым эвристикам.
|
||
|
||

|
||
|
||
в выводе видно что-то такое:
|
||
|
||
```
|
||
[CAND 1] ... VMP_Is_Watching_Y0u
|
||
[+] BEST GUESS: VMP_Is_Watching_Y0u
|
||
```
|
||
|
||
Эту строку и вводим в GUI как пароль.
|
||
|
||
### Запуск стадии 1
|
||
|
||
```powershell
|
||
frida -f .\tesseract.exe -l .\stage_1_findpassword.js --runtime=v8
|
||
```
|
||
|
||
Дальше ждем строку `BEST GUESS`.
|
||
|
||
## Стадия 2: достаем флаг при TLS и pinning
|
||
|
||

|
||
|
||
После ввода верного пароля ничего не происходит, происходит авторизация и все. Запустим proc hacker и видим, что после ввода пароля приложение создает какое-то подключение. Так же это видно через API Monitor
|
||
|
||
Интуитивный вариант - снять трафик в Wireshark/Burp/mitmproxy - здесь не помогает:
|
||
|
||
- трафик зашифрован TLS;
|
||
- pinning ломает MITM даже с подставленным сертификатом.
|
||
|
||
### Идея
|
||
|
||
Смотрим не в сеть, а в процесс. Любой TLS-клиент внутри себя в какой-то момент получает уже расшифрованные данные.
|
||
|
||
В Windows это обычно связка SSPI/Schannel. В API Monitor это видно по вызовам:
|
||
|
||
- `InitializeSecurityContextW` (handshake);
|
||
- `DecryptMessage` (расшифровка прикладных данных).
|
||
|
||

|
||
|
||
Значит, хукаем `DecryptMessage`, и после вызова читаем буферы `SECBUFFER_DATA`. Там лежит plaintext, который приложение уже расшифровало для себя.
|
||
|
||
Так мы обходим pinning без MITM: просто забираем готовые данные из памяти процесса.
|
||
|
||
### Что делает `stage_2_decryptmessage.js`
|
||
|
||
Скрипт:
|
||
|
||
1. при необходимости гасит базовые антидебаг-проверки;
|
||
2. ждет загрузку `secur32.dll` / `sspicli.dll`;
|
||
3. ставит хук на `DecryptMessage`;
|
||
4. на `onLeave` парсит `SecBufferDesc` и связанные `SecBuffer`;
|
||
5. читает все буферы типа `SECBUFFER_DATA` и печатает расшифрованный текст.
|
||
|
||

|
||
|
||
В выводе получаем флаг:
|
||
|
||
```
|
||
[+] FLAG: caplag{D0uble_H00k_And_Tim3_Warp_Cr4ck}
|
||
```
|
||
|
||
### Запуск стадии 2
|
||
|
||
```powershell
|
||
frida -f .\tesseract.exe -l .\stage_2_decryptmessage.js --runtime=v8
|
||
```
|