Определение функций класса с использованием статического и динамического анализа При реверсинге/декомпиляции больших программ написанных на с++, хочется иметь механизм определения функций, принадлежащих к тому или иному классу. Статическим анализом легко можно определять конструкторы классов, или факт того, является ли функция членом класса или нет. Например, конструктор, как правило, инициализирует переменные класса, и в асм листинге все это выглядит достаточно узнаваемо: Code: 3002E5A9 sub_3002E5A9 proc near 3002E5A9 xor eax, eax 3002E5AB push esi 3002E5AC mov esi, ecx 3002E5AE mov [esi], eax 3002E5B0 mov [esi+4], eax 3002E5B3 mov [esi+8], eax 3002E5B6 mov [esi+0Ch], eax 3002E5B9 mov [esi+10h], eax 3002E5BC mov [esi+14h], eax 3002E5BF mov [esi+18h], eax 3002E5C2 mov [esi+1Ch], eax 3002E5C5 mov [esi+20h], eax 3002E5C8 mov [esi+24h], eax 3002E5CB mov [esi+28h], eax 3002E5CE mov [esi+2Ch], eax 3002E5D1 call sub_300AC9DD 3002E5D6 mov eax, esi 3002E5D8 pop esi 3002E5D9 retn 3002E5D9 sub_3002E5A9 endp Что касается принадлежности функции к классу, тут тоже все очевидно. Как гласит стандарт, для не статических функций-членов ключевое слово this является не явно передаваемым в ф-цию адресом того объекта, для которого вызывается функция. В асм листинке (для компиляторов от микрософт), указатель на объект класса всегда приходит в ф-цию не явно, через регистр ecx. Пример: Code: 3002F131 lea ecx, [esi+34h] 3002F134 mov [esi+2Ch], ebx 3002F137 mov [esi+30h], ebx 3002F13A call sub_3002E5A9 Таким образом, статическим анализом довольно просто ответить на два вышеупомянутых вопроса. А вот ответить на вопрос, является ли ф-ция func1 членом класса A, или членом класса B - уже сложнее. Но использовав динамический анализ, это становится довольно простым делом. Итак, используемые для статического анализа инструменты: 1) IDA PRO 2) Скрипты IdaPython ( генерация листинга ф-ций, принадлежащих тому или иному классу ) Инструменты для динамического анализа: 1) PIN ( http://software.intel.com/en-us/articles/pin-a-dynamic-binary-instrumentation-tool ) 2) Python скрипты для преобразования pin output logs в читабельный формат Основная мысль: * Имея на руках все ф-ции принадлежащие классам( все это определяется в статике ), прогоняем приложение в динамике(способов много, трассировка, эмуляция, динамическая рекомпиляция) и получаем значение ecx на входе в ф-цию. Алгоритм(кратко): 1) В статике находим все функции, принадлежащие классам 2) Их адреса записываем в input data file for PIN 3) Пишем модуль для PIN, который на вход принимает файл с адресами ф-ций принадлежащих классам. А на выход дает файл содержащий пары: адрес ф-ции класса + значение ecx регистра. 4) Прогоняем исследуемое приложение под PIN, получаем output file с результатами 5) Парсим результаты, используя имена функций из базы IDA, в результате получаем: имя ф-ции + содержимое регистра ecx Алгоритм(чуть более подробно): Пожалуй единственное, что может вызвать затруднение, это нахождение всех ф-ций принадлежащих к классам. Алгоритм примерно такой: * перечисляем все ф-ции и исследуемом приложении * строим для каждой ф-ции базовые блоки * для каждого блока анализируем все инструкции * у инструкций анализируем операнды * вводим понятие состояний для регистров ( STATE_INIT, STATE_SAVE, STATE_RESTORE, STATE_MODIFICATE и так далее ) * ведем списки состояний для базовых блоков Тогда для всех ф-ций, у второго операнда с типом reg и регистром ecx, нужно будет найти состояние STATE_SAVE. Причем в списке блока это состояние должно быть первым. Пример: Code: 3002F110 sub_3002F110 proc near 3002F110 push ebx 3002F111 push esi 3002F112 push [esp+8+arg_4] 3002F116 mov esi, ecx <== интересующая инструкция 3002F118 call sub_301AC430 Список состояний для регистра ecx в данном случае будет содержать первым состоянием STATE_SAVE. Это означает, что регистр сразу же начинает использоваться, без инициализации. То есть, вспоминая про this, ф-ция является членом класса, записываем её в список. * на выходе будем иметь список ф-ций принадлежащий классам исследуемого приложения. Что касается модуля PIN, то там все тривиально: 1) устанавливается ф-ция IMG_AddInstrumentFunction, в колбеке которой указываем, за каким приложением/модулем нужно следить(нужно для оптимизации процесса). 2) устанавливается ф-ция INS_AddInstrumentFunction, в колбеке которой указываем, какие аргументы нас интересуют(адрес инструкции и содержимое ecx): INS_InsertCall( ins, IPOINT_BEFORE, (AFUNPTR)InstructionHandler, IARG_INST_PTR, IARG_REG_VALUE, REG_ECX, IARG_END ); Результаты всего этого бедлама и их анализ: Во-первых, несмотря на фильтрацию инструкций только от нужного exe/dll в PIN, результатов для большого приложения придется ждать довольно долго ( десятки минут ). Во-вторых, результаты придется анализировать. Выглядит все примерно следующим образом: Code: ... sub_30090C3F, ecx = 0x141af0a0 sub_30069312, ecx = 0x12f460 sub_30070AA7, ecx = 0x14192000 sub_30070AA7, ecx = 0x14192000 sub_3008FB93, ecx = 0x141af0a0 sub_30084CA2, ecx = 0x141af0a0 sub_3000D6B6, ecx = 0x189dc8 sub_3000242F, ecx = 0x189de4 ... Сразу заметно, что для ф-ций sub_30090C3F, sub_3008FB93, sub_30084CA2 значение ecx одинаково. Следовательно, они принадлежат к одному классу. Виртуальные ф-ции для такого способа также не являются проблемой. Конструкторы находятся также, они просто будут первыми в списке. Полученный список можно отсортировать по значению ecx, для более удобного анализа. Еще стоит упомянуть о том, что при выполнении исследуемой программы, вызываются не все ф-ции. Соответственно, не все ф-ции принадлежащие классам можно восстановить. Но полное покрытие это уже совсем другая задача, и вобще говоря, еще не решенная (см. информацию по symbolic execution). Суббота, 22 декабря 2012 г. автор: Tss http://kitrap08.blogspot.ru/2012/12/blog-post.html