首页
社区
课程
招聘
[原创]动态获取API地址详解
发表于: 2012-5-24 19:42 13196

[原创]动态获取API地址详解

2012-5-24 19:42
13196

;这个程序是从罗云彬老师的《windows环境下32位汇编语言》书上抄下来的,因为之前看的时候看得不是很懂,后来总结了一些资料后终于是弄明白了,
;这里贴出我的分析供初学者学习,大神飘过吧,若有不对之处,还请大家提出来。
;首先是一个SEH异常处理代码,这段程序是作为一个子程序来调用的,为了避免使用全局变量,这里用_lpSEH这个参数来传递需要保护的寄存器。
_SEHHandler        proc        C _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext

                pushad
                mov        esi,_lpExceptionRecord
                mov        edi,_lpContext
                assume        esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT
                mov        eax,_lpSEH
                push        [eax + 0ch]
                pop        [edi].regEbp                        ;恢复Ebp
                push        [eax + 8]
                pop        [edi].regEip                        ;恢复Eip
                push        eax
                pop        [edi].regEsp                       
                assume        esi:nothing,edi:nothing
                popad
                mov        eax,ExceptionContinueExecution
                ret

_SEHHandler        endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 在内存中扫描 Kernel32.dll 的基址,重点来了。
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_GetKernelBase        proc        _dwKernelRet
                local        @dwReturn

                pushad
                mov        @dwReturn,0
;********************************************************************
; 下面是地址重定位的代码,为了让这个子程序中的两个函数能用到任何地方
;********************************************************************
                call        @F
                @@:
                pop        ebx
                sub        ebx,offset @B
;********************************************************************
; 下面是创建用于错误处理的 SEH 结构
;********************************************************************
                assume        fs:nothing
                push        ebp
                lea        eax,[ebx + offset _PageError]
                push        eax
                lea        eax,[ebx + offset _SEHHandler]
                push        eax
                push        fs:[0]
                mov        fs:[0],esp
;********************************************************************
; 查找 Kernel32.dll 的基地址
;********************************************************************
                mov        edi,_dwKernelRet
                and        edi,0ffff0000h                ;重点:为什么要和0ffff0000h进行and运算?因为PE文件被装入内存时是按64K对齐的,取and后刚好是64K的整数
                                                ;倍,这样查找的时候就更容易找到,提高效率。
                .while        TRUE
                        .if        word ptr [edi] == IMAGE_DOS_SIGNATURE                ;先比较是不是一个DOS头部
                                mov        esi,edi
                                add        esi,[esi+003ch]
                                .if word ptr [esi] == IMAGE_NT_SIGNATURE        ;再看是不是一个有效的PE头
                                        mov        @dwReturn,edi
                                        .break
                                .endif
                        .endif
                        _PageError:
                        sub        edi,010000h                ;这儿为什么是减010000h,往低地址找?因为按照PE结构的顺序,导出表的地址是在PE文件地址的后面
                                                        ;而内存的增长方向又是从低到高,所以要找kernerl32.dll的基址就必须往前面(低地址找)。       
                        .break        .if edi < 070000000h
                .endw
                pop        fs:[0]
                add        esp,0ch
                popad
                mov        eax,@dwReturn
                ret

_GetKernelBase        endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 从内存中模块的导出表中获取某个 API 的入口地址
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_GetApi                proc        _hModule,_lpszApi
                local        @dwReturn,@dwStringLength

                pushad
                mov        @dwReturn,0
;********************************************************************
; 重定位
;********************************************************************
                call        @F
                @@:
                pop        ebx
                sub        ebx,offset @B
;********************************************************************
; 创建用于错误处理的 SEH 结构
;********************************************************************
                assume        fs:nothing
                push        ebp
                lea        eax,[ebx + offset _Error]
                push        eax
                lea        eax,[ebx + offset _SEHHandler]
                push        eax
                push        fs:[0]
                mov        fs:[0],esp
