Программирование в машинных кодах или soft-ice как logger

         

Анимэ и soft-ice


Некоторые отладчики (такие например, как OllyDbg) имеют одну полезную фишку, которую не имеет soft-ice. А именно— возможность пошаговой анимированной трассировки с условными точками останова на каждом ходу. Например, можно поставить точку останова на конструкцию 'TEST EAX,EAX/Jx XXX', заставив отладчик всплывать всякий раз когда EAX будет равен нулю или любому другому значению на наш выбор. Что-то вроде 'BPX IF (*word(EIP)==0xC085 && (*byte(EIP+2) & 70h)==70h)'. Здесь 0xC085 – опкод команды TEST EAX,EAX, а 70h — маска инструкции Jx, ну а вся точка останова в целом позволяет отлавливать код типа "if (func(1,2,3)!=0)…", которые часто используется в защитных механизмах.

Soft-ice таких шуток не понимает и требует, чтобы адрес точки останова был задан явно, например 'BPX EIP...', но и в этом случае он создает одну единственную точку останова, опираясь на текущее значение EIP (каким оно было в момент создания точки останова) и отказывается автоматически "пересчитывать" его по ходу следования программы. Какая жалость! А ведь ради этой возможности многие хакеры отказываются от привычного soft-ice и мигрируют в сторону OllyDbg, между тем решение есть!

Макросы могут быть вложенными! Попробуйте написать 'MACRO XYZ="T; XYZ;"', наберите XYZ и посмотрите что получится. soft-ice начнет анимировать программу! Не слишком быстро, но все-таки достаточно производительно. Во всяком случае для распаковки навесных упаковщиков вполне подойдет.

Коль скоро мы научились анимировать программу, создание условных точек уже не станет проблемой. Вот, например, такой полезный макрос: 'MACRO XYZ = "BPX EIP;T;XYZ;"'. Что он делает? А вот что! Он выделяет трассу следования программы, помечая выполненный код и мы сразу видим какие условные переходы выполнялись, а какие нет (см. рис. 3). Только необходимо учитывать, что кол-во точек останова ограничено и потому их переодически необходимо снимать.



Более сложные фильтры


До сих пор мы не создали ничего сложнее обычного API-шпиона, коих просто тьма. Так стоило ли огород городить? Начнем с того, что soft-ice (особенно при использовании аппаратных точек останова типа 'bpm') намного менее конфликтен, чем большинство шпионов и легко работает там, где другие средства уже не справляются (особенно если его предварительно пропатчить с помощью пакета IceExt, который скрывает отладчик от некоторых защитных механизмов). К тому же, все интересное только начинается!

Давайте чуть-чуть усложним задачу. Будем шпионить не за всеми файлами, а, скажем, только за теми, чье имя начинается на букву 'a'. Это совсем несложно!

bpx CreateFileA if byte (*esp->4)=='a' DO xxx



Легкая разминка


Прежде чем использовать soft-ice как логгер его необходимо правильно настроить. Запускам Symbol Loader, лезем в Edità Soft-ice initialization setting и увеличиваем размер буфера истории (history buffer) до нескольких мегабайт. Точное значение зависит от конкретной задачи. Чем больше информации нам необходимо собрать за один сеанс, тем длиннее должен быть буфер. Поскольку, буфер устроен по принципу кольца, то при его заполнении никакого переполнения не происходит просто свежие данные затирают самые старые. Во вкладке "Macros Definition" можно увеличить количество одновременно используемых макросов с 32 (по умолчанию) до 256. Но это уже по желанию. Для большинства задач лимита в 32 макроса оказывается будет вполне достаточно.



точка останова, шпионящая за открытием файлов, начинающихся с буквы 'a'


Проблема в том, что если функции CreateFileA передается полное имя файла с путем, наша точка останова уже не сработает, поскольку она проверяет только первый символ имени, а функции поиска подстроки в арсенале soft-ice увы, нет. Как говориться, конструктивно непредусмотрено. Какая жалость, но… хакеры мы или нет?!



