原文:http://www.pediy.com/kssd/index.html -- 病毒技术 -- 病毒知识 -- Anti Virus专题
搜索获得api函数地址的实现
我们的程序能正常的调用函数。那么这个动态链接库是如何输出函数来供我们的用户程序调用呢?它实际上是采用输出表结构来描述本dll需要导出哪些函数来供其他的程序调用,这样其他的用户程序才能正常的调用此动态链接库的输出函数.
导出表结构:
IMAGE_EXPORT_DIRECTORY struct Characteristics DWORD ? ; 未使用,总是为0 TimeDateStamp DWORD ? ; 文件的产生时刻 MajorVersion WORD ? ; 未使用,总是为0 MinorVersion WORD ? ; 未使用,总是为0 nName DWORD ? ; 指向文件名的RVA nBase DWORD ? ; 导出函数的起始序号 NumberOfFunctions DWORD ? ; 导出函数的总数 NumberOfNames DWORD ? ; 以名称导出的函数总数 AddressOfFunctions DWORD ? ; 指向导出函数地址表的RVA AddressOfNames DWORD ? ; 指向函数名地址表的RVA AddressOfNameOrdinals DWORD ? ; 指向函数名序号表的RVA IMAGE_EXPORT_DIRECTORY ends
在这里说下AddressOfFunctions、AddressOfNames和AddressOfNameOrdinals这三个成员的对应关系,比如说我们通过名称搜索MessageBoxA函数的地址, AddressOfNames的值是一个RVA数组,每个RVA加上模块基址就是实际的函数名称字符串地址,如果说MessageBoxA这个字符串在这个数组的索引位置0处, 而AddressOfNames和AddressOfNameOrdinals是对应的,用”MessageBoxA“字符串在AddressOfNames中的索引在AddressOfNameOrdinals指向的函数名序号表中取值,取出的序号值再用在AddressOfFunctions中用来取值,取得的RVA加上模块基址就是MessageBoxA函数的地址了。
下面的代码用来测试获取MessageBoxA函数的地址并调用
.386 02. .model flat, stdcall 03. option casemap:none 04. 05.include windows.inc 06.include user32.inc 07.include kernel32.inc 08.includelib user32.lib 09.includelib kernel32.lib 10. 11. .const 12.szDllName db ‘user32.dll‘, 0 13.szApiString db ‘MessageBoxA‘, 0 14.szText db ‘Get API Address Success!‘, 0 15.szCaption db ‘Success‘, 0 16. 17. .code 18._GetApiAddress proc _hDllHandle, _lpApiString 19. 20. push ebp 21. mov eax, _hDllHandle ; hModule 22. mov ecx, _lpApiString ; lpApiString 23. mov ebx, eax ; ebx = hModule 24. mov edi, ecx ; edi = lpApiString 25. xor al, al 26.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 27.; 获得_lpApiString长度 28.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 29._Scasb: 30. scasb 31. jnz _Scasb ; 单字节扫描edi指向的字符串,不和al相等跳转 32. dec edi ; 减去字符串结尾0 33. sub edi, ecx ; 字符串结尾减去起始获得长度 34. xchg edi, ecx ; 将长度放到ecx, 字符串放到edi 35.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 36.; 读取导出表的一些数据 37.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 38. mov eax, [ebx + 3ch] ; 获得PE头的位置 39. mov esi, [ebx + eax + 78h] ; 获得IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER32.IMAGE_DATA_DIRECTORY 40. lea esi, [esi + ebx + IMAGE_EXPORT_DIRECTORY.NumberOfNames] ; esi指向导出表结构的NumberOfNames 41. lodsd ; 将NumberOfNames读取到eax 42. xchg eax, edx ; edx = NumberOfNames 43. lodsd ; 将AddressOfFunctions读取到eax 44. push eax ; [esp] = AddressOfFunctions 45. lodsd ; eax = AddressOfNames 46. xchg eax, ebp ; ebp = AddressOfNames 47. lodsd ; eax = AddressOfNameOrdinals 48. xchg eax, ebp ; eax = AddressOfNames, ebp = AddressOfNameOrdinals 49. add eax, ebx ; eax = 函数名称RVA数组 50. 51. mov [esp - 4 * 1], edi ; 临时存储 52. mov [esp - 4 * 2], ecx ; 临时存储 53.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 54.; 扫描指定api是否存在dll的导出表中 55.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 56._LoopScas: 57. dec edx ; edx = NumberOfNames 58. js _Ret ; 为负转移,即为-1转移 59. mov esi, [eax + edx * 4] ; 取得函数名称RVA, 乘4是因为函数名称RVA数组是dword类型的 60. add esi, ebx ; esi = 函数名称的实际地址 61. repz cmpsb ; esi和edi循环单字节比较,相等循环 62. jz _GetAddr 63. mov edi, [esp - 4 * 1] ; 还原edi 64. mov ecx, [esp - 4 * 2] ; 还原ecx 65. jmp _LoopScas 66.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 67.; 获取指定api的地址 68.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 69._GetAddr: 70. shl edx, 1 ; edx = 函数名地址表索引, 乘以2是因为AddressOfNameOrdinals是word数组 71. add ebp, edx ; ebp = 函数序号表的RVA, 加edx为取得指定索引的序号 72. movzx eax, word ptr [ebp + ebx] ; 取得指定序号值 73. shl eax, 2 ; 乘以4是因为AddressOfFunctions为dowrd数组 74. add eax, [esp] 75. mov eax, [ebx + eax] ; 取得指定序号函数的地址RVA 76. add eax, ebx ; 获得函数实际地址 77. 78._Ret: 79. pop ecx ; 将AddressOfFunctions弹出堆栈 80. pop ebp 81. ret 82. 83._GetApiAddress endp 84. 85._WinMain proc 86. 87. invoke LoadLibrary, addr szDllName 88. invoke _GetApiAddress, eax, addr szApiString 89. push MB_OK 90. lea ebx, szCaption 91. push ebx 92. lea ebx, szText 93. push ebx 94. push 0 95. call eax 96. ret 97. 98._WinMain endp 99. 100.start: 101. 102. call _WinMain 103. invoke ExitProcess, 0 104. 105. end start
hash算法搜索获得api函数地址的实现
我们一般要获得一个函数的地址,通常采用的是明文,例如定义一个api函数字符串"MessageBoxA",然后在GetProcAddress函数中一个字节一个字节进行比较。这样弊端很多,例如如果我们定义一个杀毒软件比较敏感的api函数字符串,那么可能就会增加杀毒软件对我们的程序的判定值,而且定义这些字符串还有一个弊端是占用的字节数较大。我们想想如何我们的api函数字符串通过算法将它定义成一个4字节的值,然后在GetProcAddress中把AddressOfNames表中的每个地址指向的api字符串通过我们的算法压缩成4字节值后,与我们之前定义的4字节值进行判断,如果匹配成功则读取函数地址。
我们来看一种rol 3移位算法,这个算法是每次将目的地址循环向左移动3位,然后将低字节与源字符串的每个字节进行异或。算法很精巧方便。还有很多方便小巧的算法,例如ROR 13等算法,其实它和我们上面的基本一样,只不过它将函数名表的字符串通过我们的算法过程获得hash值后与我们之前定义的hash值进行匹配,匹配成功则获得对应函数的地址。
下面的代码通过hash搜索WinExec函数来运行Clac:
.386 02. .model flat, stdcall 03. option casemap:none 04. 05.include windows.inc 06.include user32.inc 07.include kernel32.inc 08.includelib user32.lib 09.includelib kernel32.lib 10. 11. .const 12.szCalc db ‘calc.exe‘, 0 13. 14. .code 15. 16._GetKrnl32 proc 17. 18.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 19.; 获取kernel32.dll的地址,因为xp和win7不一样 20.; 故采用比较名称的方式获取地址,具体见Kernel32基地址获得学习 21.; http://blog.csdn.net/programmingring/article/details/11357393 22.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 23. assume fs:nothing 24. mov eax, fs:[30h] 25. mov eax, [eax + 0ch] 26. mov eax, [eax + 1ch] ; 第一个LDR_MODULE 27. 28.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 29.; 压入kernel32.dll的unicode字符串 30.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 31. push dword ptr 006ch 32. push dword ptr 6c0064h 33. push dword ptr 2e0032h 34. push dword ptr 33006ch 35. push dword ptr 65006eh 36. push dword ptr 720065h 37. push word ptr 006bh 38. mov ebx, esp ; ebx指向字符串 39. 40.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 41.; 开始比较 42.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 43._Search: 44. cmp eax, 0 45. jz _NotFound 46. cmp eax, 0ffffffffh 47. jz _NotFound 48. lea esi, [eax + 1ch] ; esi指向UNICODE_STRING结构 49. xor ecx, ecx 50. mov cx, 13 ; 比较的长度 51. mov esi, [esi + 4] ; 获得UNICODE_STRING结构的Buffer成员 52. mov edi, ebx ; edi指向kernel32unicode字符串 53. repz cmpsw 54. or ecx, ecx ; ecx减完说明相等 55. jz _Found 56. mov eax, [eax] ; 获取下一个LDR_MODULE 57. jmp _Search 58. 59._NotFound: 60. mov eax, 0ffffffffh 61. jmp _Over 62. 63._Found: 64. mov eax, [eax + 08h] ; 获得地址 65. 66._Over: 67. add esp, 26 68. ret 69. 70._GetKrnl32 endp 71. 72._GetRolHash proc _lpApiString 73. 74. mov eax, _lpApiString 75. push esi 76. xor edx, edx 77. xchg eax, esi ; esi = _lpApiString 78. cld ; 递增 79. 80._Next: 81. lodsb ; 从esi指向的地址中取一个字符 82. test al, al ; 比较是否为0 83. jz _Ret 84. rol edx, 3 ; 左循环移位3位 85. xor dl, al ; 低字节和字符异或 86. jmp _Next 87. 88._Ret: 89. xchg eax, edx ; eax = 处理后的_lpApiString的hash 90. pop esi 91. ret 92. 93._GetRolHash endp 94. 95._GetApi proc _hDllHandle, _iHashApi 96. 97. push ebp 98. mov eax, _hDllHandle 99. mov ecx, _iHashApi 100. mov ebx, eax 101. mov edi, ecx 102. 103. mov eax, [ebx + 3ch] 104. mov esi, [ebx + eax + 78h] ; Get Export RVA 105. lea esi, [esi + ebx + IMAGE_EXPORT_DIRECTORY.NumberOfNames] 106. lodsd 107. xchg eax, edx ; edx = NumberOfNames 108. lodsd 109. push eax ; [esp] = AddressOfFunctions 110. lodsd 111. xchg eax, ebp ; ebp = AddressOfNames 112. lodsd 113. xchg eax, ebp ; ebp = AddressOfNameOrdinals, eax = AddressOfNames 114. add eax, ebx 115. xchg eax, esi ; esi = AddressOfNames 116. 117._LoopScas: 118. dec edx 119. js _Ret 120. lodsd ; eax = api名称的rva 121. add eax, ebx ; eax = api名称的实际地址 122. push edx 123. invoke _GetRolHash, eax ; 计算hash字符串 124. 125. pop edx 126. cmp eax, edi ; 比较和传入的hash参数是否相等 127. jz _GetAddr 128. 129. add ebp, 2 ; 递增,和esi相对应 130. jmp _LoopScas 131. 132._GetAddr: 133. movzx eax, word ptr [ebp + ebx] ; 取得序号值 134. shl eax, 2 ; 乘4 135. add eax, [esp] ; 在AddressOfFunctions取得相应序号值位置的值 136. mov eax, [ebx + eax] ; 取得函数地址 137. add eax, ebx 138. 139._Ret: 140. pop ecx 141. pop ebp 142. ret 143. 144._GetApi endp 145. 146._WinMain proc 147. 148. invoke _GetKrnl32 149. invoke _GetApi, eax, 016ef74bh ; "WinExec"的hash 150. push SW_SHOW 151. lea ebx, szCalc 152. push ebx 153. call eax 154. ret 155. 156._WinMain endp 157. 158.start: 159. call _WinMain 160. invoke ExitProcess, 0 161. 162. end start