;********************************************************************
; 下面是计算 API 字符串的长度(带尾部的0),这段程序的原理是:将al赋值为0,然后依次与API的每一个字符进行比较,当比较到API最后一个字符'0'时
;表示API的字符串结束,然后用edi现在的地址值减去API之前的地址,之差就是API的长度。                       
;********************************************************************
                mov        edi,_lpszApi
                mov        ecx,-1
                xor        al,al
                cld
                repnz        scasb                ;这条指令之前一直没弄懂,后来查看了一些资料才明白。repnz 表示在ecx!=0且ZF==0时,程序继续循环。scasb
                                        ;是对每一个字符进行比较(比较对象 是:al  与edi 中的字符。)用C语言可以这样翻译:
                                        ;while(--ecx)
                                        ;{
                                        ;        if(*(edi++)==al)
                                        ;                break;
                                        ;}
                mov        ecx,edi
                sub        ecx,_lpszApi
                mov        @dwStringLength,ecx
;********************************************************************
; 从 PE 文件头的数据目录获取导出表地址
;********************************************************************
                mov        esi,_hModule
                add        esi,[esi + 3ch]                ;3ch这个长度是从DOS头部到PE头的大小,从基地址加3ch刚好到达NT结构。
                assume        esi:ptr IMAGE_NT_HEADERS
                mov        esi,[esi].OptionalHeader.DataDirectory.VirtualAddress
                add        esi,_hModule                ;因为数据目录的第一个就是导出表,所以直接取其地址,就是EXPORT结构体。
                assume        esi:ptr IMAGE_EXPORT_DIRECTORY
;********************************************************************
; 查找符合名称的导出函数名
;********************************************************************
                mov        ebx,[esi].AddressOfNames
                add        ebx,_hModule
                xor        edx,edx
                .repeat
                        push        esi
                        mov        edi,[ebx]
                        add        edi,_hModule
                        mov        esi,_lpszApi
                        mov        ecx,@dwStringLength
                        repz        cmpsb                ;这个repz指令与上面的repnz类似,也是串循环指令。不同之处在于比较的对象不一样,
                                                ;这里比较的对象是si与di中的字符,若相等则继续循环,不相等则退出循环。
                        .if        ZERO?
                                pop        esi
                                jmp        @F        ;找到要查找的API,则直接跳到下一步。
                        .endif
                        pop        esi
                        add        ebx,4                ;没找到则继续向后找,这里,为什么是加4?因为AddressOfNames(函数名字符串地址表)是
                                                ;一个双字数组。
                        inc        edx
                .until        edx >=        [esi].NumberOfNames
                jmp        _Error
@@:
;********************************************************************
; API名称索引 --> 序号索引 --> 地址索引
;********************************************************************
                sub        ebx,[esi].AddressOfNames
                sub        ebx,_hModule
                shr        ebx,1                        ;这里为什么要右移一位?右移是除2的意思,因为AddressOfFunctions指向的是一个word数组,
                                                ;所以要除以2,以便可以和之后的地址相加。
                add        ebx,[esi].AddressOfNameOrdinals
                add        ebx,_hModule                ;这里为什么要加kernel32.dll的基址?因为目前ebx只是一个RVA,还不能直接用。
                movzx        eax,word ptr [ebx]
                shl        eax,2                        ;这里左移一位,是恢复双字数据,为下面相加作准备。
                add        eax,[esi].AddressOfFunctions
                add        eax,_hModule                ;这个也是个RVA
;********************************************************************
; 从地址表得到导出函数地址
;********************************************************************
                mov        eax,[eax]
                add        eax,_hModule                ;eax这个RVA加上kernel32.dll的基址,就是要找的API的函数地址了。
                mov        @dwReturn,eax               
_Error:
                pop        fs:[0]
                add        esp,0ch
                assume        esi:nothing
                popad
                mov        eax,@dwReturn
                ret

_GetApi                endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
下面是主程序:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                .386
                .model flat,stdcall
                option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include                windows.inc

_ProtoGetProcAddress        typedef        proto        :dword,:dword
_ProtoLoadLibrary        typedef        proto        :dword
_ProtoMessageBox        typedef        proto        :dword,:dword,:dword,:dword
_ApiGetProcAddress        typedef        ptr        _ProtoGetProcAddress
_ApiLoadLibrary                typedef        ptr        _ProtoLoadLibrary
_ApiMessageBox                typedef        ptr        _ProtoMessageBox
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                .data?
hDllKernel32        dd        ?
hDllUser32        dd        ?
_GetProcAddress        _ApiGetProcAddress        ?
_LoadLibrary        _ApiLoadLibrary                ?
_MessageBox        _ApiMessageBox                ?

                .const
