• XSS.stack #1 – первый литературный журнал от юзеров форума

Мануал/Книга Мастерство анализа малвари

yashechka

Генератор контента.Фанат Ильфака и Рикардо Нарвахи
Эксперт
Регистрация
24.11.2012
Сообщения
2 344
Реакции
3 563
Глава 1. Ускоренный курс по CISC/RISC и основам программирования

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

В этой главе мы рассмотрим основы наиболее широко используемых архитектур, от хорошо известных архитектур x86 и x64 Instruction Set Architecture (ISA) до решений, поддерживающих несколько мобильных устройств и устройств Интернета вещей (IoT), которые часто неправомерно используются семействами вредоносных программ, такие как Mirai и многие другие. Это задаст тон вашему путешествию по анализу вредоносных программ, поскольку статический анализ невозможен без понимания инструкций по ассемблеру. Хотя современные декомпиляторы действительно становятся все лучше и лучше, они существуют не для всех платформ, на которые нацелено вредоносное ПО. Кроме того, они, вероятно, никогда не смогут обрабатывать запутанный код. Не пугайтесь сложности ассемблера; просто нужно время, чтобы к нему привыкнуть, и через некоторое время его можно читать, как любой другой язык программирования. Хотя эта глава является отправной точкой, всегда имеет смысл повышать свои знания, практикуясь и исследуя дальше.

Эта глава разделена на следующие разделы для облегчения процесса обучения:

* Базовые концепции

* Языки ассемблера

* Знакомство с x86 (IA-32 и x64)

* Изучение ассемблера ARM

* Основы MIPS

* Ассемблер SuperH

* Работа со SPARC

* Переход от ассемблера к языкам программирования высокого уровня

Базовые концепции

Большинство людей на самом деле не понимают, что процессор в значительной степени является умным калькулятором. Если вы посмотрите на большинство его инструкций (независимо от языка ассемблера), вы обнаружите, что многие из них имеют дело с числами и выполняют некоторые вычисления. Однако есть несколько особенностей, которые на самом деле отличают процессоры от обычных калькуляторов, например:

* Процессоры имеют доступ к большему объему памяти по сравнению с традиционными калькуляторами. Это пространство памяти дает им возможность хранить миллиарды значений, что позволяет выполнять более сложные операции. Кроме того, они имеют несколько быстрых и небольших блоков памяти, встроенных в микросхему процессора, называемых регистрами.

* Процессоры поддерживают множество типов инструкций, кроме арифметических инструкций, например, изменение потока выполнения на основе определенных условий.

* Процессоры могут взаимодействовать с другими устройствами (например, динамиками, микрофонами, жесткими дисками, графическими картами и т.д.).

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

Регистры

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

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

Регистры могут иметь разные имена, размеры и функции в зависимости от архитектуры. Вот некоторые из видов, которые широко используются:

* Регистры общего назначения: — это регистры, которые используются для сохранения значений или результатов различных арифметических и логических операций.

* Указатели стека и фрейма: это регистры, которые используются для указания на начало и конец стека.

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

Память

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

Виртуальная память

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

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

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

Несмотря на знание виртуального адреса, доступ может быть затруднен другой проблемой, которая хранит этот виртуальный адрес. Размер виртуального адреса в 32-битных системах составляет 4 байта, а в 64-битных системах — 8 байтов. Это означает, что нам может понадобиться выделить еще одно место в памяти для хранения этого виртуального адреса. Для этого нового пространства в памяти, чтобы получить к нему прямой доступ только по его адресу, нам нужно будет сохранить его собственный адрес памяти в другом пространстве памяти, что приведет нас к бесконечному циклу, как показано на следующем рисунке:

1697024208354.png


Для решения этой проблемы в настоящее время используется несколько решений, и в следующем разделе мы рассмотрим одно из них — стек.

Стек

Стек буквально означает кучу объектов. В компьютерных науках стек — это, по сути, структура данных, которая помогает сохранять в памяти разные значения одинакового размера в виде стопки, используя принцип «последним пришел — первым вышел» (LIFO).

На вершину стека (куда будет помещен следующий элемент) указывает специальный указатель стека, который будет более подробно обсуждаться ниже.

Стек является общим для многих языков ассемблера и выполняет несколько функций. Например, это может помочь в решении математических уравнений, таких как X = 5 * 6 + 6 * 2 + 7 (4 + 6), путем сохранения каждого вычисленного значения и помещения каждого в стек, а затем извлечения (или извлечения) их обратно, чтобы вычислить сумму всех из них и сохранить их в переменной X.

Он также часто используется для передачи аргументов (особенно если их много) и хранения локальных переменных.

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

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

Ветвление, циклы и условия

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

* Безусловный переход: это тип инструкции, которая принудительно изменяет поток выполнения на другой адрес (без каких-либо условий).

* Условный переход: это похоже на логический вентиль, который переключается на другую ветвь на основе заданного условия (например, равно нулю, больше или меньше), как показано на следующем рисунке:

1697024233164.png


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

Исключения, прерывания и обмен данными с другими устройствами

На языке ассемблера связь с различными аппаратными устройствами осуществляется посредством так называемых прерываний.

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

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

Языки ассемблера

Есть две большие группы архитектур, определяющих языки ассемблера, которые мы рассмотрим в этом разделе: компьютер со сложным набором команд (CISC) и компьютер с сокращенным набором команд (RISC).

CISC против RISC

Не вдаваясь в подробности, основное различие между CISC, такими как Intel IA-32 и x64, и языками RISC, связанными с такими архитектурами, как ARM, заключается в сложности их инструкций.

Языки ассемблера CISC имеют более сложные инструкции. Они сосредоточены на выполнении задач, используя как можно меньше строк инструкций по сборке. Для этого языки ассемблера CISC включают инструкции, которые могут выполнять несколько операций, например mul в ассемблере Intel, который выполняет операции доступа к данным, умножения и хранения данных.

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

Типы инструкций

В следующих разделах мы рассмотрим основную структуру каждого языка ассемблера, три основных типа ассемблерных инструкций и то, как они реализованы в каждом из них:

* Манипуляция данными:

- Арифметические манипуляции

- Логические и битовые манипуляции

- Сдвиги и вращения

* Передача данных:


- Передачи между памятью и регистрами

- Передачи между регистрами

* Выполнение потока исполнения:

- Переход или вызов

- Ветвление на основе условия

Знакомство с x86 (IA-32 и x64)

Intel x86 (IA-32 и x64) является наиболее распространенной архитектурой, используемой в ПК и на многих серверах, поэтому неудивительно, что большинство образцов вредоносных программ, которые у нас есть на данный момент, поддерживают ее. IA-32 также обычно называют i386 (замененный i686) или даже просто x86, тогда как x64 также известен как x86-64 или AMD64. x86 — это архитектура CISC, которая включает в себя несколько сложных инструкций в дополнение к простым. В этом разделе мы представим наиболее распространенные из них, а также то, как компиляторы используют их преимущества в своих соглашениях о вызовах.

Регистры

Вот таблица, показывающая взаимосвязь между регистрами в архитектурах IA-32 и x64:

1697024282082.png


От r8 до r15 доступны только в x64, но не в IA-32, а spl, bpl, sil и dil доступны только в x64.

Первые четыре регистра (rax, rbx, rcx и rdx) являются регистрами общего назначения (GPR), но некоторые из них имеют следующие специальные варианты использования для определенных инструкций:

* rax/eax: Используется для хранения информации и является специальным регистром для некоторых вычислений.

* rcx/ecx: используется как регистр счетчика в инструкциях цикла.

* rdx/edx: используется при делении при возврата модуля

В x64 регистры с r8 по r15 также являются GPR, которые были добавлены к доступным GPR.

Регистр rsp/esp используется как указатель стека, указывающий на вершину стека. Его значение уменьшается, когда значение помещается в стек, и увеличивается, когда значение извлекается из стека. Регистр rbp/ebp используется в качестве указателя кадра и полезен для доступа к локальным переменным и аргументам функции, как мы увидим позже в этом разделе. В дополнение к этому, rbp/ebp иногда используется в качестве GPR для хранения любых данных.

rsi/esi и rdi/edi в основном используются для определения адресов при копировании группы байтов в память. Регистр rsi/esi всегда играет роль источника, а регистр rdi/edi играет роль получателя. Оба регистра являются энергонезависимыми и также являются GPR.

Специальные регистры

В сборке Intel есть два специальных регистра и они следующие:

* rip/eip: Это указатель инструкции, указывающий на следующую выполняемую инструкцию. К нему нельзя получить доступ напрямую, но есть специальные инструкции для доступа к нему.

* rflags/eflags/flags: Этот регистр содержит текущее состояние процессора. На его флаги влияют арифметические и логические инструкции, включая инструкции сравнения, такие как cmp и test, а также он используется с условными переходами и другими инструкциями. Вот самые распространенные флаги:

- Флаг переноса (CF): это когда арифметическая операция выходит за границы; посмотрите на следующую операцию:

mov al, Ffh; al = 0xFF и CF = 0

add al, 1; al = 0 & CF = 1


- Флаг нуля (ZF): Этот флаг устанавливается, когда результат арифметической или логической операции равен нулю. Это также может быть установлено с помощью инструкций сравнения.

- Флаг знака (SF): Этот флаг указывает, что результат операции отрицательный.

- Флаг переполнения (OF): Этот флаг указывает, что в операции произошло переполнение, что привело к изменению знака (только для чисел со знаком), следующим образом:

mov cl, 7Fh; cl = 0x7F (127) & OF = 0

inc cl; cl = 0x80 (-128) & OF = 1


Существуют и другие регистры, такие как регистры MMX и FPU (и инструкции по работе с ними), но мы не будем их рассматривать в этой главе.

Структура инструкции

Для Intel x86 (IA-32 или x64) общей структурой инструкций является opcode, dest, src.

Давайте углубимся в них.

код операции

код операции — это имя инструкции. Некоторые инструкции имеют только код операции без назначения или источника, например:

Nop, pushad, popad, movsb


pushad и popad недоступны в x64.

dest

dest представляет место назначения или место, где будет сохранен результат вычислений, а также становится частью самих вычислений, например:

add eax, ecx; eax = (eax + ecx)

sub rdx, rcx; rdx = (rdx - rcx)


Кроме того, он может играть роль источника и пункта назначения с некоторыми инструкциями кода операции, которые принимают только пункт назначения без источника:

inc eax

dec ecx


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

push rdx

pop rcx


dest может выглядеть следующим образом:

* REG: регистр, такой как eax и edx.

* r/m: Место в памяти, например следующее:

DWORD PTR [00401000h]

BYTE PTR [EAX + 00401000h]

WORD PTR [EDX*4 + EAX+ 30]


* Значение в стеке (используемое для представления локальных переменных), например следующее:

DWORD PTR [ESP+4]

DWORD PTR [EBP-8]

Src


src представляет источник или другое значение в вычислениях, но впоследствии не сохраняет результаты. Это может выглядеть так:

* REG: например, добавить rcx, r8

* r/m: например, добавьте ecx, dword ptr [00401000h]

* imm: непосредственное значение, такое как mov eax, 00100000h

Набор инструкций

Здесь мы рассмотрим различные типы инструкций, которые мы перечислили в предыдущем разделе.

Инструкции по обработке данных

Вот некоторые из арифметических инструкций:

1697024346957.png


Кроме того, для логики и манипуляций с битами они выглядят так:
1697024361390.png


И, наконец, для сдвига и ротаций они такие:

1697024403086.png


Инструкции по передаче данных

Есть инструкция mov, которая копирует значение из src в dest. Эта инструкция имеет несколько форм, как мы видим в этой таблице:

1697024415957.png


Другие инструкции, связанные со стеком, выглядят так:

1697024425611.png
1
Для манипуляций со строками они такие:
1697024435940.png


Инструкции по управлению потоком

Вот некоторые из безусловных переходов:

1697024446904.png


Вот некоторые из условных переходов:

1697024456429.png


Аргументы, локальные переменные и соглашения о вызовах (в x86 и x64)

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

Stdcall

Регистры стека, rsp/esp и rbp/ebp выполняют большую часть работы, когда речь идет об аргументах и локальных переменных. Инструкция call сохраняет адрес возврата в верхней части стека перед передачей выполнения новой функции, а инструкция ret в конце функции возвращает выполнение обратно в вызывающую функцию, используя адрес возврата, сохраненный в стеке.

Аргументы

Для stdcall аргументы также помещаются в стек от последнего аргумента к первому следующим образом:

Push Arg02

Push Arg01

Call Func01


В функции Func01 доступ к аргументам можно получить с помощью rsp/esp, но с учетом того, сколько значений было перемещено на вершину стека с течением времени, примерно так:

mov eax, [esp + 4] ;Arg01

push eax

mov ecx, [esp + 8] ; Arg01 keeping in mind the previous push


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

Наиболее распространенный способ доступа к аргументам, а также к локальным переменным — использование rbp/ebp. Во-первых, вызываемая функция должна сохранить текущий rsp/esp в регистре rbp/ebp, а затем получить к ним доступ следующим образом:

push ebp

mov ebp, esp

...

mov ecx, [ebp + 8] ;Arg01

push eax

mov ecx, [ebp + 8] ;still Arg01 (no changes)


И в конце вызванной функции она возвращает исходное значение rbp/ebp и rsp/esp следующим образом:

mov esp,ebp

pop ebp

ret


Так как это общий эпилог функции, Intel создала для него специальную инструкцию, которая называется leave, поэтому она стала такой:

Leave

Ret

Локальные переменные


Для локальных переменных вызываемая функция выделяет для них место, уменьшая значение регистра rsp/esp. Чтобы выделить место для двух переменных по четыре байта каждая, код будет таким:

pushebp

mov ebp,esp

sub esp, 8

Кроме того, конец функции будет таким:

mov ebp,esp

pop ebp

ret


1697024541895.png


Кроме того, если есть аргументы, инструкция ret очищает стек, учитывая количество байтов, которые нужно извлечь из вершины стека, следующим образом:

ret 8 ;2 Arguments, 4 bytes each

cdecl


cdecl (объявление c) — еще одно соглашение о вызовах, которое использовалось многими компиляторами C в x86. Он очень похож на stdcall, с той лишь разницей, что вызывающая программа очищает стек после того, как вызываемая функция (вызванная функция) возвращается следующим образом:

Caller:

push Arg02

push Arg01

call Callee

add esp, 8 ;cleans the stack

fastcall


Соглашение о вызовах __fastcall также широко используется различными компиляторами, включая компилятор Microsoft C++ и GCC. Это соглашение о вызовах передает первые два аргумента в ecx и edx и помещает оставшиеся аргументы в стек. Он используется только в x86, поскольку в Windows существует только одно соглашение о вызовах для x64.

thiscall

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

В компиляторе GCC thiscall почти идентичен соглашению о вызовах cdecl и передает адрес объекта в качестве первого аргумента. Но в компиляторе Microsoft C++ он похож на stdcall и передает адрес объекта в ecx. Такие шаблоны часто встречаются в некоторых семействах объектно-ориентированных вредоносных программ.

Соглашение о вызовах x64

В x64 соглашение о вызовах больше зависит от регистров. В Windows вызывающая функция передает первые четыре аргумента в регистры в следующем порядке: rcx, rdx, r8, r9, а остальные помещаются обратно в стек. В то время как для других операционных систем первые шесть аргументов обычно передаются в регистры в таком порядке: rdi, rsi, rdx, rcx, r8, r9, а остальные в стек.

В обоих случаях вызываемая функция очищает стек после использования ret imm, и это единственный способ очистить стек для этих операционных систем в x64.

Изучение ассемблера ARM

Большинство читателей, вероятно, более знакомы с архитектурой x86, в которой реализован дизайн CISC, и могут задаться вопросом — а зачем нам вообще нужно что-то еще? Основное преимущество RISC-архитектур заключается в том, что для процессоров, которые их реализуют, обычно требуется меньше транзисторов, что в конечном итоге делает их более энергоэффективными и теплоэффективными и снижает связанные с этим производственные затраты, что делает их лучшим выбором для портативных устройств. Мы начинаем знакомство с архитектурой RISC с ARM по уважительной причине — на данный момент это наиболее широко используемая архитектура в мире.