Будем исходить из того, что память, лежащая выше указателя стека, как правило свободна и может быть использована по нашему усмотрению. Что если записать туда крошечную ассемблерную программу и передать на нее управление? Если это получится (а это получится, уж поверьте мне, парни) мы сможем неограниченно наращивать функционал отладчика, не прибегая к плагинам, которые не совсем документированы (точнее, совсем не документированы), довольно громоздки, неповоротливы и т. д.

Для выполнения программы на стеке нам очевидно нужен исполняемый стек. Вплоть до настоящего времени это не представляло проблемы и в стеке можно было выполнять любой код без каких бы то ни было ухищрений, но теперь ситуация изменилась и на пике борьбы с вирусами и сетевыми червями, производители процессоров скооперировались с Microsoft и в последних версиях Windows XP, а так же ненавистной мне Longhorn, по умолчанию стек защищен от исполнения, впрочем, при первой же попытке выполнения машинного кода в его окрестностях, система выбрасывает диалоговое окно, предлагающее либо отключить защиту, либо сделать нехорошей программе харакири.

Чтобы осуществить задуманное, мы должны сделать следующее:

q       поместить машинный код нашей функции выше вершины стека;

q       сохранить текущее значение регистра EIP и регистра флагов (например, в том же стеке);

q       сохранить все регистры, которые изменяет наша функция;

q       установить EIP на начало нашей функции;

q       тем или иным образом передать аргументы (например, через регистры);


q       выполнить функцию, возвратив результат работы например через EAX;

q       проанализировать возвращенное значение, выполнив те или иные операции;

q       восстановить измененные регистры;

q       восстановить регистр EIP и регистр флагов;

q       продолжить нормальное выполнение программы.

Звучит устрашающе, но ничего сложного в этом нет. Давайте для начала попытаемся выполнить функцию XOR EAX,EAX/RET. Как перевести ее в машинный код? Можно, конечно, воспользоваться HIEW'ом или даже FASM'ом, но зачем выходить из soft-ice? Достаточно переместиться в любое свободное место памяти и дать команду 'a' (assemble – то есть ассемблировать), только предварительно убедитесь, что вы находитесь в контексте отлаживаемого приложения (его имя отображается в правом нижнем углу экрана), а не в ядре, иначе случится крах.

:a esp-10

0023:0012B0DC xor eax,eax

0023:0012B0DE ret

0023:0012B0DF

:d esp-10

0023:0012B0DC 33 C0 C3 00 DB 80 FB 77-88 AE F8 77 FF FF FF FF  3......w...w....

0023:0012B0EC 31 D8 43 00 E8 59 48 00-00 00 00 C0 03 00 00 00  1.C..YH.........


наш самый первый протокол


Мы видим множество строк, каждая из которых описывает причину срабатывания точки останова и время. Вроде бы хорошо, а по сути ничего хорошо. Какой файл открывался в каждой строке? Завершилась ли эта операция успешно или нет? В общем, наша точка останова нуждается в существенной доработке.

Вот улучшенный вариант (внимание! soft-ice не позволяет ставить две точки останова на одну функцию и перед тем как создавать новую, старая должна быть удалена командой 'bc 0'):

bpx CreateFileA DO "D esp->
4 L 20; x;"



условная точка останова, распечатывающая имена всех открываемых файлов


Что изменилось? Появился вывод имени файла: 'D esp->
4 L 20', где 'D' – команда отображения дампа, 'esp->
4' – указатель на первый аргумент функции CreateFileA

(что открывать), а 'L 20' — сколько байт выводить (конкретное значение выбирается по вкусу). Протестируем обновленный вариант. Нажимаем <Ctrl-D>
, выходим из отладчика, запускам какую-нибудь программу, за которой хотим прошпионить (например, FAR), затем, вновь нажимаем <Ctrl-D>
, заходим в отладчик и говорим 'bd 0' для прекращения шпионажа. Выходим из soft-ice, заходим в Symbol Loader и сохраняем историю на диск.

