Windows Shellcode开发[3]

0 前言及编写shellcode准备工作

在Windows Shellcode开发介绍的最后一部分,我们将编写一个简单的改变鼠标左右键的shellcode。我们需要用到两个函数:SwapMouseButton和ExitProcess函数。首先看一个两个函数的参数与返回值情况:

SwapMouseButton只有一个BOOL类型参数,若参为TRUE则交换鼠标左右键,返回值非0;

ExitProcess也只有一个参数,代表进程退出代码。

整理一下思路:

我们需要调用以上两个函数,在C++中体现就是:

编译器知道与user32库链接并找到SwapMouseButton函数。但我们需要在shellcode中手动执行此操作:我们需要手动加载user32库,找到SwapMouseButton函数的地址并调用。

#include<Windows.h>

typedef BOOL(WINAPI* fswapMouseButton)(BOOL);

int main()
{
	//声明函数指针
	fswapMouseButton FunctionPointer;

	//加载user32.dll库
	HMODULE user = LoadLibrary((LPCWSTR)"user32.dll");
	
	//获取SwapMouseButton函数地址
	FunctionPointer = (fswapMouseButton)GetProcAddress(user, "SwapMouseButton");

	//调用函数
	FunctionPointer(true);

	return 0;
}

编译器知道LoadLibrary和GetProcAddress函数的地址。在shellcode中,我们必须以编程方式来计算。需要注意的是,我们在C++中不需要调用ExitProcess函数,因为main函数的return 0就是退出进程的作;但是我们在shellcode中需要调用ExitProcess函数来避免程序崩溃。

1 编写shellcode

通过前面的思路整理,可以总结出编写目标shellcode的步骤:

1. 查找kernel32.dll加载到内存的位置并找到其导出的GetProcessAddress函数

2. 使用GetProcessAddress函数查找LoadLibrary函数地址

3. 使用LoadLibrary加载user32.dll库

4. 在user32.dll中找到SwapMouseButton函数地址

5. 调用SwapMouseButton函数

6. 找到ExitProcess函数地址并调用

为了编写shellcode,在VS2019中使用_asm{ }命令直接编写汇编代码:

 

1. 1 查找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:将ecx清零避免出现NULL字节(mov ecx,0)

行3--4:现在eax存放PEB指针,正如上一部分讨论的,在0xC偏移处找到Ldr,在Ldr中0x14的偏移处,找到InMemoryOrderModuleList。

行5--7:现在位于InMemoryOrderModuleList上的主模块(项目编译运行生成的exe文件:若在VS2019中建立的项目名为shellcode_test,主模块就是shellcode_test.exe)。此处第一个元素是Flink,指向下一个模块的指针。可以看到我们把这个指针放在了esi寄存器中,lodsd指令将跟随esi寄存器指定的指针,我们将在eax寄存器中获得结果。这意味着在lodsd指令之后,我们将在eax寄存器中获取到第二个模块ntdll.dll。我们通过交换eax和esi的值将该指针放置在esi中,并再次使用lodsd指令到达第三个模块:kernel32.dll。详细分析见Windows Shellcode开发[2]

行8:此时,此时,我们在eax寄存器中获得指向 kernel32.dll 的InMemoryOrderList的指针。增加0x10字节将获得DllBase指针,即加载 kernel32.dll的内存地址。

1.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:经过上一部分对PE文件格式的分析,我们在偏移0x3c处得到e_lfanew指针(e_lfanew指向PE头)。注意:虚拟地址 = 相对虚拟地址 + 基址

行3--4:在PE头的偏移0x78 处,我们可以找到导出的DataDirectory。同样,我们将这个值添加到edx寄存器,我们现在位于kernel32.dll的导出表中。

行5--7:在IMAGE_EXPORT_DIRECTORY结构中,在偏移量0x20处,我们可以找到指向AddressOfNames的指针,如此我们可以获得导出的函数名称。同时将指针保存在esi寄存器中并将ecx寄存器设置为0。

1.3 查找GetProcessAddress函数名

目前我们位于AddressOfNames字段,每4个字节代表一个指向函数名的指针。我们如下找到函数名和函数名序号:

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:第一行“Get_Function:”是一个标签,一个位置名称;我们将跳转到该位置以读取函数名。第3行递增ecx,作为函数的计数器和函数序号。

行4-5:我们在esi寄存器中有指向第一个函数名的指针。lodsd指令会将函数名的偏移量放在eax中,然后我们将其与ebx(kernel32基地址)相加。注意:lodsd指令还将esi寄存器的值增加 4,这样一来我们不必手动增加它,我们只需要再次调用lodsd以获得下一个函数名称指针。

