背景

​ 编写的 ShellCode 采用了硬编址的方式来调用相应的API函数,这会存在操作系统的版本不一样以及ASLR,调用函数在内存中的地址不同而出现失败的现象。

​ 弹窗程序中最重要的函数MessageBox,它是位于 User32.dll 这个动态链接库里,默认情况下是无法直接调用的,为了能够调用它,就需要调用 LoadLibraryA 函数来加载User32.dll模块,而 LoadLibraryA 又位于 kernel32.dll 链接库中。

有这么一个信息,就是所有的win_32程序都会加载ntdll.dll和kerner32.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加载程序

1. 首先通过段选择字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 User32Reserved   : [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]  ""

2. TEB线程环境快偏移位置为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 User32Reserved   : [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

3. 分析PEB结构

进程环境块PEB中偏移位置为0x0C的地方存放着指向PEB_LDR_DATA结构体的指针,其中,存放着已经被进程装载的动态链接库的信息

x86PEB_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 SkipPatchingUser32Forwarders : 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

4.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

5. 链表InLoadOrderModuleList中按顺序存放着PE装入运行时初始化模块信息

第一个链表节点是test03的内置内存块,第二个链表结点是ntdll.dll,第三个链表节点是kernel32.dll

6. 跟踪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 是进程环境块 PEBmov ecx, [ebx + 0x0c]          // PEB+0x0c 是PEB_LDR_DATA结构体指针 存放这已经被进程加载的动态链接库的信息mov ecx, [ecx + 0x0c]         // PEB_LDR_DATA+0x0c 指向模块初始化链表的头指针 InLoadOrderModuleListmov ecx, [ecx]                  // ntdll.dll链表mov ecx, [ecx]                    // Kernel32.dll链表mov ebp, [ecx + 0x18]          // ebp 即kernel32.dll基地址

7. 从kernel32.dll的加载基址算起,偏移0x3C的地方就是其PE头(又称NT头)

  • 注意点:win32下的DLL存放在sysWOW64

8. PE头偏移0x78的地方存放着指向函数导出表的指针

根据上图可以知道导出表RVA=00092880,导出表所在区段为.rdata段,该区段的RVA00080000,起始offset00067000

计算导出表真实偏移**offset**

offset = 导出表RVA - 导出表所在区段的RVA + 导出表所在区段的offset

计算可得offset = 00092880 - 00080000 + 00067000 =00079880

9. 查看导出表数据结构

导出表偏移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

10. 获取函数RVA地址

函数的RVA地址指针名字指针按照顺序存放在上述两个列表中,可以在名称列表中定位到所需的函数是第几个,然后在地址列表中找到对应的RVA

可以得到第一个导出函数的RVA=0x00016720

12.获取API地址

获得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     // 将user32.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基地址更新为 user32.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                                           // 和user32.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;}

结果示例



学习链接

点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