Files
Geroi-Kodeksa/tesseract-reverse/README.md
2026-03-02 21:44:22 +03:00

7.5 KiB
Raw Blame History

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

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 (расшифровка прикладных данных).

Следы Schannel в API Monitor

Значит, хукаем 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

frida -f .\tesseract.exe -l .\stage_2_decryptmessage.js --runtime=v8