行6--11:我们现在在eax寄存器中有一个指向导出函数名称的正确指针,接下来需要检查这个函数名是否是“GetProcAddress”。

1.4 查找GetProcessAddress函数地址

至此我们已找到GetProcessAdress函数序号,下面据此找到函数地址:

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结构的指针。在结构的偏移量 0x24 处,我们可以找到AddressOfNameOrdinals。在第2行,我们将此偏移量添加到ebx寄存器,它是kernel32.dll的映像库,因此我们得到了一个指向名称序数表的有效指针。

行3--4:esi寄存器包含指向名称序数数组的指针。我们在ecx寄存器中有GetProcAddress函数的名称序号,如此我们得到函数地址序号。函数名称序数从0开始所以我们需要减少相应的值。

行5--6:在偏移量 0x1c 处,我们可以找到AddressOfFunctions,即指向函数指针数组的指针。

行7--8:现在我们在ecx中获得了AddressOfFunctions数组的正确索引,我们只需在AddressOfFunctions[ecx]位置找到GetProcAddress函数指针。ecx * 4表示每个指针有 4 个字节,esi 指向数组的开头。在第8行计算虚拟地址,因此我们将在 edx中拥有指向GetProcAddress函数的指针。

1.5 找到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(LoadLibarry)

行1--3:首先将ecx置零,然后将kernel32基地址和GetprocessAdress函数指针压栈。

行4--10:将“LoadLibraryA\0”字符串压栈然后调用GetProcessAdress函数以得到LoadLibrary函数地址。

1.6 加载user32.dll

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:通过add esp将堆栈上的“LoadLibraryA”字符串清除,同时也将前面压入的0弹栈。在1.5中执行完call edx后,会将LoadLibrary函数地址存在在eax中,所以在此将函数地址压栈。

行4--10:将user32.dll字符串压栈,调用LoadLibrary函数加载user32.dll。

1.7 获取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:和上一步一样首先清理堆栈。第2行将GetProcessAdress函数地址放入edx。

行3--12:将SwapMouseButton字符串压入栈桢并调用GetProcessAdress函数以得到SwapMouseButton函数地址。

1.8 调用SwapMouseButton函数

add esp, 0x14		; Cleanup stack
xor ecx, ecx		; ECX = 0
inc ecx				; true
push ecx			; 1
call eax			; Swap!

类似前面的操作:清理堆栈、压入参数、调用函数。

1.9 获取ExitProcess函数地址并调用

add esp, 0x4		; Clean stack
pop edx				; GetProcAddress
pop ebx				; kernel32.dll base address
mov ecx, 0x61737365	; essa
push ecx
sub dword ptr[esp + 0x3], 0x61; Remove "a"
push 0x636f7250		; Proc
push 0x74697845		; Exit
push esp
push ebx			; kernel32.dll base address
call edx			; GetProc(Exec)
xor ecx, ecx		; ECX = 0
push ecx			; Return code = 0
call eax			; ExitProcess

行1--3:再次记性清理堆栈操作。

行4--11:将ExitProcess字符串压栈调用GetProcessAdress函数得到ExitProcess函数地址

行12--14:压参后调用ExitProcess函数。

最终的shellcode如下:

		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
		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 namestable
		add esi, ebx					; ESI = Names table
		xor ecx, ecx					; EXC = 0

		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
		mov esi, [edx + 0x24]			; ESI = Offset ordinals
		add esi, ebx					; ESI = Ordinals table
		mov cx, [esi + ecx * 2]			; Number of function
		dec ecx
		mov esi, [edx + 0x1c]			; Offset address table
		add esi, ebx					; ESI = Address table
		mov edx, [esi + ecx * 4]		; EDX = Pointer(offset)
		add edx, ebx					; EDX = GetProcAddress

		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)

		add esp, 0xc					; pop "LoadLibrary"
		pop ecx							; ECX = 0
		push eax						; EAX = LoadLibrary
		push ecx
		mov cx, 0x6c6c					; ll
		push ecx
		push 0x642e3233					; 32.d
		push 0x72657375					; user
		push esp						; "user32.dll"
		call eax						; LoadLibrary("user32.dll")

		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)

		add esp, 0x14					; Cleanup stack
		xor ecx, ecx					; ECX = 0
		inc ecx							; true
		push ecx						; 1
		call eax						; Swap!

		add esp, 0x4					; Clean stack
		pop edx							; GetProcAddress
		pop ebx							; kernel32.dll base address
		mov ecx, 0x61737365				; essa
		push ecx
		sub dword ptr[esp + 0x3], 0x61	; Remove "a"
		push 0x636f7250					; Proc
		push 0x74697845					; Exit
		push esp
		push ebx						; kernel32.dll base address
		call edx						; GetProc(Exec)
		xor ecx, ecx					; ECX = 0
		push ecx						; Return code = 0
		call eax						; ExitProcess

