Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
книги хакеры / Майкл_Сикорски,_Эндрю_Хониг_Вскрытие_покажет!_Практический_анализ.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

 

.

 

 

 

 

 

20

 

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

 

-xcha

 

 

 

 

 

Анализ кода на C++

 

 

 

 

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++ имеет несколько свойств и конструкций, которых нет в C, и это может усложнить исследование дизассемблированного кода.

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

Объектно-ориентированное программирование

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

ПРИМЕЧАНИЕ

Чтобы узнать больше о C++, почитайте книгу Брюса Эккеля Thinking in C++, которую можно найти в свободном доступе по адресу www.mindviewinc.com.

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

При выполнении объектно-ориентированного кода на C++ вы создаете объекты определенного класса (которые называются его экземплярами). У вас может быть несколько экземпляров одного и того же класса. Каждый экземпляр содержит собственные данные, но все объекты одного класса имеют один и тот же набор функций. Чтобы получить доступ к функции или данным, вам нужно сослаться на объект соответствующего типа.

 

 

 

 

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

 

 

Глава 20. Анализ кода на C++  459

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

В листинге 20.1 показана простая программа на языке C++ с классом и одним объектом.

Листинг 20.1. Простой класс на C++

class SimpleClass { public:

int x;

void HelloWorld() { printf("Hello World\n");

}

};

int _tmain(int argc, _TCHAR* argv[])

{

SimpleClass myObject; myObject.HelloWorld();

}

В этом примере класс называется SimpleClass. Он содержит один элемент данных, x, и одну функцию, HelloWorld. Мы создаем экземпляр SimpleClass под названием myObject и вызываем из него метод HelloWorld (ключевое слово public является абстракцией, действующей на уровне компилятора и не влияющей на ассемблерный код).

Указатель this

Как вы уже знаете, данные и функции связаны с объектами. Для обращения к элементу данных используется синтаксис вида ИмяОбъекта.имяПеременной. Вызов метода имеет похожий вид: ИмяОбъекта.имяФункции. Возьмем для примера листинг 20.1: если мы хотим обратиться к переменной x, то должны использовать запись myObject.x.

Мы можем обращаться к переменным не только другого, но и текущего объекта. Для этого достаточно имени самой переменной. Пример приведен в листинге 20.2.

Листинг 20.2. Пример указателя this в языке C++

class SimpleClass { public:

int x;

void HelloWorld() {

if (x == 10) printf("X is 10.\n");

}

...

};

int _tmain(int argc, _TCHAR* argv[])

{

SimpleClass myObject; myObject.x = 9;

myObject.HelloWorld();

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

w

 

 

to

 

 

460  Часть VI  •  Специальные темы

w Click

 

 

 

 

 

 

 

 

 

 

 

o

m

 

w

 

 

 

 

 

 

 

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

SimpleClass myOtherObject; myOtherOject.x = 10; myOtherObject.HelloWorld();

}

 

 

 

 

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

 

 

 

 

Вфункции HelloWorld обращение к переменной x осуществляется лишь по ее

имени , а не как ObjectName.x. В главном методе та же переменная, которая ссылается на тот же адрес в памяти, доступна в формате ObjectName.x .

Внутри метода HelloWorld переменная x записывается как есть, поскольку она автоматически ссылается на объект, который вызвал функцию (и в первом случае

это myObject ). Конкретный адрес, который хранит переменную x, зависит от объекта, указанного при вызове функции. Например, вызовы myOtherObject.HelloWorld

иmyObject.HelloWorld ссылаются на переменные x, которые находятся на разных участках памяти. Указатель this определяет, по какому адресу находятся данные, к которым осуществляется доступ.

Если при обращении к переменной внутри метода не указан объект, указатель this подставляется автоматически; он также является скрытым аргументом при вызове любой функции объекта. В ассемблерном коде, сгенерированном продуктами компании Microsoft, аргумент this обычно передается в регистр ЕCX, хотя иногда вместо этого используется ESI.

Вглаве 6 мы рассматривали форматы вызовов stdcall, cdecl и fastcall. В C++ формат вызова для указателя this часто называют thiscall. Обнаружение thiscall при исследовании ассемблерного кода — верный признак объектно-ориентирован- ной модели.

Приведенный ниже ассемблерный код, сгенерированный из листинга 20.2, демонстрирует использование указателя this.