Объяснение простое — процессоры, реализующие его, можно найти на множестве мобильных устройств и устройств, таких как телефоны, игровые приставки или цифровые камеры, число которых значительно превышает число ПК. По этой причине несколько семейств вредоносных программ IoT и мобильных вредоносных программ, нацеленных на платформы Android и iOS, имеют полезные нагрузки для архитектуры ARM; пример можно увидеть на следующем снимке экрана:

1697024568204.png


Таким образом, чтобы иметь возможность их анализировать, необходимо сначала понять, как работает ARM.

Первоначально ARM обозначало Acorn RISC Machine, а затем Advanced RISC Machine. Acorn была британской компанией, которую многие считали британской Apple, производившей одни из самых мощных ПК того времени. Позже он был разделен на несколько независимых организаций, при этом Arm Holdings (в настоящее время принадлежащая SoftBank Group) поддерживает и расширяет текущий стандарт.

Его поддерживают несколько операционных систем, включая Windows, Android, iOS, различные дистрибутивы Unix/Linux и многие другие менее известные встроенные ОС. Поддержка 64-битного адресного пространства была добавлена в 2011 году с выпуском стандарта ARMv8.

В целом доступны следующие профили архитектуры ARM:

* Профили приложений (суффикс A, например, семейство Cortex-A): реализует традиционную архитектуру ARM и поддерживает архитектуру системы виртуальной памяти на основе блока управления памятью (MMU). Эти профили поддерживают наборы инструкций ARM и Thumb (как будет описано ниже).

* Профили реального времени (суффикс R, например, семейство Cortex-R): они реализуют традиционную архитектуру ARM и поддерживают архитектуру системы защищенной памяти, основанную на блоке защиты памяти (MPU).

* Профили микроконтроллеров (суффикс M, например, семейство Cortex-M): реализуют программируемую модель и предназначены для интеграции в программируемые вентильные матрицы (FPGA).

Каждое семейство имеет свой собственный соответствующий набор связанных архитектур (например, 32-разрядное семейство Cortex-A включает в себя архитектуры ARMv7-A и ARMv8-A), которые, в свою очередь, включают в себя несколько ядер (например, архитектура ARMv7-R включает в себя Cortex- R4, Cortex-R5 и так далее).

Основы

Здесь мы рассмотрим как исходную 32-битную, так и более новую 64-битную архитектуру. Со временем было выпущено несколько версий, начиная с ARMv1. В этой книге мы сосредоточимся на последних их версиях.

ARM — это архитектура load-store; она делит все инструкции на следующие две категории:

* Доступ к памяти: перемещает данные между памятью и регистрами

* Операции арифметико-логического устройства (АЛУ): выполняет вычисления с использованием регистров.

ARM поддерживает арифметические операции сложения, вычитания и умножения, а некоторые новые версии, начиная с ARMv7, также поддерживают операции деления. Он поддерживает порядок с обратным порядком байтов и по умолчанию использует формат с прямым порядком байтов.

На 32-битном ARM в любое время доступны 16 регистров: R0-R15. Это число удобно, так как требуется всего 4 бита, чтобы определить, какой регистр будет использоваться. Из них 13 (иногда называемые 14, включая R14, или 15, также включая R13) являются регистрами общего назначения: каждый из R13 и R15 имеет специальную функцию, а R14 может выполнять ее время от времени. Давайте рассмотрим их подробнее:

-R0-R7: младшие регистры одинаковы во всех режимах ЦП.

- R8-R12: старшие регистры одинаковы во всех режимах ЦП, за исключением режима быстрого запроса прерывания (FIQ), недоступного для 16-битных инструкций.

- R13 (также известный как SP): указатель стека — указывает на вершину стека, и каждый режим ЦП имеет свою собственную версию. Не рекомендуется использовать его в качестве GPR.

- R14 (также известный как LR): регистр связи — в пользовательском режиме он содержит адрес возврата для текущей функции, в основном, когда выполняются инструкции BL (переход со связью) или BLX (переход с связью и обменом). Его также можно использовать как GPR, если адрес возврата хранится в стеке. Каждый режим ЦП имеет свою собственную версию.

- R15 (также известный как PC): Счетчик программ, указывает на текущую выполняемую команду. Это не GPR.

Всего на большинстве архитектур ARM имеется 30 32-битных регистров общего назначения, включая экземпляры с одинаковыми именами в разных режимах ЦП.

Помимо них, есть несколько других важных регистров, а именно:

* Регистр состояния текущей программы (CPSR): содержит биты, описывающие текущий режим процессора, состояние процессора и некоторые другие значения.

* Сохраненные регистры состояния программы (SPSR): в них сохраняется значение CPSR при возникновении исключения, поэтому его можно восстановить позже. Каждый режим ЦП имеет свою собственную версию, за исключением пользовательского и системного режимов, поскольку они не являются режимами обработки исключений.

* Регистр состояния прикладной программы (APSR): в нем хранятся копии флагов состояния ALU, также известных как флаги кода состояния, а в более поздних архитектурах он также содержит флаги Q (насыщение) и флаги больше или равно (GE).

Количество регистров с плавающей запятой (FPR) для 32-битной архитектуры может варьироваться, в зависимости от ядра, до 32.

ARMv8 (64-разрядная версия) имеет 31 универсальный X0-X30 (также можно найти нотацию R0-R30) и 32 FPR, доступных в любое время. Нижняя часть каждого регистра имеет префикс W и может быть доступна как W0-W30.

Есть несколько регистров, которые имеют определенную цель, а именно:

1697024590017.png


ARMv8 определяет четыре уровня исключений (EL0-EL3), и каждый из трех последних регистров получает свою собственную копию каждого из них; ELR и SPSR не имеют отдельной копии для EL0.

Нет регистра с именем X31 или W31; число 31 во многих инструкциях представляет нулевой регистр ZR (WZR/XZR). X29 можно использовать как указатель кадра (в котором хранится исходная позиция в стеке), а X30 — как регистр связи (в котором хранится возвращаемое значение из функций).

Что касается соглашения о вызовах, R0-R3 на 32-разрядном ARM и X0-X7 на 64-разрядном ARM используются для хранения значений аргументов, переданных функциям, а остальные аргументы, при необходимости, передаются через стек, R0-R1 и X0- X7 (и X8, также косвенно известный как XR) для хранения возвращаемых результатов. Если тип возвращаемого значения слишком велик для них, то необходимо выделить пространство и вернуть его в виде указателя. Кроме того, R12 (32-разрядный) и X16-X17 (64-разрядный) могут использоваться в качестве временных регистров внутри вызова процедуры (с помощью так называемых виниров и кода таблицы связывания процедур), R9 (32-разрядный) и X18 (64-разрядная версия) может использоваться в качестве регистров платформы (для конкретных целей ОС), если это необходимо, в противном случае они используются так же, как и другие временные регистры.

Как упоминалось ранее, в соответствии с официальной документацией реализовано несколько режимов ЦП, а именно:

1697024605574.png


Наборы инструкций

Для процессоров ARM доступно несколько наборов инструкций: ARM и Thumb. Говорят, что процессор, выполняющий инструкции ARM, работает в состоянии ARM, и наоборот. Процессоры ARM всегда запускаются в состоянии ARM, а затем программа может переключиться в состояние Thumb с помощью инструкции BX. Среда выполнения Thumb (ThumbEE) была представлена относительно недавно в ARMv7 и основана на Thumb с некоторыми изменениями и дополнениями для облегчения динамического генерирования кода.

Инструкции ARM имеют длину 32 бита (как для AArch32, так и для AArch64), в то время как инструкции Thumb и ThumbEE имеют длину 16 или 32 бита (первоначально почти все инструкции Thumb были 16-битными, а Thumb-2 представил сочетание 16- и 32-битных инструкций).

Согласно официальной документации все инструкции можно разделить на следующие категории:
1697024641172.png


1697024661750.png


Для взаимодействия с ОС к системным вызовам можно получить доступ с помощью инструкции Software Interrupt (SWI), которая позже была переименована в инструкцию Supervisor Call (SVC).

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

SVC{условие} #imm

Код {условие} в этом случае будет кодом условия. ARM поддерживает несколько кодов состояния, а именно:

EQ: равно

NE: не равно

CS/HS: установить перенос

CC/LO: сбросить перенос

MI: отрицательный

PL: Положительный или нулевой

VS: переполнение

VC: нет переполнения

HI: выше без знака

LS: беззнаковый ниже или оба

GE: со знаком больше или равно

LT: со знаком менее

GT: со знаком больше

LE: со знаком меньше или равно

AL: Всегда (обычно опускается)

Значение imm означает непосредственное значение.

Основы MIPS

Микропроцессор без взаимосвязанных конвейерных стадий (MIPS) был разработан по технологиям MIPS . Подобно ARM, сначала это была 32-битная архитектура с добавленной позже 64-битной функциональностью. Используя преимущества RISC ISA, процессоры MIPS характеризуются низким энергопотреблением и энергопотреблением. Их часто можно найти в нескольких встроенных системах, таких как маршрутизаторы и шлюзы, а также в некоторых игровых консолях, таких как Sony PlayStation. К сожалению, из-за популярности этой архитектуры системы, они стали мишенью для нескольких семейств вредоносных программ IoT. Пример можно увидеть на следующем скриншоте:
1697024686131.png


По мере развития архитектуры появилось несколько ее версий, начиная с MIPS I и заканчивая V, а затем несколько выпусков более поздних MIPS32/MIPS64. MIPS64 остается обратно совместимым с MIPS32. Эти базовые архитектуры могут быть дополнительно дополнены необязательными архитектурными расширениями, называемыми Application Specific Extension (ASE), и модулями для повышения производительности для определенных задач, которые обычно мало используются вредоносным кодом. MicroMIPS32/64 — это надмножества архитектур MIPS32 и MIPS64 соответственно с почти таким же 32-битным набором инструкций и дополнительными 16-битными инструкциями для уменьшения размера кода. Они используются там, где требуется сжатие кода, и предназначены для микроконтроллеров и других небольших встраиваемых устройств.

Основы

MIPS поддерживает двунаправленность байтов. Доступны следующие регистры:

*32 GPR r0-r31, 32-битный размер для MIPS32 и 64-битный размер для MIPS64.

* Специальный регистр PC, на который некоторые инструкции могут влиять только косвенно.

* Два специальных регистра для хранения результатов целочисленного умножения и деления (HI и LO). Эти регистры и связанные с ними инструкции были удалены из базового набора инструкций в выпуске 6 и теперь существуют в модуле процессора цифровых сигналов (DSP).

Причина использования 32 GPR проста — MIPS использует 5 бит для указания регистра, поэтому таким образом мы можем иметь максимум 2^5 = 32 различных значения. Два GPR имеют определенную цель, а именно:

* Регистр r0 (иногда называемый $0 или $zero) является постоянным регистром, в нем всегда хранится ноль, и он обеспечивает доступ только для чтения. Его можно использовать как аналог /dev/null для отбрасывания вывода какой-либо операции или как быстрый источник нулевого значения.

* r31 (также известный как $ra) хранит адрес возврата во время инструкций перехода/перехода вызова процедуры и ссылки.

Другие регистры обычно используются для определенных целей, а именно:

* r1 (также известный как $at): временный ассемблер — используется при разрешении псевдоинструкций.

* r2-r3 (также известные как $v0 и $v1): значения — содержат значения возвращаемой функции.

* r4-r7 (также известные как $a0-$a3): Аргументы — используются для передачи аргументов функции.

* r8-r15 (также известные как $t0-$t7/$a4-$a7 и $t4-$t7): временные — первые четыре могут также использоваться для предоставления аргументов функций в соглашениях о вызовах N32 и N64 (еще один вызов O32). соглашение использует только регистры r4-r7; последующие аргументы передаются в стеке)

* r16-r23 (также известные как $s0-$s7): cохраненные временные — сохраняются при вызовах функций.

* r24-r25 (также известные как $t8-$t9): dременные

* r26-r27 (также известный как $k0-$k1): обычно зарезервирован для ядра ОС.

* r28 (также известный как $gp): глобальный указатель — указывает на глобальную область (сегмент данных).

* r29 (также известный как $sp): указатель стека

* r30 (также известный как $s8 или $fp): Сохраненный указатель значения/кадра — сохраняет исходный указатель стека (до вызова функции).

MIPS также имеет следующие доступные сопроцессоры:

* CP0: система управления

*CP1: FPU

* CP2: зависит от реализации

CP3: FPU (имеет специальные инструкции типа кода операции COP1X)

Набор инструкций

Большинство основных инструкций были введены в MIPS I и II. MIPS III представила 64-битные целые числа и адреса, а MIPS IV и V улучшили операции с плавающей запятой и добавили новый набор для повышения общей эффективности. Каждая инструкция там имеет одинаковую длину — 32 бита (4 байта), и любая инструкция начинается с кода операции, который занимает 6 бит. Поддерживаются три основных формата инструкций: R, I и J:
1697024705350.png

1697024715283.png


Для операций, связанных с FPU, существуют аналогичные типы FR и FI.

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

В документации регистры обычно имеют следующие суффиксы:

* Источник(s)

* Цель (t)

* Назначение (d)

Все инструкции можно разделить на следующие несколько групп в зависимости от типа функциональности:

* Поток управления — в основном состоит из условных и безусловных переходов и ответвлений:

- JR: перейти на регистр (формат J)

- BLTZ: переход если меньше нуля (формат I)

* Доступ к памяти — операции загрузки и сохранения:

- LB: загрузить байт (формат I)

- SW: сохранить слово (формат I)

* ALU — охватывает различные арифметические операции:

- ADDU: сложить без знака (формат R)

- XOR: исключающее или (формат R)

- SLL: логический сдвиг влево (формат R)

*Взаимодействие с ОС через исключения — взаимодействует с ядром ОС:

- SYSCALL: системный вызов (пользовательский формат)

- BREAK: точка останова (пользовательский формат)

В большинстве случаев инструкции с плавающей запятой будут иметь одинаковые имена для одних и тех же типов операций, например, ADD.S. Некоторые инструкции более уникальны, например Check for Equal (C.EQ.D).

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

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

* ABS: абсолютное значение — преобразуется в комбинацию ADDU, BGEZ и SUB.

* BLT: переходи если меньше чем — переводится как комбинация SLT и BNE.

* BGT/BGE/BLE: аналогично BLT

* LI/LA: немедленная загрузка/адрес — преобразуется в комбинацию LUI и ORI или ADDIU для 16-битного LI.

* MOVE: перемещает содержимое одного регистра в другой — преобразует в ADD/ADDIU с нулевым значением.

* NOP: нет операции — преобразуется в SLL с нулевыми значениями.

* NOT: логическое NOT — преобразуется в NOR.

Погружение в PowerPC

PowerPC расшифровывается как «Оптимизация производительности с помощью Enhanced RISC — Performance Computing» и иногда пишется как PPC. Он был создан в начале 1990-х годов альянсом Apple, IBM и Motorola (обычно сокращенно AIM). Первоначально он предназначался для использования в ПК и до 2006 года использовался в продуктах Apple, включая PowerBook и iMac. Процессоры, реализующие его, также можно найти в игровых консолях, таких как Sony PlayStation 3, XBOX 360 и Wii, а также в серверах IBM и множестве встроенных устройств, таких как контроллеры автомобилей и самолетов, и даже в знаменитом роботе ASIMO. Позже административные обязанности были переданы органу открытых стандартов Power.org, членами которого остались некоторые из бывших создателей, такие как IBM и Freescale. Затем они отделились от Motorola и позже были приобретены NXP Semiconductors, а также многими другими компаниями. OpenPOWER Foundation — это новая инициатива IBM, Google, NVIDIA, Mellanox и Tyan, целью которой является содействие совместной разработке этой технологии.

PowerPC был в основном основан на IBM POWER ISA, а позже была выпущена унифицированная Power ISA, которая объединила POWER и PowerPC в единую ISA, которая теперь используется во многих продуктах под общим термином Power Architecture.

Существует множество семейств вредоносных программ для Интернета вещей, которые имеют полезную нагрузку для этой архитектуры.

Основы

Power ISA делится на несколько категорий; каждую категорию можно найти в определенной части спецификации или книги. ЦП реализуют набор этих категорий в зависимости от своего класса; только базовая категория является обязательной. Вот список основных категорий и их определений в последнем втором стандарте:

