Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
книги хакеры / Майкл_Сикорски,_Эндрю_Хониг_Вскрытие_покажет!_Практический_анализ.pdf
Скачиваний:
18
Добавлен:
19.04.2024
Размер:
17.17 Mб
Скачать

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

 

w Click

 

 

 

 

 

 

m

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

.

 

 

 

 

6

 

 

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Распознавание конструкций языка C в ассемблере

В главе 4 мы познакомились с архитектурой x86 и ее самыми распространенными инструкциями. Но в успешном обратном проектировании отдельные инструкции просто так не рассматриваются. Это слишком утомительный процесс, так как дизассемблированная программа может состоять из тысяч и миллионов элементов. Аналитик безопасности должен уметь получать общее представление о назначении кода, анализируя целые его участки, и останавливаться на отдельных инструкциях только при необходимости. Развитие этого навыка требует времени.

Чтобы разобраться, как именно нужно группировать инструкции, для начала подумаем о том, как автор вредоноса разрабатывает код. Вредоносные программы обычно пишутся на языках высокого уровня, чаще всего на C. Конструкции кода — это уровень абстракции, который определяет функциональные свойства, но не подробности их реализации. Примером таких конструкций могут служить циклы, операторы if, связные списки, оператор switch и т. д. Программа состоит из отдельных конструкций, которые вместе составляют ее общую функциональность.

Эта глава содержит описание более чем десяти разных конструкций языка C и должна послужить для вас отправной точкой. Мы рассмотрим каждую конструкцию с точки зрения ассемблера, хотя данная глава предназначена для того, чтобы научить вас делать обратное: как аналитику безопасности вам придется переводить дизассемблированный код на более высокий уровень. Изучение этого процесса в обратном направлении часто оказывается более простым, так как программисты привыкли к чтению и пониманию исходного кода.

В этой главе мы сосредоточимся на том, как компилируются самые распространенные и сложные конструкции, такие как циклы и условные выражения. Усвоив эти базовые принципы, вы научитесь быстро составлять общее представление о том, как работает код.

Помимо обсуждения конструкций мы также рассмотрим различия между компиляторами, разные версии и параметры которых могут влиять на то, как именно та или иная конструкция будет представлена в ассемблере. В качестве примера мы возьмем два способа компиляции выражения switch. Эта глава содержит довольно глубокий материал по конструкциям языка C, поэтому чем лучше вы понимаете этот язык и программирование в целом, тем больше пользы вы сможете из нее извлечь. Если вам нужна помощь с языком C, обратите внимание на классическую книгу

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

w

 

 

to

 

 

136  Часть II  •  Продвинутый статический анализ

w Click

 

 

 

 

 

 

 

 

 

 

 

o

m

 

w

 

 

 

 

 

 

 

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Брайана Кернигана и Денниса Ритчи «Язык программирования C» (The C Pro­ gramming Language) (Prentice-Hall, 1988). Большая часть вредоносного ПО написана именно на этом языке, хотя в некоторых случаях используются Delphi и C++. C является простым языком, имеющим непосредственное отношение к ассемблеру, так что это самая логичная отправная точка для начинающего аналитика безопасности.

Во время чтения данной главы не забывайте, что ваша цель заключается в понимании общей функциональности программы, а не в анализе каждой отдельной инструкции. Не позволяйте мелочам скрыть от вас общую картину. Сосредоточивайтесь на работе программ в целом, а не на их отдельных возможностях.

Переменные: локальные и глобальные

Доступ к глобальным переменным имеет любая функция в программе. Локальные переменные доступны только в той функции, в которой они были объявлены. В языке C объявление переменных этих двух видов мало чем различается, но в ассемблере они выглядят совсем по-разному.

Ниже представлены два примера кода на языке C: один для глобальных переменных, а другой — для локальных. Обратите внимание на небольшое различие между ними. В листинге 6.1 переменные x и y объявляются глобально, за пределами функции, а в листинге 6.2 — внутри (то есть локально).

Листинг 6.1. Простая программа с двумя глобальными переменными

int x = 1; int y = 2;

void main()

{

x = x+y;

printf("total = %d\n", x);

}

Листинг 6.2. Простая программа с двумя локальными переменными

void main()

{

int x = 1; int y = 2;

x = x+y;

printf("total = %d\n", x);

}

Вязыке C разница между глобальными и локальными переменными невелика,

ив данном случае обе программы дают один и тот же результат. Однако дизассемблированные варианты, представленные в листингах 6.3 и 6.4, довольно сильно разнятся. К глобальным переменным обращаются по адресу в памяти, а к локаль-

ным — по адресу в стеке.

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

Глава 6. Распознавание конструкций языка C в ассемблере  137

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Влистинге 6.3 глобальная переменная x обозначена как dword_40CF60, с адресом