Листинг 20.3. Указатель this, представленный в дизассемблированном виде

;Main Function

 

 

00401100

push

ebp

00401101

mov

ebp, esp

00401103

sub

esp, 1F0h

00401109

mov

[ebp+var_10], offset off_404768

00401110

mov

[ebp+var_C], 9

00401117

lea

ecx, [ebp+var_10]

0040111A

call

sub_4115D0

0040111F

mov

[ebp+var_34], offset off_404768

00401126

mov

[ebp+var_30], 0Ah

0040112D

lea

ecx, [ebp+var_34]

00401130

call

sub_4115D0

;HelloWorld Function

 

 

004115D0

push

ebp

004115D1

mov

ebp, esp

004115D3

sub

esp, 9Ch

004115D9

push

ebx

004115DA

push

esi

004115DB

push

edi

004115DC

mov

[ebp+var_4], ecx

 

 

 

 

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

 

 

Глава 20. Анализ кода на C++  461

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

004115DF

mov

eax, [ebp+var_4]

004115E2

cmp

dword

ptr [eax+4], 0Ah

004115E6

jnz

short

loc_4115F6

004115E8

push

offset aXIs10_ ; "X is 10.\n"

004115ED

call

ds:__imp__printf

Первым делом главный метод выделяет место в стеке. Начало объекта хранится

впеременной стека var_10 . Первый элемент данных в этом объекте — это переменная x, которая имеет сдвиг 4 относительно начала объекта. В IDA Pro значение x

помечено как var_C; доступ к нему осуществляется в строке. IDA Pro не может определить, принадлежат ли оба значения одному и тому же объекту, поэтому x выводится

ввиде отдельной переменной. Затем указатель на объект помещается в регистр ECX

для последующего вызова функции . Метод HelloWorld извлекает содержимое ECX и использует его в качестве указателя this . Затем код обращается к переменной x со сдвигом 4 . При втором вызове HelloWorld главная функция загружает

вECX другой указатель.

Перегрузка и коррекция имен

Язык C++ поддерживает механизм перегрузки методов, который позволяет иметь несколько методов с одним и тем же именем, но с разными аргументами. При вызове функции компилятор смотрит на количество и тип переданных аргументов и определяет подходящую версию. Пример показан в листинге 20.4.

Листинг 20.4. Пример перегрузки функций

LoadFile (String filename) {

...

}

LoadFile (String filename, int Options) {

...

}

Main () {

LoadFile ("c:\myfile.txt"); // Вызывает первую функцию LoadFile function LoadFile ("c:\myfile.txt", GENERIC_READ); // Вызывает вторую функцию LoadFile

}

Вэтом примере есть две функции LoadFile: одна принимает только строку,

адругая — строку и целое число. Когда происходит вызов внутри главного метода, компилятор выбирает ту из них, которая имеет подходящее количество аргументов.

Для поддержки перегрузки функций в C++ используется прием под названием «коррекция имен». В двоичных файлах формата PE каждая функция идентифицируется исключительно по имени, без указания параметров.

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

будет выглядеть как ?TestFunction@SimpleClass@@QAEXHH@Z.

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

w

 

 

to

 

 

462  Часть VI  •  Специальные темы

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

 

 

 

 

Алгоритм коррекции имен зависит от компилятора, но в большинстве случаев IDA Pro может восстановить оригинальные названия. Например, на рис. 20.1 показана функция TestFunction. IDA Pro восстанавливает ее исходное имя и параметры.

Рис. 20.1. Имя функции, восстановленное в IDA Pro

Внутренние имена функций видны только при наличии соответствующих символов в коде, который вы анализируете. Во вредоносном ПО внутренние символы обычно удаляются, однако IDA Pro может распознать некоторые функции импорта или экспорта языка C++ с откорректированными именами.

Наследование и переопределение функций

Наследование — это одна из концепций объектно-ориентированного программирования, которая описывает отношения между родительскими и дочерними классами. Дочерний класс автоматически наследует все функции и данные своего родителя, а также, как правило, определяет свои собственные. В листинге 20.5 показан класс под названием Socket.

Листинг 20.5. Пример наследования

class Socket {

...

public:

void setDestinationAddr (INetAddr * addr) {

...

}

...

};

class UDPSocket : publicSocket { public:

void sendData (char * buf, INetAddr * addr) {

setDestinationAddr(addr)

...

}

...

};