На этот раз мы получаем:

Break due to BPX KERNEL32!CreateFileA  DO "d esp->
4 L 20;x;" (ET=3.64 seconds)

0010:004859E8 43 4F 4E 4F 55 54 24 00-43 4F 4E 49 4E 24 00 49  CONOUT$.CONIN$.I

0010:004859F8 6E 74 65 72 66 61 63 65-00 4D 6F 75 73 65 00 25  nterface.Mouse.%

Break due to BPX KERNEL32!CreateFileA  DO "d esp->
4 L 20;x;" (ET=8.98 milliseconds)

0010:004859F0 43 4F 4E 49 4E 24 00 49-6E 74 65 72 66 61 63 65  CONIN$.Interface

0010:00485A00 00 4D 6F 75 73 65 00 25-63 00 25 30 32 64 3A 25  .Mouse.%c.%02d:%

Break due to BPX KERNEL32!CreateFileA  DO "d esp->
4 L 20;x;" (ET=16.93 milliseconds)

0010:00492330 43 3A 5C 50 72 6F 67 72-61 6D 20 46 69 6C 65 73  C:\Program Files

0010:00492340 5C 46 61 72 5C 46 61 72-45 6E 67 2E 6C 6E 67 00  \Far\FarEng.lng.



усовершенствованный протокол с именами открываемых файлов


Совсем другое дело! Теперь отображается имя открываемого файла и наш импровизированный шпион мало-помалу начинает работать. Однако отсутствуют такие важнейшие ингредиенты как идентификатор процесса, вызывавшего API-функцию и код возврата. Но что нам стоит добавит к точке останова еще несколько строк?

bpx CreateFileA DO "? PID; D esp->
4 L 20; P RET; ? EAX; x;"



финальная точка останова


Команда '? PID' выводит идентификатор процесса, 'P RET' выполняет API-функцию, дожидаясь возврата, а '? EAX' сообщает содержимое регистра EAX в котором находится код возврата из API-функции и все вместе это работает так:

Break due to BPX KERNEL32!CreateFileA  DO "? PID; D esp->
4 L 20; P RET; ? EAX; x;"

000001DC  0000000476  "-"               ; PID

0010:0012138C 43 44 2E 73 6E 61 69 6C-2E 65 78 65 00 61 5F 65  CD.snail.exe.a_e

0010:0012139C 2E 65 78 65 00 00 00 00-00 00 00 00 00 00 00 00  .exe............

00000074  0000000116  "t"                ; код возврата

Break due to BPX KERNEL32!CreateFileA  DO "? PID; D esp->
4 L 20; P RET; ? EAX; x;"

000001DC  0000000476  "-"               ; PID

0010:0012138C 64 65 6D 6F 2E 63 72 6B-2E 65 78 65 00 61 5F 65  demo.crk.exe.a_e

0010:0012139C 2E 65 78 65 00 00 00 00-00 00 00 00 00 00 00 00  .exe............

00000074  0000000116  "t"                ; код возврата

Break due to BPX KERNEL32!CreateFileA  DO "? PID; D esp->
4 L 20; P RET; ? EAX; x;" 000001DC  0000000476  "-"                 ; PID

0010:0012138C 64 65 6D 6F 2E 70 72 6F-74 65 63 74 65 64 2E 63  demo.protected.c

0010:0012139C 72 6B 2E 65 78 65 00 00-00 00 00 00 00 00 00 00  rk.exe..........

00000074  0000000116  "t"                ; код возврата



полная версия протокола


Согласитесь, что с таким отчетом можно и поработать! Мы уже достигли функционала стандартного API-шпиона, однако, при желании фильтр легко усложнить, добавив новые критерии "отбора проб". Формат протокола отчета так же легко обогатить новыми деталями, выводя все необходимые подробности, которые только потребуется (например, содержимое стека вызовов).