впамяти 0x40CF60. Заметьте, что при перемещении eax в dword_40CF60 перемен-

ная x меняет свой адрес . Это повлияет на все последующие функции, которые используют эту переменную.

Листинг 6.3. Пример глобальной переменной из листинга 6.1, представленный в ассемблере

00401003

mov

eax, dword_40CF60

00401008

add

eax, dword_40C000

0040100E

mov

dword_40CF60, eax

00401013

mov

ecx, dword_40CF60

00401019

push

ecx

0040101A

push

offset aTotalD ;"total = %d\n"

0040101F

call

printf

В листингах 6.4 и 6.5 локальная переменная x находится в стеке с постоянным сдвигом относительно ebp. В листинге 6.4 адрес [ebp-4] используется функцией

внеизменном виде для адресации локальной переменной x. Это означает, что адрес ebp-4 находится в стеке и что обратиться к нему можно только из той функции,

вкоторой была объявлена соответствующая переменная.

Листинг 6.4. Пример локальной переменной из листинга 6.2, представленный в ассемблере

00401006

mov

dword ptr [ebp-4], 0

0040100D

mov

dword ptr [ebp-8], 1

00401014

mov

eax, [ebp-4]

00401017

add

eax, [ebp-8]

0040101A

mov

[ebp-4], eax

0040101D

mov

ecx, [ebp-4]

00401020

push

ecx

00401021

push

offset aTotalD ; "total = %d\n"

00401026

call

printf

В листинге 6.5 программа IDA Pro любезно дала нашей переменной x фиктивное имя var_4. Как упоминалось в главе 5, фиктивные имена можно поменять на более осмысленные, которые отражают их назначение. Замена -4 на var_4 упрощает анализ, поскольку после переименования переменной в x вам не придется искать в функции сдвиг -4.

Листинг 6.5. Пример локальной переменной из листинга 6.2, представленный в ассемблере и промаркированный

00401006

mov

[ebp+var_4], 0

0040100D

mov

[ebp+var_8], 1

00401014

mov

eax, [ebp+var_4]

00401017

add

eax, [ebp+var_8]

0040101A

mov

[ebp+var_4], eax

0040101D

mov

ecx, [ebp+var_4]

00401020

push

ecx

00401021

push

offset aTotalD ; "total = %d\n"

00401026

call

printf

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

w

 

 

to

 

 

138  Часть II  •  Продвинутый статический анализ

w Click

 

 

 

 

 

 

 

 

 

 

 

o

m

 

w

 

 

 

 

 

 

 

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

Дизассемблирование арифметических операций

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

В программе на языке C может выполняться множество разных математических операций. В этом разделе мы рассмотрим то, как они представлены в ассемблере.

В листинге 6.6 показан код на C с двумя переменными и несколькими арифметическими выражениями. Операции -- и ++ используются для декрементирования и соответственно инкрементирования значения на 1. Операция % применяется для получения остатка после деления двух переменных.

Листинг 6.6. Код на C с двумя переменными и набором арифметических операций

int a = 0; int b = 1; a = a + 11; a = a - b; a--;

b++;

b = a % 3;

Ниже показано, как листинг 6.6 будет выглядеть в ассемблере. Этот код можно разбить на части и транслировать обратно в C.

Листинг 6.7. Пример арифметических операций из листинга 6.6, переведенный на ассемблер

00401006

mov

[ebp+var_4], 0

0040100D

mov

[ebp+var_8], 1

00401014

mov

eax, [ebp+var_4]

00401017

add

eax, 0Bh

0040101A

mov

[ebp+var_4], eax

0040101D

mov

ecx, [ebp+var_4]

00401020

sub

ecx, [ebp+var_8]

00401023

mov

[ebp+var_4], ecx

00401026

mov

edx, [ebp+var_4]

00401029

sub

edx, 1

0040102C

mov

[ebp+var_4], edx

0040102F

mov

eax, [ebp+var_8]

00401032

add

eax, 1

00401035

mov

[ebp+var_8], eax

00401038

mov

eax, [ebp+var_4]

0040103B

cdq

 

0040103C

mov

ecx, 3

00401041

idiv

ecx

00401043

mov

[ebp+var_8], edx

В этом примере а и b являются локальными переменными, поскольку они адресуются в рамках стека. Программа IDA Pro пометила a как var_4, а b — как var_8. Изначально переменным var_4 и var_8 присваиваются значения соответственно 0 и 1. Затем a перемещается в регистр eax , после чего туда добавляется 0x0b, то есть происходит инкремент на 11. После этого из a вычитается b . Компилятор решил использовать инструкции sub и add вместо функций inc и dec.