首页
社区
课程
招聘
[原创]查找系统动态链接库(Dll)基地址和指定函数地址的一种方法
发表于: 2012-6-4 22:25 21308

[原创]查找系统动态链接库(Dll)基地址和指定函数地址的一种方法

2012-6-4 22:25
21308
在我的上一篇帖子查找进程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解题方法汇总!

上传的附件:
收藏
免费 6
支持
分享
最新回复 (14)
雪    币: 659
活跃值: (484)
能力值: ( LV9,RANK:210 )
在线值:
发帖
回帖
粉丝
2
写完帖子后发现一个问题:


打开程序,点“查找”即可在编辑中显示OpenFileMapping在内存中的地址,那个“调用地址”按钮本来是想写段代码演示下用找到的地址调用OpenFileMapping的,但我用"查找进程API内存地址的小程序"查了下,验证是一致的,就懒得写了,大家将就着用下,或者在源文件中去掉,界面很丑,已经被朋友批评过了,没办法,汇编写界面是软肋

还有各位童鞋要注意的是OpenFileMapping在Kernel32.dll中的名字是“OpenFileMappingA”,不要弄错了,这个程序是区分大小写的,不然比较会出错

欢迎高手指点
上传的附件:
2012-6-4 22:50
0
雪    币: 3080
活跃值: (5104)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
大哥,这个东西早就有了,叫做“API地址专家”,多看看那些你不屑的小工具,有时候会有大发现,谢谢,想把那个软件什么样,传上来,可是不会弄图片呀!你百度去吧!
2012-6-4 23:34
0
雪    币: 659
活跃值: (484)
能力值: ( LV9,RANK:210 )
在线值:
发帖
回帖
粉丝
4
呵呵,我写这个不是为了写工具,只是发源代码,分析方法和思路,程序只是把自己的想法表达出来的东西而已,例外提高下自己的PE分析水平
2012-6-5 00:17
0
雪    币: 615
活跃值: (172)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
5
主要就获取映像基址,
映像基址计算式子:*(ULONG*)(base+*(ULONG*)(base+0x3c)+0x34);

输出表地址计算式子: *(ULONG*)(base+*(ULONG*)(base+0x3c)+0x78);

还有
遍历输出表

这不能说是新方法,说白了就是最常规的方法了,首选的方法。

另外,我觉得Kernel32.dll是在系统目录下,CreateFile第一个参数应该可以直接写Kernel32.dll 吧?
2012-6-5 02:40
0
雪    币: 316
活跃值: (128)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
6
遍历PE输入表函数名称,GetProcAddress + wsprintf。
2012-6-5 02:46
0
雪    币: 659
活跃值: (484)
能力值: ( LV9,RANK:210 )
在线值:
发帖
回帖
粉丝
7
对于系统文件的确可以直接读取文件,忘了,多谢指导
2012-6-5 17:26
0
雪    币: 615
活跃值: (172)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
8
不客气,还得向你学习。。
2012-6-7 18:39
0
雪    币: 209
活跃值: (26)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
弱弱的问个问题,ImageBase说的是优先被加载的内存基址,对于dll来说这个地址很少会被加载到这个地址的。用这种方法获取加载的dll基址是不是有问题啊?我不太懂的,只是有点疑惑,如果我错了,就当笑话了。
2012-6-10 12:24
0
雪    币: 1689
活跃值: (379)
能力值: ( LV15,RANK:440 )
在线值:
发帖
回帖
粉丝
10
微软在发布系綂的时候,针对每个系统dll都进行过处理,使得ImageBase不重叠,这样使得系统dll可以优先加载到自己的首选基址上。详细情况参考《windows核心编程》

还有对于ASLR技术的应用可能导致系统dll的加载地址在首选加载基址附近浮动,不过一旦系统启动以后,不同进程中的加载基址是相同的。
2012-6-10 13:22
0
雪    币: 71
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
学习一下·1

谢谢分享

有码
2012-6-10 21:51
0
雪    币: 73
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
不会呀。已经预定好了。就在优先加载基址上加载。
2012-6-12 18:51
0
雪    币: 78
活跃值: (85)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
13
大哥,你这个程序有个巨大的BUG哦~~

    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                                  ;找到则退出循环

代码简单看了下,居然没有用AddressOfNameOrdinals这个字段对AddressOfNames和AddressOfFunctions的对应关系进行转换呢?函数名RVA数组和函数地址RVA数组可不是顺序对应的哦,不过恰好kernel32.dll里的函数名RVA数组和函数地址RVA数组一一对应,你随便换个dll估计就计算错误了,比如lpk.dll

另外,不管怎么说你的asm写的让人看得太纠结了呢
mov    al, 0
sub    edx, edx
sub    eax, eax
随便看了一下,这些指令优化一下总好看些吧
2012-6-14 16:41
0
雪    币: 6499
活跃值: (3117)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
14
Win7下按 查找 会崩溃
2012-6-19 21:48
0
雪    币: 203
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hjm
15
可以多加深理解!谢谢分享
2012-6-19 21:52
0
游客
登录 | 注册 方可回帖
返回
//