*База: описана в Книге I (Архитектура набора пользовательских инструкций Power ISA) и Книге II (Архитектура виртуальной среды Power ISA)

*Сервер: описан в Книге III-S (Архитектура операционной среды Power ISA — Серверная среда)

* Ембедед: Книга III-E (Архитектура операционной среды Power ISA — встроенная среда)

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

В другой книге, Book VLE (Архитектура операционной среды Power ISA — Архитектура инструкций кодирования переменной длины (VLE)), определяются альтернативные инструкции и определения, предназначенные для увеличения плотности кода за счет использования 16-битных инструкций в отличие от более распространенных 32-битных. те.

Power ISA версии 3 состоит из трех книг с теми же названиями, что и книги с I по III предыдущего стандарта, без различий между средами.

Процессор запускается в режиме с обратным порядком байтов, но может переключаться, изменяя бит в MSR (регистре состояния машины), так что поддерживается двунаправленный порядок байтов.

В Power ISA задокументировано множество наборов регистров, в основном сгруппированных либо по соответствующему объекту, либо по категории. Вот краткое изложение наиболее часто используемых:

* 32 GPR для операций с целыми числами, обычно используются только по их количеству (64-битные)

* 64 векторных скалярных регистра (VSR) для векторных операций и операций с плавающей запятой:

- 32 векторных регистра (VR) как часть VSR для векторных операций (128 бит)

- 32 FPR в составе VSR для операций с плавающей запятой (64-бит)

* Регистры средств фиксированной точки специального назначения, такие как следующие:

- Регистр исключений с фиксированной запятой (XER) — содержит несколько битов состояния (64 бита).

* Регистры переходов:

- Регистр условий (CR) — состоит из 8 4-битных полей, CR0-CR7, включая такие вещи, как поток управления и сравнение (32-битные)

- Link регистр (LR) — обеспечивает целевой адрес перехода (64-битный)

- Регистр счетчика (CTR) — содержит счетчик циклов (64-разрядный).

- Целевой регистр доступа (TAR) — указывает целевой адрес ветки (64-разрядный).

* Регистры объекта таймера:

- Time Base (TB) — периодически увеличивается с заданной частотой (64-бит)

* Другие регистры специального назначения из определенной категории, в том числе следующие:

- Аккумулятор (ACC) (64-разрядный) — категория механизма обработки сигналов (SPE).

Как правило, функции могут передавать все аргументы в регистры для нерекурсивных вызовов; дополнительные аргументы передаются в стек.

Набор инструкций

Большинство инструкций имеют 32-битный размер, только группа кодирования переменной длины (VLE) меньше, чтобы обеспечить более высокую плотность кода для встраиваемых приложений. Все инструкции разбиты на следующие три категории:

* Определено: все инструкции определены в книгах Power ISA.

* Недопустимо: Доступно для будущих расширений Power ISA. Попытка выполнить их вызовет обработчик ошибок недопустимых инструкций.

* Зарезервировано: предназначено для определенных целей, которые не входят в сферу действия Power ISA. Попытка выполнить их либо выполнит реализованное действие, либо вызовет обработчик ошибок недопустимой инструкции, если реализация недоступна.

Биты с 0 по 5 всегда определяют код операции, и многие инструкции также имеют расширенный код операции. Поддерживается большое количество форматов инструкций; Вот некоторые примеры:

* I-ФОРМА [OPCD+LI+AA+LK]

* B-ФОРМА [OPCD+BO+BI+BD+AA+LK]

Каждое поле инструкций имеет собственное сокращение и значение; имеет смысл обратиться к официальному документу Power ISA, чтобы получить их полный список и соответствующие им форматы. В случае ранее упомянутой I-FORM они таковы:

* OPCD: код операции

* LI: непосредственное поле, используемое для указания 24-битного целого числа в дополнении до двух со знаком.

* AA: Бит абсолютного адреса

* LK: Бит ссылки, влияющий на регистр ссылки

Инструкции также разделены на группы в соответствии с соответствующим средством и категорией, что делает их очень похожими на регистры:

* Инструкции ветвления:

- b/ba/bl/bla: ветвление

- bc/bca/bcl/bcla: условное ветвление

- sc: системный вызов

* Инструкции с фиксированной точкой:

- lbz: загрузить байт и ноль

- stb: сохранить байт

- addi: добавить немедленно

- ori: или немедленно

* Инструкции с плавающей запятой:

- fmr: Floating move register

- lfs: загрузить одно число с плавающей запятой

- stfd: сохранить двойное число с плавающей запятой

* Инструкции SP:

- brinc: приращение с обратным битом

Ассемблер SuperH

SuperH, часто сокращенно обозначаемый как SH, представляет собой RISC ISA, разработанный Hitachi. SuperH прошел несколько итераций, начиная с SH-1 и заканчивая SH-4. Более поздний SH-5 имеет два режима работы, один из которых идентичен инструкциям пользовательского режима SH-4, а другой, SHmedia, совершенно другой. Каждое семейство занимает свою рыночную нишу:

* SH-1: Бытовая техника

* SH-2: Автомобильные контроллеры и игровые приставки, такие как Sega Saturn.

* SH-3: Мобильные приложения, такие как автомобильные навигаторы

* SH-4: Автомобильные мультимедийные терминалы и игровые приставки, такие как Sega Dreamcast.

* SH-5: Мультимедийные приложения высокого класса.

Микроконтроллеры и процессоры, реализующие его, в настоящее время производятся Renesas Electronics, совместным предприятием групп Hitachi и Mitsubishi Semiconductor. Поскольку вредоносное ПО IoT в основном нацелено на системы на базе SH-4, мы сосредоточимся на этом семействе SuperH.

Основы

Что касается регистров, SH-4 предлагает следующее:

* 16 регистров общего назначения R0-R15 (32-разрядные)

* 7 регистров управления (32-разрядные):

- Глобальный базовый регистр (GBR)

- Регистр состояния (SR)

- Сохраненный регистр состояния (SSR)

- Сохраненный счетчик программ (SPC)

- Векторный базовый счетчик (VBR)

- Сохраненный общий регистр 15 (SGR)

- Debug Base Register (DBR) (только из привилегированного режима)

* 4 системных регистра (32-битные):

- MACH/MACL: регистры умножения и накопления

- PR: регистр процедур

- PC: счетчик программ

- FPSCR: Регистр состояния/управления с плавающей запятой

* 32 регистра FPU FR0-FR15 (также известные как DR0/2/4/... или FV0/4/...) и XF0-XF15 (также известные как XD0/2/4/... или XMTRX); два банка по 16 одинарной точности (32-разрядные) или восемь с двойной точностью (64-разрядные) FPR и FPUL (регистр связи с плавающей запятой) (32-разрядный)

Обычно R4-R7 используются для передачи аргументов функции, результат которой возвращается в R0. R8-R13 сохраняются для нескольких вызовов функций. R14 служит указателем кадра, а R15 — указателем стека.

Что касается форматов данных, в SH-4 слово занимает 16 бит, длинное слово — 32 бита, а учетверенное слово — 64 бита.

Поддерживаются два режима процессора: пользовательский режим и привилегированный режим. SH-4 обычно работает в пользовательском режиме и переключается в привилегированный режим в случае исключения или прерывания.

Набор инструкций

SH-4 имеет набор инструкций, который совместим снизу вверх с семействами SH-1, SH-2 и SH-3. Он использует 16-битные инструкции фиксированной длины, чтобы уменьшить размер программного кода. За исключением BF и BT, все инструкции ветвления и RTE (инструкция возврата из исключения) реализуют так называемые отложенные ветвления, когда инструкция, следующая за ветвью, выполняется до инструкции назначения ветвления.

Все инструкции разделены на следующие категории (с некоторыми примерами):

* Инструкции по передаче с фиксированной точкой:

- MOV: перемещение данных (или указанных определенных типов данных)

- SWAP: обмен местами половинки регистра

* Инструкции по арифметическим операциям:

- SUB: вычесть двоичные числа

- CMP/EQ: условное сравнение (в данном случае на равное)

* Инструкции по логической операции:

- AND: логический и

- XOR: исключающее ИЛИ

* Инструкции сдвига:

- ROTL: сдвиг влево

- SHLL: логический сдвиг влево

* инструкции ветвления:

- BF: перейти, если ложь

- JMP: перейти (безусловная ветвь)

* Инструкции по управлению системой:

- LDC: загрузить в управляющий регистр

- STS: сохранить в системный реестр

* Инструкции одинарной точности с плавающей запятой:

- FMOV: перемещение с плавающей запятой

* Инструкции двойной точности с плавающей запятой:

- FABS: абсолютное значение с плавающей запятой

* Инструкции управления с плавающей запятой:

- LDS: загрузить в системный регистр FPU

Работа со SPARC

Архитектура масштабируемого процессора (SPARC) — это RISC ISA, первоначально разработанная Sun Microsystems (теперь часть корпорации Oracle). Первая реализация использовалась в собственных рабочих станциях и серверных системах Sun. Позже он был лицензирован для нескольких других производителей, одним из которых была Fujitsu. Поскольку Oracle закрыла SPARC Design в 2017 году, все будущие разработки продолжались с Fujitsu в качестве основного поставщика серверов SPARC.

Существует несколько реализаций архитектуры SPARC с полностью открытым исходным кодом. В настоящее время его поддерживают несколько операционных систем, включая системы Oracle Solaris, Linux и BSD, а для нескольких семейств вредоносных программ IoT также есть специальные модули.

Основы

Согласно документации по архитектуре Oracle SPARC, конкретная реализация может содержать от 72 до 640 64-битных регистров R общего назначения. Однако одновременно видны только 31/32; 8 — это глобальные регистры, от R[0] до R[7] (также известные как g0-g7), причем первый регистр, g0, жестко привязан к 0; и 24 связаны со следующими окнами регистров:

* Восемь в регистрах in[0]-in[7] (R[24]-R[31]): для передачи аргументов и возврата результатов

* Восемь локальных регистров local[0]-local[7] (R[16]-R[23]): для хранения локальных переменных

* Восемь выходных регистров out[0]-out[7] (R[8]-R[15]): для передачи аргументов и возврата результатов

Инструкция CALL записывает свой адрес в регистр out[7] (R[15]).

Чтобы передать аргументы функции, они должны быть помещены в регистры out и, когда функция получит управление, она будет обращаться к ним в своих регистрах in. Дополнительные аргументы могут быть предоставлены через стек. Результат помещается в первый входной регистр, который затем становится первым выходным регистром, когда функция возвращается. Инструкции SAVE и RESTORE используются для выделения нового окна регистра и последующего восстановления предыдущего соответственно.

SPARC также имеет 32 FPR с одинарной точностью (32-битные), 32 FPR с двойной точностью (64-битные) и 16 FPR с четырехкратной точностью (128-битные), некоторые из которых перекрываются.

Помимо этого, существует множество других регистров, которые служат конкретным целям, в том числе следующие:

* FPRS: содержит информацию о режиме и состоянии FPU.

* Вспомогательные регистры состояния (ASR 0, ASR 2-6, ASR 19-22 и ASR 24-28 не зарезервированы): служат нескольким целям, включая следующие:

- ASR 2: Регистр кодов состояния (CCR)

- ASR 5: PC

- ASR 6: FPRS

- ASR 19: Регистр общего состояния (GSR)

* Регистры состояния PR окна регистров (PR 9-14): определяют состояние окон регистров, включая следующее:

- PR 9: Указатель текущего окна (CWP)

- PR 14: Состояние окна (WSTATE)

* Регистры состояния PR без регистрации окна (PR 0–3, PR 5–8 и PR 16): видны только программному обеспечению, работающему в привилегированном режиме.

32-разрядный SPARC использует обратный порядок байтов, а 64-разрядный SPARC использует инструкции с обратным порядком байтов, но может обращаться к данным в любом порядке. В SPARC также используется понятие ловушек, реализующих передачу управления привилегированному программному обеспечению с использованием специальной таблицы, которая может содержать первые 8 инструкций (32 для некоторых часто используемых ловушек) каждого обработчика ловушек. Базовый адрес таблицы устанавливается программным обеспечением в регистре базового адреса прерывания (TBA).

Набор инструкций

Команда из ячейки памяти, указанной ПК, извлекается и выполняется, а затем новые значения присваиваются ПК и счетчику следующей программы (NPC), который является псевдорегистром.

Подробные форматы инструкций можно найти в описаниях отдельных инструкций.

Вот основные категории поддерживаемых инструкций с примерами:

* Доступ к памяти:

- LDUB: загрузить беззнаковый байт

- ST: сохранить

* Арифметические/логические/сдвиг:

- ADD: добавить

- SLL: логический сдвиг влево

* Передача управления:

- BE: ветвление если равно

- JMPL: переход

- CALL: вязов

- RETURN: Возврат из функции

* Доступ к регистру статуса:

- WRCCR: запись CCR

* Операции с плавающей запятой:

- FOR: логическое ИЛИ для F-регистров

* Условнное перемещение:

- MOVcc: переместить, если условие истинно для выбранного кода условия (cc)

* Регистр управления окнами:

- SAVE: сохранить окно вызова

- FLUSHW: очистить окна регистров

Переход от ассемблера к языкам программирования высокого уровня

Разработчики в основном не пишут на ассемблере. Вместо этого они пишут на языках более высокого уровня, таких как C или C++, и компилятор преобразует этот высокоуровневый код в низкоуровневое представление на языке ассемблера. В этом разделе мы рассмотрим различные блоки кода, представленные в сборке.

Арифметические операторы

Теперь мы рассмотрим различные операторы C и то, как они представлены в ассемблере. В качестве примера мы возьмем Intel IA-32, и та же концепция применима и к другим языкам ассемблера:

* X = 50 (при условии, что 0x00010000 — это адрес переменной X в памяти):

mov eax, 50
mov dword ptr [00010000h],eax

X = Y+50 (при условии, что 0x00010000 представляет X, а 0x00020000 представляет Y):

mov eax, dword ptr [00020000h]
add eax, 50
mov dword ptr [00010000h],eax


X = Y + (50 * 2):

mov eax, dword ptr [00020000h]
push eax ;save Y for now
mov eax, 50 ;do the multiplication first
mov ebx,2
imul ebx ;the result is in edx:eax
mov ecx, eax
pop eax ;gets back Y value
add eax,ecx
mov dword ptr [00010000h],eax


X = Y + (50 / 2):
mov eax, dword ptr [00020000h]
push eax ;save Y for now
mov eax, 50
mov ebx,2
div ebx ;the result in eax, and the remainder is in edx
mov ecx, eax
pop eax
add eax,ecx
mov dword ptr [00010000h],eax

X = Y + (50 % 2)
mov eax, dword ptr [00020000h]
push eax ;save Y for now
mov eax, 50
mov ebx,2
div ebx ;the remainder is in edx
mov ecx, edx
pop eax
add eax,ecx
mov dword ptr [00010000h],eax


Будем надеяться, что это объясняет, как компилятор преобразует эти арифметические выражения в язык ассемблера.

Условия

Основные операторы If могут выглядеть следующим образом:

* if (X == 50) (при условии, что 0x0001000 представляет переменную X):

mov eax, 50

cmp dword ptr [00010000h],eax

* if (X | 00001000b) (| представляет логический элемент ИЛИ):

mov eax, 000001000b

test dword ptr [00010000h],eax

Чтобы понять ветвление и перенаправление потока, давайте взглянем на следующую диаграмму, чтобы увидеть, как это проявляется в псевдокоде:

1697024909620.png


Чтобы применить эту последовательность ветвления в ассемблере, компилятор использует сочетание условных и безусловных переходов, как показано ниже:

IF.. THEN.. ENDIF:
cmp dword ptr [00010000h],50
jnz 3rd_Block ; if not true

Some Code

3rd_Block:
Some code
IF.. THEN.. ELSE.. ENDIF:
cmp dword ptr [00010000h],50
jnz Else_Block ; if not true
...
Some code
...
jmp 4th_Block ;Jump after Else
Else_Block:
...
Some code
...
4th_Block:
...
Some code


Цикл while


Цикла while очень похожи на условия if с точки зрения того, как они представлены в ассемблере:
1697024941374.png


