关于PE病毒编写的学习(八)——定位API的N种方法(2010年10月25更新)

来源:互联网 发布:两个iphone软件同步 编辑:程序博客网 时间:2024/05/21 16:15

 

由于大部分的文件感染型病毒框架都不可以像“前置病毒”那样拥有自己的输入表,因此需要自行定位API

说一千道一万,想要定位API,向是要定位Kernel32.dll的基地址

总结所以这些方法,可以分为是五个小方向,它们又产生很多变种。

一、定位kernel32.dll基地址的方法

(1)硬编码方式

    由于kernel32.dll的基地址在相同版本windows下,基本上它的位置是固定的。这种方法在早期的PE病毒中很常见,现在已经很少使用了。

(2)利用程序初始化时,首先寄存器或堆栈中保留的kernel32.dll内存模块中的某个地址,之后无论哪种变体,都是以这个kernel内的地址向前搜索,找到kernel.dll的基地址。

以下地方就存储着这些地址: 

                                     ①寄存器EDI

                                     ②刚刚初始化的堆栈:[esp]、[esp+4H]、[esp+10H]

以[esp+10h]为例,我们看一下参考代码(另外注释里有一些常见错误代码的写法):

Start: 

  mov edx,[esp+10h]
SearchDosHeader:
  dec  edx
  xor  dx,dx                       ;加速搜索,因为DLL以1M长度对齐,所以这里以64K字节为跨度来加速搜索
  cmp word ptr[edx],'ZM'  ;不要自作聪明写成'MZ',那是以字节逐个读取的结果

                                        ;,这里是以双字节读取的,'Z'在高位,'M'在低位,因此写为'ZM'
  jnz SearchDosHeader

IsNTHeaders:
  mov eax,[edx+3ch]        ;这里也不要写为[edx+IMAGE_NT_HEADERS32.Signatrue],这样不会得到想要的结果
  cmp word ptr [eax+edx],'EP' ;和'ZM'是一个道理
  jnz  SearchDosHeader

  mov KernelImageBase,edx  ;KernelImageBase是自定义局部变量,可以放在堆栈里

  ret
end Start

(3)遍历seh异常链,然后获得EXCEPTION_REGISTRATION结构prev为-1的异常处理过程地址,这个异常处理过程地址是位于kernel32.dll中,通过它向前搜索得到kernel32.dll的基地址。

以下是参考代码:

Start:

assume fs:nothing     ;Masm默认是不使用fs寄存器的,写上这句话才能使用
  mov edx,[fs:0]         ;获得EXCEPTION_REGISTRATION 结构地址
Next:
  inc   dword ptr [edx];将prev 成员 + 1,判断是否为零,然后恢复
  jz     Kml
  dec  dword ptr [edx]
  mov edx,[edx]
  jmp  Next

Kml:
  dec  dword ptr [edx];恢复 -1
  mov edx,[edx+4]

SearchDosHeader:
  dec  edx
  xor  dx,dx
  cmp word ptr[edx],'ZM'
  jnz SearchDosHeader

IsNTHeaders:
  mov eax,[edx+3ch]
  cmp word ptr [eax+edx],'EP'
  jnz SearchDosHeader

  mov KernelImageBase,edx

  ret

end Start

(4)通过TEB获得PEB结构地址,然后再获得PEB_LDR_DATA 结构地址,然后遍历模块列表,查找kernel32.dll 模块的基地址。

Start:

assume fs:nothing

  mov edx, [fs:30h]    ;Get Peb
  mov edx, [edx+0ch] ;Get _PEB_LDR_DATA

  mov edx, [edx+1ch] ;Get InInitializationOrderModuleList.Flink, 此时eax 指向的是ntdll 模块的
                                ;InInitializationOrderModuleList 线性地址。所以我们获得它的下一个则是kernel32.dll
  mov edx, [edx]
  mov edx, [edx+8]    ; 8 = sizeof.LIST_ENTRY

  mov KernelImageBase,edx

  ret

end Start

