Windows Shellcode开发[2]

0 前言

在这部分我们将了解正确编写Windows系统shellcode所需的信息:进程环境块和PE文件格式。上一部分在这里:Windows Shellcode开发[1]

1 进程环境块

在Windows操作系统中,PEB作为一种结构可以用于内存中固定地址的每个进程。此结构包含进程相关信息,如:可执行文件加载到内存中的地址、模块列表(DLL)、指定进程是否在调试等。在不同版本的Windows系统中,PEB各字段偏移可能会随之改变。

由于地址空间布局随机化(ASLR),DLL将被加载到不同的内存地址,所以我们不能在自己的shellcode中使用固定的内存地址。但是对于每个进程来说,PEB的地址是固定的,那么我们就可以使用PEB来找到DLL在内存中的位置。PEB结构如下(调试环境为Windows 7 7601 x86):

偏移0xc处的Ldr字段指向了一个_PEB_LDR_DATA结构它包含进程中加载的dll的详细信息。_PEB_LDR_DATA结构如下:

偏移0x14处InMemoryOrderModuleList字段是一个链表,按照模块在进程的虚拟内存布局中出现的顺序来组织。

理一下思路:

1. 读取PEB结构

2. 转到偏移量0xc处的Ldr指针

3. 转到偏移量0x14处的InMemoryOrderModuleList字段

也就是若我们现在打开一个计算器进程,则calc.exe被第一个放置于InMemoryOrderModuleList链表中,若我们想遍历所有加载的DLL,通过遍历链表的方式就可以实现。InMemoryOrderModuleList链表按以下顺序为我们提供了所有已知加载模块:

1. calc.exe

2. ntdll.dll

3. kernel32.dll

如第一部分所述,我们需要访问kernel32.dll才能访问GetprocAddress和LoadLibrary之类的函数,才能调用其他API函数。为达成目的,我们需从当前的_LDR_DATA_TABLE_ENTRY结构中读取DllBase字段。_LDR_DATA_TABLE_ENTRY结构如下:

0x18偏移处的DllBase字段表明了DLL加载到内存中的位置。

*注意:此时我们已经在偏移0x8(InMemoryOrderModuleList字段),所以只需要跳转0x10字节即可获取DllBase。

2 PE文件格式

可移植可执行文件是Windows系统上的可执行文件和动态链接库使用的文件格式。关于PE文件的简图如下:

如图所示,一个PE文件包括:

1. DOS头

2. DOS存根

3. PE头

4. 段表

5. 各段

使用010editor打开PE文件查看,010editor能够直观的看到PE文件的细节:

从DOS头开始,DOS头结构如下:

所有PE文件(exe或dll)都以这个结构开始。所以,当我们在内存中找到一个模块后,在对应的内存地址也会找到DOS头结构,MZ就是DOS头的签名。 其中偏移0x3c处的e_lfanew字段指向PE头的位置。

可选头是一个包含重要信息的结构,如下所示:

偏移0x10处AddressOfEntryPoint字段表示exe或dll开始执行代码的位置;

偏移0x1c处ImageBase字段表示DLL应该被加载到内存的位置;

偏移0x60处DataDirectory字段表示导入和导出函数等信息。

最后一个字段DataDirectory对我们编写shellcode很重要,因为我们需要获取导出的函数。DLL包含不同的函数并且将这些函数导出,因此其他应用程序可以将DLL加载到自己的内存中,找到导出函数并调用他们,例如上一部分提到的user32.dll导出的MessageBox函数。DataDirectory字段是一个_IMAGE_DATA_DIRECTORY结构体数组,_IMAGE_DATA_DIRECTORY结构如下:

为了得到导出目录,我们需要根据VirtualAddress字段,该字段指向导出目录的起始地址。

梳理一下思路,我们的目的是为了找到DLL的导出函数,_IMAGE_DATA_DIRECTORY结构下的VirtualAddress表示到处目录的起始地址,那么现在差的就是偏移量。从PE头(50 45)开始,到_IAMGE_OPTIONAL_HEADER.DataDirectory为止,能够计算出偏移量为120个字节(0x78),如下图所示。

所以PE头偏移0x78处,我们能够找到带出目录的虚拟地址(VirtualAddress字段)。导出目录结构如下:

AddressOfFunctions表“函数指针”数组的地址

AddressOfNames表指向“函数名的指针”数组的地址

AddressOfNameOrdinals表序数数组的地址

*注意:以上三个字段都是相对虚拟地址

以一个具有三个导出函数的DLL为例:

1. ​​​​​​​AddressOfFunctions = 0x11223344 —> [0x11111111, 0x22222222, 0x33333333] ; 0x11223344 是指向包含函数地址的数组的指针:0x11111111、0x22222222 和 0x33333333 是函数的地址。

2. AddressOfNames = 0x12345678 —> [0xaaaaaaaa -> “func0”, 0xbbbbbbbb -> “func1”, 0xcccccccc -> “func2”] –;0x12345678 是指向函数名称指针数组的指针:0xaaaaaaaa 是指向“func1”的指针” 字符串表示导出的函数名称等。

3. AddressOfNameOrdinals = 0xabcdef —> [0x00, 0x01, 0x02] – 0xabcdef 是指向整数数组(在两个字节上)的指针,表示 AddressOfFunctions数组中每个函数的偏移量。

为了通过名称获取函数地址,我们通过解析 AddressOfNames数组来检查名称。第一个函数 (func0) 的序号为 0,第二个函数 (func1) 的序号为 1,第三个函数 (func2) 的序号为 2。因此,如果我们正在搜索 func2 函数,我们将访问元素 2 ( AddressOfFunctions数组的从 0) 开始 。

简而言之,就是这样:function_address= AddressOfFunctions [ Ordinal(function_name) ]。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值