1697024950004.png


Краткое содержание

В этой главе мы рассмотрели основы компьютерного программирования и описали универсальные элементы, общие для нескольких архитектур CISC и RISC. Затем мы рассмотрели несколько языков ассемблера, в том числе языки, лежащие в основе Intel x86, ARM, MIPS и другие, и поняли области их применения, которые в конечном итоге сформировали дизайн и структуру. Мы также рассмотрели фундаментальные основы каждого из них, изучили наиболее важные понятия (такие как используемые регистры и поддерживаемые режимы процессора), получили представление о том, как выглядят наборы инструкций, узнали, какие форматы кодов операций поддерживаются, и изучили, какие вызовы используются соглашения. Наконец, мы перешли от языков ассемблера низкого уровня к их высокоуровневому представлению в C или других подобных языках и познакомились с набором примеров для универсальных блоков, таких как условия if и циклы.

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

В главе 2 «Базовый статический и динамический анализ для x86/x64» мы начнем анализ реального вредоносного ПО для конкретных платформ, а наборы инструкций, с которыми мы познакомились, будут использоваться в качестве языков, описывающих его функциональность.
 
Глава 2. Базовый статический и динамический анализ для x86/x64

В этой главе мы рассмотрим основные принципы, которые вам необходимо знать для анализа 32-битного или 64-битного вредоносного ПО на платформе Windows. Мы рассмотрим заголовок переносимого исполняемого файла Windows (заголовок PE) и посмотрим, как он может помочь нам ответить на различные вопросы, связанные с обработкой инцидентов и анализом угроз.

Мы также рассмотрим концепции и основы статического и динамического анализа, включая процессы и потоки, поток создания процессов и процессы WOW64. В конце мы рассмотрим процесс отладки, установку точек останова и оповещение о выполнении программы.

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

Эта глава разделена на следующие разделы для облегчения процесса обучения:

* Работа со структурой заголовка PE

* Статические и динамические линковки

* Использование информации заголовка PE для статического анализа

* Загрузка PE и создание процесса

* Динамический анализ с OllyDbg/Immunity Debugger

* Отладка вредоносных сервисов

Работа со структурой заголовка PE

Когда вы начнете выполнять базовый статический анализ файла, вашей первой ценной информацией будет PE-заголовок. Заголовок PE представляет собой структуру, которой следует любой исполняемый файл Windows.

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

В этом разделе мы рассмотрим структуру PE-заголовка и узнаем, как мы можем анализировать PE-файл и читать его информацию.

Почему PE?

Структура или дизайн переносимых исполняемых файлов позволили решить множество проблем, возникших в предыдущих структурах, таких как MZ для исполняемых файлов MS-DOS или ранних стадиях структур COM. Он представляет собой довольно полный дизайн для любого исполняемого файла. Некоторые из особенностей структуры PE заключаются в следующем:

* Она разделяет код и данные на секции, что позволяет легко управлять данными отдельно от программы и связывать любую строку обратно в ассемблерном коде.

* Каждая секция имеет отдельные права доступа к памяти, которые в основном представляют собой уровень безопасности виртуальной памяти каждой запущенной программы, чтобы разрешить или запретить чтение с определенной страницы памяти, запись на определенную страницу памяти или выполнение кода на определенной странице памяти. Страница памяти имеет размер 0x1000 байт, что составляет 4096 байт в десятичном виде.

* Файл расширяем в памяти, что позволяет создать место для неинициализированных переменных (или переменных, которые не важны для включения определенного значения перед их использованием приложением) и, в то же время, экономит место на жестком диске и не заполняет его пустыми байтами или нулями.

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

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

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

* Переносимость для нескольких процессоров, подсистем и типов файлов, что позволяет использовать структуру PE на многих платформах, процессорах и устройствах, таких как Windows CE и Windows Mobile.

Изучение структуры PE

Здесь мы рассмотрим структуру исполняемого файла в операционной системе Windows. Эта структура используется Microsoft для представления нескольких файлов, таких как исполняемые файлы или библиотеки в операционной системе Windows, на нескольких устройствах, таких как ПК, планшеты и мобильные устройства.

Заголовок MZ

В начале эры MS-DOS Windows и DOS сосуществовали, и обе имели исполняемые файлы с одинаковым расширением .exe. Таким образом, каждое приложение Windows должно было начинаться с небольшого приложения DOS, которое печатало сообщение «Эта программа не может быть запущена в режиме DOS» (или любое подобное сообщение).Таким образом, когда приложение Windows запускается в среде DOS, небольшое приложение DOS при его запуске запускается и печатает это сообщение пользователю, чтобы запустить его в среде Windows. На следующем рисунке вы можете увидеть заголовок PE-файла, начинающийся с заголовка MZ программы DOS:

1697107697710.png


Этот заголовок DOS начинается с MZ, а заканчивается полем e_lfanew, которое указывает на начало заголовка переносимого исполняемого файла или PE-заголовка.

Заголовок PE

Заголовок PE начинается с двух букв, PE, за которыми следуют два важных заголовка: заголовок файла и необязательный заголовок, а затем все дополнительные заголовки, на которые указывает массив каталогов данных.

Файловый заголовок

Наиболее важные значения из этого заголовка следующие:

1697107718523.png


* Machine: это поле представляет тип процессора, например, значение 0x14c представляет процессоры Intel 386 или более поздних версий.

* NumberOfSections: это значение представляет количество разделов, следующих за заголовками, таких как раздел кода, раздел данных или раздел ресурсов (для файлов или изображений).

* TimeDateStamp: Это точная дата и время компиляции этой программы. Это очень полезно для разведки угроз и создания графика атаки.

* Characteristics: это значение представляет тип исполняемого файла, является ли это программой, динамической библиотекой (мы рассмотрим ее позже в этой главе) или, может быть, драйвером?

Необязательный заголовок

За заголовком File следует дополнительный заголовок, содержащий гораздо больше информации, как показано здесь:
1697107730929.png


Наиболее важные значения из этого заголовка следующие:

* Magic: Идентифицирует платформу, которую поддерживает этот PE-файл (будь то x86 или x64).

* AddressOfEntryPoint: Это очень важное поле для нашего анализа, оно указывает на начальную точку выполнения программы (на первую ассемблерную инструкцию, которая будет выполняться в программе).

* ImageBase: это адрес, по которому программа должна быть загружена в виртуальную память. Если в программе есть секция перемещения, ее можно переместить в другое место, если она перекрывается с другим исполняемым файлом, загруженным по тому же адресу.

* SectionAlignment: Размер каждого раздела и размер всех заголовков должны быть выровнены по этому значению при загрузке в память (обычно это значение 0x1000).

* FileAlignment: размер каждой секции в PE-файле (а также размер всех заголовков) должен быть выровнен по этому числу (например, для секции с размером 0x1164 и выравниванием файла 0x200 размер секции будет изменен до 0x1200 на жестком диске).

* MajorSubsystemVersion: минимальная версия Windows для запуска приложения, например Windows XP или Windows 7.

* SizeOfImage: Это размер всего приложения в памяти (обычно он больше, чем размер файла на жестком диске из-за неинициализированных данных и по другим причинам).

* SizeOfHeaders: это размер всех заголовков.

* Subsystem: Указывает, что это может быть приложение пользовательского интерфейса Windows или консольное приложение, или что оно может работать даже в других подсистемах Windows, таких как Microsoft POSIX.

Каталог данных

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

Он включает 16 записей следующего формата:

* Адрес: указывает на начало заголовка в памяти (относительно начала файла).

* Размер: это размер заголовка.

Массив каталога данных включает множество различных значений; не все из них так важны для анализа вредоносных программ, но некоторые из важных блоков, которые следует упомянуть, следующие:

* Таблица импорта: представляет функции кода (или API), которые эта программа не включает, но хочет импортировать из других исполняемых файлов или библиотек кода (или DLL).

* Таблица экспорта: представляет функции кода (или API), которые эта программа включает в свой код и готова экспортировать и разрешить использование другими приложениями, а не переписывать их с нуля.

* Таблица ресурсов: всегда находится в начале раздела ресурсов, и ее цель — представить файлы пакетов с программой, такие как значки, изображения и другие.

* Таблица перемещений: она всегда находится в начале раздела перемещения и используется для исправления адресов в коде, когда PE-файл загружается в другое место в памяти.

* Таблица TLS: Локальное хранилище потоков можно использовать для обхода отладчиков, что будет объяснено позже.

Таблица секций

За 16 элементами массива каталогов данных следуют заголовки разделов. Это список заголовков, каждый из которых представляет собой раздел PE-файла. Общее количество заголовков — это точное число, хранящееся в поле NumberOfSections в FileHeader.

Заголовок раздела — это очень простой заголовок, и он выглядит так:

1697107808464.png


И эти поля используются для следующего:

* Name: Имя раздела (макс. 8 байт).

* VirtualAddress: Указатель на начало раздела в памяти (относительно начала файла). Эти типы адресов называются адресами RVA.

* VirtualSize: Размер раздела (в памяти).

* SizeOfRawData: Размер раздела (на жестком диске).

* PointerToRawData: Указатель на начало раздела в файле на жестком диске (относительно начала файла). Эти типы адресов называются смещениями.

* Characteristics: Флаги защиты памяти (EXECUTE, READ или WRITE).

PE+ (x64 PE)

Возможно, сейчас вы думаете, что в PE-файлах x64 все поля имеют размер 8 байт, а не 4 байта в PE-файлах x86. Но правда в том, что заголовок PE+ очень похож на старый добрый заголовок PE с очень небольшими изменениями:

* ImageBase: 8 байтов вместо 4 байтов.

* BaseOfData: это поле было удалено из дополнительного заголовка.

* Others: Некоторые другие поля, такие как SizeOfHeapCommit, SizeOfHeapReserve, SizeOfStackReserve и SizeOfStackCommit теперь составляют 8 байтов вместо 4 байтов.

* Magic: это значение изменилось с 0x10B (представляющее x86) на 0x20B (представляющее x64).

Файлы PE+ остались с максимальным размером 2 ГБ, а все остальные адреса RVA, включая AddressOfEntrypoint, остались 4-байтовыми.

Инструменты анализа заголовка PE

После того, как мы поняли формат PE, нам нужно иметь возможность анализировать различные PE-файлы (например, файлы .exe) и читать значения их заголовков. К счастью, нам не нужно делать это самим; существует множество различных инструментов, которые могут помочь нам легко прочитать информацию заголовка PE. Наиболее известные бесплатные инструменты для анализа заголовка PE-файла:

* PEiD:

1697107821756.png


Это, пожалуй, самый известный инструмент для анализа PE-заголовков. Это базовый инструмент, но он может обнаруживать компилятор (например, Visual Studio) или обнаруживать упаковщик, который используется для упаковки этой вредоносной программы, используя статические подписи, хранящиеся в приложении (более подробно об этом будет рассказано в Главе 3, Распаковка) , расшифровка и деобфускация).

* CFF Explorer:

1697107832268.png


Это относительно новый и более продвинутый инструмент, чем PEiD, созданный FireEye. Этот инструмент анализирует больше информации из PE-файла, а также может определить используемый там компилятор/упаковщик (и он может быть более точным, чем PEiD).

В следующем разделе мы расширим наши знания и изучим мельчайшие детали статической и динамической компоновки.

Статическая и динамическая линковка

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

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

Статическая линковка

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

1697107844310.png


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

Динамическое связывание

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

В современных операционных системах, таких как Windows и Linux, есть сотни библиотек, и каждая из них имеет тысячи функций для пользовательского интерфейса, графики, 3D, интернет-коммуникаций и многого другого. Из-за этого статическое связывание оказалось ограниченным, и для решения этой проблемы появилось динамическое связывание. Это позволило программам больше расширяться и становиться более функциональными, как мы видим сегодня:

1697107871896.png


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

Эти библиотеки называются библиотеками динамической компоновки (DLL), как вы можете видеть на предыдущем рисунке.

DLL

DLL — это полноценный PE-файл, включающий в себя все заголовки, разделы и, самое главное, таблицу экспорта.

Таблица экспорта включает все функции, экспортируемые этой библиотекой. Не все библиотечные функции экспортируются, так как некоторые из них предназначены для внутреннего использования. Но к экспортируемым функциям можно получить доступ по их имени или порядковому номеру (номер индекса), и они называются интерфейсами прикладного программирования (API).

Windows предоставляет программистам Windows множество библиотек для доступа к своим функциям, и некоторые из этих библиотек следующие:

* kernel32.dll: Эта библиотека включает основные и основные функции для всех программ, включая чтение файла и запись в файл.

* ntdll.dll: эта библиотека экспортирует собственные API-интерфейсы Windows; kernel32.dll использует эту библиотеку как серверную часть для собственных функций. Некоторые авторы вредоносных программ пытаются получить доступ к недокументированным API-интерфейсам внутри этой библиотеки, чтобы затруднить понимание функциональности вредоносных программ реверс-инженерами, например LdrLoadDll.

* user32.dll: Эта библиотека используется в основном для графического интерфейса Windows.

* advapi32.dll: Эта библиотека используется в основном для работы с реестром и шифрования.

* shell32.dll: отвечает за операции оболочки, такие как выполнение и открытие файлов.

* ws2_32.dll: Все функции, связанные с интернет-сокетами и сетевыми коммуникациями (очень важно для понимания пользовательских сетевых протоколов связи).

* wininet.dll: функции HTTP и FTP, включая прокси и многое другое.

* urlmon.dll: это дополнение к wininet.dll, используемое для работы с URL-адресами, веб-сжатия, загрузки файлов и многого другого.

* gdi32.dll: используется для простых графических функций.

Интерфейс прикладного программирования

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

API можно экспортировать из файла .exe, а также из библиотеки, и эта программа (файл .exe) может работать как программа, загружаться как библиотека или вызываться из других библиотек, загружаемых программой во время работы.

Таблица импорта каждой программы содержит имена всех необходимых библиотек и всех их API, которые использует эта программа. И в каждой библиотеке таблица экспорта содержит имя API, порядковый номер API и адрес RVA этого API в библиотеке.

У каждого API есть порядковый номер, но не у всех API есть имя.

Динамическая загрузка API

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

Динамическая загрузка API поддерживается Windows (а также другими операционными системами) с использованием двух очень известных API:

* LoadLibraryA: этот API загружает динамическую библиотеку в виртуальную память вызывающей программы и возвращает ее адрес (варианты включают LoadLibraryW, LoadLibraryExA и LoadLibraryExW).

* GetProcAddress: этот API получает адрес API по его имени или порядковому номеру и адрес библиотеки, содержащей этот API.

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

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

Эти API — не единственные API, допускающие динамическую загрузку API; есть и другие приемы, которые будут рассмотрены позже в главе 7 «Обработка эксплойтов и шелл-кода».

Использование информации заголовка PE для статического анализа

Теперь, когда мы рассмотрели PE-заголовок, библиотеки динамической компоновки и API, возникает вопрос: как мы можем использовать эту информацию в нашем статическом анализе? Это полностью зависит от вопросов, на которые вы хотите ответить, и это то, что мы рассмотрим прямо сейчас.

Как использовать PE-заголовок для обработки инцидентов

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

* Запаковано ли это вредоносное ПО?

Заголовок PE может помочь вам определить, упаковано ли это вредоносное ПО. Упаковщики склонны менять имена разделов со знакомых имен (.text, .data и .rsrc) на другие имена, такие как UPX1 или другие.

Кроме того, они скрывают большинство API-интерфейсов, которые должны присутствовать в таблице импорта. Итак, вы увидите, что таблица импорта содержит очень мало API, и это также может быть еще одним признаком. Мы подробно рассмотрим распаковку в главе 3 «Распаковка, расшифровка и деобфускация».

* Является ли эта вредоносная программа дроппером или загрузчиком?

Очень часто можно увидеть, что дропперы имеют дополнительный PE-файл внутри своих ресурсов. Несколько инструментов, таких как Resource Hacker, могут обнаружить этот PE-файл (или, например, содержащий его ZIP-файл), и вы сможете найти удаленный модуль.

Для загрузчиков обычно используется API с именем URLDownloadToFile из DLL с именем urlmon.dll для загрузки файла и API ShellExecuteA для выполнения файла. Существуют и другие API, которые можно использовать для достижения той же цели, но эти два API являются наиболее известными и одними из самых простых в использовании для авторов вредоносных программ.