(5)一个正常的程序的输入表都会加载Kernel32.dll,所以通过搜索宿主本身的输入表,再找到Kernel32.dll,然后搜索它基地址。但是这个方法缺点首先是代码长度比较长,并且病毒首次编译后,其本身输入表不加载Kernel32.dll,需要手工抽取代码,然后绑定到宿主程序上,这样病毒才算真正完成。

步骤:

1.找到本程序的PE头文件,方法有三种:①利用默认文件内存加载点400000h

                                                       ②利用进程初始化堆栈的[esp+34h]保存的程序入口点,向前找到问PE文件头

                                                       ③重定位当前点,向前搜索

2.再找到输入表、进而找到Kernel32.dll,代码就不写了,一点也不难

3.手工绑定,具体步骤请参考《Windows应用程序捆绑核心编程》(张正秋著)第一版的第11章。

二、通过自己实现的GetProcAddress定位API

   在Kernel32.dll中有GetProcAddress这个函数,它可以通过函数名定位函数入口地址。可是由于不知道GetProcAddress的地址,只好由我们自己实现,以下代码可供参考,如果对PE结构了解十分清楚,那么代码是很容易读懂的。

 

My_Get_API_Address proc Base:dword,sFunctionName:dword
LOCAL NumberOfName:dword
LOCAL AddressOfFunctions:dword
LOCAL AddressOfNames:dword
LOCAL AddressOfOrdinarls:dword
;定位输出表
  mov ebx,Base
  mov eax,[ebx+3ch]
  mov eax,[ebx+eax+78h]
  add eax,ebx
;取出输出表中一些有用的值   
  mov  ebx,[eax+18h]
  mov  NumberOfName,ebx
  mov  ebx,[eax+1ch]
  add  ebx,Base
  mov  AddressOfFunctions,ebx
  mov  ebx,[eax+20h]
  add  ebx,Base
  mov  AddressOfNames,ebx
  mov  ebx,[eax+24h]
  add  ebx,Base
  mov  AddressOfOrdinarls,ebx
;根据函数名找出函数ID
  xor eax,eax
  mov edi,AddressOfNames
  mov ecx,NumberOfName
  
  LoopNumberOfName:
        mov esi,sFunctionName
        push eax
        mov ebx,[edi]
        add ebx,Base
        Match_API:
         mov al,byte ptr[ebx]
         cmp al,[esi]
         jnz Not_Match
         or al,0h
         jz GetKernel_API_Index_Found
         inc ebx
         inc esi
         jmp Match_API
        Not_Match:
        pop eax
        inc eax
        add edi,4h
  loop LoopNumberOfName
GetKernel_API_Index_Found:
  pop eax  
;用函数ID找出函数入口地址  
Get_API_Address:  
  mov ebx,AddressOfOrdinarls
  movzx eax,word ptr[ebx+eax*2]
  imul eax,4h
  add  eax,AddressOfFunctions
  mov  eax,[eax]
  add  eax,Base
  ret
My_Get_API_Address endp

OK,既然已经有了自己编写的GetProcAddress,那么我们就可以通过它定位Kernel32.DLL里的正牌GetProcAddress和LoadLibraryA。

不过小陈告诉另一个方法,GetProcAddress和LoadLibraryA都可以不必定位,其中GetProcAddress可以使用自己编写了,自然可以不必定位。至于LoadLibrary返回的动态链接库DLL模块句柄,实际上就是动态链接库的基地址,虽然由于我们现在只有Kernel32.dll的基地址,如果想定位其他动态链接库,只有搜索宿主文件是否加载了相应的动态链接库,如果没有,也可修改它的PE文件头,让它在启动时加载该动态链接库,然后仿照搜索Kernel32.dll类似的方法,确定其入口地址,即模块句柄。

终于完成这章了,好幸福!关于动态重定位API方法肯定不止这些方法,比如“三、通过API的名称来定位API的地址”这个标题下应该不止一种方法,学海无涯啊。再次欢迎有好方法的高手,不吝赐教,帮助我不断更新这篇文章。

 

这里感谢pencil 和小陈,pencil提醒了我应该仿写GetProcAddress,小陈告诉了我,替代LoadLibraryA的实现方法。

 

原创粉丝点击