通用shellcode编写动态定位API
背景:
如果编写的 ShellCode 采用了硬编址的方式来调用相应的API函数,这会存在操作系统的版本不一样,调用函数在内存中的地址不同而出现失败的现象。如下图在win10中就不能正常运行
这时需要通过编写一些定位程序,让 ShellCode 能够动态定位所需要的API函数地址,从中解决ShellCode 的通用性问题。
1.上述弹窗程序中最重要的函数MessageBox,它是位于 User32.dll 这个动态链接库里,默认情况下是无法直接调用的,为了能够调用它,就需要调用 LoadLibraryA 函数来加载User32.dll模块,而 LoadLibraryA 又位于 kernel32.dll 链接库中。
有这么一个信息,就是所有的win_32程序都会加载ntdll.dll和kerner32.dll这两个最基础的动态链接库。所以只要找到 LoadLibraryA 函数,就能加载动态链接库,并调用其它的函数。
在win_32平台下定位kernel32.dll动态链接库中的API函数地址,有如下公式:
(1).首先通过段选择字FS在内存中找到当前的线程环境快TEB。
(2).TEB线程环境快偏移位置为0x30的地方存放着指向进程环境块PEB的指针。
(3).进程环境块PEB中偏移位置为0x0C的地方存放着指向PEB_LDR_DATA结构体的指针,其中,存放着已经被进程装载的动态链接库的信息。
(4).PEB_LDR_DATA结构体偏移位置为0x1C的地方存放着指向模块初始化链表的头指针InInitizationOrderModuleList.
(5).模块初始化链表InInitizationOrderModuleList中按顺序存放着PE装入运行时初始化模块信息,第一个链表结点是ntdll.dll,第二个链表结点就是kernel32.dll。
(6).找到属于kernel32.dll的结点后,在其基础上再偏移0x08就是kernel32.dll在内存中的加载基地址。
(7).从kernel32.dll的加载基址算起,偏移0x3C的地方就是其PE头。
(8).PE头偏移0x78的地方存放着指向函数导出表的指针。
(9).导出表0x1C处的指针指向存储导出函数偏移地址(RVA)的列表
导出表偏移0x20处的指针指向存储导出函数函数名的列表
函数的RVA地址和名字按照顺序存放在上述两个列表中,可以在名称列表中定位到所需的函数是第几个,然后在地址列表中找到对应的RVA
获得RVA后,再加上前面已经得到的动态链接库的加载基址,就获得了所需API此刻在内存中的虚拟地址。如下图:
- 下面使用Windbg内核调试,按照公式来查找Kernel32.dll的地址
(1) 打开Windbg,然后按【Ctrl + K】-> 选择本地(Local) ,点击确定。
(2) 按【Ctrl + S】
选择要加载的符号文件,否则无法进行查看
(3) 通过段选择字FS在内存中找到当前的线程环境快TEB,输入命令!teb
(4) 在TEB线程环境快偏移位置为0x30的地方存放着指向进程环境块PEB的指针
(5) 进程环境块PEB中偏移位置为0x0C的地方存放着指向PEB_LDR_DATA结构体的指针,其中,存放着已经被进程装载的动态链接库的信息。
(6) PEB_LDR_DATA结构体偏移位置为0x1C的地方存放着指向模块初始化链表的头指针InInitizationOrderModuleList.
(7) 在模块初始化链表InInitizationOrderModuleList中按顺序存放着PE装入运行时初始化模块信息,第一个链表结点是ntdll.dll
地址0x00191f28保存第一个链表结点的指针,解析这个链表结点,
(8) 找到属于kernel32.dll的结点后,在其基础上再偏移0x08就是kernel32.dll在内存中的加载的基地址。
通过一段汇编来获取kernel32.dll 的基地址
3.查找kernel32.dll 的导出表及函数API
(1)打开win32下的kernel32.dll文件,偏移 0x3c 的地方就是PE头
(2) PE头偏移0x78的地方存放着指向函数导出表
导出表的RVA地址是0x0000262C;那么它的文件OFFSET是多少?
在LordPE先查看各个段的RVA和OFFSET:
显然,导出表在.text区段
.data区段的RVA为0x1000,.data区段的起始Offset是0x400
RVA到Offset的转化:
Offset = 导出表RVA - 导出表所在区段的RVA + 导出表所在区段的 Offset
即:Offset=0x0262C – 0x1000 + 0x400= 0x1a2c
在LordPE计算器中计算
在PE文件中文件偏移0x1a2c为导出表结构,一个导出表大小是 0x28个字节
(3) 在导出表0x1C处的指针,指向存储导出函数偏移地址(RVA)的列表
导出表偏移0x20处的指针,指向存储导出函数函数名的列表
如下图是导出表的数据结构