* Подключается ли он к серверу (серверам) Command & Control (C&C или веб-сайт злоумышленника)? И как?

Существуют различные API-интерфейсы, которые могут сообщить вам, что эта вредоносная программа подключается к Интернету, например socket, send и recv, и они могут сообщить вам, действительно ли они подключаются к серверу или прослушивают ли они порт, такой как connect и listen соответственно.

Некоторые API могут даже сообщить вам, какой протокол они используют, например HTTPSendRequestA или FTPPutFile, и оба они взяты из wininet.dll.

* Какие функции есть у этой вредоносной программы?

Некоторые API связаны с поиском файлов, например FindFirstFileA, что может указывать на то, что это вредоносное ПО, возможно, является программой-вымогателем.

Он может использовать такие API, как Process32First, Process32Next и CreateRemoteThread, что может означать функциональность внедрения процесса, или использование или TerminateProcess, что может означать, что это вредоносное ПО может завершать работу других приложений, таких как антивирусные программы или инструменты анализа вредоносных программ.

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

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

Как использовать PE-заголовок для анализа угроз

Мы рассмотрели, как заголовок PE может помочь вам ответить на вопросы, связанные с обработкой инцидентов или обычным тактическим отчетом. Теперь мы рассмотрим следующие вопросы, связанные с аналитикой угроз, и то, как PE-заголовок может помочь вам ответить на них:

* Когда был создан этот образец?

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

Заголовок PE включает в себя значение TimeDateStamp в заголовке файла. Это значение включает точную дату и время компиляции этого образца, что может помочь ответить на этот вопрос и помочь исследователям угроз построить график атаки. Однако стоит отметить, что его также можно подделать.

* Какова страна происхождения этих злоумышленников?

Он был из США? Из России? Китая? А может из Ирана? Это может многое рассказать о мотивах злоумышленника.

Один из способов ответить на этот вопрос — снова TimeDateStamp, просмотрев множество образцов и время их компиляции. Вы можете видеть, что в некоторых случаях они попадают в 9-5 рабочих мест для российского часового пояса или китайского часового пояса. В некоторых случаях удается определить страну происхождения злоумышленников, как видно на следующем снимке экрана:

1697107917145.png


* Это украденный сертификат? Все эти образцы связаны?

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

Вот некоторые вопросы, на которые может помочь ответить статический анализ PE-заголовка. Как мы уже говорили ранее, PE-заголовок — это кладезь информации, если вы посмотрите на детали, скрывающиеся внутри его полей. Мы только даем намеки и идеи; есть так много всего, что можно извлечь из этого, и вы должны исследовать это дальше.

Загрузка PE и создание процесса

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

Основная терминология

Чтобы понять загрузку PE и создание процесса, мы должны рассмотреть некоторые основные термины, такие как процесс, поток, блок среды потока (TEB), блок среды процесса (PEB) и другие, прежде чем мы углубимся в процесс загрузки и выполнения исполняемого файла. PE-файл.

Что за процесс?

Процесс — это не только представление работающей программы в памяти, но и контейнер всей информации запущенного приложения. Этот контейнер инкапсулирует всю виртуальную память для этого процесса (каждый процесс в Windows x86 имеет адресное пространство 4 ГБ, а в x64 — 16 ТБ) и эквивалентную им физическую память. Он включает в себя все загруженные библиотеки DLL, открытые файлы, открытые сокеты, список потоков, запущенных в этом процессе (мы рассмотрим это позже), идентификатор процесса и многое другое.

Процесс — это, по сути, структура в ядре, которая содержит всю эту информацию внутри, работая как сущность для представления этого работающего исполняемого файла, как показано на следующей диаграмме:

1697107937147.png


Давайте сравним различные аспекты виртуальной и физической памяти в следующем разделе.

Отображение виртуальной памяти на физическую память

Что отличает современные операционные системы от MS-DOS и других операционных систем и позволяет им одновременно запускать несколько процессов, так это изобретение виртуальной памяти.

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

Эта виртуальная память имеет сопоставление с эквивалентной физической памятью. Не все страницы виртуальной памяти сопоставляются с физической памятью, и каждая сопоставляемая имеет собственное разрешение (ЧТЕНИЕ|ЗАПИСЬ, ЧТЕНИЕ|ВЫПОЛНЕНИЕ или, возможно, ЧТЕНИЕ|ЗАПИСЬ|ВЫПОЛНЕНИЕ), как показано на следующей диаграмме:

1697107950546.png


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

Потоки

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

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

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

Каждый поток имеет свой собственный стек, указатель команд, кодовые функции для обработки ошибок , свой собственный идентификатор потока и структуру информации о потоке, называемую TEB (которая будет рассмотрена ниже), как показано на следующем рисунке:

1697107962473.png


Далее мы поговорим о важнейших структурах данных, необходимых для понимания потоков и процессов. Давайте рискнем.

Важные структуры данных: TIB, TEB и PEB.

Последнее, что вам нужно понять, связанное с процессами и потоками, — это эти структуры данных (TIB, TEB и PEB). Эти структуры хранятся в памяти процесса и доступны через его код. Их основная функция состоит в том, чтобы включать всю информацию о процессе и каждом потоке и делать их доступными для кода, чтобы он мог легко узнать имя файла процесса, загруженные библиотеки DLL и другую связанную информацию.

Все они доступны через специальный сегментный регистр FS, например:

mov eax, DWORD PTR FS:[XX]

И эти структуры данных имеют следующие функции:

* Блок информации о потоке (TIB): содержит некоторую информацию о потоке, включая список функций, используемых для обработки ошибок, и многое другое.

* Блок среды потока (TEB): содержит больше информации о потоке, включая идентификатор потока.

* Блок среды процесса (PEB): включает информацию о процессе, такую как имя процесса, идентификатор процесса (PID), загруженные модули (все PE-файлы, загруженные в память, включая саму программу и ее библиотеки DLL) и многое другое.

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

Процесс загрузки шаг за шагом

Теперь, когда мы знаем основную терминологию, мы можем погрузиться в загрузку PE и создание процессов. Мы рассмотрим его последовательно, как показано в следующих шагах:

1. Запуск программы: когда вы дважды щелкаете программу в проводнике Windows, скажем, calc.exe, другой процесс под названием explorer.exe (процесс проводника Windows) вызывает API под названием CreateProcess, который отправляет операционной системе запрос чтобы создать этот процесс и начать выполнение.

2. Создание структур данных процесса: затем Windows создает структуру данных процесса в ядре (которая называется EProcess) и устанавливает уникальный идентификатор для этого процесса (ProcessID), а также устанавливает идентификатор процесса explorer.exe в качестве родительского PID для только что созданный процесс calc.exe.

3. Инициализирование виртуальной памяти: затем Windows создает процесс, виртуальную память и ее представление физической памяти и сохраняет его в структуре EProcess, создает структуру PEB со всей необходимой информацией, а затем загружает две основные библиотеки DLL, которые приложения Windows всегда будут нужны, а именно ntdll.dll и kernel32.dll (некоторые приложения работают на других подсистемах Windows, таких как POSIX, и они не используют kernel32.dll).

4. Загрузка PE-файла. После этого Windows начинает загрузку PE-файла (что мы объясним далее), загружая все необходимые сторонние библиотеки (DLL), включая все DLL, которые требуются этим библиотекам, и обязательно находит необходимые API из этих библиотек и сохраняют их адреса в таблице импорта загруженного PE-файла, чтобы код мог легко получить к ним доступ и вызвать их.

5. Выполнение. Последнее, но не менее важное: Windows создает первый поток в процессе, который выполняет некоторую инициализацию и вызывает точку входа PE-файла, чтобы начать выполнение программы.

Загрузка PE файла шаг за шагом

Загрузчик Windows PE выполняет следующие шаги при загрузке исполняемого PE-файла в память (включая библиотеки динамической компоновки):

1. Разбор заголовков: Windows сначала начинает с анализа заголовка DOS, чтобы найти заголовок PE, а затем анализирует заголовок PE (заголовок файла и необязательный заголовок), чтобы собрать некоторую важную информацию:

* ImageBase: для загрузки PE-файла (если возможно) по этому адресу в его виртуальной памяти.

* NoOfSections: Используется при загрузке разделов.

* SizeOfImage: поскольку это будет окончательный размер всего PE-файла после загрузки в память, это значение будет использоваться для первоначального выделения пространства.

2. Разбор таблицы разделов: с помощью поля NoOfSections анализирует все разделы в PE-файле и обеспечивает получение всей необходимой информации, включая их адреса и размеры в памяти (VirtualAddress и VirtualSize соответственно), а также указатель и размер раздела на жестком диске для чтения его данных.

3. Отображение файла в памяти: с помощью SectionAlignment загрузчик копирует все заголовки, а затем перемещает каждый раздел в новое место, используя его значения VirtualAddress и VirtualSize (если VirtualAddress или VirtualSize не выровнены с SectionAlignment, загрузчик сначала выровняет их, а затем использует), как показано на следующей диаграмме:

1697107994322.png


4. Работа со сторонними библиотеками: на этом этапе загрузчик загружает все необходимые библиотеки DLL, повторяя этот процесс снова и снова рекурсивно, пока все библиотеки DLL не будут загружены. После этого он получает адреса всех необходимых API и сохраняет их в таблице импорта загруженного PE-файла.

5. Работа с релокацией: если программа или любая сторонняя библиотека имеет таблицу перемещений (в своем каталоге данных) и загружается не в свою базу образа, загрузчик фиксирует все абсолютные адреса в коде с новым адресом программы/библиотеки (с новой ImageBase).

6. Выполнение: На последнем шаге, как и при создании процесса, Windows создает первый поток, который выполняет программу из ее точки входа. Некоторые методы предотвращения обратного проектирования могут заставить его начать где-то еще раньше, что мы рассмотрим в главе 5 «Обход методов предотвращения обратного проектирования».

WOW64 процессы

Теперь вы можете легко понять, как загружается 32-разрядный процесс в среде x86, а также 64-разрядный процесс в среде x64. Итак, как насчет 32-битного процесса в среде x64?

Для этого особого случая Windows создала так называемый эмулятор WOW64.Этот эмулятор состоит из следующих трех DLL:

wow64.dll

wow64cpu.dll

wow64win.dll


Эти библиотеки DLL в основном создают смоделированное окружение для 32-разрядного процесса, включающего 32-разрядную ntdll.dll и 32-разрядную kernel32.dll.

Эти библиотеки DLL вместо прямого подключения к ядру Windows вызывают API X86SwitchTo64BitMode, который затем переключается на x64 и вызывает 64-разрядную ntdll.dll, которая связывается напрямую с ядром, как показано на следующей диаграмме:

1697108008261.png


Кроме того, процессы WOW64-песочницы (процессы x86, работающие в среде x64) представили новые API, такие как IsWow64Process, который используется вредоносными программами для определения того, работает ли он как 32-разрядный процесс в среде x64 или в среде x86. И он представил несколько новых API, а также специфичных для среды WOW64.

Динамический анализ с OllyDbg/Immunity Debugger

После того, как мы объяснили процессы, потоки и выполнение PE-файлов, пришло время приступить к отладке запущенного процесса и понять его функциональность путем отслеживания его кода в среде выполнения.

Инструменты отладки

Есть несколько инструментов отладки, которые мы можем использовать, и здесь мы просто приведем три примера, которые очень похожи по своему пользовательскому интерфейсу и на самом деле имеют много общего кода (по крайней мере, два из них):

* OllyDbg: Это, вероятно, самый известный отладчик на платформе Windows, и его пользовательский интерфейс стал стандартом для большинства отладчиков Windows:

1697108027573.png


* Immunity Debugger: По сути, это клон OllyDbg с поддержкой сценариев, созданный в основном для эксплуатации и поиска ошибок:

1697108038162.png


* x64_dbg: Это отладчик для исполняемых файлов x86 и x64 с интерфейсом, очень похожим (если не идентичным) на OllyDbg. Это также отладчик с открытым исходным кодом:

1697108047558.png


Мы рассмотрим OllyDbg 1.10, поскольку это наиболее распространенная версия OllyDbg, и большинство плагинов поддерживают эту версию.

Как проанализировать образец с помощью OllyDbg

Пользовательский интерфейс OllyDbg довольно прост и легок в освоении. Здесь будут описаны шаги и различные окна, которые могут помочь вам в анализе:

1. Выберите образец для отладки. Файл примера можно открыть напрямую из меню Файл | Откройте и выберите PE-файл для открытия (это также может быть файл DLL, но убедитесь, что это 32-битный образец). Или вы можете подключиться к запущенному процессу следующим образом:

1697108062669.png


2. Окно ЦПУ. Ваше главное окно. Это окно, в котором вы проводите большую часть времени отладки. Это окно включает ассемблерный код в верхнем левом углу и предоставляет возможность установить точки останова, дважды щелкнув адрес или изменив ассемблерный код программы.

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

Вы можете просто изменить любые данные в памяти в следующих двух представлениях:

1697108081868.png


3. Окно «Исполняемые модули». В OllyDbg есть несколько окон, которые помогут вам в анализе, например окно «Исполняемые модули» (вы можете получить к нему доступ через «Просмотр | Исполняемые модули»), как показано на следующем снимке экрана:

1697108090707.png


Это окно поможет вам увидеть все загруженные PE-файлы в виртуальной памяти этого процесса, включая образец вредоносного ПО и все загруженные с ним библиотеки или DLL. Если вы подключаетесь к процессу, это может помочь вам увидеть любые внедренные вредоносные библиотеки (DLL) внутри этого процесса и его виртуальный адрес.

4. Окно карты памяти. Кроме того, вы можете выделить всю память внутри виртуальной памяти процесса (выделенная память — это память, представление которой находится в физической памяти или в ее кеше на жестком диске). Вы можете увидеть, что они представляют, их защиту памяти (чтение, запись и/или выполнение), а также вы можете сделать дамп любого фрагмента памяти из этого окна, как показано на следующем снимке экрана:

1697108098996.png


5. Отладка. В меню «Отладка» у вас есть несколько вариантов запуска ассемблерного кода программы, например, полное выполнение до достижения точки останова с помощью «Выполнить» или просто с помощью F9.

Другой вариант — просто перешагнуть. Step over в основном выполняет одну строку кода. Однако, если эта строка кода является вызовом другой функции, она выполняет эту функцию полностью и останавливается сразу после возврата из этой функции, что отличает ее от Step in , который входит внутрь функции и останавливается в ее начале, как показано на следующем скриншоте:

1697108107468.png


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

6. И многое другое: OllyDbg дает возможность модифицировать код программы; изменить его регистры, состояние, память; дамп любой части памяти; и сохраните изменения PE-файла в памяти обратно на жесткий диск для дальнейшего статического анализа, если это необходимо.

Типы точек останова

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

Существует два типа точек останова прерываний, которые обсуждаются в следующих разделах.

Step into/step over breakpoint

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

Эта точка останова создается путем изменения флага в регистре Eflags. Эта точка останова может быть обнаружена вредоносным ПО для обнаружения присутствия отладчика, который мы рассмотрим в главе 5 «Обход методов защиты от обратного проектирования».

Точка останова INT3

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

1697108118197.png


Это то, что вы видите через пользовательский интерфейс отладчика, но чего вы не видите, так это того, что первый байт этой инструкции (в данном случае 0xB8) был изменен на 0xCC (инструкция INT3), что останавливает выполнение, как только процессор достигает его и возвращается обратно в отладчик.

Как только отладчик возвращается к этой точке останова INT3, он заменяет 0xCC обратно на 0xB8 и выполняет эту инструкцию в обычном режиме.

Проблема этой точки останова в том, что если вредоносное ПО попытается прочитать или изменить байты этой инструкции, оно прочитает первый байт как 0xCC вместо 0xB8, что может сломать некоторый код или обнаружить присутствие отладчика (о котором мы поговорим ниже) в главе 5 «Обход методов защиты от обратного проектирования»).

Точки останова в памяти

