Цели урока:
- Применить техники, представленные в Уроках 3 и 4 для анализа анти-отладочного трюка Max++.
- Поупражняться в реверсинге приложений на x86 платформе.
Задача дня:
- Написать кусок кода на Python, для Immunity Debugger, который исполнит Max++ и сгенерирует Log для каждого вызова инструкции INT 2D.
1. Введение
Примечание: мы предполагаем, что вы будете запускать виртуальную машину в режиме NON-DEBUG. В этом уроке мы будем использовать Immunity Debugger.
Давайте вернемся к Max++ и применим знания, полученные из Уроков 3 и 4. На рис. 1 представлено дизассемблированное представление первых 20 инструкция Max++. Точка входа расположена по адресу 0x00403BC8. Пошагово выполните код до адреса 0x403BD5, там вы столкнетесь с первой задачей: Как обойти инструкцию INT 2D?
Есть несколько вариантов: (1) Просто нажать F8 и IMM ПРОПУСТИТ (SKIP) инструкцию RETN и перейдет прямо на адрес 0x413BD8 (выполняя инструкцию CALL 0x413BD8, которая находится сразу после инструкции RETN); (2) Либо выполнить инструкцию RETN, изменив значение регистра EIP. В IMM можно изменить значение EIP с помощью командного окна Python (нажмите на 2-ю кнопку в toolbar, с права от кнопки «open file») в котором нужно выполнить следующую команду Python:
«imm.setReg("EIP", 0x00413BD7);»
Рис. 1. Точка входа в Max++
Какое из действий будет зависеть от поведения IMM? Если мы нажмем F8, то будет ли его поведение тем же, что и у программы без присоединенного отладчика? Или нет? Следуя аналогичному подходу из Урока 4, можно произвести эксперимент для случая, когда EAX = 1 (т.е. вызвать из INT 2D сервис «Debug print»). Ниже дан вывод, сделанный после проведения эксперимента:
Когда загрузка операционной системы производится в режиме NON-DEBUG, поведение IMM будет точно таким же, как и вовремя обычного исполнения, при EAX = 1 (обратите внимание, что в Уроке 4, в нашем эксперименте рассматривался случай с EAX = 0). Исходя из этого, можно [B]спокойно нажать F8 (и пропустить инструкцию RETN) в IMM![/B]
2. Отклонение потока управления инструкцией INT 2D
Сейчас мы находимся по адресу 0x00413A38 (Рис. 2). Эта функция состоит всего из четырех инструкций: STD, MOV EDI, EDI, CALL 0x00413BB4 и IRETD.
Цель инструкции STD состоит в том, чтобы установить направление роста EDI (т.е. флаг направления, direction flag, df) в 1. Регистры EDI/ESI часто используются с инструкциями копирования памяти, такими как «REP STOSB» (для многократного копирования из памяти, адрес которой указан в ESI, в адрес назначения, указанный в EDI). Позже мы увидим использование этих инструкций в операциях кодирования/декодирования злонамеренного кода в Max++.
Инструкция «MOV EDI, EDI» ничего не делает (и не воздействует ни на какие флаги регистров). После нее идет вызов функции из адреса 0x00413BB4.
Обратите внимание, что как только мы вернемся из функции 0x00413BB4, следующей инструкцией для выполнения будет (IRETD), однако, это не так. Функция 0x00413BB4 получит участок зашифрованного кода, расшифрует и поместит его, начиная от места размещения инструкции IRETD. Таким образом, если инструмент статического анализа произведет анализ данной программы, т.е. отобразит графит потока исполнения Max++, то он введет в заблуждение аналитиков малвари. Мы вернемся к функции декодирования в следующем уроке.
Рис. 2. Функция 0x413A38
Нажмите «F7», чтобы войти внутрь функции 0x00413BB4. Тут мы подходим к интересной точке. Посмотрите на инструкцию CALL 0x00413BB9 по адресу 0x413BB4 (Рис. 3)!
В основном, инструкция CALL делает две вещи: (1) помещает адрес следующей инструкции в стэк (так, что когда мы возвратимся из функции, выполнение возобновится со следующей инструкции); если вы понаблюдаете за содержимым стэка (в IMM это нижняя правая панель), то заметите, что в него помещается 0x413BB9. (2) Затем она переходит на адрес функции, который равен 0x00413BB9.
Следующие две инструкции должны вызвать сервис INT 2D. Заметьте, что EAX = 3 (означая сервис «Load image»). И снова, используя тот же подход из Урока 4, можно придумать эксперимент, чтобы ответить на вопрос: «каким будет следующее действие?». Проведя эксперимент, вывод будет следующим: когда EAX = 3, при режиме NON-DEBUG, поведение IMM будет таким же, как и при нормальном выполнении, а именно: одно-байтовая инструкция, следующая сразу за инструкцией INT 2D, будет пропущена.
А что, если инструкция RETN выполнится (т.е. однобайтовая инструкция не будет пропущена, предположим, что автоматический анализатор не правильно обрабатывает INT 2D)? Если это произойдет, то вы начнете выходить из раннее вызванных функций, что в свою очередь, приведет к завершению программы. Этот трюк заключается в следующем: напомним, что RETN снимает верхний элемент из стэка и переходит на этот адрес. Верхний элемент стэка сейчас равен 0x00413BB9. Поэтому, произойдет возврат выполнения, обратно к адресу 0x00413BB9. Затем снова выполнится INT 2D и снова инструкцию RETN заставят выполниться и перейти на адрес 0x00413A40 (где расположена инструкция IRETD, которая идет сразу после CALL 0X00413BB4 в функции 0x00413A38, см. Рис. 2.). Что в свою очередь приведет к возврату в main program и завершит саму программу. Таким образом, какие-либо другие вредоносные действия не будут выполняться в этом сценарии. К этому моменту, вы уже можете видеть цель трюка с INT 2D: автор вредоноса пытается избежать инструментов автоматического анализа (если они не правильно обрабатывают INT 2D) и некоторые отладчики ядра, такие как WinDbg.
Вопрос дня: воспользуйтесь WinDbg, вместо Immunity Debugger, для отладки Max++ (c режимом DEBUG, включенным при загрузке). Что вы наблюдаете?
Рис. 3. Трюк: бесконечный цикл вызовов
3. Заключение
Мы показали вам несколько примеров использования инструкции INT 2D в Max++, которые позволяют определить наличие отладчика и изменить поведение вредоносной программы, чтобы избежать анализ с помощью отладчика. Отладчику, автоматически справиться с инструкцией INT 2D, будет не легко. Во-первых, существует множество сценариев для борьбы с ним (зависит от типа отладчика, наличия отладчика ядра, и (booting options) опций загрузки). Во-вторых, во время загрузки программы, не ожидайте перехвата всех инструкций INT 2D, потому что программа может быть самораспаковывающейся (т.е. модифицирующей свой сегмент кода, во время выполнения).
4. Задача дня
Вам будет полезно написать Python-скрипт для управления Immunity Debugger, который будет автоматически справляться с инструкцией INT 2D. Ниже мы предлагаем несколько основных идей для решения этой задачи:
В IMM, есть глобальная переменна «imm», которая предназначена для того, чтобы управлять отладчиком. Можно использовать «imm» для установки и наблюдения за всеми значениями регистров, а так же для осмотра и изменения памяти (RAM). В вашей программе может быть использован простой цикл, который мог бы выполнять инструкцию за инструкцией (чтобы сделать это, вы можете использовать «breakpoint functions», доступные в Python API отладчика IMM). Перед выполнением каждой инструкции, можно было бы проверить ее опкод (используя «libanalyze.opcode», см. документацию IMM), и затем предпринять соответствующие действия (пропуская следующий байт, основываясь на значение регистра EAX, имитируя нормальную NON-DEBUG среду), если встретилась инструкция INT 2D.
© Translated by Prosper-H from r0 Crew