Files
2026-03-02 21:44:22 +03:00

163 lines
7.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tesseract
Забудьте про статику: истина доступна только в динамике.
## Решение
Подсказка в задании сразу задает направление: **"забудь про статику, смотри на динамику"**.
![Интерфейс задания](image.png)
![Подсказка](image-1.png)
Первым делом загружаем сэмпл в **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`
## Что видно при запуске
После старта видим простое окно:
![Окно программы](image-2.png)
- поле ввода
- кнопка `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. От найденного адреса сканирует память, собирает строки-кандидаты и ранжирует их по простым эвристикам.
![Результат первой стадии](image-3.png)
в выводе видно что-то такое:
```
[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
![Сетевое поведение](image-4.png)
После ввода верного пароля ничего не происходит, происходит авторизация и все. Запустим proc hacker и видим, что после ввода пароля приложение создает какое-то подключение. Так же это видно через API Monitor
Интуитивный вариант - снять трафик в Wireshark/Burp/mitmproxy - здесь не помогает:
- трафик зашифрован TLS;
- pinning ломает MITM даже с подставленным сертификатом.
### Идея
Смотрим не в сеть, а в процесс. Любой TLS-клиент внутри себя в какой-то момент получает уже расшифрованные данные.
В Windows это обычно связка SSPI/Schannel. В API Monitor это видно по вызовам:
- `InitializeSecurityContextW` (handshake);
- `DecryptMessage` (расшифровка прикладных данных).
![Следы Schannel в API Monitor](image-5.png)
Значит, хукаем `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` и печатает расшифрованный текст.
![Результат второй стадии](image-6.png)
В выводе получаем флаг:
```
[+] FLAG: caplag{D0uble_H00k_And_Tim3_Warp_Cr4ck}
```
### Запуск стадии 2
```powershell
frida -f .\tesseract.exe -l .\stage_2_decryptmessage.js --runtime=v8
```