欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

动态获取API地址详解

动态获取API地址详解

;这个程序是从罗云彬老师的《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

上传的附件 动态获取API地址详解.rar

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功