Windows平台shellcode开发⼊门(三)
⼀、简介
在“Windows平台shellcode开发⼊门”系列的最后⼀部分,我们将会编写⼀个简单的”SwapMouseButton“的shellcode,该shellcode会互换⿏标的左键和右键。⽂中涉及的基础知识已在前两篇⽂章中介绍,本⽂不再详述,有需要的朋友可以阅读本系列的和。我们先从⼀个已知shellcode着⼿:Allwin URLDownloadToFile + WinExec + ExitProcess Shellcode。此名称可以透漏shellcode的相关功能,⽐如它使⽤:
1. URLDownloadToFile Windows API函数下载⽂件
2. WinExec执⾏⽂件(可执⾏⽂件:.exe)
3. ExitProcess终⽌运⾏shellcode的进程
使⽤这个⽰例程序,我们需要调⽤SwapMouseButton函数和ExitProcess函数。
BOOL WINAPI SwapMouseButton(
_In_ BOOL fSwap
);
VOID WINAPI ExitProcess(
_In_ UINT uExitCode
);
正如你看到的,每个函数只需要1个参数:
1.fSwap参数可以是TRUE或FALSE,⿏标的按键便会被互换,否则被恢复。
2.uExitCode表⽰进程退出码。每个进程在退出时必须
返回⼀个值(如果⼀切顺利的话,返回值为零,否则返回其他数值)。这是为什么main函数通常需要return 0。
⼆、程序概览
现在我们需要调⽤这两个函数。在C++中,调⽤过程⾮常简单:
因为编译器知道去链接“user32”函数库,然后查相关函数。但是我们需要在shellcode⼿动完成这个过程。我们需要⼿动加
载“user32”库,到SwapMouseButton函数的地址,并进⾏调⽤。
但是,此处编译器已经知道LoadLibrary和GetProcAddress函数的地址。在shellcode中,我们需要通过编程的⽅式来寻。
注意我们不需要在C++中调⽤ExitProcess函数,因为main函数在执⾏return 0之后,程序便会终⽌运⾏。但从shellcode上,我们需要确保程序能够”优雅地“终⽌⽽不是“崩掉”(crash)。
三、逐步编写shellcode
在前⾯⼏部分已经讨论过,为了制作出稳定可靠的shellcode,我们需要遵循以下的步骤。我们已经知道调⽤哪些函数,但是,我们⾸先需要定位这些函数的地址。所需的步骤如下:
1. 查kernel3
2.dll加载到内存的位置
2. 到其导出表
3. 定位kernel32.dll导出的GetProcAddress函数
4. 使⽤GetProcAddress函数获取LoadLibrary的函数地址
5. 使⽤LoadLibrary函数加载user32.dll动态链接库
6. 获取user32.dll中SwapMouseButton的函数地址
7. 调⽤SwapMouseButton函数
8. 查ExitProcess的函数地址
9. 调⽤ExitProcess函数
我们使⽤Visual Studio 2015开发⼯具来编写shellcode,当然你也可以其他版本或类似masm,nasm的汇编器。在Visual Studio开发环境中,我们使⽤__asm { }来直接编写汇编代码。请仔细阅读和理解这部分代码。
#include "stdafx.h"
int main() {
__asm
{
// ASM code here
}
return 0;
}
1. 查kernel3
2.dll基址
如下所⽰,我们可以使⽤下述代码查kernel32.dll加载到内存中的位置。
xor ecx, ecx
mov eax, fs:[ecx + 0x30] ; EAX = PEB
mov eax, [eax + 0xc] ; EAX = PEB->Ldr
mov esi, [eax + 0x14] ; ESI = PEB->Ldr.InMemOrder
lodsd ; EAX = Second module
xchg eax, esi ; EAX = ESI, ESI = EAX
lodsd ; EAX = Third(kernel32)
mov ebx, [eax + 0x10] ; EBX = Base address
(1-2 ⾏):第1条指令将ecx寄存器清零,然后在下⼀条指令中使⽤。但为什么要这么做?还记得我们在前⾯提到过要避免“空”字节。如果第⼆条指令为mov eax,fs:[30]指令,将会汇编成机器码序列:64 A1 30 00 00 00,便会出现空字节。然⽽mov eax, fs:[ecx+0x30]将会汇编成64 8B 41 30,这种⽅式可以避免“空”字节。
(3-4 ⾏):现在PEB指针已经保存到eax寄存器。正如上篇⽂章提到的,我们可以在PEB指针的0xC偏移处获得Ldr,然后顺着指针
在Ldr的0×14偏移处获取模块列表。
(5-7 ⾏):当前位于“InMemoryOrderLinks”链表的第1个模块,即“” 。此处,该结构的第1个元素是Flink指针,指向下⼀个模块。然后,我们将这个指针存放在esi寄存器。接着,lodsd指令会根据esi寄存器指向的地址读取双字,然后把结果存放在eax寄存器。这就意味着在lodsd指令执⾏之后,我们可以通过eax寄存器获取到第2个模块的地址,即ntdll.dll。我们通过xchg指令交换eax和esi 寄存器中的值,便把第2个模块的指针存放到esi寄存器,再次调⽤lodsd指令,从⽽遍历到第3个模块:kernel32.dll。
(8 ⾏):此时,eax寄存器指向kernel32.dll的“InMemoryOrderLinks”。再加上0×10字节便可以获得“DllBase”指针,即
kernel32.dll加载到内存中的位置。
2. 到kernel32.dll的导出表
我们已经在内存中到kernel32.dll。现在,我们需要解析PE⽂件,然后到导出表。幸好,这个过程并不复杂。
mov edx, [ebx + 0x3c] ; EDX = DOS->e_lfanew
add edx, ebx ; EDX = PE Header
mov edx, [edx + 0x78] ; EDX = Offset export table
add edx, ebx ; EDX = Export table
mov esi, [edx + 0x20] ; ESI = Offset names table
add esi, ebx ; ESI = Names table
xor ecx, ecx ; EXC = 0
(1-2 ⾏):我们已经知道可以在0x3C偏移处获得e_lfanew指针,因为MS-DOS头的⼤⼩是0×40字节,⽽最后4个字节就是e_lfanew指针。我们需要把基地址加上这个偏移值,因为这个指针是相对于基地址的(只是个偏移值,不是绝对地址)。
(3-4 ⾏):在PE头的0×78偏移处,我们可以到导出表的”DataDirectory“。这是因为PE头(签名,⽂件头,可选头)
在”DataDirectory“之前的⼤⼩是0×78字节,⽽导出表是”DataDirectory“表的第1个元素。再次,我们把edx寄存器加上这个数值,现在已经抵达kernel32.dll的导出表。
(5-7 ⾏):在IMAGE_EXPORT_DIRECTORY结构上,我们可以在0×20偏移处获得“AddressOfNames”的指针,从⽽得导出函数的名称。这个步骤是需要的,因为我们尝试通过函数名称来查函数,尽管可以使⽤其他的⽅法。我们将指针保存到esi寄存器,然后把ecx 寄存器清零。
现在,我们了解⼀下”AddressOfNames“,⼀个指针数组(此处的指针是相对于映像基址的偏移⽽已,即kernel32.dll加载到内存的位置)。所以每4个字节代表⼀个指向函数名称的指针。我们可以通过如下代码来到函数名称和函数名称的序号(GetProcAddress函数的序号):
Get_Function:
inc ecx ; Increment the ordinal
lodsd ; Get name offset
add eax, ebx ; Get function name
cmp dword ptr[eax], 0x50746547 ; GetP
jnz Get_Function
cmp dword ptr[eax + 0x4], 0x41636f72 ; rocA
jnz Get_Function
cmp dword ptr[eax + 0x8], 0x65726464 ; ddre
jnz Get_Function
(1-3 ⾏):第1⾏什么也没做。它只是⼀个标签,为某个位置起个名称,我们可以跳转这⾥来读取函数的名称,接下来你将会看到。在第3⾏,我们可以⾃增ecx寄存器,它是函数的计数器,也是函数的序号。
(4-5 ⾏):esi寄存器指向第1个函数的名称。lodsd指令会把函数名称(⽐如”ExportedFunction“)的偏移存放在eax寄存器,然后ebx(存放kernel32的基址)加上这个偏移值便可以获取正确的指针。注意lodsd指令也会把esi寄存器值增加4。这点有助于我们,因为我们不需要⼿动增加它的值,我们只需要再次调⽤lodsd便可以获取下⼀个函数名称的指针。
(6-11 ⾏)eax寄存器存储了导出函数名称的正确指针,⽽不是偏移值。因此,它指向⼀个函数名称的字符串,我们需要检查⼀下此函数是否是GetProcAddress。在第6⾏,我们把导出函数的名称和”0×50746547“进⾏⽐较,实际上是”PteG“的ASCII码值”50 74 65 47“代表。你可能猜到翻过来便是”GetP“,表⽰GetProcAddress的前4个字节,但由于x86使⽤little-endian模式,意味着数字在内存中
是逆序排列的。因此,我们实际上是⽐较当前函数名前4个字节是否是”GetP“。如果不匹配,jnz指令跳转到Get_Function标签,继续⽐较下⼀个函数名。如果匹配,我们也会⽐较后4个字节,必须是”rocA“,再后⾯4个字节是”ddre“,从⽽确保排除以”GetP“开头的其他函数。
3. 到GetProcAddress函数地址
此时,我们只是到GetProcAddress函数的序号,但是我们可以利⽤序号来到函数的实际地址:
mov esi, [edx + 0x24] ; ESI = Offset ordinals
add esi, ebx ; ESI = Ordinals table
mov cx, [esi + ecx * 2] ; CX = Number of function
dec ecx
mov esi, [edx + 0x1c] ; ESI = Offset address table
add esi, ebx ; ESI = Address table
mov edx, [esi + ecx * 4] ; EDX = Pointer(offset)
add edx, ebx ; EDX = GetProcAddress
(1-2 ⾏):此处,edx寄存器指向IMAGE_EXPORT_DIRECTORY结构。在此结构的0×24偏移处,我们可以
到AddressOfNameOrdinals偏移。在第2⾏,这个偏移值加上ebx寄存器,即kernel32.dll基地址,我们可以获得指向名称序号表的有效指针。
(3-4 ⾏):esi寄存器指向指向名称序号数组。这个数组包含2字节⼤⼩的数字。我们已经知道GetProcAddress函数名称的序号(索引)存储在ecx寄存器,因此我们便可以获得函数地址的序号(索引)。这可以帮助我们获取函数的地址。我们需要递减这个数字,因为名称序号从0开始的。
(5-6 ⾏):在0x1c偏移处,我们可以到AddressOfFunctions,指向函数指针的数组。我们只需加上kernel32.dll的基地址便可以访问这个数组的开始位置。
(7-8 ⾏):现在,ecx寄存器存储了AddressOfFunctions数组的索引值,我们可以从AddressOfFunctions[ecx]位置读
取GetProcAddress的函数指针(是相对于映像基地址的偏移)。我们使⽤ecx * 4,因为每个指针占⽤4个字节,且esi指针指向数组的开始位置。在第8⾏,加上映像的基地址之后,edx寄存器便可以指向
GetProcAddress函数。
4. 获取LoadLibrary函数地址
xor ecx, ecx ; ECX = 0
push ebx ; Kernel32 base address
push edx ; GetProcAddress
push ecx ; 0
push 0x41797261 ; aryA
push 0x7262694c ; Libr
push 0x64616f4c ; Load
push esp ; "LoadLibrary"
push ebx ; Kernel32 base address
call edx ; GetProcAddress(LL)
(1-3 ⾏):⾸先,我们将ecx清零,因为后续会使⽤。其次,在第2⾏和第3⾏,我们把ebx和edx压⼊栈上以备后⽤,其中ebx存储kernel32的基地址,edx存储GetProcAddress的函数指针。
(4-10 ⾏):现在,我们可以进⾏如下调⽤:GetProcAddress(kernel32, “LoadLibraryA”)。我们已经获知kernel32的基地址,但是如何使⽤字符串?我们再次利⽤栈来实现。我们需要把“LoadLibraryA\0”存放在栈上。是的,字符串必须以空字节结尾,这就是为什么需要在第4⾏把ecx清零后压⼊栈上。我需要把LoadLibraryA字符串拆分成4个字节⼀组,按照相反的顺序压⼊栈上。我们⾸先需要放
置“aryA”,然后是“Libr“和”Load“,所以最终在栈上字符串将会是”LoadLibraryA“。因为我们已经把数据存⼊栈上,esp寄存器,即栈指针,便会指向”LoadLibraryA“字符串的开头。我们现在需要从后往前把函数参数压⼊栈上,因此⾸先在第8⾏把esp压⼊栈上,其次是在第9⾏把ebx,即kernel32基地址,然后我们调⽤存储GetProcAddress函数指针的edx。
注意我们安放存⼊在栈上的是”LoadLibraryA“,⽽不是“LoadLibrary”。这是因为kernel32.dll并不导出“LoadLibrary”函数,⽽是导出两个函数:适⽤于ANSI字符串参数的“LoadLibraryA”函数和适⽤于Unicode字符串参数的“LoadLibraryW”函数。
5. 加载 user32.dll动态链接库
上⾯已经获取LoadLibrary函数的地址,我们现在使⽤它来把“user32.dll”动态链接库加载到内存,这个动态链接库包含我们需要
的SwapMouseButton函数。
add esp, 0xc ; pop "LoadLibraryA"
pop ecx ; ECX = 0
push eax ; EAX = LoadLibraryA
push ecx
mov cx, 0x6c6c ; ll
push ecx
push 0x642e3233 ; 32.d
push 0x72657375 ; user
push esp ; "user32.dll"
call eax ; LoadLibrary("user32.dll")
(1-3 ⾏):之前把“LoadLibraryA”字符串存放在栈上,所以我们现在需要清除它。最简单的⽅式并不是3条“pops”指令,⽽是仅需要把esp寄存器增加0xc(意味着12个字节的字符串)即可。在第2⾏,我们也需要清除函数调⽤之前存放在栈上的0,然后将ecx寄存器清零。我们现在需要把LoadLibrary函数地址从eax寄存器备份到栈上,因为调⽤函数之后,返回值会保存在eax寄存器,从可能
把LoadLibrary函数地址给清除了。
(4-19 ⾏):因为需要调⽤LoadLibrary(“user32.dll”),所以我们需要再次在栈上存放字符串。现在的情况可能更为棘⼿,因为字符串的长度不是4的倍数,不能直接通过⼀些push指令进⾏存放。取⽽代之的是,我们⾸先把取值为0的ecx寄存器压⼊栈上,然后再把CX寄存器设置为“ll”字符串。CX寄存器是ecx寄存器的后半部分。所以,我们可以把它压⼊栈上。在第7-8⾏,我们把“user32.d”字符串存放在栈上,所以现在esp指向“user32.dll”字符串。我们把这个参数再压⼊栈上,然后调⽤LoadLibrary加载动态链接库,然后eax寄存器返回user32.dll动态链接库的基地址。
6. 获取SwapMouseButton函数地址
既然已经把user32.dl加载⾄内存中,我们需要调⽤GetProcAddress来获取SwapMouseButton函数地址。
add esp, 0x10 ; Clean stack
mov edx, [esp + 0x4] ; EDX = GetProcAddress
xor ecx, ecx ; ECX = 0
push ecx
mov ecx, 0x616E6F74 ; tona
push ecx
sub dword ptr[esp + 0x3], 0x61 ; Remove "a"
push 0x74754265 ; eBut
push 0x73756F4D ; Mous
push 0x70617753 ; Swap
push esp ; "SwapMouseButton"
push eax ; user32.dll address
call edx ; GetProc(SwapMouseButton)
(1-2 ⾏):像前⾯⼀样,我们需要清理⼀下栈。在前两⾏,我们把上⾯保存的GetProcAddress函数地址存⼊edx寄存器。之前提到过,在函数调⽤之后,eax、ecx、及edx寄存器值可能会改变,因为这些寄存器的值在函数调⽤过程中不会被保存下来。
(3-13 ⾏):因为需要调⽤GetProcAddress(user32.dll, “SwapMouseButton”),所以我们需要再次把字符串存⼊栈上。⾸先,在第3-4⾏,我们把ecx寄存器清零,然后压⼊栈上。其次,我们把“tona”压⼊栈上。“ton”字符串代表着“SwapMouseButton”字符串最后3个字节,但是现在后⾯多加了⼀个“a”字符。这是⼀个⼩技巧,在第7⾏,我们从栈上存储字符“a”的位置减去0×61.因为字
符“a”的ASCII值为0×61,这就意味着把“a”字符转换成了“空(NULL)”字节。接下来,我们把字符串的其余部分压⼊栈上。我们把存放user32.dll基地址的eax寄存器压⼊栈上,然后调⽤GetProcAddress函数。
7. 调⽤SwapMouseButton函数
既然已经获得SwapMouseButton函数地址,我们只需要使⽤“正确的”参数进⾏调⽤即可。
add esp, 0x14 ; Cleanup stack
xor ecx, ecx ; ECX = 0
windows开发平台inc ecx ; true
push ecx ; 1
call eax ; Swap!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论