Глава 58
Last updated
Last updated
Хорошо, у нас остаются 3 наиболее тяжелых варианта execryptor, посмотрим повезет ли нам в их решении, и, если нет, то по крайней мере в главе останутся попытки, которые мы сделаем, что тоже будет полезным, поскольку execryptor - один из самых сложных пакеров сегодня.
Когда мы выполняем unpackme "h", мы видим, что он имеет следующую защиту:
То есть он прячет entry point и мы с помощью распакованного варианта, который у нас есть, постараемся разобраться в том, как именно он это делает, чтобы уметь снимать эту защиту в любом execryptor, если у нас получится.
Хорошо, вперед, хе-хе, если мы используем метод предыдущих unpackmes с BREAK ON EXECUTE, мы попадаем сюда.
Здесь я вижу довольно некрасивый поступок, хе-хе, если мы сравним с исправленным вариантом :
Действительно он ужасен, хе-хе.
Другой снимок оригинала :
и той же зоны в h
Видно, что они отличаются, хе-хе
Давайте сравним stack, это stack unpackme h
а это распакованного
Значение стэка в распакованном варианте, когда мы находимся на OEP, равно 12ffc4, и не смотря на то, что на других машинах значение может быть другим, оно должно быть одинаковым для EP обычной программы и OEP запакованной, за исключением редких приемов, в случае же с execryptor, так как он использует трюк с tls, стэк меняется до прибытия в EP, так как до прибытия в EP выполняется код, но мы можем проверить, что у большинства упакованных в EP начальное значение ESP и оно совпадает со значением ESP в OEP.
В нашем случае начальное значение ESP равно 12ffc4 на моей машине, конечно вы можете найти его посмотрев значение ESP любой загруженной программы, когда вы находитесь на EP.
Вернувшись к распакованному варианту, который находится на EP, мы понимаем, что после выполнения первой строки в стэке окажется значение EBP, так как первая строка это PUSH EBP
После выполнения push ebp
он помещается в stack и стэк принимает вид
Мы видим, что значение EBP, которое в моем случае 12ffc0, помещается в stack, это первая инструкция, выполненная оригинальной программой, так что давайте посмотрим в unpackme h, была ли выполнена эта инструкция обращая внимание на значение в стэке чуть выше 12ffc4, есть ли там 12FFc0 или нет. (прим. пер. видимо, в обоих случаях имеется в виду 12fff0)
Посмотрим только stack
Значение, которое будет при выполнении программы с OEP, должно быть точно равно 12ffc4, и, как мы видим, оно даже не 12ffc0
Следовательно, unpackme будет выполнять эмуляцию первых инструкций программы вне секции code и вместо того, чтобы сделать PUSH EBP выполнит большое количество мусорных инструкций, которые в итоге дадут тот же эффект, что и PUSH EBP
Здесь можно было бы поместить один HE или BP в этой зоне
так как здесь начинается выполнение программы без эмуляции, то очень терпеливо анализируя stack, можно было бы легко определить инструкции, которые эмулировались, например :
Возникает исключение, и, если мы посмотрим на stack
Мы видим, что первое добавленное в stack значение равно 12fff0, это начальное значение EBP, так что первая выполнившаяся инструкция - PUSH EBP, следующая, выше ее FFFFFFFF, то есть -1, так что вторая инструкция, которая выполняет запись в stack - PUSH-1, хотя мы и знаем, что между ними есть MOV EBP, ESP
С толикой терпения, анализируя код, мы сможем найти все начальные инструкции, но меня такой подход не устраивает, авторы execryptor утверждают, что его невозможно взломать и я не сталкивался даже с попытками это сделать, но если мы попробуем и добьемся успеха, это будет существенным достижением. Crackslatinos всегда были первыми, кто бросал вызов невозможному и, если мы с вами погибнем в попытках, никто не назовет нас трусами, хе-хе.
Я думаю, что подобные защиты существуют в 2-х вариантах: тяжелые и самые тяжелые, не смотря на то, что я пишу статью, я не представляю, какая здесь используется, но из-за того факта, что ее еще никто не взломал, я предполагаю, что здесь используется самая тяжелая. После завершения, если мое предчувствие оправдается, я объясню различие между обоими, сейчас же объяснить это невозможно, так как надо предварительно показать, как все это функционирует, и только увидев это, можно будет понять различие.
Это начальные регистры произвольной программы без TLS на моей машине, единственным что здесь меняется от раза к разу является значение EBX, но поскольку этот регистр зачастую имеет значения близкие к 7FFDB000 или 7FFDF000, отличить его все же можно, кроме того, его значение не похоже на другие регистры
Хорошо, давайте переместимся снова в зону OEP, мы попадаем снова в
004271CD 83C4 A8 ADD ESP,-58
и сравним регистры с регистрами распакованной программы в том же положении
РАСПАКОВАННЫЙ:
УПАКОВАННЫЙ:
Мы видим, что после процесса эмуляции кроме стэка, о котором мы говорили ранее, все регистры, за исключением значений ECX и EDX, становятся равными регистрам в распакованном варианте, остановленном в этом же самом месте, а значит мы должны следить за стэком и значениями регистров, которые восстанавливаются, а именно EAX, EBX, ESP, EBP, ESI и EDI, регистры ECX и EDX нас не интересуют, поскольку не совпадают с изначальной программой.
Хорошо, давайте вернемся к началу эмуляции.
Если мы запустим трассировку до прибытия в BP, мы увидим огромное количество мусорных инструкций, хотя реальных инструкций всего лишь около 5-ти.
Ну вот мы у входа в ад, хе-хе, начнем?
Первое, что нам надо установить, это то, куда осуществляется сохранение рабочих значений, проведя несколько тестов, установив BPM ON WRITE в нескольких секциях execryptor, мы сразу видим, что значения сохраняются в той же секции, где располагается сама процедура, это логично, так как обычно подобные процедуры не любят распределять данные по различным секциям и стараются работать со всем в той, где они сами и находятся, по крайней мере это так в данном случае и некоторых других, с которыми я сталкивался, если мы столкнемся с другими вариантами - будем искать куда происходит сохранение, но здесь видно, что сохранение идет в ту же секцию.
Хорошо, разместим BPM ON WRITE и выполним RUN, который покажет нам что происходит.
Держитесь, начинается ерунда
Так как здесь:
Важно следить за тем, ЧТО сохраняется и ГДЕ это сохраняется, мы видим, что у этого значения EAX нет никакого смысла, так как это ни один из начальных регистров , ни известное значение и что оно сохраняется в 47AD0C, давайте сделаем небольшой список с данными что и куда сохраняется.
EAX=122601B0 сохраняется в 47ad0c
EAX=5A731601 сохраняется в 4815e0
EBX=5A731601 сохраняется в 4815e0
EAX=0046C5DE сохраняется в 47a90C
в 00496CBB 01 меняется на 00
в 00496C9C 01 меняется на 02
в 00496CAB 00 меняется на 01
Пока мы это делаем искоса смотрим, не появилось ли значение 12fff0 в stack, где оно должно присутствовать, но пока оно не появилось, давайте продолжим делать листинг.
0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0]; 0012FFF0
с помощью этого POP 12fff0 перемещается в 47a0CC
EDI=7C920738 сохраняется в 47a4cc
здесь
0047949B 89BD C0A44700 MOV DWORD PTR SS: [EBP+47A4C0], EDI
Я выделяю значение EDI, так как это одно из начальных значений.
ESI=0046C5DE сохраняется в 0047A8CC
EDX=0047F3EF сохраняется в 0047BCF8
ECX=0047F3EF сохраняется в 0047B8EC
EAX=0047E97E сохраняется в 0047B4E4
EBX=7FFDB000 сохраняется в 00481198
здесь
00498D03 899D 8C114800 MOV DWORD PTR SS: [EBP+48118C], EBX
EAX=0012FFC0 сохраняется в 0047B0D8.
посматриваем на 12ffc0, где должно быть сохранено следующее значение.
EAX=14F43E15 сохраняется в 0047ACCC
в 00496CAB 01 меняется на 00
в 00496C9C 02 меняется на 03
в 00496D9A 00 меняется на 01
Снова тот же POP
посредством этого POP 12fff0 перемещается в 0047A488
EDI=7C920738 сохраняется в 0047A888
Даже после всех этих танцев в 12ffc0 не появляется значение, соответствующее PUSH EBP, то есть 12fff0.
ESI=FFFFFFFF сохраняется в 0047AC88
Здесь
00470F82 89B5 C0A84700 MOV DWORD PTR SS: [EBP+47A8C0], ESI
EDX=0047F3EF сохраняется в 0047C0B4
ECX=0047F3EF сохраняется в 0047BCA8
EAX=0047E97E сохраняется в 0047B8A0
EBX=7FFDB000 сохраняется в 00481554
та же инструкция, которая раньше сохраняла EBX
00498D03 899D 8C114800 MOV DWORD PTR SS: [EBP+48118C], EBX
Давайте терпеливо продолжать, мы уже прошли тысячи инструкций и до сих пор не встретили первую реальную инструкцию программы
EAX=0012FFC4 сохраняется в 0047B494.
здесь
00498D0B 8985 CCB04700 MOV DWORD PTR SS: [EBP+47B0CC], EAX
0012FFC4 - начальное значение ESP, и это имеет важное значение
давайте продолжим
EAX=1E500000 сохраняется в 0047B088
0047B494 C4 FF 12 00
мы видим значение 12ffc4, которое является начальным значением ESP и сейчас оно уменьшается на 4, это происходит, когда выполняется push, значение ESP уменьшается на 4, хе-хе.
0047711C 83AD CCB04700 0> SUB DWORD PTR SS: [EBP+47B0CC], 4
Или же
0047B494 C0 FF 12 00
Сейчас оно становится 12ffc0
в 00496D9A 01 меняется на 00
в 00496C9C 03 меняется на 04
в 00496D8A 00 меняется на 01
Снова то же решение с использованием POP
0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0]; 0012FFF0
в 0047A448 помещается 12fff0
Не забываем, что до сих пор не выполнилась ни одна реальная инструкция, хе-хе
EDI=7C920738 сохраняется в 0047A848
снова в
0047949B 89BD C0A44700 MOV DWORD PTR SS: [EBP+47A4C0], EDI; ntdll.7C920738
ESI=FFFFFFFF сохраняется в 0047AC48
в той же инструкции, которая сохраняла ESI раньше
00470F82 89B5 C0A84700 MOV DWORD PTR SS: [EBP+47A8C0], ESI
Терпеливо продолжаем
EDX=0047F3EF сохраняется в 0047C074
ECX=0047F3EF сохраняется в 0047BC68
EAX=0047E97E сохраняется в 0047B860
EBX = 00478304 сохраняется в 00481514
здесь
00498D03 899D 8C114800 MOV DWORD PTR SS: [EBP+48118C], EBX; UnPackMe.0047830
EAX=0012FFBC сохраняется в 0047B454
обращаем внимание на 12FFbc, это второй адрес для записи в stack
EAX=192082C0 сохраняется в 0047B048
мы видим, что начинает повторяться цикл, давайте продолжим
EAX=0048F082 сохраняется в 0048191C
EBX=0048F082 сохраняется в 0048191C
в 00496D8A 01 меняется на 00
в 00496C9C 04 меняется на 05
в 00496CFB 00 меняется на 01
с помощью POP снова
0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0]; 0012FFF0
в 0047A20C помещается 12fff0.
EDI=7C920738 сохраняется в 0047A60C
та же инструкция, которую мы уже видели перед сохранением EDI
ESI = FFFFFFFF сохраняется в 0047AA0C
По-прежнему не выполнена первая реальная инструкция, grrr
EDX=0047F3EF сохраняется в 0047BE38
ECX=0047F3EF сохраняется в 0047BA2C
EAX=0047E97E сохраняется в 0047B624
Хорошо, я стану Сан Рикардо после этого, хе-хе
EBX=7FFDB000 сохраняется в 004812D8
снова
00498D03 899D 8C114800 MOV DWORD PTR SS: [EBP+48118C], EBX
EAX=0012FFC0 сохраняется в 0047B218
Мы замечаем, так как мы очень внимательные, что в прошлый раз, когда выполнялся цикл, EAX был 12ffc4, а сейчас он 12ffc0, эти детали - это то, что нужно видеть.
EAX=1B700602 сохраняется в 0047AE0C
EAX=FFFFFFFF сохраняется в 0047B624 7E E9 47 00
Мы видим, что сохранение происходит по адресу, где сохранялось верхнее значение stack и оно заменяется FFFFFFFF
в 00496CFB 01 меняется на 00
в 00472B9C 00 меняется на 01
в 00472B9C 01 меняется на 00
в 00496C9C 05 меняется на 06
в 00496CEB 00 меняется на 01
С помощью POP снова
сохраняется в 0047A1CC значение 12fff0
Потом
EDI=7C920738 сохраняется в 0047A5CC
ESI=FFFFFFFF сохраняется в 0047A9CC
EDX=00172CF0 сохраняется в 0047BDF8
ECX=00000012 сохраняется в 0047B9EC
EAX=00472B00 сохраняется в 0047B5E4
Цель защиты заключается в том, чтобы утомить того, кто пытается в ней разобраться, но я очень твердолобый, поэтому продолжим, хе-хе, первая реальная инструкция
по-прежнему не выполнена
EBX = 7FFDB000 сохраняется в 00481298
EAX = 0012FFC4 сохраняется в 0047B1D8
EAX = 18000500 сохраняется в 0047ADCC
и наконец, поскольку я поместил hardware breakpoint on write в 12ffc0, я вижу, где сохраняется значение PUSH EBP
004923C8 871C24 XCHG DWORD PTR SS: [ESP], EBX
Несколькими строками выше мы видим, что значение 12fff0 берется из ареса 47a1cc
Если мы посмотрим немногим ранее, то увидим, что значение 12fff0 сохраняется в 47a1cc с помощью инструкции POP
Этот POP очень важен, поскольку он размещает значение EBP, которое затем с помощью инструкции XCHG эмулирует инструкцию PUSH
Понемногу мы находим что-то полезное среди всей этой бессмыслицы, хе-хе следующая реальная инструкция, которая должна выполниться
004271B1 8BEC MOV EBP, ESP
ESP сейчас имеет значение 12ffc0 и, следовательно, после перемещения EBP приобретет то же значение 12FFc0, это легко увидеть при отладке распакованного варианта.
Мы помним, что это значение получено вычитанием 4-х
0047711C 83AD CCB04700 0> SUB DWORD PTR SS: [EBP+47B0CC], 4
0047B1D8 C4 FF 12 00
После чего оно стало равным 12ffc0, что позволяет предположить, что 47b1d8 хранит значение EBP, и сейчас должно выполниться MOV EBP, ESP, в чем я не совсем уверен, но это одна из возможностей, посмотрим.
Значение становится
0047B1D8 C0 FF 12 00
Это, как мы сказали, может быть место хранения EBP или ESP, так как вторая инструкция делает их равными.
Хорошо, на всякий случай для того, чтобы выполнение остановилось после следующей инструкции сохранения, поставим HARDWARE BPX ON WRITE в stack в следующий адрес, то есть 12FFBC, там, посредством, PUSH -1, происходит сохранение FFFFFFFF, т.е -1.
Здесь мы видим эмулируемые инструкции
Хорошо, давайте продолжать следить за тем, где происходит сохранение, посмотрим сумеем ли мы найти вторую реальную инструкцию.
в 00496CEB 01 меняется на 00
в 00496C9C 06 меняется на 07
в 00496CDB 00 меняется на 01
снова тот же POP, что и раньше
0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0]; 0012FFF0
в 0047A18C F0 FF 12 00 помещается 12FFF0
давайте продолжим
EDI=7C920738 помещается в 0047A58C
ESI=FFFFFFFF помещается в 0047A98C
EDX=00172CF0 помещается в 0047BDB8
ECX=00000012 помещается в 0047B9AC
EAX=00472B00 помещается в 0047B5A4
Достаточно интересный момент заключается в том, что большинство программ, занимающихся эмуляцией инструкций, почти всегда имеют фиксированные места для хранения значений регистров, здесь же мы видим, что значения сохраняются каждый раз в новых местах, что может означать две вещи : либо значения распределены между различными адресами, либо фиксированные места появятся через какое-то время, поскольку сейчас эмулируются 5 инструкций, но в конечной версии execryptor эмулируется достаточно большой кусок программы и я не думаю, что сохранение значений происходит каждый раз в новые места, на это просто не хватило бы памяти, я думаю, что рано или поздно он должен начать сохранять их в фиксированное место, по крайней мере это я наблюдал в большинстве подобных программ, если только мы не имеем дело с какой-то действительно удивительной защитой.
EBX=7FFDB000 сохраняется в 00481258
EAX=12FFc0 сохраняется в 0047B198
и снова начинается цикл
EAX=1590F683 сохраняется в 0047AD8C
EAX=004820F6 сохраняется в 00481660
EBX=004820F6 сохраняется в 00481660
Впрочем, поскольку у нас есть HARDWARE BPX в stack, мы видим, что она тоже постоянно срабатывает, как например сейчас
00478520 68 7F354700 PUSH 47357F
но поскольку мы знаем что это мусор, я не упоминаю об этом
в 00496CDB 01 меняется на 00
Пропустим немного инструкций
00495F2D 6A FF PUSH-1
значение FFFFFFFF помещается в 12FFBC.
Хорошо, дальше на очереди две инструкции push, которые сохраняют 450e60 в 12FFb8 и 4292c8 в 12ffb4, поэтому я помещу другой HARDWARE BPX ON WRITE в 12ffb8
в 00496C9C 07 меняется на 08
в 00496CCB 00 меняется на 01
потом снова POP
0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0]; 0012FFC0
Если вы не обратили внимание, я выделяю данные POP синим цветом, чтобы читателю было легче найти повторение каждого цикла между ними.
Действительно кажется, что каждый раз при выполнении POP обновляется значение EBP, в данном случае, происходит сохранение 12FFC0, что является текущим значением EBP, и если мы помним, когда эмулировался первый PUSH EBP, незадолго до этого был POP, сохраняющий 12FFF0, которое потом было помещено в stack, так что всегда нужно быть внимательным.
Полезно делать выводы по мере накопления информации, например сейчас, я делаю вывод, что здесь всегда обновляется EBP, если в следующий раз это не подтвердится, я откажусь от этой идеи, но мы обрисовываем возможные варианты, судя по всему, в этой эмуляции регистры сохраняются в разных местах, но всегда в одних и тех же инструкциях, то есть в данном случае важным является инструкция, а не место сохранения, мы увидим, так ли это.
То есть в варианте с POP важно знать, что происходит обновление EBP, а не где происходит сохранение, так как сохранение происходит в различных местах, вследствие того, что обычно ведется поиск эмулируемых инструкций и адресов для хранения значений регистров.
Если нам удастся понять логику работы защиты, это поможет нам позднее, поэтому сейчас я проявляю много терпения, видно, что процедура эмуляция хорошая, большинство подобных ей сохраняют значения регистров в 30 смежных байтах и они служат как бы отображением реальных (как в CONTEXT), после выполнения реальной инструкции они сразу же меняются, на основе чего легко сделать вывод о том, какая инструкция выполнилась, здесь же все довольно неясно.
EDI=7C920738 помещается в 0047A54C
этот пример наиболее наглядный, здесь EDI помещается в пустую ячейку всегда в той же самой инструкции, то есть здесь
0047949B 89BD C0A44700 MOV DWORD PTR SS: [EBP+47A4C0], EDI; ntdll.7C920738
То есть в каждом цикле эта инструкция показывает настоящее значение EDI, то же самое сейчас происходит с ESI
ESI=FFFFFFFF помещается в 0047A94C
т.е в пустое место, но инструкция та же, что и раньше
00470F82 89B5 C0A84700 MOV DWORD PTR SS: [EBP+47A8C0], ESI
конечно это происходит среди кучи мусора, различных переходов и возвратов, чтобы запутать, но мы стараемся выделять из всего этого полезные инструкции, будем надеяться что это принесет свои плоды
Давайте не будем забывать что если кто-то выделит этот метод, он может сделать скрипт, который просматривает инструкции, обновляющие регистры и производит запись в log, что поможет интерпретировать многое из того, что делает программа
Также мы видим, что каждый раз при сохранении значения регистра в пустую ячейку, как видно на изображении, видны предыдущие разы, когда происходила запись ESI раньше и что получается некий симметричный рисунок, который сейчас обновится в месте, на которое указывает стрелка.
EDX=00172CF0 помещается в 0047BD78
ECX=00000012 помещается в 0047B96C
Я повторяю, что каждый раз, когда происходит сохранение значения в новом месте видны предыдущие сохраненные значения, в данном случае 12, которое было сохранено только что
Давайте продолжим, уже написано целых 18 страниц, хе-хе, но Сан Рикардо останется по крайней мере до тех пор, пока не закончится эмуляция и мы не попадем в код программы.
EAX=00472B00 помещается в 0047B564
EBX=7FFDB000 помещается в 00481218
это инструкция, которая обновляет EBX
00498D03 899D 8C114800 MOV DWORD PTR SS: [EBP+48118C], EBX
и снова мы видим, что сохранение значений происходит симметрично
EAX=12FFBC сохраняется в 0047B158
здесь
00498D0B 8985 CCB04700 MOV DWORD PTR SS: [EBP+47B0CC], EAX
в этой инструкции всегда сохраняется текущее значение ESP, если мы смотрим, где происходит сохранение, можно проследить за изменением значения
Здесь мы видим текущее значение 12FFBC и предыдущее значение 12FFC0, хорошо по-крайней мере мы находим совпадения.
Давайте продолжим, начинается новый цикл
EAX=1EF00200 помещается в 0047AD4C
А сейчас мы видим, как инструкция SUB вычитает 4 из последнего места, где как мы сказали, сохраняется значение ESP, происходит PUSH, хе-хе
0047711C 83AD CCB04700 0> SUB DWORD PTR SS: [EBP+47B0CC], 4
То есть, в данное место сохраняется значение ESP, далее оно уменьшается на 4, и как и в предыдущий раз, это делается перед инструкцией PUSH, хе-хе.
Данная область памяти принимает следующий вид
Это значение совпадает со значением ESP после PUSH, который выполняется в два шага : сначала обновляется значение ESP, затем сохраняется значение в stack, хе-хе.
в 00496CCB 01 меняется на 00
в 00496C9C 08 меняется на 09
в 00496D3B 00 меняется на 01
Данная инструкция POP обновляет значение EBP
0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0]; 0012FFC0
12FFc0 сохраняется в 0047A30C
здесь видно, что следующая инструкция не укладывается в сформированную мной схему, так как у EDI не текущее значение и оно записывается в той же инструкции, что и всегда, хорошо, давайте продолжим
0047949B 89BD C0A44700 MOV DWORD PTR SS: [EBP+47A4C0], EDI; UnPackMe.0049B8C7
EDI=0049B8C7 помещается в 0047A70C
возможно, это отвлекающий маневр и рассуждения по-прежнему справедливы, давайте продолжим
ESI принимает текущее значение, хорошо
ESI=FFFFFFFF сохраняется в 0047AB0C
EDX=00172CF0 сохраняется в 0047BF38
ECX=00000012 сохраняется в 00000012
EAX=00472B00 сохраняется в 0047B724
Обновляется EBX
00498D03 899D 8C114800 MOV DWORD PTR SS: [EBP+48118C], EBX
EBX=7FFDB000 помещается в 004813D8
Далее обновляется, как мы уже видели, значение ESP здесь
00498D0B 8985 CCB04700 MOV DWORD PTR SS: [EBP+47B0CC], EAX
EAX=12ffb4 сохраняется в 0047B318
хотя мы по-прежнему не выполнили первый push, уже сейчас происходит уменьшение значения ESP для последующего push, для которого оно было бы равно 12ffc4, так ли все произойдет скоро мы узнаем, давайте продолжать
EAX=12A43F15 помещается в 0047AF0C
Кажется, начинается следующий цикл
в 00496D3B 01 меняется на 00
в 00496C9C 09 меняется на 0a
в 00496D2B 00 меняется на 01 .
0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0]; 0012FFC0
снова POP, который обновляет значение EBP
0047A2CC C0 FF 12 00
Он по-прежнему 12FFc0
Происходит обновление EDI, и как мы видим, предыдущее обновление было лишь отвлекающим маневром, так как возвращается верное значение.
0047949B 89BD C0A44700 MOV DWORD PTR SS: [EBP+47A4C0], EDI; ntdll.7C920738
EDI=7C920738 сохраняется в 0047A6CC
ESI=FFFFFFFF сохраняется в 0047AACC
00470F82 89B5 C0A84700 MOV DWORD PTR SS: [EBP+47A8C0], ESI
EDX=00172CF0 сохраняется в 0047BEF8
ECX=0047BEF8 сохраняется в 0047BAEC
возможно, в этих инструкциях происходит обновление EDX и ECX, и хотя, как мы помним, в нашем случае роли они не играют, это может быть полезно, когда будет происходить эмуляция большего участка программы, а не только нескольких первых инструкций OEP
вот эти инструкции
не смотря на то, что это не важно, все же полезно, что мы можем найти значения этих регистров
EAX=00472B00 сохраняется в 0047B6E4
обновляется EBX
00498D03 899D 8C114800 MOV DWORD PTR SS: [EBP+48118C], EBX
EBX=7FFDB000 сохраняется в 00481398
обновляется ESP
00498D0B 8985 CCB04700 MOV DWORD PTR SS: [EBP+47B0CC], EAX
EAX=0012FFB8 сохраняется в 0047B2D8
теперь понятно, почему не было двух последующих push, сейчас в ESP заносится 12FFB8, и это значение, соответствующее первому PUSH
Другой цикл, хе-хе
EAX=09AFCDC0 сохраняется в 0047AECC
EAX=00493FCD сохраняется в 004817A0
EBX=00493FCD сохраняется в 004817A0
Мы видим, что есть случаи, когда происходит чтение значения регистров, как например в этой инструкции
004965A4 8BB5 C0A84700 MOV ESI, DWORD PTR SS: [EBP+47A8C0]
здесь читается последнее сохраненное значение ESI
0047AACC FF FF FF FF
это означает, что наша теория верна, только места для сохранения регистров перемещаются, что подтверждается наличием инструкций, читающих эти регистры, хотя если бы мы поставили BPM ON ACCESS, появившихся исключений было бы слишком много.
в 00496D2B 01 меняется на 00
в 00496C9C 0a меняется на 0b
в 00496D1B 00 меняется на 01
Снова POP обновляет значение EBP
0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0]; 0012FFC0
Обновляет EDI И ESI верными значениями в тех же инструкциях, что раньше.
обновляются EDX, ECX и EAX
Далее обновляется EBX=7FFDE000
00498D03 899D 8C114800 MOV DWORD PTR SS: [EBP+48118C], EBX
ESP обновляется до 0012FFB8
00498D0B 8985 CCB04700 MOV DWORD PTR SS: [EBP+47B0CC], EAX
Хорошо, другой цикл, уфф, это как идти пешком до китая
Начиная с этого места, я буду помечать только ситуации, когда в регистры помещается новое значение, для записей повторных значений в тех же инструкциях я пометок делать не буду, поскольку значения и инструкции будут приведены ранее.
Из ESP вычитается 4
0047711C 83AD CCB04700 0> SUB DWORD PTR SS: [EBP+47B0CC], 4
0047B418 B4 FF 12 00
Далее я вижу, что появляется значение, которое является аргументом следующей инструкции PUSH, если вы помните, это PUSH 450E60.
Данная инструкция сохраняет это значение
0048D299 870C24 XCHG DWORD PTR SS: [ESP], ECX
Мы видели, что в ECX попало нужное значение и теперь оно помещается по адресу ESP, таким образом эмулируется инструкция PUSH 450E60, расположенная посреди тысяч мусорных инструкций, действительно найти ее довольно непросто.
Хорошо, давайте продолжим помечая только важное и исключая повторения
Следующая оригинальная инструкция - PUSH 4292C8
EAX содержит значение ESP, которое меньше по размеру корректного, но в дальнейшем это исправится, как мы уже видели ранее.
На рисунке мы видим, как менялся ESP, сейчас он имеет значение слегка меньшее нужного, но он был 12ffb8, когда пришло время делать предыдущий PUSH, поэтому и в этот раз непосредственно перед операцией PUSH он примет верное значение
ESP увеличивается на 4.
А затем уменьшается на 4, хе-хе.
00471718 83AD CCB04700 0> SUB DWORD PTR SS: [EBP+47B0CC], 4
Сохраняется неверное значение ESI, как уже было с EDI, но потом восстановится корректное.
00470F82 89B5 C0A84700 MOV DWORD PTR SS: [EBP+47A8C0], ESI
ESI=CB4A9B05
Хорошо, далее
00498D0B 8985 CCB04700 MOV DWORD PTR SS: [EBP+47B0CC], EAX
EAX здесь хранит ESP и сейчас содержит 12ffb0
ESI продолжает принимать неверные значения, видно, что эти изменения размещаются между правильными инструкциями, чтобы запутывать(еще больше?)
00470F82 89B5 C0A84700 MOV DWORD PTR SS: [EBP+47A8C0], ESI
ESI=F2AFFFFC
Другим приемом, сбивающим со следа, является сложение и вычитание значения ESP, но это мы уже поняли, хе-хе.
Здесь мы видим, что не только делается обычная эмуляция PUSH с помощью XCHG, но даже непосредственно выполняется следующая оригинальная инструкция
004820D9 873424 XCHG DWORD PTR SS: [ESP], ESI
004820DC 64:A1 00000000 MOV EAX, DWORD PTR FS: [0]
и если я продолжаю трассировку, я прибываю в секцию code, где выполняются оставшиеся инструкции
После чего работа продолжается уже обычным образом
Хорошо, теперь мы знаем немного больше о том, как работает эмуляция инструкций, в аналогичных простых вариантах регистры хранятся в фиксированных ячейках памяти, что позволяет на основе мониторинга этих ячеек видеть моменты, когда выполняется реальная инструкция, а не мусор, кроме того в большинстве случаев перед выполнением реальной инструкции восстанавливаются регистры EAX, EBX и т.д, что позволяет использовать трассировку с условием вида EAX=XXXX && EBX==YYYYY & и т.д. чтобы останавливаться на инструкциях, здесь же это не так, регистры никогда не находятся одновременно в поле зрения, они рассеяны и это усложняет поиск реальных инструкций.
Хорошо, будем продвигаться дальше с собранной информацией:
Мы попадаем в OEP и помещаем BP в место возврата из процедуры эмуляции, то есть здесь
Можно убедиться в том, что на данную инструкцию действительно попадает управление, а сейчас давайте размещать CONDITIONAL BREAKPOINT на всех инструкциях, которые сохраняли реальные значения регистров.
00470F82 89B5 C0A84700 MOV DWORD PTR SS: [EBP+47A8C0], ESI
сохраняет ESI поэтому мы ставим
следующий CONDITIONAL BREAKPOINT
далее инструкция, которая сохраняет EDI
0047949B 89BD C0A44700 MOV DWORD PTR SS: [EBP+47A4C0], EDI
И мы делаем то же самое
инструкция, обновляющая EBP
0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0]
Значение EBP здесь берется из ESP, этим мы отмечаем начало нового цикла
00498D0B 8985 CCB04700 MOV DWORD PTR SS: [EBP+47B0CC], EAX
Здесь сохраняется значение ESP с использованием регистра EAX, поэтому мы также размещаем CONDITIONAL BREAKPOINT
в 0049125A происходит обновление ECX
Хотя мы и не придаем значение EDX, все же поставим и для него CONDITIONAL BREAKPOINT
00491254 8995 ECBC4700 MOV DWORD PTR SS: [EBP+47BCEC], EDX
И для EBX
00498D03 899D 8C114800 MOV DWORD PTR SS:[EBP+48118C],EBX
Не найденным остался только EAX, который не укладывается в схему.
Формируем лог.
Если все рассуждения верны, после последнего витка цикла регистры должны совпадать с оригинальными.
Мы видим, что совпадение только частичное
EBP совпадает, так же как и ECX, EDX, EBX, EDI, остается разобраться с ESP, EAX и ESI, которые имеют близкие, но неравные значения.
Здесь можно сделать следующее : можно начать трассировку с условием равенства ESI FFFFFFFF и после того, как будет найдена инструкция, поступить так же, как и ранее - поставить на нее CONDITIONAL BREAKPOINT
Мы здесь
и ESI равно FFFFFFFF давайте поместим CONDITIONAL BP и посмотрим всегда ли данная инструкция обновляет значение ESI.
Старый CONDITIONAL BREAKPOINT я помечаю как disabled
Мы видим, что он не повторяется в каждом loop, а значит, это не то, что нам нужно, давайте терпеливо продолжим искать дальше.
Если мы продолжим трассировку с 47ce1e, мы попадем сюда
где в регистр вновь записывается значение FFFFFFFF, кроме того данная инструкция, если мы разместим в ней BP, вызывается неоднократно и всегда со значением FFFFFFFF, давайте разместим CONDITIONAL BREAKPOINT.
На следующей строке, так как на ней ESI уже принимает верное значение.
Если я делаю полную трассировку, я вижу, что только в самом конце ESI принимает верное значение, так что пока мы оставим это в качестве корректной условной точки останова, и хотя он присутствует не во всех LOOPS, с более длинной процедурой у нас будет больше возможностей улучшить точность.
Я думаю, в этой главе написано уже достаточно много, мы продолжим исследование эмуляции в следующей главе.
До 59
[C] Рикардо Нарваха, 22.11.06