Конечно, этот путь не обходится без проблем. Постоянно всплывающий soft-ice противно мерцает и жрет производительность. Можно ли как ни будь заставить его вести протокол не всплывая? Можно! Отладчик поддерживает специальную функцию BPLOG, всегда возвращающую TRUE и подавляющую всплытие отладчика. К сожалению, вместе с этим подавляется и последовательность команд, следующая за DO, а значит, создание подробных отчетов становится невозможным, так что для наших целей такой способ не годиться. Других "анти-всплывающих" средств в нашем распоряжении к сожалению нет.

Еще одним источником головой боли становится мусор в протоколе. Полезные данные перемешиваются с прочей информацией, выводящейся на экран и... Какая там легкость чтения! Без написания специального форматера отчетов мы буквально утонем. Но программировать на Perl'е лениво и на помощь приходят... правильно! Макросы! Только на этот раз не из soft-ice, а те, что встроены в FAR.

Нажимаем <F4>
(в FAR'e) и внимательно смотрим на текст, приведенный на листинге 5. Как видно, каждая порция "отчетной" информации начинается со строки "break due to". Вот ее-то мы и будем искать! Нажимаем <Ctrl-.>
для начала записи макросов, затем <F7>
"break due to" <ENTER>
. Теперь <Shift-стрелка вправо>
пока мы не выделим все лишнее до "CreateFileA", <Crl-Del>
, чтобы вырезать его нафиг, <Ctrl-Shift-стрелка вправо>
, чтобы перейти на "DO", которое мы так же удаляем вместе с остатком строки и т.д. и т. п. Короче кромсаем текст как хотим. Это тяжело описывать словами, легче показать конечный результат. После того как макрос будет создан, достаточно будет его применить к протоколу заданное число раз (просто нажать назначенную ему комбинацию клавиш и не отпускать), в результате чего получится следующее:

CreateFileA, PID:1DCh; NAME: CD.snail.exe;                    RET: 74h

CreateFileA, PID:1DCh; NAME: demo.crk.exe;                    RET: 74h

CreateFileA, PID:1DCh; NAME: demo.protected.crk.exe;          RET: 74h



прилизанный протокол, из которого выброшено все ненужное


Вполне достойный результат для нескольких минут работы! С помощью макросов можно сделать все или практически все! И пускай некоторые презрительно ухмыльнуться, мол, этот путь непрофессионален и вообще. Главное, что поставленная задача была выполнена в рекордно короткие сроки, а все остальное уже неважно.



ассемблирование нашей функции в soft-ice


Теперь программа лежит на стеке, но вот как ее исполнить? Да очень просто! Сказать 'G esp-10' (перейти к адресу esp-10) и пусть процессор выкручивается как может. Только ведь хрен он выкрутиться. Чтобы возвратить управление в текущее место отлаживаемой программы необходимо предварительно сохранить регистр EIP, а сделать это не так-то просто. Команда 'E (esp-10) EIP' не работает поскольку не допускает использования выражений (а имя регистра — это выражение) и обламывает нас по полный "syntax error". Как быть? Кого мочить? Что делать?!

А давайте воспользуется командой 'M' (move), копирующий блоки памяти из одного адреса в другой. Тогда мы сможем сохранить кусочек оригинальной программы на стеке, а саму оригинальную программу модифицировать по своему усмотрению. Конкретно мы будем должны записать PUSH EAX/MOV EAX,ESP/SUB EAX,10h/CALL EAX или что-то того. Короче нам нужна команда CALL ESP-N, по поскольку такой команды в лексиконе x86 процессоров нет и никогда не существовало, нам приходится ее эмулировать через математические преобразования с любым дополнительным регистром, например, EAX. В машинном коде это выглядит так: "50h/8Bh C4h/83h E8h 10h/FFh D0h".

Копируем кусок отлаживаемой программы на стек: 'M EIP L 10 ESP-20', где 'ESP?20' — адрес-приемник, лежащий выше указателя вершины стека и не затирающий нашу машинную программу. Теперь модифицируем окрестности отлаживаемой программы: 'ED EIP 83C48B50; ED EIP+4 D0FF10E8'. Как видно, это тот же самый код, только набранный задом наоборот, потому что в x86 процессорах младший байт располагается по меньшему адресу.

На этом подготовительный этап можно считать законченным. Говорим 'T' (TRACE), повторяя эту команду четыре раза до входа в нашу функцию, а затем отдаем приказ 'P RET' для выхода оттуда. И все!!! В регистре EAX теперь содержится ноль! Наша функция завершила свою работу и возвратила все, что хотела! Разве это не здорово — выполнять в отладчике свой собственный код, написанный с чистого листа?!

Но вот проблема — как проанализировать возвращенное значение в отладчике? Если попытаться пойти прямым путем: 'IF (eax==0) DO xxxx' нас поимеют по всему мясокомбинату. Ну не понимает soft-ice условных команд и ключевое слово IF может встречаться только в точках останова. Так давайте и создадим ему фиктивную точку останова, которая срабатывает всегда! Что-то вроде:

BPX EIP IF (EAX==0) DO xxx



фиктивная точка останова позволяет использовать ключевое слово IF


Естественно, независимо от того сработает ли точка останова или нет, нам необходимо восстановить регистр EAX (про флаги мы помним, но не сохраняем их, чтобы не загромождать код), вернуть кусок оригинальной программы на место и удалить фиктивную точку, поскольку количество точек останова не безгранично. Что касается регистра EAX, то он может быть восстановлен командой POP EAX, следующей за CALL EAX, а вернуть программу на место поможет конструкция 'M ESP-20 L 10 EIP-9'. Откуда взялось 'EIP-9'? Прочему не EIP? Так ведь в процессе выполнения "заплатки" значение EIP

изменилось! Число "9" и есть размер нашей заплатки вместе с командой POP EAX. Остается сказать "R EIP = EIP-9", чтобы вернуть EIP на место и выполнение отлаживаемой программы можно смело продолжать. Если все было сделано правильно и никакой защитный механизм не использовал незадействованный стек, отлаживаемая программа не рухнет.

Кстати говоря, под Windows 9x с некоторой вероятностью сбои все-таки будут происходить, поскольку она активно мусорит в стеке и чтобы не дать ей хулиганить, регистр ESP следует на время выполнения всех операций подтянуть наверх, а затем снова опустить назад.

Естественно, необязательно каждый раз набивать машинные коды вручную. Занятие это утомительное и приятным его никак не назовешь. Вот тут-то нам и пригодятся макросы! Говорим 'MACRO MACRO_NAME = "xxxxx"' и заносим макрос в список постоянных. Это делается так: запускам Symbol Loader, заходим в Edit à Soft-Ice Initialization Setting, переходим к вкладке "Macro Definitions", нажимаем "Add", даем макросу имя (name) и тело (definition). Теперь, макрос будет автоматически загружаться вместе с soft-ice. Можно создать библиотеку собственных расширенных условных точек останова, поддерживающих таких функции поиска подстроки в строке или сравнения строк по шаблонам '*' и '?'. Это действительно можно сделать и тогда мощь soft-ice многократно возрастет, кроме того, мы получим замечательный шанс попрактиковаться программированию в машинных кодах!

Кстати говоря, макросы позволяют решить и другую проблему. Дело в том, что soft-ice не поддерживает вложенные точки останова, без которых нам никак не обойтись (как мы помним, для анализа содержимого регистра EAX нам пришлось прибегнуть к созданию фиктивной точки останова). Если мы попытаемся написать: 'BPX CreateFileA DO "xxx; bpx EIP DO "XXXX"; x;"' ничего не получится! Soft-ice запутается в кавычках и откажется переваривать такую конструкцию. Но если оформить 'bpx EIP DO "XXXX"' в виде макроса, названного, например, XYZ, то конструкция 'BPX CreateFileA DO "xxx; XYZ; x;"' будет воспринята отладчиком вполне благосклонно.



Программирование в машинных кодах или soft-ice как logger


крис касперски ака мыщъх

существует множество полезных софтин, отслеживающих всякие системные события (например, шпионящих за API-функциями). зачастую их возможности весьма ограничены и поэтому хакеры пишут свои, просиживая за компилятором долгие зимние ночи. хотите узнать более короткий путь?



NuMega SoftIce Symbol Loader собственной персоной


Теперь попробуем в качестве разминки проследить за вызовом функции CreateFileA, использующуюся для открытия устройств и файлов. Создадим условную точку останова следующего вида: 'bpx CreateFileA DO "x;"'. Ключевое слово DO определяет последовательность команд отладчика, которые тот должен выполнить после того, как эта точка сработает. Команды разделяются точкой с запятой и подробнее об их синтаксисте можно прочитать в главе "Conditional Breakpoints" руководства пользователя по отладчику. В данном случае здесь стоит только команда 'x', означающая немедленный выход из отладчика.



настойка размера буфера истории


Нажмем <Ctrl-D> для возврата в Windows и попробуем пооткрывать файлы, а когда это надоест, вызовем Symbol Loader и сохраним историю soft-ice в файл протокола (File à Save Soft-ice history as). После непродолжительного шуршания на диске образуется файл winice.log (по умолчанию). Посмотрим, что там?

Break due to BPX KERNEL32!CreateFileA  DO "x;" (ET=1.44 seconds)

Break due to BPX KERNEL32!CreateFileA  DO "x;" (ET=940.19 milliseconds)

Break due to BPX KERNEL32!CreateFileA  DO "x;" (ET=14.51 seconds)

Break due to BPX KERNEL32!CreateFileA  DO "x;" (ET=19.23 milliseconds)

Break due to BPX KERNEL32!CreateFileA  DO "x;" (ET=13.88 milliseconds)



У меня тоже давно чесались


У меня тоже давно чесались лапы написать статью на тему логинга. Взять хотя бы тех же API-шпионов, к примеру. Все программы, которые я видел, очень часто падали без всяких видимых причин или обходились вирусами/защитными механизмами, оставляя самые ценные API-функции за пределами лога. К тому же, размер сгенерированных логов просто ошеломляет. Среди миллионов бестолковых строк нет практически ничего интересного, а система фильтрации функций (даже если она и присутствует) тупее моего хвоста. Можно, конечно, пропустить лог через внешний фильтр, написанный на Perl'е или Си, но ведь это же сколько программировать надо! (не говоря уже о том, что нам может потребоваться информация, отсутствующая в логе, скажем, следует ли за данной API-функцией команда TEST EAX,EAX или нет). А если мы захотим шпионить не только за API, но и за чем-нибудь совершенно другим? Типа перехватить протокол обмена с драйвером или "железом" например.
Soft-ice дает нам такую возможность! Мы просто создаем условную точку останова с хитрыми параметрами и заставляем отладчик вместо всплытия на экран, выводить всю информацию в лог, причем какие данные выводить и в каком порядке, опять-таки определяем мы сами. Система макросов великая вещь, но далеко не все хакеры используют ее на полную. Хотите получить гибкий и конфигурабельный логгер с практически неограниченными возможностями? Ну так чего же мы ждем?!

ice это действительно мощный инструмент


Soft- ice это действительно мощный инструмент необычной разрушительной силы, который позволяет делать все что нужно и даже намного больше сверх этого. Главное — фантазию иметь. Русский мужик всегда отличался умением собирать из всяких подручных средств потрясающие вещи. Вот так и с отладкой. Вместо того, чтобы искать отладчик, реализующий необходимый нам функционал, мы можем взять в руки напильник и доработать уже существующий, тем более, что логинг — это не единственная альтернативная профессия soft-ice. При желании из него можно соорудить отличный дампер или что-то еще. Но это тема уже другого разговора.
Главное —схватить идею. Эта статья не предлагает готовых решений, но зато поднимает целый пласт возможностей, которые каждый может использовать по своему усмотрению.