Поиск в коде того что нужно
From Wiki.conus.info
Современный софт, в общем-то, минимализмом не отличается.
Но не потому, что кодеры слишком много написали, а потому что к исполняемым файлам обыкновенно прикомпиливают все подряд библиотеки. Если бы все вспомогательные библиотеки выносили во внешние DLL, мир был бы иным.
Таким образом, очень полезно сразу понимать, какая функция из стандартной библиотеки или не очень стандартной (вроде boost, libpng), а какая - скорее всего имеет отношение к нашим вопросам.
Переписывать весь код на Си, чтобы разобраться в нем, безусловно, не имеет никакого смысла.
Одна из задач реверсера это быстрый поиск в коде того что собственно его интересует.
Дизассемблер IDA позволяет делать поиск как минимум строк, последовательностей байт, констант... В принципе, можно даже сделать экспорт в текстовый файл .lst или .asm и затем натравить на него grep, awk, итд.
Вообще, когда вы пытаетесь понять, что делает тот или иной код, это запросто может быть какая-то опенсорсная библиотека вроде libpng. Поэтому когда находите константы, или строки которые выглядят явно знакомыми, всегда полезно это погуглить. А если вы найдете искомый опенсорс, то тогда будет достаточно будет просто сравнить вашу функцию с ней. Это решит часть проблем.
К примеру, однажды я пытался разобраться как происходит компрессия-декомпрессия сетевых пакетов в SAP 6.0. Это очень большой софт, но к нему идет подробный .PDB-файл, это очень удобно. Я в конце концов пришел к тому что одна из функций декомпрессирующая пакеты называется CsDecomprLZC. Не сильно раздумывая, я решил погуглить и оказалось что функция с таким же названием имеется в MaxDB (это опен-сорсный проект SAP).
http://www.google.com.ua/search?q=CsDecomprLZC
Каково же было мое удивление, когда оказалось, что в MaxDB используется точно такой же алгоритм, скорее всего, с таким же исходником.
Contents |
Связь с внешним миром
Первое на что нужно обратить внимание, это какие функции из API операционной системы и какие функции стандартных библиотек использует софт.
Если программа поделена на главный исполняемый файл и группу DLL-файлов, то имена функций в этих DLL, бывает так, могут помочь.
Если нас интересует, что именно приводит к вызову MessageBox с определенным текстом, то первое что можно попробовать сделать: найти в сегменте данных этот текст, найти ссылки на него, найти, откуда может передаться управление к интересующему нас вызову MessageBox.
Если речь идет об игре, и нам интересно какие события там более-менее случайны, мы можем найти функцию rand() или её заменитель, и посмотреть, из каких мест эта функция вызывается и что самое главное: как используется результат этой функции.
Но если мы и не в игре, а rand() используется, то также весьма любопытно, зачем. Бывают неожиданные случаи вроде использования rand() в алгоритме для сжатия данных: [1].
Строки
Очень сильно помогают отладочные сообщения, если они имеются. В некотором смысле, отладочные сообщения, это отчет о том, что сейчас происходит в программе. Зачастую, это printf-подобные функции, которые пишут куда-нибудь в лог, а бывает так что и не пишут ничего, но вызовы остались, так как эта сборка - не отладочная, а release. Если в отладочных сообщениях дампятся значения некоторых локальных или глобальных переменных, это тоже может помочь, как минимум, узнать их имена. Например, в Oracle RDBMS одна из таких функций: ksdwrt().
Может также помочь наличие assert() в коде: обычно этот макрос оставляет название файла-исходника, номер строки, и условие.
Осмысленные текстовые строки вообще очень сильно могут помочь. Дизассемблер IDA может сразу указать, из какой функции и из какого её места используется эта строка. Попадаются и смешные случаи.
Парадоксально, но сообщения об ошибках также могут помочь найти то что нужно. В Oracle RDBMS сигнализация об ошибках проходит при помощи вызова некоторой группы функций. Тут еще немного об этом. Можно довольно быстро найти, какие функции сообщают о каких ошибках, и при каких условиях. Это, кстати, одна из причин, почему в защите софта от копирования, бывает так, что сообщение об ошибке заменяется маловнятным кодом или номером ошибки. Мало кому приятно, если взломщик быстро поймет, из за чего именно срабатывает защита от копирования, просто по сообщению об ошибке.
Константы
Некоторые алгоритмы, особенно криптографические, используют константы, которые при помощи IDA легко находить в коде. Например алгоритм MD5 инициализирует свои внутренние переменные так:
var int h0 := 0x67452301 var int h1 := 0xEFCDAB89 var int h2 := 0x98BADCFE var int h3 := 0x10325476
Если в коде найти использование этих четырех значений подряд - очень высокая вероятность что эта функция имеет отношение к MD5.
Magic numbers
Немало форматов файлов определяет стандартный заголовок где используются magic numbers.
Скажем, все исполняемые файлы для винды и MS-DOS начинаются с двух символов "MZ".
В начале MIDI-файла должно быть "MThd". Если у нас есть софта, которая использует MIDI-файлы для чего-нибудь, очень вероятно, что она будет проверять файлы на правильность хотя бы проверяя первые 4 байта.
Это можно сделать при помощи:
; buf указывает на начало загруженного в память файла cmp [buf], 0x6468544D ; "MThd" jnz _error_not_a_MIDI_file
Либо вызвав функцию сравнения блоков памяти memcpy() или любой аналогичный код, вплоть до инструкции cmpsb.
Найдя такое место мы получаем как минимум информацию о том, где начинается загрузка MIDI-файла, во вторых, мы можем увидеть где располагается буфер с содержимым файла, и что еще оттуда берется, и как используется.
DHCP
Это касается также и сетевых протоколов. Например, пакеты протокола DHCP содержат так называемую magic cookie:0x63538263. Какой-либо код генерирующий пакеты по протоколу DHCP где-то и как-то должен внедрять в пакет также и эту константу. Найдя её в коде мы сможем найти место где происходит это и не только это. Что-либо что получает пакеты по DHCP должно где-то как-то проверять magic cookie, сравнивая это поле пакета с константой.
Например, берем файл dhcpcore.dll из Windows 7 x64 и ищем эту константу. И находим, два раза:
.rdata:000007FF6483CBE8 dword_7FF6483CBE8 dd 63538263h ; DATA XREF: DhcpExtractOptionsForValidation+79 .rdata:000007FF6483CBEC dword_7FF6483CBEC dd 63538263h ; DATA XREF: DhcpExtractFullOptions+97
А вот те места в функциях где имеется обращение к константам:
.text:000007FF6480875F mov eax, [rsi] .text:000007FF64808761 cmp eax, cs:dword_7FF6483CBE8 .text:000007FF64808767 jnz loc_7FF64817179
И:
.text:000007FF648082C7 mov eax, [r12] .text:000007FF648082CB cmp eax, cs:dword_7FF6483CBEC .text:000007FF648082D1 jnz loc_7FF648173AF
Поиск нужных инструкций
Если программа использует инструкции сопроцессора, и их не очень много, то можно попробовать проверить отладчиком какую-то из них.
К примеру, нас может заинтересовать, при помощи чего Microsoft Excel считает результаты формул введенных юзером. Например, операция деления.
Если загрузить excel.exe (из Office 2010) версии 14.0.4756.1000 в IDA, затем сделать полный листинг и найти все инструкции FDIV (но кроме тех, которые в качестве второго операнда используют что-то в сегменте данных):
cat EXCEL.lst | grep fdiv | grep -v dbl_ > EXCEL.fdiv
То окажется, что их всего 144.
Мы можем вводить в Excel строку вроде "=(1/3)" и проверить все эти инструкции.
Проверяя каждую инструкцию в отладчике или generic tracer (проверять эти инструкции можно по 4 за раз), окажется, что нам везет и срабатывает 14-я по счету:
.text:3011E919 DC 33 fdiv qword ptr [ebx]
PID=13944|TID=28744|(0) 0x2f64e919 (Excel.exe!BASE+0x11e919) EAX=0x02088006 EBX=0x02088018 ECX=0x00000001 EDX=0x00000001 ESI=0x02088000 EDI=0x00544804 EBP=0x0274FA3C ESP=0x0274F9F8 EIP=0x2F64E919 FLAGS=PF IF FPU ControlWord=IC RC=NEAR PC=64bits PM UM OM ZM DM IM FPU StatusWord= FPU ST(0): 1.000000
В ST0 содержится первый аргумент (1), второй содержится в [ebx].
Следующая за FDIV инструкция записывает результат в память:
.text:3011E91B DD 1E fstp qword ptr [esi]
Если поставить breakpoint на ней, то мы можем видеть результат:
PID=32852|TID=36488|(0) 0x2f40e91b (Excel.exe!BASE+0x11e91b) EAX=0x00598006 EBX=0x00598018 ECX=0x00000001 EDX=0x00000001 ESI=0x00598000 EDI=0x00294804 EBP=0x026CF93C ESP=0x026CF8F8 EIP=0x2F40E91B FLAGS=PF IF FPU ControlWord=IC RC=NEAR PC=64bits PM UM OM ZM DM IM FPU StatusWord=C1 P FPU ST(0): 0.333333
А также, в рамках practical joke, модифицировать его:
gt -l:excel.exe bpx=excel.exe!base+0x11E91B,set(st0,666)
PID=36540|TID=24056|(0) 0x2f40e91b (Excel.exe!BASE+0x11e91b) EAX=0x00680006 EBX=0x00680018 ECX=0x00000001 EDX=0x00000001 ESI=0x00680000 EDI=0x00395404 EBP=0x0290FD9C ESP=0x0290FD58 EIP=0x2F40E91B FLAGS=PF IF FPU ControlWord=IC RC=NEAR PC=64bits PM UM OM ZM DM IM FPU StatusWord=C1 P FPU ST(0): 0.333333 Set ST0 register to 666.000000
Excel показывает в этой ячейке 666, что окончательно убеждает нас в том что мы нашли нужное место.
Если попробовать ту же версию Excel, только x64, то окажется что там инструкций FDIV всего 12, причем нужная нам - третья по счету.
gt64.exe -l:excel.exe bpx=excel.exe!base+0x1B7FCC,set(st0,666)
Видимо, все дело в том что много операций деления переменных типов float и double компилятор заменил на SSE-инструкции вроде DIVSD, коих здесь теперь действительно много (DIVSD присутствует в количестве 268 инструкций).
Подозрительные паттерны кода
См.также: Rare x86 instructions.
