文章作者:Hit
;以下是利用SEH和PE文件结构搜索API地址的例子
;由Hit于2003年8月7日改写Hume(CVC.GB)的例子而成
;程序原理及原版程序参见:
;《CVC内部杂志 Issue 1 特别版》中《API函数地址的获取(2)》
;CVC(中国毒客公社)论坛地址:
http://bbs.logincom.com/bbs/cgi-bin/leoboard.cgi
;原理:
;利用遍历SEH链表取得API的步骤如下:
;a)遍历SEH链,找到prev等于0xFFFFFFFF的EXCEPTION_REGISTER结构,获取handler值;
;b)利用PE文件特征在内存中搜索kernel32.dll的基地址;
;c)搜索kernel32.dll的IAT,获取GetProcAddress的地址;
;d)以GetProceAddress获取其他任何Win32 API函数地址。
;代码说明:
; === 以内为关键代码,以外为演示代码
; *** 以内为移植时可能需要修改的关键代码
.386
.model flat, stdcall
option casemap:none
.data
;=========================================================================
KernelBase dd ? ;Kernel32.dll的基地址
UserBase dd ? ;User32.dll的基地址
GdiBase dd ? ;Gdi32.dll的基地址
LLA_Addr dd ? ;LoadLibraryA的地址
GPA_Addr dd ? ;GetProcAddress的地址
;=========================================================================
APIName1 db 'MessageBoxA',0
APIName2 db 'ExitProcess',0
Text db 'Success!',0
Caption db 'Message',0
.code
Start:
;=========================================================================
cld ;清方向标志
xor esi,esi ;将esi置0
assume fs:nothing ;使用FS时,编译器不报错
lods dword ptr fs:[esi] ;将SEH起始节点地址存入eax
@@: inc eax ;┓ 判断eax是否为-1,
jz @F ;┣ 如果是,则跳出循环
dec eax ;┛
xchg esi,eax ;┓ 指针移向下一个节点
lodsd ;┛
jmp short @B ;一直循环
@@: lodsd ;┓
xchg esi,eax ;┛ 得到handler值
;以下开始搜索Kernel32.dll
;的基地址
@@: dec esi ;┓ Kernel32.dll的基地址
xor si,si ;┛ 是64K的倍数,esi为指针
mov eax,[esi] ;读取指针指向的内容
add ax,0a5b3h ;0a5b3h就是'MZ'的相反数
jnz @B ;判断内容是不是'MZ'
mov edi,[esi+3ch] ;edi存放PE头的RVA
mov eax,[esi+edi] ;eax存放PE头的起始内容
add eax,0ffffbab0h ;0ffffbab0h就是"PE\0\0"的相反数
jnz @B ;判断内容是不是"PE\0\0"
;************************
mov KernelBase,esi ;保存Kernel32.dll的基地址
;************************
mov ebp,esi ;┓edi为PE结构中DataDirectory
mov edi,[ebp+edi+78h] ;┛的RVA
push edi ;保存DataDirectory的RAV
push esi ;保存Kernel32.dll的基地址
mov eax,[ebp+edi+20h] ;eax为AddressOfNames的地址
mov edx,[ebp+edi+24h] ;edx为AddressOfNameOrdinals地址
call @F ;将下面字符串地址压入堆栈
db "GetProcAddress",0 ;API的字符串
@@:
pop edi ;从堆栈中弹出字符串地址
mov ecx,15 ;字符串长15字节
sub eax,4 ;┓
@@: ;┃
add eax,4 ;┃
add edi,ecx ;┃
sub edi,15 ;┣查找"GetProcAddress"字符串
mov esi,[ebp+eax] ;┃
add esi,ebp ;┃
mov ecx,15 ;┃
repz cmpsb ;┃
jnz @B ;┛
pop esi ;恢复Kernel32.dll的基地址
pop edi ;恢复DataDirectory的RAV
sub eax,[ebp+edi+20h] ;┓
shr eax,1 ;┃
add edx,ebp ;┣ ebp为GetProcAddress的地址
movzx eax,word ptr [edx+eax] ;┃
add esi,[ebp+edi+1ch] ;┃
add ebp,[esi+eax*4] ;┛
;************************
mov GPA_Addr,ebp ;保存GetProcAddress的地址
;************************
call @F ;┓
db 'LoadLibraryA',0 ;┃
@@: ;************************ ;┃
push KernelBase ;┃ 调用API函数GetProcAddress
;************************ ;┣ 从Kernel32.dll中获取API函
;┃ 数LoadLibraryA的地址
call ebp ;┃
;************************ ;┃
mov LLA_Addr,eax ;┛
;************************
call @F ;┓
db 'User32.dll',0 ;┃
@@: call eax ;┣ 如上,调用API函数LoadLibraryA
;************************* ;┃ 获取User32.dll的基地址
mov UserBase,eax ;┛
;*************************
call @F ;┓
db 'Gdi32.dll',0 ;┃
@@: ;************************* ;┣ 如上,调用API函数LoadLibraryA
call dword ptr [LLA_Addr] ;┃ 获取Gdi32.dll的基地址
mov GdiBase,eax ;┛
;*************************
;=========================================================================
push offset APIName1
push UserBase
call dword ptr [GPA_Addr] ;获取MessageBoxA的地址
push 0 ;一个"确定"按钮
push offset Caption ;标题栏内容
push offset Text ;消息内容
push 0 ;消息框没有父窗口
call eax ;调用MessageBoxA显示消息框
push offset APIName2
push KernelBase
call dword ptr [GPA_Addr] ;获取ExitProcess的地址
push 0
call eax ;退出程序
end Start
;以上程序开发工具和测试环境:
;编译器: MASM32 7.00.9466
;编辑器: NoteXPad 1.4.0.0 RC6
;操作系统: Windows XP SP1
;程序编译后运行,出现Success!字样的消息框,但在源程序中没有引入
;任何头文件(*.inc)和库文件(*.lib)。程序的目的达到了。
;如转载本文,请保持文章所有内容完整,谢谢!