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

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

w

 

 

to

 

 

144  Часть 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

 

 

 

 

Стрелки, ведущие вверх после инкремента, являются признаком цикла. Благодаря им цикл легче увидеть на схеме, чем в стандартном окне дизассемблирования. Схема состоит из пяти блоков. Четыре верхних представляют собой этапы цикла for, размещенные в определенном порядке (инициализация, сравнение, выполнение и инкремент). Блок, находящийся в правой нижней части, является эпилогом функции, который в главе 4 был описан как часть функции, ответственная за очистку стека и возвращение значения.

Поиск циклов while

Цикл while часто используется авторами вредоносного ПО при ожидании, пока не будет выполнено какое-то условие, например получение команды или пакета. В ассемблере циклы while похожи на for, но их легче понять. В листинге 6.14 показан цикл while, который продолжает работу, пока checkResult возвращает 0.

Листинг 6.14. Цикл while в языке C

int status=0; int result = 0;

while(status == 0){

result = performAction(); status = checkResult(result);

}

В ассемблере это выражение похоже на цикл for, но без инкремента (листинг 6.15). Присутствуют условный и безусловный переходы, однако выполнение первого является единственным условием прекращения работы цикла.

Листинг 6.15. Код на ассемблере для цикла while из листинга 6.14

00401036

mov

[ebp+var_4], 0

0040103D

mov

[ebp+var_8], 0

00401044

loc_401044:

 

00401044

cmp

[ebp+var_4], 0

00401048

jnz

short loc_401063

0040104A

call

performAction

0040104F

mov

[ebp+var_8], eax

00401052

mov

eax, [ebp+var_8]

00401055

push

eax

00401056

call

checkResult

0040105B

add

esp, 4

0040105E

mov

[ebp+var_4], eax

00401061

jmp

short loc_401044

Соглашения, касающиеся вызова функций

В главе 4 мы обсудили то, как для вызова функций используются стек и инструкция call. В ассемблере функции могут вызываться по-разному. Эта процедура регули-

 

 

 

 

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 в ассемблере  145

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

руется отдельными соглашениями, которые определяют, какие параметры помещаются в стек или регистры и кто ответственен за очистку стека после завершения функции — вызывающая или вызываемая сторона.

Выбор тех или иных соглашений зависит в том числе и от компилятора. Их реа­ лизации могут немного отличаться, поэтому код, собранный разными компиляторами, может оказаться сложным для понимания. Чтобы достичь совместимости, соглашения, касающиеся использования Windows API, реализуются одинаково (см. главу 7).

В листинге 6.16 с помощью псевдокода записаны все форматы вызова функций.

Листинг 6.16. Вызовы функций, выполненные в псевдокоде

int test(int x, int y, int z); int a, b, c, ret;

ret = test(a, b, c);

Самыми распространенными способами вызова функций являются инструкции cdecl, stdcall и fastcall. Ключевые различия между ними будут рассмотрены в следующих разделах.

ПРИМЕЧАНИЕ

Одни и те же соглашения могут по-разному использоваться в разных компиляторах, но мы сосредоточимся на самых популярных реализациях.

cdecl

cdecl — это одно из наиболее распространенных соглашений. Мы сталкивались с ним в главе 4 при знакомстве со стеком и вызовами функций. Операнды cdecl помещаются в стек справа налево, за очистку стека после завершения функции отвечает вызывающая сторона, а возвращаемое значение сохраняется в регистре EAX. Ниже показан пример того, как бы выглядел ассемблерный код из листинга 6.16, если бы он был скомпилирован с использованием cdecl.

Листинг 6.17. Вызов функции cdecl

push c push b push a call test add esp, 12

mov ret, eax

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

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

w

 

 

to

 

 

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

w Click

 

 

 

 

 

 

 

 

 

 

 

o

m

 

w

 

 

 

 

 

 

 

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

stdcall

 

 

 

 

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

 

 

 

 

Популярное соглашение stdcall похоже на cdecl, но требует, чтобы очисткой стека после завершения функции занималась вызываемая сторона. Следовательно, если бы мы использовали stdcall в листинге 6.17, нам бы не понадобилась инструкция add, поскольку за очистку стека отвечала бы вызванная функция.

Функция test из листинга 6.16 тоже была бы скомпилирована иначе, поскольку ей пришлось бы очищать стек. Эта процедура выполнялась бы в ее конце.

stdcall является стандартным соглашением для Windows API. Код, который вызывает функции этого интерфейса, не должен заниматься очисткой стека, так как это ответственность динамических библиотек, содержащих реализацию этих функций.

fastcall

Соглашение fastcall имеет больше всего различий в разных компиляторах, но в целом оно работает похожим образом в любых ситуациях. Первые несколько аргументов (обычно два) попадают в регистры, среди которых чаще других используются EDX и ECX (такой вариант применяют в Microsoft). Остальные аргументы загружаются справа налево, а вызывающая функция обычно выполняет очистку стека, если это требуется. Это соглашение часто оказывается наиболее эффективным, поскольку оно не так активно использует стек.

Сравнение инструкций push и mov

Помимо разных соглашений о вызове функций, описанных выше, компиляторы могут использовать разные инструкции для выполнения одних и тех же операций. Обычно это касается того, как данные попадают в стек — с помощью mov или push.

В листинге 6.18 показан пример вызова функции на языке C. Функция adder слагает два аргумента и возвращает результат. Функция main вызывает adder и выводит возвращенное значение посредством printf.

Листинг 6.18. Вызов функции на языке C

int adder(int a, int b)

{

return a+b;

}

void main()

{

int x = 1; int y = 2;

 

 

 

 

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 в ассемблере  147

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

printf("the function returned the number %d\n", adder(x,y));

}

Ассемблерный код функции adder не зависит от компилятора и представлен в листинге 6.19. Этот код добавляет arg_0 к arg_4 и сохраняет результат в регистре EAX (как упоминалось в главе 4, EAX хранит возвращаемое значение).

Листинг 6.19. Ассемблерный код функции adder из листинга 6.18

00401730

push

ebp

00401731

mov

ebp, esp

00401733

mov

eax, [ebp+arg_0]

00401736

add

eax, [ebp+arg_4]

00401739

pop

ebp

0040173A

retn

 

В табл. 6.1 перечислены разные соглашения о вызове функций, которые используются двумя компиляторами: Microsoft Visual Studio и GNU Compiler Collection (GCC). Слева параметры для функций adder и printf помещаются в стек с помощью инструкции push, а справа — с использованием инструкции mov.

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

ПРИМЕЧАНИЕ

Помните, что даже один и тот же компилятор может использовать разные соглашения о вызове в зависимости от настроек и параметров.

Таблица 6.1. Ассемблерный код вызова функции с использованием двух разных соглашений

Visual Studio

 

GCC

 

 

 

 

 

 

 

 

00401746

mov

[ebp+var_4], 1

00401085

mov

[ebp+var_4], 1

0040174D

mov

[ebp+var_8], 2

0040108C

mov

[ebp+var_8], 2

00401754

mov

eax, [ebp+var_8]

00401093

mov

eax, [ebp+var_8]

00401757

push

eax

00401096

mov

[esp+4], eax

00401758

mov

ecx, [ebp+var_4]

0040109A

mov

eax, [ebp+var_4]

0040175B

push

ecx

0040109D

mov

[esp], eax

0040175C

call

adder

004010A0

call

adder

00401761

add

esp, 8

004010A5

mov

[esp+4], eax

00401764

push

eax

004010A9

mov

[esp], offset TheFunctionRet

00401765

push

offset TheFunctionRet

004010B0

call

printf

0040176A

call

ds:printf