szLoadLibrary        db        'LoadLibraryA',0
szGetProcAddress db        'GetProcAddress',0
szUser32        db        'user32',0
szMessageBox        db        'MessageBoxA',0

szCaption        db        'A MessageBox !',0
szText                db        'Hello, World !',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                .code
include                _GetKernel.asm
start:
;********************************************************************
; 从堆栈中的 Ret 地址转换 Kernel32.dll 的基址,并在 Kernel32.dll
; 的导出表中查找 GetProcAddress 函数的入口地址
;********************************************************************
                invoke        _GetKernelBase,[esp]
                .if        eax
                        mov        hDllKernel32,eax
                        invoke        _GetApi,hDllKernel32,addr szGetProcAddress
                        mov        _GetProcAddress,eax
                .endif
;********************************************************************
; 用得到的 GetProcAddress 函数得到 LoadLibrary 函数地址并装入其他 Dll
;********************************************************************
                .if        _GetProcAddress
                        invoke        _GetProcAddress,hDllKernel32,addr szLoadLibrary
                        mov        _LoadLibrary,eax
                        .if        eax
                                invoke        _LoadLibrary,addr szUser32
                                mov        hDllUser32,eax
                                invoke        _GetProcAddress,hDllUser32,addr szMessageBox
                                mov        _MessageBox,eax
                        .endif
                .endif
;********************************************************************
                .if        _MessageBox
                        invoke        _MessageBox,NULL,offset szText,offset szCaption,MB_OK
                .endif
                ret
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                end        start


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 6
支持
分享
最新回复 (14)
雪    币: 458
活跃值: (306)
能力值: ( LV12,RANK:400 )
在线值:
发帖
回帖
粉丝
2
若哪儿还有问题可以提出来,大家一起讨论,我也是个初学者。。
2012-5-24 20:02
0
雪    币: 31
活跃值: (48)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
顶,收藏了,谢谢大牛。
2012-5-24 20:23
0
雪    币: 458
活跃值: (306)
能力值: ( LV12,RANK:400 )
在线值:
发帖
回帖
粉丝
4
大家一起学习。。
2012-5-25 14:16
0
雪    币: 949
活跃值: (18)
能力值: ( LV9,RANK:330 )
在线值:
发帖
回帖
粉丝
5
个人觉得写汇编用.if .while 这样的宏,不如直接用C
2012-5-25 16:49
0
雪    币: 292
活跃值: (153)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
6
256*A+Shellcode+jmp  esp+jmp -0x80;
2012-5-25 16:55
0
雪    币: 71
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
你学的很认真
有发展
2012-5-25 21:48
0
雪    币: 107
活跃值: (404)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
神马玩意????????????????
2012-5-26 18:43
0
雪    币: 458
活跃值: (306)
能力值: ( LV12,RANK:400 )
在线值:
发帖
回帖
粉丝
9
谢谢夸奖,还在努力中。
2012-5-26 19:10
0
雪    币: 16
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
呵呵 好东西!~
2012-5-26 20:38
0
雪    币: 1737
活跃值: (110)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
我也看过那本书,没有看明白~~
2012-5-27 16:05
0
雪    币: 458
活跃值: (306)
能力值: ( LV12,RANK:400 )
在线值:
发帖
回帖
粉丝
12
这本书写得很好,遇到难的地方我就会多花时间,边写程序边思考,所以至今,还没有多少盲点。
2012-5-27 16:59
0
雪    币: 69
活跃值: (242)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wmg
13
mark  一下
2012-5-28 22:39
0
雪    币: 458
活跃值: (306)
能力值: ( LV12,RANK:400 )
在线值:
发帖
回帖
粉丝
14
我的另一篇文章“一步一步实现在PE文件中添加可执行代码”里面也用到了本文的一些知识,大家可以结合起来看。
2012-5-29 18:24
0
雪    币: 9
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
这要收藏~ 多谢LZ了~
2012-6-12 15:13
0
游客
登录 | 注册 方可回帖
返回
//