2 测试shellcode

使用以下代码测试shellcoed:

#include "stdafx.h"
#include <Windows.h>
 
int main()
{
    char *shellcode =
    "\x33\xC9\x64\x8B\x41\x30\x8B\x40\x0C\x8B\x70\x14\xAD\x96\xAD\x8B\x58\x10\x8B\x53\x3C\x03\xD3\x8B\x52\x78\x03\xD3\x8B\x72\x20\x03"
    "\xF3\x33\xC9\x41\xAD\x03\xC3\x81\x38\x47\x65\x74\x50\x75\xF4\x81\x78\x04\x72\x6F\x63\x41\x75\xEB\x81\x78\x08\x64\x64\x72\x65\x75"
    "\xE2\x8B\x72\x24\x03\xF3\x66\x8B\x0C\x4E\x49\x8B\x72\x1C\x03\xF3\x8B\x14\x8E\x03\xD3\x33\xC9\x53\x52\x51\x68\x61\x72\x79\x41\x68"
    "\x4C\x69\x62\x72\x68\x4C\x6F\x61\x64\x54\x53\xFF\xD2\x83\xC4\x0C\x59\x50\x51\x66\xB9\x6C\x6C\x51\x68\x33\x32\x2E\x64\x68\x75\x73"
    "\x65\x72\x54\xFF\xD0\x83\xC4\x10\x8B\x54\x24\x04\x33\xC9\x51\xB9\x74\x6F\x6E\x61\x51\x83\x6C\x24\x03\x61\x68\x65\x42\x75\x74\x68"
    "\x4D\x6F\x75\x73\x68\x53\x77\x61\x70\x54\x50\xFF\xD2\x83\xC4\x14\x33\xC9"
    "\x41" // inc ecx - Remove this to restore the functionality
    "\x51\xFF\xD0\x83\xC4\x04\x5A\x5B\xB9\x65\x73\x73\x61"
    "\x51\x83\x6C\x24\x03\x61\x68\x50\x72\x6F\x63\x68\x45\x78\x69\x74\x54\x53\xFF\xD2\x33\xC9\x51\xFF\xD0";
 
    // Set memory as executable
 
    DWORD old = 0;
    BOOL ret = VirtualProtect(shellcode, strlen(shellcode), PAGE_EXECUTE_READWRITE, &old);
 
    // Call the shellcode
 
    __asm
    {
        jmp shellcode;
    }
 
    return 0;
}

Shellcode Helper v1.62 Coded by TeLeMan (c) 2008-2013 Usage: schelper.exe [options] Options: -i [input file] input file (Default: stdin) -o [output file] output file (Default: stdout) -s input file format (Default: Auto-Detection) -sb input file format is Binary -sp the input file format's parameters -d output file format (Default: C format) -db output file format is Binary -dp the output file format's parameters -search get the start offset by the pattern: e.g. PK\x03\x04 -soff fix the match offset after searching (Default: 0) -off convert the input file from the offset (Default: 0) -len convert the input file with the length (Default: 0 - MAX) -en [encoder] encode shellcode (Default: XorDword) -de [encoder] decode shellcode (Default: Auto-Detection) -ex exclude characters: e.g. 0x00,0x01-0x1F,0xFF (Default: 0x00) -in incude characters only -ep the encoder's parameters -t [pid] execute or inject shellcode into process for testing -td [pid] execute or inject shellcode into process for debugging -stack put shellcode into stack and execute it (ESP is the shellcode start) -noinfo display no normal messages except error messages Available formats: 0 - C 1 - C(HexArray) 2 - Perl 3 - Python 4 - Ruby 5 - JavaScript(Escape) 6 - VBScript(Escape) 7 - Pascal 8 - MASM(Data) 9 - HexDump 10 - BitString 11 - HexString 12 - HexArray(C like) 13 - Base64 14 - Binary 15 - HexString(C like) 16 - HexString(Escape) 17 - HexString(JavaScript,UNICODE) 18 - URI(ISO-8859-1) 19 - XML(PCDATA) 20 - BigNumber 21 - BigNumber(Hex) 22 - BigNumber(BaseX) 23 - FloatPoint 24 - UnixTimestamp 25 - GUID 26 - MASM(ASM) 27 - NASM 28 - YASM(ASM) 29 - FASM(ASM) 30 - JWASM(ASM) 31 - POASM(ASM) 32 - GOASM(ASM) 33 - GNU ASM Available encoders:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值