编写的 ShellCode 采用了硬编址的方式来调用相应的API函数,这会存在操作系统的版本不一样以及ASLR,调用函数在内存中的地址不同而出现失败的现象。
弹窗程序中最重要的函数MessageBox,它是位于 Use***.dll 这个动态链接库里,默认情况下是无法直接调用的,为了能够调用它,就需要调用 LoadLibraryA 函数来加载Use***.dll模块,而 LoadLibraryA 又位于 kernel32.dll 链接库中。
有这么一个信息,就是所有的win_32程序都会加载ntdll.dll和kerne***.dll这两个最基础的动态链接库。所以只要找到 LoadLibraryA 函数,就能加载动态链接库,并调用其它的函数。
跟踪InLoadOrderModuleList
获取Kernel32.dll
地址
#include<stdio.h>
#include<string.h>
#include<windows.h>
int ExceptionHandler(void);
int main(int argc,char *argv[]){
char temp[512];
printf("Application launched");
__try {
strcpy(temp,argv[1]);
} __except ( ExceptionHandler() ){
}
return 0;
}
int ExceptionHandler(void){
printf("Exception");
return 0;
}
使用VC++ 6.0
编译,VS2019
会自带异常处理,干扰分析
使用Windbg加载程序
FS
在内存中找到当前的线程环境快TEB
。当我们开启了异常处理例程TEB结构位于FS:[0]
SEH结构图中FS:[0]
指向TEB
结构
0:000> dd FS:[0]
003b:00000000 0014ff30 00150000 0014d000 00000000
003b:00000010 00001e00 00000000 003f0000 00000000
003b:00000020 00000f00 000016b8 00000000 003f002c
003b:00000030 003ef000 00000000 00000000 00000000
003b:00000040 00000000 00000000 00000000 00000000
003b:00000050 00000000 00000000 00000000 00000000
003b:00000060 00000000 00000000 00000000 00000000
003b:00000070 00000000 00000000 00000000 00000000
0:000> !teb
TEB at 003f0000
ExceptionList: 0014ff30
StackBase: 00150000
StackLimit: 0014d000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 003f0000
EnvironmentPointer: 00000000
ClientId: 00000f00 . 000016b8
RpcHandle: 00000000
Tls Storage: 003f002c
PEB Address: 003ef000 <== 0x30指向PEB
LastErrorValue: 0
LastStatusValue: 0
Count Owned Locks: 0
HardErrorMode: 0
0:000> dt _teb 003f0000
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : (null)
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : (null)
+0x02c ThreadLocalStoragePointer : 0x003f002c Void
+0x030 ProcessEnvironmentBlock : 0x003ef000 _PEB <== 0x30指向PEB
+0x034 LastErrorValue : 0
+0x038 CountOfOwnedCriticalSections : 0
+0x03c CsrClientThread : (null)
+0x040 Win32ThreadInfo : (null)
+0x044 Use***Reserved : [26] 0
+0x0ac UserReserved : [5] 0
+0x0c0 WOW32Reserved : (null)
+0x0c4 CurrentLocale : 0x804
+0x0c8 FpSoftwareStatusRegister : 0
+0x0cc ReservedForDebuggerInstrumentation : [16] (null)
+0x10c SystemReserved1 : [26] (null)
+0x174 PlaceholderCompatibilityMode : 0 ''
+0x175 PlaceholderReserved : [11] ""
0x30
的地方存放着指向进程环境块PEB
的指针。PEB Address
位于TEB结构体偏移0x30
处
0:000> !teb
TEB at 003f0000
ExceptionList: 0014ff30
StackBase: 00150000
StackLimit: 0014d000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 003f0000
EnvironmentPointer: 00000000
ClientId: 00000f00 . 000016b8
RpcHandle: 00000000
Tls Storage: 003f002c
PEB Address: 003ef000 <==位于teb结构体偏移0x30的位置
LastErrorValue: 0
LastStatusValue: 0
Count Owned Locks: 0
HardErrorMode: 0
也可以使用dt _teb 003f0000
查看偏移量
0:000> dt _teb 003f0000
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : (null)
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : (null)
+0x02c ThreadLocalStoragePointer : 0x003f002c Void
+0x030 ProcessEnvironmentBlock : 0x003ef000 _PEB <==位于teb结构体偏移0x30的位置
+0x034 LastErrorValue : 0
+0x038 CountOfOwnedCriticalSections : 0
+0x03c CsrClientThread : (null)
+0x040 Win32ThreadInfo : (null)
+0x044 Use***Reserved : [26] 0
+0x0ac UserReserved : [5] 0
+0x0c0 WOW32Reserved : (null)
+0x0c4 CurrentLocale : 0x804
+0x0c8 FpSoftwareStatusRegister : 0
+0x0cc ReservedForDebuggerInstrumentation : [16] (null)
+0x10c SystemReserved1 : [26] (null)
+0x174 PlaceholderCompatibilityMode : 0 ''
+0x175 PlaceholderReserved : [11] ""
所以这里的PEB地址为:0x003ef000
进程环境块PEB中偏移位置为0x0C
的地方存放着指向PEB_LDR_DATA
结构体的指针,其中,存放着已经被进程装载的动态链接库的信息
在x86
上PEB_LDR_DATA结构体的指针
位于0x0C
0:000> dt _peb 003ef000
ntdll!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0x1 ''
+0x003 BitField : 0 ''
+0x003 ImageUsesLargePages : 0y0
+0x003 IsProtectedProcess : 0y0
+0x003 IsImageDynamicallyRelocated : 0y0
+0x003 SkipPatchingUse***Forwarders : 0y0
+0x003 IsPackagedProcess : 0y0
+0x003 IsAppContainer : 0y0
+0x003 IsProtectedProcessLight : 0y0
+0x003 IsLongPathAwareProcess : 0y0
+0x004 Mutant : 0xffffffff Void
+0x008 ImageBaseAddress : 0x00400000 Void
+0x00c Ldr : 0x77d3ab40 _PEB_LDR_DATA
偏移位置为0x0C
的地方存放着指向模块加载顺序链表的头指针InLoadOrderModuleList
偏移位置为0x14
的地方存放着指向模块在运行中的模块链表的头指针InMemoryOrderModuleList
偏移位置为0x1C
的地方存放着指向模块初始化装载顺序链表的头指针InInitizationOrderModuleList
0:000> dt _peb_ldr_data 0x77d3ab40
ntdll!_PEB_LDR_DATA
+0x000 Length : 0x30
+0x004 Initialized : 0x1 ''
+0x008 SsHandle : (null)
+0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x481ed0 - 0x482660 ]
+0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x481ed8 - 0x482668 ]
+0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x481dd8 - 0x4822c0 ]
+0x024 EntryInProgress : (null)
+0x028 ShutdownInProgress : 0 ''
+0x02c ShutdownThreadId : (null)
我们先观察模块InLoadOrderModuleList
的链表顺序
0:000> lm
start end module name
00400000 00426000 test03 C (no symbols)
74710000 748e8000 KERNELBASE (deferred)
77950000 779e5000 KERNEL32 (pdb symbols) C:\ProgramData\Dbg\sym\kernel32.pdb\EFA698598E9A5A3CB89EC02E7DE288041\kernel32.pdb
77c20000 77db0000 ntdll (pdb symbols) C:\ProgramData\Dbg\sym\ntdll.pdb\C771EA3FB471AEB18DF6186EC9D80CD81\ntdll.pdb
InLoadOrderModuleList
中按顺序存放着PE装入运行时初始化模块信息第一个链表节点是test03
的内置内存块,第二个链表结点是ntdll.dll
,第三个链表节点是kernel32.dll
3步
找到属于kernel32.dll
的结点后,在其基础上再偏移0x18
就是kernel32.dll
在内存中的加载基地址0:000> dd 0x481ed0
00481ed0 00481dc8 77d3ab4c 00481dd0 77d3ab54
00481ee0 00000000 00000000 00400000 00401170 <== 00400000 程序基址
00481ef0 00026000 00560054 00481b6c 001c001a
00481f00 00481ba6 800022cc 0000ffff 77d3aa10
00481f10 77d3aa10 615e9a65 00000000 00000000
00481f20 00481f90 00481f90 00481f90 00000000
00481f30 00000000 77c21284 00000000 004826c8
00481f40 00482318 00000000 004826d4 00482324
0:000> dd 00481dc8
00481dc8 004822b0 00481ed0 004822b8 00481ed8
00481dd8 00482670 77d3ab5c 77c20000 00000000 <== 77c20000 ntdll.dll基址
00481de8 00190000 003c003a 00481ca8 00140012
00481df8 77c279e0 0000a2c4 0000ffff 77d3aa40
00481e08 77d3aa40 1d27c592 00000000 00000000
00481e18 00481e88 00481e88 00481e88 00000000
00481e28 00000000 00000000 00000000 00000000
00481e38 00482318 00000000 00000000 00482324
0:000> dd 004822b0
004822b0 00482660 00481dc8 00482668 00481dd0
004822c0 77d3ab5c 00482670 77950000 779695e0 <== 77950000 Kernel32.dll基址
004822d0 00095000 00420040 004823b8 001a0018
004822e0 004823e0 000ca2cc 0000ffff 77d3aa30
004822f0 77d3aa30 57ce72fd 00000000 00000000
00482300 00482370 00482370 00482370 00000000
00482310 00000000 77c212f4 00481f38 00481e30
00482320 00000000 00481e3c 00481f44 00000000
一段汇编获取kernel32.dll的基地址
mov ebx, fs: [edx + 0x30] // FS得到当前线程环境块TEB TEB+0x30 是进程环境块 PEB mov ecx, [ebx + 0x0c] // PEB+0x0c 是PEB_LDR_DATA结构体指针 存放这已经被进程加载的动态链接库的信息 mov ecx, [ecx + 0x0c] // PEB_LDR_DATA+0x0c 指向模块初始化链表的头指针 InLoadOrderModuleList mov ecx, [ecx] // ntdll.dll链表 mov ecx, [ecx] // Kernel32.dll链表 mov ebp, [ecx + 0x18] // ebp 即kernel32.dll基地址
0x3C
的地方就是其PE头(又称NT头)注意点:win32下的DLL存放在sysWOW64
0x78
的地方存放着指向函数导出表的指针根据上图可以知道导出表RVA=00092880
,导出表所在区段为.rdata
段,该区段的RVA
为00080000
,起始offset
为00067000
计算导出表真实偏移**offset**
offset = 导出表RVA - 导出表所在区段的RVA + 导出表所在区段的offset
计算可得offset = 00092880 - 00080000 + 00067000 =00079880
导出表偏移0x1C
处的指针指向存储导出函数偏移地址(RVA)的列表
导出表偏移0x20
处的指针,指向存储导出函数函数名的列表
typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; // 未使用,总为0 DWORD TimeDateStamp; // 文件创建时间戳 WORD MajorVersion; // 未使用,总为0 WORD MinorVersion; // 未使用,总为0 DWORD Name; // 指向一个代表此 DLL名字的 ASCII字符串的 RVA DWORD Base; // 函数的起始序号 DWORD NumberOfFunctions; // 导出函数的总数 DWORD NumberOfNames; // 以名称方式导出的函数的总数 DWORD AddressOfFunctions; // 指向输出函数地址的RVA <<<<<<============= DWORD AddressOfNames; // 指向输出函数名字的RVA <<<<<<============= DWORD AddressOfNameOrdinals; // 指向输出函数序号的RVA } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
所以导出函数RVA为000928A8
函数的RVA地址指针
和名字指针
按照顺序存放在上述两个列表中,可以在名称列表中定位到所需的函数是第几个,然后在地址列表中找到对应的RVA
可以得到第一个导出函数的RVA=0x00016720
获得RVA后,再加上前面已经得到的动态链接库的加载基址,就获得了所需API此刻在内存中的虚拟地址
第一个导出函数在内存中的**虚拟内存地址(VA)0x76416720=基地址0x76400000 +相对虚拟地址(RVA)0x00016720**
//获取导出函数名称表内存虚拟地址(VA) //ebp为获取的Kernel32.dll基址 find_funcs : pushad // 保存寄存器环境 mov eax, [ebp + 0x3c] // 指向PE头 mov ecx, [ebp + eax + 0x78] // 得到导出表的指针 add ecx, ebp // 得到导出函数表内存虚拟地址(VA) mov ebx, [ecx + 0x20] // 得到导出函数名称表(RVA) add ebx, ebp // 得到导出函数名称表内存虚拟地址(VA) xor edi, edi // 初始化计数器 // 循环读取导出表函数对比是否是自己需要的 next_func_loop : inc edi // 函数计数器+1 mov esi, [ebx + edi * 4] // 得到 当前函数名的地址(RVA) add esi, ebp // 得到 当前函数名的内存虚拟地址(VA) cdq;
使用VC++6.0编译
前提知识点:
根据Hash计算函数名进而调用目标函数
#include<stdio.h> #include<windows.h> int main() { _asm { // 将要调用的函数hash值入栈保存 CLD // 清空标志位DF push 0x1e380a6a // 压入 MessageBoxA 字符串的hash push 0x4fd18963 // 压入 ExitProcess 字符串的hash push 0x0c917432 // 压入 LoadLibraryA 字符串的hash mov esi, esp // 指向栈中存放LoadLibraryA的 hash 地址 lea edi, [esi - 0xc] // 用于存放后边找到的 三个函数地址 // 开辟0x400大小的栈空间 xor ebx, ebx //ebx清零 mov bh, 0x04 sub esp, ebx //sub esp,0x400 // 将use***.dll入栈 mov bx, 0x3233 push ebx // 压入字符'32' push 0x72657375 // 压入字符 'user' push esp xor edx, edx // 查找 kernel32.dll 的基地址 mov ebx, fs: [edx + 0x30] // FS得到当前线程环境块TEB TEB+0x30 是进程环境块 PEB mov ecx, [ebx + 0x0c] // PEB+0x0c 是PEB_LDR_DATA结构体指针 存放这已经被进程加载的动态链接库的信息 mov ecx, [ecx + 0x0c] // PEB_LDR_DATA+0x0c 指向模块初始化链表的头指针 InLoadOrderModuleList mov ecx, [ecx] // ntdll.dll链表 mov ecx, [ecx] // Kernel32.dll链表 mov ebp, [ecx + 0x18] // ebp即kernel32.dll基地址 // 与 hash 的查找相关 find_lib_funcs : lodsd // 将[esi]中的4字节 传到eax中 cmp eax, 0x1e380a6a // 比较 MessageBoxA 字符串的hash值 jne find_funcs // 如果不相等则继续查找 xchg eax, ebp // 记录当前hash值 call[edi - 0x8] xchg eax, ebp // 还原当前hash值 并且把exa基地址更新为 use***.dll的基地址 //在PE文件中查找相应的API函数 //获取导出函数名称表内存虚拟地址(VA) //ebp为获取的Kernel32.dll基址 find_funcs : pushad // 保存寄存器环境 mov eax, [ebp + 0x3c] // 指向PE头 mov ecx, [ebp + eax + 0x78] // 得到导出表的指针 add ecx, ebp // 得到导出函数表内存虚拟地址(VA) mov ebx, [ecx + 0x20] // 得到导出函数名称表(RVA) add ebx, ebp // 得到导出函数名称表内存虚拟地址(VA) xor edi, edi // 初始化计数器 // 循环读取导出表函数对比是否是自己需要的 next_func_loop : inc edi // 函数计数器+1 mov esi, [ebx + edi * 4] // 得到 当前函数名的地址(RVA) add esi, ebp // 得到 当前函数名的内存虚拟地址(VA) cdq; // 计算hash值 hash_loop: // 循环得到当前函数名的hash movsx eax, byte ptr[esi] // 得到当前函数名称 第esi的一个字母 cmp al, ah // 比较到达函数名最后的0没有 jz compare_hash // 函数名hash 计算完毕后跳到 下一个流程 ror edx, 7 // 循环右移7位 add edx, eax // 累加得到hash inc esi // 计数+1 得到函数名的下一个字母 jmp hash_loop // 循环跳到 hash_loop // hash值的比较 compare_hash : cmp edx, [esp + 0x1c] // 比较 目标函数名hash 和 当前函数名的hash jnz next_func_loop // 如果 不等于 继续下一个函数名 mov ebx, [ecx + 0x24] // 得到 PE导出表中的 函数序号列表的 相对位置 add ebx, ebp // 得到 PE导出表中的 函数序号列表的 绝对位置 mov di, [ebx + 2 * edi] // 得到 PE导出表中的 当前函数的序号 mov ebx, [ecx + 0x1c] // 得到 PE导出表中的 函数地址列表的 相对位置 add ebx, ebp // 得到 PE导出表中的 函数地址列表的 绝对位置 add ebp, [ebx + 4 * edi] // 得到 PE导出表中的 当前函数的绝对地址 // 循环依次得到kernel32.dll中的 LoadLibraryA ExitProcess // 和use***.dll中的 MessageBoxA xchg eax, ebp // 把函数地址放入eax中 pop edi // pushad中最后一个压入的是edi 正好是开始预留 用于存放的三个函数地址 的栈空间 stosd // 把找到函数地址出入 edi对应的栈空间 push edi // 继续压栈 平衡栈 popad // 还原环境 cmp eax, 0x1e380a6a // 比较是否是 MessageBoxA 函数 如果是说明全部函数已经找齐 可以调用函数执行功能 jne find_lib_funcs // 下方的代码,就是弹窗 func_call : xor ebx, ebx // 将 ebx 清0 push ebx push 0x206f7265 push 0x5a5f7466 // 注意数据大小端问题 push 0x6973696d // 标题“misift_Zero” mov eax, esp // 把标题赋值给 eax push ebx push 0x216e7770 // 再push一个“pwn!”当做内容 mov ecx, esp // 把内容 hello 赋值给 ecx // 下面就是将MessageBox的参数压栈 push ebx // messageBox 第四个参数 push eax // messageBox 第三个参数 push ecx // messageBox 第二个参数 push ebx // messageBox 第一个参数 call[edi - 0x04] // 调用 MessageBoxA push ebx call[edi - 0x08] // 调用 ExitProcess nop nop nop nop } return 0; }
本文作者:m1sIft
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/168841.html