动态加载技术

动态加载技术

动态加载技术可以让程序设计者脱离复杂的导入表结构,在程序空间中构造类似于导入表的调用引入函数机制。

在了解这一知识前最好先了解windows虚拟内存管理,详情见我博客

动态库技术

DLL静态调用

又称隐式调用。调用一个动态链接库的函数通常采取的方式是:把产生动态链接库时产生的".lib"库文件和".inc"包含的文件加入到应用程序的工程中,想使用DLL中的函数时,直接使用函数的名字即可,例如,加入user32.lib user32.inc调用MessageBox函数,例子可以见我另一篇博客;

DLL动态调用

又称显式调用。通过API函数加载和卸载DLL来达到调用DLL函数的目的。与动态库调用有关的函数主要包括:

  1. LoadLibrary(或MFC的AfxLoadLibrary),装载动态链接库
  2. GetProcAddress,获取要引入函数的VA(虚拟内存地址),将符号名或标识号转换为DLL内部地址
  3. FreeLibrary(或MFC的AfxFreeLibrary),释放动态链接库

导出函数起始地址

程序引进动态链接库的最终目的是要调用动态链接库里的函数代码,所以,获取动态连接库里的导出函数起始地址是动态加载技术的关键。

现在假设user32.dll被动态装载到内存的0x77DF0000处,那么MessageBoxA的入口地址VA就是:
0x77DF000 + 0x00026544 = 0x7E16544

如果一个函数在进程空间中的VA 确定以后,最简单拿的调用方式就是通过一下硬编码方式来调用

push xx		    ;显示往栈里压入该函数的参数,个数由调用的函数决定
......
mov eax,77E16544;
call eax

在编程中使用动态加载技术

分三步:
1.获取kernel32.dll的基地址
2.获取GetProcAddress函数的地址(然后通过此函数获取LoadLibrary函数的地址)
3.在代码中使用获取的函数地址编程

1.查找kernel32.dll的基地址
    .386
    .model flat,stdcall
    option casemap:none

include    windows.inc
include    user32.inc
includelib user32.lib
include    kernel32.inc
includelib kernel32.lib

;数据段
    .data
szText         db  'kernel32.dll在本程序地址空间的基地址为:%08x',0dh,0ah,0
kernel32Base   dd  ?
szBuffer       db 256 dup(0)

;代码段
    .code
_getKernelBase  proc _dwKernelRetAddress
   local @dwRet

   pushad
   mov @dwRet,0
   
   mov edi,_dwKernelRetAddress
   and edi,0ffff0000h  ;查找指令所在页的边界,以1000h对齐

   .repeat
     .if word ptr [edi]==IMAGE_DOS_SIGNATURE  ;找到kernel32.dll的dos头
        mov esi,edi
        add esi,[esi+003ch]
        .if word ptr [esi]==IMAGE_NT_SIGNATURE ;找到kernel32.dll的PE头标识
          mov @dwRet,edi
          .break
        .endif
     .endif
     sub edi,010000h
     .break .if edi<070000000h
   .until FALSE
   popad
   mov eax,@dwRet
   ret
_getKernelBase  endp   


start:
    mov eax,dword ptr [esp]
    invoke _getKernelBase,eax
    invoke wsprintf,addr szBuffer,addr szText,eax
    invoke MessageBox,NULL,addr szBuffer,NULL,MB_OK
    ret
    end start
2.获取GetProcAddress地址

通过_getApi,直到某个动态链接库的基地址,并知道调用函数名称的情况下,可以通过调用该函数得到地址

;------------------------------------------------
; 从内存中模块的导出表中获取某个 API 的入口地址
;------------------------------------------------
_getApi  proc  _hModule,_lpszApi
  local @dwReturn,@dwStringLen
  
  pushad
  mov @dwReturn,0
  call @F
@@:
  pop ebx
  sub ebx,offset @B

  ;创建用于错误处理的SEH结构
  assume fs:nothing
  push ebp
  lea eax,[ebx+offset _ret]
  push eax
  lea eax,[ebx+offset _SEHHandler]
  push eax
  push fs:[0]
  mov fs:[0],esp

  ;计算API字符串的长度(注意带尾部的0)
  mov edi,_lpszApi
  mov ecx,-1
  xor al,al
  cld
  repnz scasb
  mov ecx,edi
  sub ecx,_lpszApi
  mov @dwStringLen,ecx
  ;从DLL文件头的数据目录中获取导出表的位置
  mov esi,_hModule
  add esi,[esi+3ch]
  assume esi:ptr IMAGE_NT_HEADERS
  mov esi,[esi].OptionalHeader.DataDirectory.VirtualAddress
  add esi,_hModule
  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,@dwStringLen
    repz cmpsb
    .if ZERO?
      pop esi
      jmp @F
    .endif
    pop esi
    add ebx,4
    inc edx
  .until edx>=[esi].NumberOfNames
  jmp _ret
@@:
  ;API名称索引->序号索引->地址索引
  sub ebx,[esi].AddressOfNames
  sub ebx,_hModule
  shr ebx,1
  add ebx,[esi].AddressOfNameOrdinals
  add ebx,_hModule
  movzx eax,word ptr [ebx]
  shl eax,2
  add eax,[esi].AddressOfFunctions
  add eax,_hModule
  ;从地址表得到导出函数地址
  mov eax,[eax]
  add eax,_hModule
  mov @dwReturn,eax
_ret:
  pop fs:[0]
  add esp,0ch
  assume esi:nothing
  popad
  mov eax,@dwReturn
  ret
_getApi  endp

start:
    invoke _getApi,hDllKernel32,addr szGetProcAddress   ;获取GetProcAddress函数的内存地址
    mov _GetProcAddress,eax
    ...
    ret
    end start

3.在代码中使用获取的函数地址编程
	;声明函数
	_QLMessageBoxA  typedef  proto :dword,:dword,:dword,:dword
	;声明函数引用
	_ApiMessageBoxA  typedef  ptr _QLMessageBoxA
	...
	;定义函数
	_messageBox  _ApiMessageBoxA  ?
	...;动态获取_messageBox的地址
	;调用函数
	invoke _messageBox,NULL,offset szText,NULL,MB_OK
发布了43 篇原创文章 · 获赞 16 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/include_IT_dog/article/details/89320215