在我的上一篇帖子
查找进程API内存地址的小程序中写了一个小工具,枚举进程的输入表函数地址,有朋友要我公布源代码,很惭愧,那个程序代码有待优化,就不公布了,这个帖子公布源代码,那个工具中用到的代码和这篇类似,可以作为相互参考。
看到老罗写的一篇“暴力搜索内存获取API地址”的帖子,很受启发,现在有些程序有保护,用我那个查找进程API内存地址的小程序可能读不了其进程,从而也就无法获取他们的API地址了。老罗的方法可以直接获取用户系统中的API地址,因为它不需要读取其他程序内存地址。按老罗的方法,查找API地址必须首先找到系统Dll的基址,老罗用的方法就是现在网上流行的3种之一,这里我用一种和网上流行的不一样的方法获取系统Dll基址,并将老罗未完成的搜索内存获取API地址未写的代码写出来。
思路:
1、学习过PE文件格式的童鞋应该知道,PE的头文件中就包含了PE文件的基址,那么我们直接读取Dll的PE中的基址不就可以轻松的得到Dll的基址了么,OK,go on
2、要得到Dll文件,首先要知道其地址,一般都放在C:\WINDOWS\system32文件夹中,有的童鞋马上会扔鞋子过来,我丫系统没装C盘呢,我丫系统不是XP呢
3、威武的Windos API来了,GetWindowsDirectory就可以得到系统目录,再在系统目录后加个\system32\xxx.dll就完美解决了
4、OK,找到系统Dll了,载入,读取其基址,读取其输出表RVA
5、然后根据输出表RVA和基址取输出表的FAT,FNT
6、再然后就是暴力男人狂搜FNT说指向的API名称,并和我们想要的API名称进行对比,找到后返回FAT找其地址
是不是很简单呢,同时聪明的童鞋们还可以发现,我们载入了系统Dll,那么直接在Dll中就可以找到我们想要的API地址,具体方法和内存中暴搜一样的,只是把基址换成我们读取的Dll文件在内存中的地址就行了
说了这么多废话,我们以查找开始“OpenFileMapping”这个函数地址为例,上代码:
invoke FAA_FindKernel32Path, addr szKernel32Path ;获取Kernel32.dll绝对路径
invoke FAA_FindExportRVA, addr szKernel32Path ;载入Kernel32.dll文件,查其输出表RVA和基址
invoke FAA_FindExportAPIAddr, Kernel32BaseAddr, Kernel32ExportRVA, addr szOpenFileMapping ;搜索内存查找OpenFileMapping函数的地址
.if eax == 0
invoke FAA_MsgBox , addr szFindAPIFail
.else
invoke wsprintf, addr EditBuffer, addr szFormat, eax
invoke SetDlgItemText, hDlgmain, IDC_EDT_OutPut, addr EditBuffer
.endif
ret
看完上面代码是不是感觉很简单,3个函数就搞定了,
,下面我们来分析这几个函数
1、获取Dll路径,因为OpenFileMapping在Kernel32.dll中,我们要查找Kernel32.dll的绝对路径,放入szKernel32Path中
;名称:FAA_FindKernel32Path
;功能:获取Kernel32.dll路径
;参数:pPathBuffer=存储路径的地址指针
;返回:eax,eax=0表示获取失败
FAA_FindKernel32Path proc uses esi pPathBuffer:DWORD
invoke GetWindowsDirectory, pPathBuffer, 256
invoke wsprintf, pPathBuffer, addr szPathFormat, pPathBuffer ;合并系统目录,获取Kernel32.dll完整路径
ret
FAA_FindKernel32Path endp
2、读取Kernel32.dll,查其基址和输出表RVA,分别放入Kernel32BaseAddr和Kernel32ExportRVA中
;名称:FAA_FindExportRVA
;功能:搜索当前程序的内存查找Kernel32.dll基址和输出表RVA
;参数:无
;返回:无
FAA_FindExportRVA proc uses ebx esi _szFilePath:DWORD
local @pImage_NT_Headers:DWORD ;IMAGE_DOS_HEADER(PE头文件)在映像文件中的地址指针
local @hFile:DWORD
local @hMemory:DWORD
local @pMemory:DWORD
local @MEMSIZE:DWORD
mov @MEMSIZE, 1000000h ;分配16M大小的内存
invoke CreateFile, _szFilePath, \
GENERIC_READ ,\
FILE_SHARE_READ ,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov @hFile, eax
invoke GlobalAlloc, GMEM_MOVEABLE or GMEM_ZEROINIT, @MEMSIZE
mov @hMemory, eax
invoke GlobalLock, @hMemory
mov @pMemory, eax
invoke ReadFile, @hFile, @pMemory, @MEMSIZE, edi, NULL
mov esi, @pMemory ;esi保存映像文件的首地址
mov ebx, esi
assume ebx:ptr IMAGE_DOS_HEADER
mov eax, [ebx].e_lfanew ;取PE的首地址,即PE标志位
mov eax, [eax+esi] ;将PE首地址转化为映像文件地址,并将PE标志放入eax
cmp eax, 4550h ;检测PE标志是否正确,不正确则退出
jne FAA_FindExportRVA_Exit
add ebx, [ebx].e_lfanew ;取PE首地址作为IMAGE_NT_HEADERS结构首地址
mov @pImage_NT_Headers, ebx
assume ebx:ptr IMAGE_NT_HEADERS
mov eax, [ebx].OptionalHeader.ImageBase
mov Kernel32BaseAddr, eax ;获取Kernel32.dll基地址
lea ebx, [ebx].OptionalHeader.DataDirectory ;取DataDirectory的地址
assume ebx:ptr IMAGE_DATA_DIRECTORY
mov eax, [ebx].VirtualAddress
mov Kernel32ExportRVA, eax ;获取Kernel32.dll输出表RVA
FAA_FindExportRVA_Exit:
assume ebx:nothing
invoke CloseHandle, @hFile
invoke GlobalUnlock, @pMemory
invoke GlobalFree, @hMemory
ret
FAA_FindExportRVA endp
3、最后就是暴力搜索内存了
;名称:FAA_FindExportAPIAddr
;功能:查找Dll中指定名称的API地址
;参数:_BaseAddr=Dll基址
;参数:_ImportRVA=进程的输出表RVA
;参数:_szAPIName=要查找的API名称首地址
;返回:eax,eax=API函数地址,eax=0表示查找失败
FAA_FindExportAPIAddr proc uses ebx esi edi _BaseAddr:DWORD, _ImportRVA:DWORD, _szAPIName:DWORD
local @SizeOfFNT:DWORD
mov ebx, _BaseAddr
add ebx, _ImportRVA ;获取输出表在内存中的地址
assume ebx:ptr IMAGE_EXPORT_DIRECTORY
mov eax, [ebx].NumberOfNames ;将函数总数乘以4,得FNT表大小
shl eax, 2
mov @SizeOfFNT, eax
mov edi, [ebx].AddressOfNames ;获取输出表API名称查询表(FNT)RVA
add edi, _BaseAddr ;获取输出表API名称查询表(FNT)内存地址
push edi
mov edi, _szAPIName ;查找0计算字符串长度
mov ecx, 256 ;设置查找计算器
mov al, 0
cld
repne scasb ;不相等则重复
mov ecx, edi ;将字符串长度放入ecx(包含结尾0)
sub ecx, _szAPIName
pop edi
mov esi, _szAPIName
sub edx, edx
sub eax, eax ;eax置0
.while edx < @SizeOfFNT ;遍历Dll所有函数名称,当计数edx=Dll函数总数时退出循环
push ecx ;保存字符串长度
push edi ;保存edi,比较API名称
push esi
mov edi, [edi] ;取API名称的RVA
add edi, _BaseAddr ;取API名称的内存地址
cld
repe cmpsb
pop esi ;将esi重新指向_szAPIName首地址
pop edi
pop ecx
jnz FAA_FindExportAPIAddr_NoFind ;如果ecx=0,说明函数字符全部相同
mov eax, [ebx].AddressOfFunctions ;取FAT表RVA
add eax, _BaseAddr ;取FAT表RVA内存地址
add eax, edx ;取查找函数FAT表项的地址
mov eax, [eax] ;取查找函数的RVA
add eax, _BaseAddr ;取查找函数的内存地址
.break ;找到则退出循环
FAA_FindExportAPIAddr_NoFind:
add edx, 4 ;计数+4指向下一个FNT表项
add edi, 4 ;edi指向下一个FNT表项
.endw
assume ebx:nothing
ret
FAA_FindExportAPIAddr endp
OK,到此全部完工,代码中注释很明确了,代码中命名很长,但英语稍好的童鞋一眼就能看出其代表的什么意思。
附源文件(编译软件:MASMPlus,语言:汇编):
[课程]Android-CTF解题方法汇总!