Глава 58

Хорошо, у нас остаются 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

вот эти инструкции

00491254 8995 ECBC4700 MOV DWORD PTR SS: [EBP+47BCEC], EDX

0049125А 898D E0B84700 MOV DWORD PTR SS: [EBP+47B8E0], ECX

не смотря на то, что это не важно, все же полезно, что мы можем найти значения этих регистров

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 верными значениями в тех же инструкциях, что раньше.

00491254 8995 ECBC4700 MOV DWORD PTR SS: [EBP+47BCEC], EDX

0049125A 898D E0B84700 MOV DWORD PTR SS: [EBP+47B8E0], ECX

00491260 8985 D8B44700 MOV DWORD PTR SS: [EBP+47B4D8], EAX

обновляются 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.

00491254 8995 ECBC4700 MOV DWORD PTR SS: [EBP+47BCEC], EDX

0049125A 898D E0B84700 MOV DWORD PTR SS: [EBP+47B8E0], ECX

00491260 8985 D8B44700 MOV DWORD PTR SS: [EBP+47B4D8], EAX

Данная инструкция сохраняет это значение

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

Last updated