Точки останова в памяти используются не для остановки на определенных инструкциях, а для остановки, когда какая-либо инструкция пытается прочитать определенную часть памяти или изменить ее. Этот тип точки останова создается путем изменения защиты памяти этой страницы памяти, либо делая ее недоступной, если точка останова используется при доступе (или чтении) к этой странице памяти, либо делая ее доступной только для чтения, если точка останова находится при изменении (или записи). на этой странице памяти.

Доступ к ним можно получить, щелкнув правой кнопкой мыши Breakpoint | Memory, on access или Memory, onwrite, как показано на следующем снимке экрана:

1697108127722.png


Вы можете задаться вопросом, почему нет memory on-execute при использовании защиты памяти, и причина в том, что защита от выполнения не применялась до Windows 8. Если ваша виртуальная машина работает под управлением Windows XP или Windows 7, я покажу вам, как обеспечить эту защиту и как создать точки останова в памяти при выполнении, в главе 3 «Распаковка, расшифровка и деобфускация».

Другой способ установки точки останова памяти при доступе многими отладчиками — это добавление защиты PAGE_GUARD (0x100) к исходной защите страницы и удаление PAGE_GUARD после достижения точки останова.

Аппаратные точки останова

Аппаратные точки останова основаны на восьми регистрах, недоступных из пользовательского режима (от DR0 до DR7).

Эти регистры позволяют вам установить максимум четыре точки останова по конкретным адресам для чтения, записи или выполнения 1, 2 или 4 байтов, начиная с данного адреса. Они очень полезны, поскольку не изменяют байты инструкций, такие как устанавливаемые точки останова INT3, и их гораздо сложнее обнаружить (поскольку эти регистры недоступны для сборки программы). Однако они по-прежнему могут быть обнаружены и удалены вредоносным ПО, о котором мы поговорим в главе 5 «Обход методов защиты от обратного проектирования».

Вы можете просмотреть их из меню «Отладка», перейдя в раздел «Аппаратные точки останова», как показано на следующем снимке экрана:

1697108138594.png


Изменение выполнения программы

Чтобы иметь возможность обойти приемы защиты от отладки, заставить вредоносную программу взаимодействовать с C&C или даже протестировать различные ветви выполнения вредоносной программы, вам необходимо иметь возможность изменять поток выполнения вредоносной программы. Теперь мы рассмотрим различные методы изменения потока выполнения и поведения любого потока.

Патчинг — изменение инструкций программы.

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

1697108147682.png


Изменение EFlags

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

В правом верхнем углу после регистров есть несколько флагов, которые вы можете изменить. Каждый флаг представляет собой конкретный результат любого сравнения (другие инструкции также меняют эти флаги). Например, ZF указывает, равны ли два значения или регистр стал нулевым. Изменяя флаг ZF, вы заставляете условные переходы, такие как jnz и jz, перейти на противоположную ветвь и принудительно изменить путь выполнения.

Изменение значения указателя инструкции

Вы можете принудительно выполнить определенную ветвь или любую инструкцию, просто изменив EIP или указатель инструкции, и это можно сделать, щелкнув правой кнопкой мыши «Новое начало здесь».

Изменение данных программы

Поскольку вы можете изменить код инструкции, вы можете изменить значения данных. В нижнем левом представлении (шестнадцатеричном представлении) вы можете изменять байты данных, щелкнув правой кнопкой мыши Binary | Edit. Вы также можете скопировать/вставить шестнадцатеричные значения, как показано на следующем снимке экрана:

1697108158340.png


Отладка вредоносных сервисов

Хотя загрузка отдельных исполняемых файлов и DLL для отладки, как правило, является довольно простой задачей, все становится немного сложнее, когда мы говорим об отладке служб Windows.

Что такое сервис?

Службы — это задачи, которые обычно должны выполнять определенную логику в фоновом режиме, подобно демонам в Linux. Поэтому неудивительно, что авторы вредоносных программ обычно используют их для достижения персистенса.

Службы контролируются диспетчером управления службами (SCM), реализованным в %SystemRoot%\System32\services.exe. Все службу имеют соответствующий Ключ реестра HKLM\SYSTEM\CurrentControlSet\services\<имя_службы>. Он содержит несколько значений, описывающих службу, включая следующие:

* ImagePath: путь к соответствующему исполняемому файлу с необязательными аргументами.

* Type: значение REG_DWORD указывает тип службы. Примеры поддерживаемых значений включают следующее:

- 0x00000001 (ядро): в этом случае логика реализуется в драйвере (который будет рассмотрен более подробно в главе 6 «Понимание руткитов режима ядра», посвященной угрозам режима ядра).

- 0x00000010 (own): служба запускается в собственном процессе.

- 0x00000020 (share): служба работает в общем процессе.

* Start: еще одно значение REG_DWORD, которое описывает способ запуска службы. Обычно используются следующие варианты:

- 0x00000000 (boot) и 0x00000001 (system): эти значения используются для драйверов. В этом случае они будут загружены загрузчиком или во время инициализации ядра соответственно.

- 0x00000002 (auto): служба будет запускаться автоматически при каждом перезапуске компьютера, что является очевидным выбором для вредоносного ПО.

- 0x00000003 (demand): указывает службу, которую следует запустить вручную. Эта опция особенно полезна для отладки.

- 0x00000004 (отключено): служба не будет запущена.

Обычно существует несколько способов создания сервисов:

* В виде исполняемого файла: здесь фактическая логика реализована в выделенном исполняемом файле, а ранее упомянутый ImagePath будет содержать полный путь к файлу.

* В виде DLL (собственный загрузчик): В этом случае логика службы находится в DLL, имеющей собственный загрузчик (либо специальную программу, либо какую-то стандартную, например rundll32.exe). Полная командная строка сохраняется в ключе ImagePath, как и в предыдущем случае.

* В виде DLL (svchost): здесь вместо собственного EXE-файла вся сервисная логика реализована в DLL, загруженной в адресное пространство одного из процессов svchost.exe. Для загрузки вредоносная программа обычно создает новую группу в HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost, а затем передает это значение в svchost.exe, используя аргумент -k. Путь к DLL будет указан не в значении ImagePath ключа реестра службы, как в предыдущем случае (здесь он будет содержать путь к svchost.exe с аргументом группы обслуживания), а в значении ServiceDll файла HKLM\SYSTEM\CurrentControlSet\services\<имя_службы>\Раздел реестра Parameter. Служебная DLL должна содержать функцию экспорта ServiceMain (если используется собственное имя, оно должно быть указано в значении реестра ServiceMain). Если экспорт SvchostPushServiceGlobals присутствует, он будет выполнен до ServiceMain.

Службу пользовательского режима с выделенным исполняемым файлом (или DLL с собственным загрузчиком) можно зарегистрировать с помощью стандартного инструмента командной строки sc, например:

sc create <service_name> type= own binpath= <path_to_executable>

Для служб на основе svchost DLL процесс немного сложнее:

reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost" /v "<service_group>" /t REG_MULTI_SZ /d "<service_name>\0" /f

reg add "HKLM\SYSTEM\CurrentControlSet\Services\<service_name>\Parameters" /v ServiceDll /t REG_EXPAND_SZ /d <path_to_dll> /f

sc create <service_name> type= share binpath= "C:\Windows\System32\svchost.exe -k <service_group>


При таком подходе созданный сервис можно запустить по требованию, когда это необходимо, например, с помощью следующих команд:

sc start <service_name>

Или:

net start <service_name_or_display_name>

Привязка к сервису

Существует несколько способов подключения служб сразу после их запуска:

* Создание специального раздела реестра: можно создать такой раздел, как HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<имя файла> с соответствующим значением строковых данных Debugger, содержащим полный путь к отладчику. Здесь есть нюанс, что окно подключенного отладчика может не появиться, если сервис не интерактивный. Исправить это можно одним из следующих способов:

- Откройте файл Services.msc, затем откройте «Свойства» отлаживаемой службы, затем перейдите на вкладку «Вход в систему» и установите флажок «Разрешить службе взаимодействовать с рабочим столом».

- Это также можно сделать вручную, открыв значение Тип HKLM\SYSTEM\CurrentControlSet\services\<имя_службы> и замена его данных результатом побитовой операции ИЛИ с текущим значением и DWORD 0x00000100 (флаг SERVICE_INTERACTIVE_PROCESS). Например, 0x00000010 станет 0x00000110.

* Кроме того, его можно изначально создать как интерактивный при использовании инструмента sc с аргументами type= Interact Type= Own или Type= Interact Type= Share. Другой вариант — использовать удаленную отладку.

* Использование GFlags: инструмент GFlags (редактор глобальных флагов), который является частью инструментов отладки для Windows (так же, как WinDbg), предоставляет множество возможностей для настройки процесса отладки приложения-кандидата. Чтобы подключить отладчик, он изменяет ключ реестра, упомянутый ранее, поэтому в этом случае оба подхода можно использовать практически как взаимозаменяемые. Для того, чтобы сделать это с помощью ее пользовательского интерфейса, необходимо указать имя файла интересующей программы (а не полный путь) на вкладке «Файл образа», поле «Образ», затем обновить окно с помощью клавиши Tab и установить галочку напротив поле «Отладчик», в котором следует указать полный путь к предпочитаемому отладчику. Как и в предыдущем случае, необходимо убедиться, что сервис интерактивный.

* Включение дочерней отладки: здесь можно подключиться к Services.exe с отладчиком, поддерживающим прерывания при создании дочернего процесса, включить его (например, с помощью команды .childdbg 1 в WinDbg), а затем запустить интересующую службу.

* Исправление точки входа. Идея состоит в том, чтобы поместить байты \xEB\xFE в точку входа анализируемого образца, которая представляет инструкцию JMP, чтобы перенаправить выполнение в начало самого себя, что создает бесконечный цикл. Тогда появляется возможность найти соответствующий процесс (он будет потреблять большое количество ресурсов ЦП), подключиться к нему с помощью отладчика, восстановить исходные байты и продолжить выполнение в обычном режиме, убедившись, что восстановленные инструкции выполняются успешно.

После подключения отладчика можно разместить точку останова в точке входа примера, чтобы остановить выполнение там.

Распространенной проблемой служб отладки является тайм-аут. По умолчанию служба завершается примерно через 30 секунд, если она не сигнализирует об успешном выполнении, что может усложнить процесс отладки. Например, WinDbg в этом случае случайно начинает показывать ошибку No runnable debuggees при попытке выполнить любую команду. Чтобы продлить этот временной интервал, необходимо создать или обновить значение DWORD ServicesPipeTimeout в разделе реестра HKLM\SYSTEM\CurrentControlSet\Control, указав новое время ожидания в миллисекундах, и перезагрузить компьютер.

Экспорт служебных DLL, таких как ServiceMain, можно отлаживать с помощью любого из ранее упомянутых подходов. В этом случае можно либо подключиться к соответствующему процессу svchost.exe сразу после его создания и включить прерывание при загрузке DLL (например, с помощью команды sxe ld[:<dll_name>] в WinDbg), либо исправить DLL-файлы, точку входа или любой другой интересующий экспорт с помощью инструкции бесконечного цикла и прикрепите его к svchost.exe в любой момент после его запуска.

Это подводит нас к концу этой захватывающей главы. Давайте теперь кратко взглянем на то, что мы узнали и это мы рассмотрим в главе 3 «Распаковка, расшифровка и деобфускация».

Краткое содержание

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

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

Затем мы рассмотрели динамический анализ с базовой стороны, например, что такое процесс и что такое поток, с пошаговыми инструкциями о том, как Windows создает процесс и загружает PE-файл, дважды щелкнув приложение в Проводник Windows и до тех пор, пока перед вами не запустится программа.

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

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

В главе 3 «Распаковка, расшифровка и деобфускация» мы продолжим обсуждение и перейдем к распаковке, расшифровке и деобфускации в контексте вредоносного ПО. Мы рассмотрим различные методы, предложенные авторами вредоносных программ для обхода обнаружения и обмана неопытных реверс-инженеров. Мы также научимся обходить эти приемы и бороться с ними.
 
Распаковка, расшифровка и деобфускация

В этой главе мы собираемся изучить различные методы, предложенные авторами вредоносных программ для обхода статических сигнатур антивирусного программного обеспечения и обмана неопытных реверс-инженеров, в основном это упаковка, шифрование и обфускация. Мы научимся идентифицировать упакованные образцы, как их распаковывать, как работать с различными алгоритмами шифрования — от простых, таких как шифрование со скользящим ключом, до более сложных алгоритмов, таких как 3DES, AES и шифрование с открытым ключом (PKA) — и как обращаться с шифрованием API, шифрованием строк и шифрованием сетевого трафика.

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

Для облегчения процесса обучения данная глава разделена на следующие разделы:

* Исследование упаковщиков

* Идентификация упакованного образца

* Автоматическая распаковка упакованных образцов

* Распаковка вручную с помощью OllyDbg.

* Дамп распакованного образца и исправление таблицы импорта

* Определение различных алгоритмов и функций шифрования.

* Методы обнаружения строк для простых алгоритмов.

* Определение алгоритма шифрования RC4.

* Стандартные симметричные и асимметричные алгоритмы шифрования.

* Приложения шифрования в современных вредоносных программах — банковский троян Vawtrak.

* Использование IDA для расшифровки и распаковки

Исследование пакеров

Упаковщик — это инструмент, который упаковывает код исполняемого файла, данные, а иногда и ресурсы, и содержит код для распаковки программы на лету и ее выполнения:

1697452637407.png


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

Изучение инструментов упаковки и шифрования

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

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

- UPX: это упаковщик с открытым исходным кодом, и его инструмент командной строки позволяет распаковывать упакованный файл.

- ASPack: это широко используемый упаковщик, имеющий бесплатную и премиум-версию. Та же компания, которая предоставляет ASPack, также предоставляет такие средства защиты, как ASProtect.

* Легальные протекторы: основная цель этих инструментов — защитить программы от попыток обратного проектирования — например, защитить систему лицензирования условно-бесплатных продуктов или скрыть детали реализации от конкурентов. Они часто включают в себя шифрование и различные приемы защиты от обратного проектирования. Некоторые из них могут быть использованы не по назначению для защиты от вредоносного ПО, но это не их цель.

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

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

Идентификация упакованного образца

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

Метод 1 – проверка статических сигнатур инструментом PE

Первый способ определить, упаковано ли вредоносное ПО, — использовать статические сигнатур. Каждый упаковщик имеет уникальные характеристики, которые помогут вам его идентифицировать. Например, упаковщик UPX переименовывает все разделы в UPX1, UPX2 и т. д., а упаковщик UPX присваивает последнему разделу имя .aspack. Некоторые инструменты PE, такие как PEiD и CFF Explorer, могут сканировать PE-файл, используя эти сигнатуры или признаки, и идентифицировать упаковщик, который использовался для сжатия файла (если он упакован); в противном случае они идентифицируют компилятор, который использовался для компиляции этого исполняемого файла (если он не запакован):

1697452666051.png


Все, что вам нужно сделать, это открыть этот файл в PEiD — вы увидите сигнатуру, которая была активирована на этом PE-файле (на предыдущей диаграмме он был идентифицирован как UPX). Однако, поскольку они не всегда могут определить используемый упаковщик/компилятор, вам нужны другие способы определить, упакован ли он и какой упаковщик использовался, если таковой имеется.

Метод 2 – оценка названий разделов PE

Названия разделов могут многое рассказать о компиляторе или упаковщике, если файл упакован. Распакованный PE-файл содержит такие разделы, как .text или .code, .data, .idata, .rsrc и .reloc, а упакованные файлы могут содержать определенные имена разделов, например UPX0, .aspack, .stub и т. д.:

1697452679846.png


Эти имена разделов помогут вам определить, упакован ли этот файл.

Поиск названий этих разделов в Интернете может помочь вам определить упаковщик, который использует эти имена для своих упакованных данных или своего стаба (кода распаковки). Вы можете легко найти названия секций, открыв файл в PEiD и нажав кнопку > рядом с секцией EP. Сделав это, вы увидите список разделов в этом PE-файле, а также их названия.

Техника 3 – использование знаков выполнения стаба

Большинство упаковщиков сжимают разделы PE-файла, включая раздел кода, раздел данных, таблицу импорта и т. д., а затем добавляют в конце новый раздел, содержащий код распаковки (стаб). Поскольку большинство распакованных PE-файлов начинают выполнение с первого раздела (.text или .code), упакованные PE-файлы начинают выполнение с одного из последних разделов, что является явным признаком того, что будет запущен процесс расшифровки. Следующие признаки указывают на то, что это происходит:

