# 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 ```