* Точка входа не указывает на первую секцию (в большинстве случаев она указывает на один из двух последних разделов), а разрешение памяти для этого раздела — EXECUTE (в характеристиках раздела).

* Разрешение памяти для первого раздела будет в основном READWRITE.

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

Техника 4 – обнаружение небольшой таблицы импорта

Для большинства приложений таблица импорта заполнена API из системных библиотек, а также сторонних библиотек; однако в большинстве упакованных PE-файлов таблица импорта будет довольно маленькой и будет включать несколько API из известных библиотек. Этого достаточно, чтобы распаковать файл. После распаковки будет использоваться только один API из каждой библиотеки PE-файла. Причина этого в том, что большинство упаковщиков загружают таблицу импорта вручную после распаковки PE-файла, как вы можете видеть на следующем скриншоте:

1697452699407.png


В упакованном образце из ADVAPI32.dll удалены все API-интерфейсы и оставлен только один, поэтому библиотека будет автоматически загружаться загрузчиком Windows. После распаковки код-заглушка распаковщика снова загрузит все эти API с помощью API GetProcAddress.

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

Автоматическая распаковка упакованных образцов

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

Метод 1 – официальный процесс распаковки

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

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

Техника 2 – использование OllyScript с OllyDbg

Существует плагин OllyDbg под названием OllyScript, который может помочь автоматизировать процесс распаковки. Он делает это путем написания сценариев действий OllyDbg, таких как установка точки останова, продолжение выполнения или изменение некоторых байтов.

В настоящее время OllyScript не получил широкого распространения, но он определенно послужил источником вдохновения для следующей техники.

Техника 3 – использование универсальных распаковщиков

Универсальные распаковщики — это отладчики, предназначенные для распаковки определенных упаковщиков или для автоматизации процесса распаковки вручную, который мы опишем в следующем разделе:
1697452720151.png


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

Техника 4 – эмуляция

Еще одна группа инструментов, о которой стоит упомянуть, — это эмуляторы. Эмуляторы — это программы, моделирующие среду выполнения, включая процессор (для выполнения инструкций, работы с регистрами и т. д.), память, операционную систему и т. д.

Эти инструменты имеют больше возможностей для безопасного запуска вредоносных программ (поскольку все это моделируется) и имеют больший контроль над процессом выполнения. Таким образом, они могут помочь установить более сложные точки останова, а также их можно легко создать в сценариях (как в libemu и эмуляторе Pokas x86), как показано в следующем коде:

from pySRDF import *
emu = Emulator("upx.exe")
x = emu.SetBp("__isdirty(eip)") #which set bp on Execute on modified data

emu.Run() # OR emu.Run("ins.log") to log all running instructions
emu.Dump("upx_unpacked.exe",DUMP_FIXIMPORTTABLE) #DUMP_FIXIMPORTTABLE
create new import table for new API

print "File Unpacked Successfully\n\nThe Disassembled Code\n--------------
--"


В этом примере мы использовали эмулятор Pokas x86. Гораздо проще было установить более сложные точки останова, такие как «Выполнение» для измененных данных, которые срабатывают, когда указатель инструкции (EIP) указывает на расшифрованное/распакованное место в памяти.

Техника 5 – дампы памяти

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

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

Некоторые распространенные инструменты песочницы предоставляют дамп памяти процесса в качестве основной функции или в качестве одной из функций своих плагинов, например Cuckoo Sandbox.

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

В следующем разделе мы рассмотрим распаковку вручную с помощью OllyDbg.

Ручная распаковка с помощью OllyDbg

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

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

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

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

* Очень легко пропустить некоторые модули; например, оригинальный загрузчик может распаковать только образец для 32- или 64-битной платформы.

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

В этом разделе мы рассмотрим несколько распространенных универсальных методов распаковки сэмплов.

Техника 6 – точка останова в памяти при выполнении

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

Шаг 1 – установка точек останова

Мы можем использовать аппаратную точку останова при выполнении, но эта точка останова может быть установлена максимум на четыре байта, а это означает, что вам нужно знать OEP, чтобы иметь возможность установить ее. Более эффективное решение — использовать точки останова в памяти при выполнении.

Возможность использовать точки останова в памяти при выполнении доступна в OllyDbg, доступ к ней можно получить, перейдя в меню «Вид | Память. Теперь мы можем изменить права доступа к памяти для первого раздела на READWRITE, если это был полный доступ:

1697452775259.png


В этом случае мы не сможем выполнить код в этом разделе, пока он не получит разрешение на выполнение. По умолчанию в нескольких версиях Windows он по-прежнему будет исполняемым для некритических процессов, даже если разрешения на память не включают разрешение EXECUTE. Поэтому вам необходимо обеспечить так называемое предотвращение выполнения данных (DEP), которое обеспечивает соблюдение разрешения EXECUTE и не позволяет выполнять какие-либо неисполняемые данные.

Эта технология используется для предотвращения попыток эксплуатации, о чем мы более подробно расскажем в главе 7 «Обработка эксплойтов и шелл-кода»; однако это может пригодиться, когда мы хотим легко распаковать образцы вредоносного ПО.

Шаг 2 – включение предотвращения выполнения данных

Чтобы включить DEP, вы можете перейти в «Дополнительные настройки системы», а затем «Предотвращение выполнения данных». Вам нужно будет включить его для всех программ и служб, как показано на следующем скриншоте:

1697452794950.png


Теперь необходимо применить эти типы точек останова и запретить выполнение вредоносного ПО в этом разделе, особенно в начале расшифрованного кода (OEP).

Шаг 3. Предотвращение любых дальнейших попыток изменения прав доступа к памяти.

К сожалению, этого недостаточно. Заглушка распаковки может легко обойти эту точку останова, снова изменив разрешение этого раздела на полный доступ с помощью API VirtualProtect.

Этот API дает программе возможность изменять права доступа к памяти любого фрагмента памяти на любые другие разрешения. Вам необходимо установить точку останова для этого API, перейдя в представление ЦП и щелкнув правой кнопкой мыши область дизассемблирования, затем выберите Ctrl+G, введите имя API (в нашем случае это VirtualProtect) и установите точку останова на адресе, по которому он вас приведет.

Если заглушка попытается вызвать VirtualProtect для изменения разрешений на память, отлаживаемый процесс прервется, и вы сможете изменить разрешение, которое она пытается установить в первом разделе. Вы можете изменить значение NewProtect на READONLY или READWRITE и удалить из него бит EXECUTE:


1697452807845.png


Шаг 4 – выполнение и получение OEP

Как только вы нажмете «Выполнить», отлаживаемый процесс прервется непосредственно на OEP, что приведет к появлению ошибки нарушения прав доступа, как вы можете видеть на следующем снимке экрана:

1697452844584.png


Это не всегда так, поскольку некоторые упаковщики модифицируют первые несколько байтов первого раздела с помощью таких инструкций, как ret, jmp, call, просто для того, чтобы остановить отлаживаемый процесс на этой точке останова; однако после нескольких итераций программа сломается. Это происходит после полной расшифровки/распаковки первой секции.

Техника 7 – обратная трассировка стека вызовов

Стек вызовов — относительно сложная для понимания тема, но он очень полезен для ускорения процесса анализа вредоносного ПО. Это также полезно в процессе распаковки.

Взгляните на следующий код и представьте, как будет выглядеть стек:

1697452856004.png


Вы заметите, что сразу после адреса возврата из вызова func03 в стеке сохраняется адрес предыдущего esp. Предыдущее значение esp сохраняется в стеке. Это сохраненное значение esp указывает на вершину стека сразу после инструкции 5. Поверх стека из этого предыдущего значения esp сохраняется первое значение esp (это потому, что инструкция 4 ebp равна первому значению esp), за которым следует адрес возврата из вызова func02 и так далее.

Здесь за сохраненным значением esp следует обратный адрес. Это значение esp указывает на ранее сохраненное значение esp, за которым следует предыдущий адрес возврата и так далее. Это известно как стек вызовов. На следующем снимке экрана показано, как это выглядит в OllyDbg:

1697452866647.png


Как видите, сохраненное значение esp указывает на следующий стек вызовов (еще одно сохраненное значение esp и адрес возврата предыдущего вызова) и так далее.

OllyDbg включает окно просмотра стека вызовов, доступ к которому можно получить через View | Call Stack. Это выглядит следующим образом:

1697452874959.png


Теперь вам может быть интересно: как стек вызовов может помочь нам быстро и эффективно распаковать наше вредоносное ПО?

Здесь мы можем установить точку останова, которая, как мы уверены, приведет к остановке отлаживаемого процесса в середине выполнения расшифрованного кода (фактического программного кода после фазы распаковки). Как только выполнение остановится, мы сможем проследить стек вызовов и вернуться к первому вызову в расшифрованном коде. Оказавшись там, мы можем просто скользить вверх, пока не достигнем начала первой функции, которая была выполнена в расшифрованном коде, и мы можем объявить этот адрес как OEP.

Шаг 1 – установка точек останова

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

Некоторые примеры некоторых известных API: GetModuleFileNameA, GetCommandLine, CreateFileA, VirtualAlloc, HeapAlloc, memset и т. д.

Сначала вы устанавливаете точку останова на этих API (используете все известные вам, кроме тех, которые могут использоваться заглушкой распаковки) и выполняете программу до тех пор, пока выполнение не прервется:

1697452887827.png


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

Шаг 2 – следование стеку вызовов

Следуйте сохраненному значению esp в стеке, а затем следующему сохраненному значению esp, пока не дойдете до первого адреса возврата, как показано на следующем снимке экрана:

1697452898586.png


Теперь следуйте обратному адресу в дизассемблированном разделе в окне ЦП следующим образом:

1697452908323.png


Теперь вы достигли первого вызова в распакованном разделе, и остался единственный шаг — добраться до OEP.

Шаг 3 – достижение OEP

Теперь вам нужно только скользить вверх, пока не дойдете до OEP:

1697452922169.png


Это та же самая точка входа, которой мы смогли достичь в предыдущем методе.

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

Метод 8 – мониторинг выделенного пространства памяти для распакованного кода

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

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

Существует несколько API-интерфейсов Windows, которые можно использовать для выделения памяти в пользовательском режиме. Злоумышленники обычно используют следующие методы:

1697452934823.png


В режиме ядра есть и другие функции, такие как RtlAllocateHeap, ZwAllocateVirtualMemory и ExAllocatePoolWithTag, которые можно использовать практически таким же образом.

Если пример написан на C, имеет смысл сразу отслеживать функции malloc/calloc. В случае вредоносного ПО C++ мы также можем отслеживать новый оператор.

Пока мы останавливаемся в точке входа примера (или в начале процедуры TLS, если она доступна), мы можем установить точку останова при выполнении следующих функций. Как правило, можно поставить точку останова на первой инструкции функции, но если есть опасения, что вредоносное ПО может перехватить ее (то есть заменить первые несколько байтов каким-либо пользовательским кодом), точка останова на последней инструкции будет работать лучше.

Еще одним преимуществом этого является то, что в этом случае требуется только одна точка останова как для VirtualAllocEx, так и для VirtualAlloc . В отладчике IDA можно перейти к API, нажав горячую клавишу G и указав перед именем API соответствующую DLL без расширения файла и разделив его подчеркиванием, например, kernel32_VirtualAlloc.

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

Другие функции API, которые можно использовать в том же подходе, включают следующее:

- VirtualProtect: авторы вредоносных программ могут использовать это, чтобы сделать блок памяти, в котором хранится распакованный образец, исполняемым файлом.

- WriteProcessMemory: часто используется для внедрения распакованной полезной нагрузки либо в какой-либо другой процесс, либо в себя.

В большинстве случаев вредоносное ПО распаковывает сразу весь образец, чтобы после его дампа мы получили правильный файл MZ-PE, который можно проанализировать самостоятельно. Однако существуют и другие варианты, например следующие:

- Расшифрованный блок представляет собой поврежденный исполняемый файл, корректная работа которого зависит от исходного упаковщика.

- Упаковщик расшифровывает образец по разделам и загружает каждый из них по одному. Есть много способов решить эту проблему, например:

* Дампите секции, пока они становятся доступными, и объединяйте их позже.

* Измените процедуру расшифровки, чтобы обработать весь образец одновременно.

* Напишите скрипт, который расшифровывает весь зашифрованный блок.

Если на каком-либо этапе вредоносная программа завершает работу, это может быть признаком того, что ей либо требуется что-то дополнительное (например, аргументы командной строки или внешний файл, либо, возможно, ее необходимо загрузить определенным образом), либо существует трюк антиреверс-инжиниринга, который необходимо обойти. Подтвердить это можно разными способами — например, перехватив момент завершения работы программы (например, поставив точку останова на ExitProcess, TerminateProcess или более причудливый API-вызов PostQuitMessage) и проследив, какая часть кода является ответственный за это.

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

Метод 9 – in-place unpacking

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

В этом случае имеет смысл выполнить следующие действия:

1. Найдите большой зашифрованный блок (обычно он имеет высокую энтропию и виден невооруженным глазом в шестнадцатеричном редакторе).

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

3. Поставьте здесь точку останова на чтение и/или запись.

4. Запустите программу и дождитесь срабатывания точки останова.

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

Стоит отметить, что этот подход можно использовать вместе с подходом, основанным на выделении памяти вредоносным ПО, описанным в методе 8 — мониторинге выделенных пространств памяти для распакованного раздела кода.

Техника 10 – восстановление стека

Восстановление стека обычно выполняется быстрее, чем два предыдущих метода, но он гораздо менее надежен. Идея здесь в том, что некоторые упаковщики поддерживают стек в порядке и передают управление распакованному образцу, когда он имеет тот же уровень стека, с которого они начали. Это означает, что он получит доступ к значению, расположенному по адресу, который изначально был указан в регистре указателя кадра (ebp/rbp), минус одно значение размера длины адреса для выбранной архитектуры например, 4-байтный DWORD для 32-битной платформы непосредственно перед передачей управления распакованному коду, даже при использовании инструкции jmp.

В этом случае можно установить точку останова при доступе к значению [ebp-4], оставаясь в точке входа примера, а затем выполняя его, так что точка останова, как мы надеемся, сработает непосредственно перед передачей управления распакованному коду. Часто это происходит, когда упаковщик восстанавливает исходные значения регистров — например, с помощью инструкции popad.

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

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

Дамп распакованного образца и исправление таблицы импорта

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

Дамп процесса

Чтобы сделать дамп процесса, вы можете использовать OllyDump. OllyDump — это плагин OllyDbg, который может выгружать процесс обратно в исполняемый файл. Он выгружает PE-файл обратно из памяти в необходимый формат файла:

1697452969559.png


Как только вы достигнете OEP в результате предыдущего процесса распаковки вручную, вы можете установить OEP в качестве новой точки входа. В OllyDump есть возможность исправить таблицу импорта (о чем мы скоро опишем). Вы можете использовать ееё или снять флажок «Перестроить импорт», если хотите использовать другие инструменты.

Другой вариант — использовать такие инструменты, как PETools или Lord PE для 32-битной версии и VSD для 64-битной Windows. Основное преимущество этих решений заключается в том, что помимо так называемой опции Dump Full, которая в основном выгружает исходные разделы, связанные с образцом, также можно сделать дамп определенной области памяти — например, выделенной памяти с расшифрованным/распакованным образцом. :

1697452980403.png


Далее мы рассмотрим исправление таблицы импорта вредоносного ПО.

Исправление таблицы импорта

Теперь вам может быть интересно: что происходит с таблицей импорта, которую нужно исправить? Ответ таков: когда PE-файл загружается в память процесса или заглушка распаковщика загружает таблицу импорта, загрузчик проходит через заголовок таблицы импорта из каталога данных и заполняет его фактическими адресами API-функций из DLL, доступных на машине:
1697452991103.png


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

1697452997741.png


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

К счастью, есть инструменты, которые делают это автоматически. В этом разделе мы поговорим о реконструкторе импорта (ImpREC):

1697453010177.png


Чтобы исправить таблицу импорта, необходимо выполнить следующие действия:

1. Создайте дамп процесса или любой библиотеки, которую вы хотите создать, используя, например, OllyDump (и снимите флажок Rebuild Import) или любой другой инструмент по вашему выбору.

2. Откройте ImpREC и выберите процесс, который вы сейчас отлаживаете.

3. Теперь установите правильное значение OEP и нажмите «Автопоиск IAT».

4. После этого нажмите «Получить импорт» и удалите все строки с НЕТ из раздела «Найдены импортированные функции».

5. Нажмите кнопку «Исправить дамп» и выберите ранее сохраненный файл. Теперь у вас будет рабочий распакованный PE-файл. Вы можете загрузить его в PEiD или любое другое приложение PE explorer, чтобы проверить, работает ли он.

Для 64-битной системы Windows вместо этого можно использовать Scylla или CHimpREC.

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

Определение различных алгоритмов и функций шифрования.

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

Типы алгоритмов шифрования

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

Существует два основных метода шифрования информации: симметричное шифрование (также называемое шифрованием с секретным ключом) и асимметричное шифрование (также называемое шифрованием с открытым ключом):

- Симметричные алгоритмы. Эти типы алгоритмов используют один и тот же ключ для шифрования и дешифрования. Это секретный ключ, который используется обеими сторонами:

1697453027419.png


- Асимметричные алгоритмы: в этом алгоритме используются два ключа. Один используется для шифрования, а другой — для дешифрования. Эти два ключа называются открытым ключом и закрытым ключом. Один ключ доступен публично (открытый ключ), а другой хранится в секрете (закрытый ключ):

1697453035635.png


Основные алгоритмы шифрования

Большинство алгоритмов шифрования, используемых вредоносным ПО, состоят из основных математических и логических инструкций, то есть xor, add, sub, rol и ror. Эти инструкции обратимы, и вы не теряете данные при шифровании с их помощью по сравнению с shl, shr, где можно потерять некоторые биты слева и справа. Подобное случается и с И, ИЛИ, что может привести к потере данных при использовании или с 1 или и с 0.

Некоторые основные алгоритмы шифрования следующие:

- Простое статическое шифрование: здесь вредоносная программа просто использует обычные операции, такие как xor, add или rol:

1697453045273.png


- Запуск шифрования ключей. Здесь вредоносное ПО может вносить изменения в ключи следующим образом:

1697453052436.png


- Шифрование с подстановочным ключом: вредоносное ПО может заменять байты друг другом или заменять каждое значение другим значением (например, для каждого байта со значением 0x45 вредоносное ПО может изменить это значение на 0x23).

- Другие алгоритмы шифрования. У авторов вредоносных программ никогда не заканчиваются идеи, когда дело доходит до создания новых алгоритмов, представляющих собой комбинацию этих арифметических и логических инструкций. Это подводит нас к следующему вопросу: как мы можем идентифицировать функции шифрования?

Как определить функции шифрования

На следующем снимке экрана показаны разделы, пронумерованные от 1 до 4. Эти разделы являются ключом к пониманию и идентификации алгоритмов шифрования, используемых во вредоносных программах:

1697453062688.png


Чтобы определить функцию шифрования, вам следует искать четыре вещи, как показано в следующей таблице:

1697453108885.png


Эти четыре пункта являются основными частями любого цикла шифрования. Их можно легко обнаружить в небольшом цикле шифрования, но труднее обнаружить в более сложном цикле шифрования, таком как шифрование RC4, о котором мы поговорим позже.

Методы обнаружения строкового поиска для простых алгоритмов

В этом разделе мы рассмотрим технику под названием X-RAYING (впервые представленное Питером Ферри в статье ПРИНЦИПЫ И ПРАКТИКА X-RAYING на VB2004). Этот метод используется антивирусными продуктами и другими инструментами статических сигнатур для обнаружения образцов с подписями, даже если они зашифрованы. Этот метод позволяет проникнуть под уровни шифрования, чтобы выявить образец кода и обнаружить его, вообще не зная ключа шифрования и не прибегая к трудоемким методам, таким как перебор. Здесь мы опишем теорию и применение этой техники, а также некоторые инструменты, которые мы можем использовать, чтобы помочь нам в ее использовании. Мы можем использовать этот метод для обнаружения встроенных PE-файлов или расшифровки вредоносных образцов.

Основы X-RAYING

Для типов алгоритмов, которые мы описали ранее, если у вас есть зашифрованные данные, алгоритм шифрования и секретный ключ, вы можете легко расшифровать данные (что и является целью всех алгоритмов шифрования); однако, если у вас есть зашифрованные данные (зашифрованный текст) и часть расшифрованных данных, сможете ли вы расшифровать оставшиеся части зашифрованных данных?

В X-RAYING вы можете перебрать алгоритм и его секретный ключ(и), если у вас есть часть расшифрованных данных (открытый текст), даже если вы не знаете смещение этих простых текстовых данных во всем зашифрованном блоке. Он работает практически со всеми простыми алгоритмами, которые мы описали ранее, даже с несколькими уровнями шифрования.

Для большинства зашифрованных PE-файлов обычный текст включает такие строки, как «Эта программа не может работать в режиме DOS» или «kernel32.dll», и может содержать массив нулевых байтов или байтов INT3 (0xCC).

Строки вредоносного ПО (если все они зашифрованы одним и тем же ключом) могут включать такие строки, как «HTTP» или некоторые общие имена API.

Простое статическое шифрование

Если предположить, что алгоритм шифрования представляет собой простое статическое шифрование с использованием xor, мы можем просто искать открытый текст внутри зашифрованного текста, например:

1697453125129.png


Все очень просто — мы принимаем ключ из результата xor зашифрованного текста и первых нескольких байтов открытого текста, а затем проверяем этот ключ с оставшимся открытым текстом. Если этот ключ сработает, он раскроет оставшийся открытый текст зашифрованного текста, а это означает, что вы найдете секретный ключ и сможете расшифровать оставшиеся данные.

Другие алгоритмы шифрования

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

Мы не планируем здесь рассматривать их все, но при желании вы можете углубиться в это исследование.

Инструменты X-RAYING для анализа и обнаружения вредоносного ПО

Были написаны некоторые инструменты, помогающие исследователям вредоносного ПО использовать метод X-RAYING для сканирования. Ниже приведены некоторые из этих инструментов, которые вы можете использовать либо из командной строки, либо с помощью сценария:

- XORSearch: это инструмент, созданный Дидье Стивенсом, который выполняет поиск внутри зашифрованного текста, используя для поиска заданный образец простого текста. Он охватывает не только xor — он также охватывает другие алгоритмы, включая битовый сдвиг (например, rol,ror):
1697453140116.png


- Сканер Yara: Yara — это инструмент статических сигнатур, который помогает сканировать файлы с предопределенными сигнатурами. Он допускает использование регулярных выражений, подстановочных знаков и других типов подписей. Он также позволяет использовать подписи xor:

1697453151044.png


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

Определение алгоритма шифрования RC4

Алгоритм RC4 — один из наиболее распространенных алгоритмов шифрования, используемый авторами вредоносных программ, главным образом потому, что он прост и в то же время достаточно надежен, чтобы его нельзя было взломать, как другие простые алгоритмы шифрования. Он недоступен в виде WinAPI, поэтому авторы вредоносных программ обычно реализуют его вручную. Это означает, что начинающим реверс-инженерам может быть сложно его идентифицировать. В этом разделе мы увидим, как выглядит этот алгоритм и как его можно идентифицировать.

Алгоритм шифрования RC4

Алгоритм RC4 — это симметричный алгоритм, использующий один секретный ключ (максимум 256 байт). Алгоритм состоит из двух частей: алгоритма планирования ключей (KSA) и алгоритма псевдослучайной генерации (PRGA). Давайте рассмотрим каждый из них более подробно.

Алгоритм планирования ключей

Часть алгоритма, связанная с планированием ключей, по сути создает массив из 256 байт из секретного ключа, который является еще одной, более крупной версией ключа. Этот массив будет ключом, который впоследствии будет использоваться для шифрования и дешифрования данных. Эта часть состоит из следующих двух частей:

* Он последовательно создает массив со значениями от 0 до 256:

1697453160415.png



Он меняет местами байты на основе ключа — это генерирует индексный номер j на основе секретного ключа:

1697453168300.png


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

Алгоритм псевдослучайной генерации

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

1697453190756.png


Как видите, фактически использовался алгоритм xor. Однако вся эта замена направлена на то, чтобы каждый раз генерировать другое значение ключа (аналогично алгоритмам скользящего ключа).

Идентификация алгоритмов RC4 в образце вредоносного ПО

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

- Генерация массива размером 256 байт. Эту часть легко распознать, и она совершенно уникальна для типичного алгоритма RC4, такого как этот:
1697453201302.png


- Существует множество свопингов: если вы сможете распознать функцию или код свопинга, вы найдете их повсюду в алгоритме RC4. Части алгоритма KSA и PRGA являются хорошим признаком того, что это алгоритм RC4:

1697453208730.png


- Фактический алгоритм — XOR. В конце цикла вы заметите, что этот алгоритм по сути является алгоритмом XOR. Все замены производятся через ключ. Единственные изменения, которые влияют на данные, выполняются через xor:

1697453214741.png


- Сходство шифрования и дешифрования: вы также заметите, что функции шифрования и дешифрования — это одна и та же функция. Логический вентиль xor является обратимым. Вы можете зашифровать данные с помощью xor и секретного ключа и расшифровать эти зашифрованные данные с помощью xor и того же ключа (который отличается, например, от алгоритмов добавления/подключения).

Стандартные симметричные и асимметричные алгоритмы шифрования.

Авторы вредоносных программ широко используют стандартные алгоритмы шифрования, такие как симметричный DES и AES или асимметричный RSA. Однако подавляющее большинство образцов, включающих эти алгоритмы, никогда не реализуют эти алгоритмы сами и не копируют их код в свои вредоносные программы. В основном они реализуются с использованием основных API-интерфейсов Windows или сторонних библиотек, таких как OpenSSL.

Эти алгоритмы математически сложнее, чем простые алгоритмы шифрования или RC4. Вам не нужно понимать их математическую основу, чтобы понять, как они реализованы — вам нужно только понять, как определить способ использования любого из этих алгоритмов и как определить точный используемый алгоритм, ключ шифрования/дешифрования ( с) и данные.

Извлечение информации из API шифрования Windows

Существует несколько распространенных API-интерфейсов, которые используются для обеспечения доступа как к симметричным, так и к асимметричным алгоритмам, включая DES, AES, RSA и даже шифрование RC4. Некоторые из этих API — CryptAcquireContext, CryptCreateHash, CryptHashData, CryptEncrypt, CryptDecrypt, CryptImportKey, CryptDestroyKey, CryptDestroyHash и CryptReleaseContext (из Advapi32.dll).

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

Шаг 1 – инициализация и подключение к поставщику криптографических услуг (CSP)

Поставщик криптографических услуг — это библиотека, которая реализует API-интерфейсы, связанные с криптографией, в Microsoft Windows. Чтобы образец вредоносной программы инициализировал и использовал одного из этих поставщиков, он выполняет API CryptAcquireContext следующим образом:

1697453226455.png


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

- PROV_RSA_FULL: обеспечивает доступ к DES, Triple DES, RC2 и RC4 для шифрования, а также к RSA для обмена ключами и подписями.

- PROV_RSA_AES: используется для шифрования AES, RC2 и RC4 (опять же, вместе с RSA).

Вы можете найти всех поддерживаемых провайдеров в вашей системе в реестре по следующему ключу:

1697453234440.png


Шаг 2 – подготовка ключа

Существует два способа подготовки ключа шифрования. Как вы, возможно, знаете, ключи шифрования для этих алгоритмов обычно имеют фиксированный размер (112 бит или 128 бит и т. д.). Вот шаги, которые обычно предпринимают авторы вредоносных программ для подготовки ключа:

1. Сначала автор использует свой текстовый ключ и хеширует его с помощью любого из известных алгоритмов хеширования, таких как MD5, SHA128, SHA256 или других:

1697453240225.png


2. Затем они создают сеансовый ключ из этого хэша с помощью CryptDeriveKey, например CryptDeriveKey(hProv,CALG_3DES,hHash,0,&hKey);. Отсюда они могут легко идентифицировать алгоритм по значению второго аргумента, предоставленного этому API. Наиболее распространенные алгоритмы/значения следующие:


1697453249569.png



3. Некоторые авторы вредоносных программ используют KEYBLOB, включающий их ключ, вместе с CryptImportKey. KEYBLOB — это простая структура, содержащая тип ключа, используемый алгоритм и секретный ключ для шифрования. Структура KEYBLOB следующая:

1697453274710.png


Фраза bType представляет тип этого ключа. Наиболее распространенные виды следующие:

- PLAINTEXTKEYBLOB (0x8): указывает простой текстовый ключ для симметричного алгоритма, такого как DES, 3DES или AES.

- PRIVATEKEYBLOB (0x7): указывает, что этот ключ является закрытым ключом асимметричного алгоритма.

- PUBLICKEYBLOB (0x6): указывает, что этот ключ является открытым ключом асимметричного алгоритма.

Фраза aiKeyAlg включает тип алгоритма в качестве второго аргумента CryptDeriveKey. Вот некоторые примеры этого KEYBLOB:

1697453287048.png


Как видите, первый байт (bType) показывает нам, что это PLAINTEXTKEYBLOB, а алгоритм (0x01,0x66) представляет собой CALG_DES (0x6601).

Другой пример этого заключается в следующем:

1697453293904.png


Это представляет собой PUBLICKEYBLOB (0x6), а алгоритм представляет CALG_RSA_KEYX (0xa400). После этого они загружаются через CryptImportKey:

CryptImportKey(akey->prov, (BYTE *) &key_blob, sizeof(key_blob), 0, 0,
&akey->ckey)


Шаг 3 – шифрование или расшифровка данных


Теперь, когда ключ готов, вредоносная программа использует CryptEncrypt или CryptDecrypt для шифрования или дешифрования данных. С помощью этих API вы можете определить начало зашифрованного большого двоичного объекта (или большого двоичного объекта, который нужно зашифровать). Эти API используются следующим образом:

1697453300352.png


Шаг 4 – освобождение памяти

Это последний шаг, на котором мы освобождаем память и все дескрипторы, которые использовались с помощью API CryptDestroyKey, CryptDestroyHash и CryptReleaseContext.

Криптографический API нового поколения (CNG)

Существуют и другие способы реализации этих алгоритмов шифрования. Один из них — использование Cryptography API: Next Generation (CNG), который представляет собой новый набор API, реализованный Microsoft. Они до сих пор не получили широкого распространения во вредоносных программах, но на самом деле их гораздо легче понять и извлечь из них информацию. Шаги по их использованию следующие:

1. Инициализируйте поставщика алгоритма. На этом этапе вы можете определить точный алгоритм (список поддерживаемых алгоритмов см. в MSDN):

1697453311038.png


2. Подготовьте ключ. Это отличается от подготовки ключа в симметричных и асимметричных алгоритмах. Этот API может использовать импортированный ключ или генерировать ключ. Это может помочь вам извлечь секретный ключ, используемый для шифрования, например:

1697453318907.png


3. Зашифруйте или дешифруйте данные. На этом этапе вы можете легко определить начало блока данных, который необходимо зашифровать (или расшифровать):
1697453328410.png


4. Очистка. Это последний шаг, в нем используются такие API, как BCryptCloseAlgorithmProvider, BCryptDestroyKey и HeapFree для очистки данных.

Применение шифрования в современных вредоносных программах – банковский троян Vawtrak

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

В этом разделе мы рассмотрим известный банковский троян Vawtrak. Мы увидим, как это семейство вредоносных программ шифрует свои строки и имена API, а также запутывает собственную сетевую связь.

Шифрование строк и имен API

Vawtrak реализует довольно простой алгоритм шифрования. Он основан на принципах алгоритма скользящего ключа и использует вычитание в качестве основного метода шифрования. Его шифрование выглядит так:

1697453341816.png


Алгоритм шифрования состоит из двух частей:

* Генерация следующего ключа: генерируется 4-байтовое число (называемое начальным числом) и используется только 1 его байт в качестве ключа:

1697453356821.png
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх