首页
社区
课程
招聘
[原创]调用ZwQueryVirtualMemory遍历进程模块示例(再次更新)
发表于: 2009-4-5 03:09 24751

[原创]调用ZwQueryVirtualMemory遍历进程模块示例(再次更新)

2009-4-5 03:09
24751
菜鸟学习帖,高手请飘过

一直只知道IceSword(以及看雪论坛上开源的山寨版IceSword)是用ZwQueryVirtualMemory来遍历进程的虚拟内存,从而得到加载的模块的,不过从来没有自己试过。
看了山寨版IceSword相关代码,原来是在Ring3中实现的,就想自己动手整一整,结果发现原来不是那么难。于是用汇编写了个Win32控制台程序来做演示。
基本原理不复杂,倒是细枝末节耗去很多时间。
首先我是以区段内存对齐的单位0x1000字节进行遍历,这样会出现多个区段属于同一个文件。这样我使用两次调用ZwQueryVirtualMemory:
第一次使用参数MemoryBasicInformation,判断传回的MEMORY_BASIC_INFORMATION结构中的AllocationBase字段值与传入的Base是否相等,相等则说明这是文件的第一个区段,这时才进行第二次调用;
第二次使用参数MemorySectionName,获得区段文件名。
山寨版IceSword使用的是0x10000字节为间隔,似乎就避免了这个麻烦,而且也不会漏掉模块。

第一次写Win32控制台程序,也是第一次用msvcrt.dll中的函数(由masm32rt.inc中封装)做字符串处理,发现自己这一块居然超不熟,耗费了不少时间。

其实说原创也不是太合适了,因为参考了一些网上的代码,比如山寨版IceSword,还有提SeDebugPrivilege的那部分也是直接copy的网上的代码(其实只要不是访问系统进程,提权不是必须的)。

代码写得比较挫,又一次证实了自己在Win32汇编这块还很菜

调用方法:把进程ID作为命令行参数传入,如Query.exe 1000

update:看了大家的回帖之后又修改了一下,主要是在盘符转换方面不再偷懒,现在应该可以支持非本地磁盘了。
对这个功能,发现KsBinSword好像没有做?
网上搜索发现QueryDosDevice,但据说没有功能反过来的,这样只能先对A-Z的盘符调用QueryDosDevice,把结果保存起来,然后需要转换的时候再跟全文件名比对。

另外调试了一下ZwQueryVirtualMemory的调用,因为我很在意那几个nls文件,记得好像说是内核创建进程过程中将其映射的。想看看怎么把它们跟后面的Module区分开来。
发现这几个nls映像头部的AllocationProtect都有PAGE_EXECUTE属性,而其他各个exe和dll的映像头部的AllocationProtect属性没有PAGE_EXECUTE,但两者当前的Protect属性都有PAGE_EXECUTE。
因此加了几行代码来做检测,在原文中被注释掉了,如果把注释掉的还原,则显示出来的模块中不包括nls文件。

update2:
之前的版本只能取A-Z盘符,现在是遍历所有DOS符号设备,对所有设备取其映射的设备名,与ZwQueryVirtualMemory得到的结果一一做比对,找到相应的DOS符号设备名。
由于同一个设备可能映射到多个DOS设备,比如\Device\HarddiskVolume1,除了C:之外还有其他的DOS设备与之对应,为了取到C:,这里我取长度最短的一个DOS设备名来显示。
实现还是用QueryDosDevice,代码写得一团乱 不禁想Ring0真好啊,有Rtl(Io)VolumeDeviceToDosName可以用……
;===============================================================================
;use ZwQueryVirtualMemory to enum Modules of a process
;subsystem:console
;OS Platform:tested on Windows XP Professional simplified with Service Pack 3
;轩辕小聪 http://hi.baidu.com/yicong2007
;
;release notes:
;2009.04.11
;完全改写CreateDeviceList和ConvertDeviceStr两个函数,Ring3下使用QueryDosDevice,
;通过对所有的DOS符号连接获取其对应设备名,与得到的模块路径一一对照,克服了上一版本
;只能转换A-Z盘符的问题。
;
;2009.04.06 
;改写ConvertDeviceStr函数,实现A-Z盘符的转换,以支持非本地磁盘盘符的获取。
;遍历时只对具有PAGE_EXECUTE属性的内存查找MemorySectionName。
;如将被注释掉的四处代码还原,可过滤掉nls文件的显示。
;
;2009.04.05 
;第一个版本
;
;===============================================================================
include \masm32\include\masm32rt.inc
include \masm32\include\w2k\native.inc
includelib \masm32\lib\masm32.lib
include \masm32\macros\macros.asm
include \masm32\include\advapi32.inc
includelib \masm32\lib\advapi32.lib

.data
PID dd 0
Base dd 0
hProcess dd 0
DataLength dd 0
lpszPID db 120 dup(0)
lpoutdata db 300h dup(0)
lpFileName db 120h dup(0)
lpDosList dd 0
lpDeviceList dd 0
lpDosBuffer dd 0
lpDeviceBuffer dd 0
.code

CreateDeviceList proto

CreateDeviceList proc uses esi edi ebx edx
	local	num
	local	lpBuffer
	local	BufferLen

	mov	edi, 0
	xor	esi, esi
	.repeat	
		.if	esi!=0
			hfree(esi)	
		.endif
		add	edi, 1000h		
		mov	esi,halloc(edi)
		invoke	QueryDosDevice, NULL, esi, edi
	.until	eax!=0
	mov	lpDosBuffer, esi	
	mov	num, 0
	xor	eax, eax
	.repeat	
		inc	num
		invoke	crt_strlen, esi		
		lea	esi, [esi+eax+1]
	.until eax==0
	dec	num
	mov	eax, num
	lea	eax, [eax*4+4]
	mov	lpDosList, halloc(eax)
	mov	edi, lpDosList
	mov	eax, num
	mov	[edi], eax
	add	edi, 4
	xor	ebx, ebx
	mov	esi, lpDosBuffer
	.repeat	
		inc	ebx
		mov	[edi], esi
		add	edi, 4
		invoke	crt_strlen, esi		
		lea	esi, [esi+eax+1]
	.until ebx==num		
	mov	eax, num
	shl	eax, 8
	mov	ebx, num
	shl	ebx, 2
	add	eax, ebx
	push	eax
	mov	esi, halloc(eax)	
	mov	lpBuffer, esi
	pop	eax
	invoke	RtlZeroMemory, lpBuffer, eax
	mov	eax, num
	lea	eax, [eax*4+4]
	mov	lpDeviceList, halloc(eax)
	mov	edx, lpDeviceList
	mov	eax, num
	mov	[edx], eax
	mov	edi, lpDosList
	add	edi, 4
	xor	ebx, ebx
	mov	BufferLen, 0
	.repeat
		inc	ebx
		mov	ecx, [edi]
		invoke	QueryDosDevice, ecx, esi, MAX_PATH
		.if	eax!=0
			invoke	crt_strlen, esi
			inc	eax
			add	BufferLen, eax	
		.endif		
		add	edi, 4
		lea	esi, [esi+MAX_PATH]	
	.until	ebx==num
	inc	BufferLen
	mov	lpDeviceBuffer, halloc(BufferLen)
	invoke	RtlZeroMemory, lpDeviceBuffer, BufferLen
	mov	edi, lpDeviceBuffer
	mov	edx, lpDeviceList
	add	edx, 4
	mov	esi, lpBuffer
	xor	ebx, ebx
	.repeat
		inc	ebx
		push	edx
		invoke	crt_strlen, esi
		pop	edx
		.if	eax!=0	
			inc	eax
			push	eax
			push	edx
			invoke	crt_strncpy, edi, esi, eax
			pop	edx
			mov	[edx], edi
			pop	eax
			add	edi, eax
		.else
			mov	[edx], eax
		.endif	
		add	edx, 4	
		lea	esi, [esi+MAX_PATH]	
	.until	ebx==num
	hfree(lpBuffer)	
	mov	eax, 1		
	ret	
CreateDeviceList endp

ConvertDeviceStr proto :DWORD, :DWORD

;=====================================================
;把得到的文件名中的盘符翻成平常的C、D……并将字符串改
;成输出所需的格式
;=====================================================
ConvertDeviceStr proc uses esi edi ebx lpSource:DWORD, ImageBase:DWORD

	local	lptmp[MAX_PATH]:byte
	local	totalnum
	local	match
	local	lpmatchDev
	local	DevLen
	local	NameLen
	lea	edi, lptmp
	xor	eax, eax
	mov	match, eax
	mov	lpmatchDev, eax
	mov	NameLen, eax
	mov	DevLen, eax
	mov	ecx, MAX_PATH
	rep	stosb
	mov	esi, lpDeviceList
	mov	eax, [esi]
	mov	totalnum, eax
	add	esi, 4
	xor	ebx, ebx
	.repeat
		inc	ebx
		push	esi
		mov	edi, [esi]
		.if	edi!=0
			invoke	crt_strstr, lpSource, edi
			.if	eax==lpSource 
				.if	match==0
					mov	match, 1
				.endif	
				mov	eax, ebx
				lea	eax, [eax*4]
				mov	esi, lpDosList
				add	esi, eax
				mov	esi, [esi]
				invoke	crt_strlen, esi
				.if	NameLen==0 || NameLen>eax
					mov	NameLen, eax
					invoke	crt_strlen, edi
					mov	DevLen, eax
					mov	lpmatchDev, esi
				.endif	
			.endif	
		.endif	
		pop	esi
		add	esi, 4
	.until	ebx==totalnum	

	.if	match==0
		invoke	crt_strncpy, addr lptmp, lpSource, MAX_PATH
	.else	
		mov	ebx, lpmatchDev
		mov	eax, DevLen
		mov	esi, lpSource
		lea	esi, [esi+eax]
		invoke	crt_sprintf, addr lptmp, CTXT('%s%s'), ebx, esi
	.endif	
	invoke	crt_sprintf, lpSource, CTXT('%08X   %s',0dh,0ah), ImageBase, addr lptmp
	ret
			
ConvertDeviceStr endp

CleanUp proto

CleanUp proc uses esi edi ebx

	hfree(lpDosBuffer)
	hfree(lpDosList)
	hfree(lpDeviceList)
	hfree(lpDeviceBuffer)
	ret
CleanUp endp

DebugPrivilege   PROTO :DWORD

;=====================================================
;提SeDebugPrivilege,copy from network
;=====================================================

DebugPrivilege    proc uses esi edi ebx dwEnbled

	local	hToken
	local	tmpLuid:LUID,tkp:TOKEN_PRIVILEGES
    
	invoke	GetCurrentProcess
	lea	ebx,hToken
	invoke	OpenProcessToken, eax, TOKEN_ADJUST_PRIVILEGES Or TOKEN_QUERY, ebx
	invoke	LookupPrivilegeValue, NULL, CTXT('SeDebugPrivilege'), addr tmpLuid
	mov	tkp.PrivilegeCount,1
	push	tmpLuid.LowPart
	pop	tkp.Privileges[0].Luid.LowPart
	push	tmpLuid.HighPart
	pop	tkp.Privileges[0].Luid.HighPart
	.if	dwEnbled
		mov	tkp.Privileges[0].Attributes, SE_PRIVILEGE_ENABLED
	.else	
		mov	tkp.Privileges[0].Attributes,NULL
	.endif	
	invoke	AdjustTokenPrivileges, hToken, FALSE, addr tkp, sizeof TOKEN_PRIVILEGES, NULL, NULL
	invoke	GetLastError
	.if	eax == ERROR_SUCCESS
		push	TRUE
	.else	
		push	FALSE
	.endif	       
	invoke	CloseHandle,hToken
	pop	eax
	ret
	
DebugPrivilege    endp

Start        proc uses esi edi ebx

	invoke	GetCL, 1, offset lpszPID	
	invoke	crt_sscanf, offset lpszPID, CTXT('%d'), offset PID
	.if	PID==0
		invoke	StdOut, CTXT('Invaild PID.')
		ret	
	.endif	
	invoke	DebugPrivilege, 1
	.if	eax==FALSE
		invoke	StdOut, CTXT('Enable SeDebugPrivilege failed.')
		ret
	.endif	
	invoke	OpenProcess, PROCESS_QUERY_INFORMATION, 0, PID
	.if	eax==0
		invoke	StdOut, CTXT('OpenProcess failed.')
		ret
	.endif	
	mov	hProcess, eax
	invoke	GetModuleHandle, CTXT('ntdll.dll')
	mov	esi, eax
	invoke	GetProcAddress, esi, CTXT('ZwQueryVirtualMemory')
	mov	edi, eax
	invoke	StdOut, CTXT('ModuleBase ImageFileName',0dh,0ah)
	invoke	CreateDeviceList
	.repeat	
		push	offset DataLength
		push	300h
		push	offset lpoutdata
		push	MemoryBasicInformation
		push	Base
		push	hProcess
		call	edi
		mov	esi, offset lpoutdata
		;mov	edx, [esi+18h]
		assume	esi:ptr MEMORY_BASIC_INFORMATION
		;mov	ebx, [esi].AllocationProtect
		;and	ebx, WSLE_PAGE_EXECUTE
		mov	ecx, [esi].Protect
		and	ecx, WSLE_PAGE_EXECUTE
		mov	esi, [esi].AllocationBase
		assume	esi:nothing
		.if	eax==0 && esi==Base && ecx!=0 ;&& edx == SEC_IMAGE && ebx==0		
			push	offset DataLength
			push	300h
			push	offset lpoutdata
			push	MemorySectionName
			push	Base
			push	hProcess
			call	edi	
			.if	eax==0
				mov	esi, offset lpoutdata
				assume	esi:ptr MEMORY_SECTION_NAME
				movzx	eax, [esi].SectionFileName._Length
				.if	eax!=0
					mov	esi, [esi].SectionFileName.Buffer
					assume	esi:nothing
					invoke	crt_sprintf, offset lpFileName, CTXT('%ws'),esi
					invoke	ConvertDeviceStr, offset lpFileName, Base
					invoke	StdOut, offset lpFileName
				.endif					
			.endif
		.endif	
		mov	eax, Base
		add	eax, 1000h
		mov	Base, eax
	.until	Base==80000000h
	invoke	CleanUp
	ret

Start endp

end Start

结果演示:以下是一个svchost.exe进程的内存模块:

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
免费 8
支持
分享
最新回复 (19)
雪    币: 581
活跃值: (149)
能力值: ( LV12,RANK:600 )
在线值:
发帖
回帖
粉丝
2
ZwQueryVirtualMemory在ring3很容易被XXX..比如简单的HOOK就失效...不过目前这种办法还是不错的...绕过办法可以参考小伟的文章...不过yas anti rootkit不存在这个问题,至少能得到一个加载地址.. .
2009-4-5 07:20
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
3
Hook内存管理可以绕过yaskit吗?另外,Yaskit的ObjectHook好像只检测文件和注册表部分的?
2009-4-5 07:29
0
雪    币: 581
活跃值: (149)
能力值: ( LV12,RANK:600 )
在线值:
发帖
回帖
粉丝
4
Hook内存管理可以绕过yaskit吗?? 你是说隐藏内存???
如果隐藏内存是可以的..因为yaskit枚举DLL就是在内存找相关结构的...
2009-4-5 07:38
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
5
恩,我说的就是隐藏内存。不过应该也可以DKOM让进程中dll占用的内存看起来不像是一个可执行的映像
2009-4-5 10:04
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
6
还可以加上暴力枚举PE部分,我以前写的那个程序拿gmer实验时,把gmer自己加载的ntoskrnl都给找出来了~~不过对抗暴力也很容易啊~
2009-4-5 13:31
0
雪    币: 8865
活跃值: (2379)
能力值: ( LV12,RANK:760 )
在线值:
发帖
回帖
粉丝
7
用notify+overshadow就行了~~
没有一点hook,VMM技术保护内核某些内存不可写,notify记录加载的DLL~~
2009-4-5 14:30
0
雪    币: 8026
活跃值: (2511)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
牛太多了,只能潜
2009-4-5 20:49
0
雪    币: 148
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
to nObele:

我在你的冰刃源代码上单击了鼠标.
但那似乎不是源代码的链接.
期待你上传这个东西.
注:如果不是masm的就算了.
2009-4-5 21:22
0
雪    币: 7309
活跃值: (3778)
能力值: (RANK:1130 )
在线值:
发帖
回帖
粉丝
10
不支持动态卷,不支持映射驱动器
2009-4-5 21:36
0
雪    币: 722
活跃值: (123)
能力值: ( LV12,RANK:300 )
在线值:
发帖
回帖
粉丝
11
这个W3是加上去想使使有没有效果,试后忘了删掉
那几行include和.mode的确应该删,因为masm32rt.inc已经有包含了,如果不删会报warning
开始的时候我是删掉的,不过后来想写进去也没啥,如果为了不报warning,就应该删掉。

至于那英文,当时都凌晨了,头脑昏昏的,没注意语法语义,反正就那意思
2009-4-5 21:43
0
雪    币: 722
活跃值: (123)
能力值: ( LV12,RANK:300 )
在线值:
发帖
回帖
粉丝
12
这个是在识别盘符的时候偷懒了。因为主要是演示ZwQueryVirtualMemory,怎么正确识别盘符我还要自己再想想
2009-4-5 22:45
0
雪    币: 722
活跃值: (123)
能力值: ( LV12,RANK:300 )
在线值:
发帖
回帖
粉丝
13
更新了一下,解决这个问题
貌似KsBinSword里没解决?
2009-4-6 02:41
0
雪    币: 1653
活跃值: (1600)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
楼主也没解决,你试试运行个网络路径的程序,或者是VMWARE共享目录下的文件,又或者在LOCAL下挂载个盘符,你看你还能解析得出来么?呵呵!
2009-4-6 06:01
0
雪    币: 1653
活跃值: (1600)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
另外,判断是否镜像模块应判顿其是否是SEC_IMAGE,具体参考冰刃源代码
2009-4-6 06:03
0
雪    币: 722
活跃值: (123)
能力值: ( LV12,RANK:300 )
在线值:
发帖
回帖
粉丝
16
呃,没有测试过这种情况,只做了有映射到A-Z盘符的
SEC_IMAGE这个知道,但是如果不是SEC_IMAGE的,ZwQueryVirtualMemory(MemorySectionName)会返回文件名吗?我不太确定,如果不会,那实际上就等于自动排除了不是SEC_IMAGE的情况。

另外,冰刃有源代码?
2009-4-6 19:50
0
雪    币: 722
活跃值: (123)
能力值: ( LV12,RANK:300 )
在线值:
发帖
回帖
粉丝
17
这次不偷懒了,取系统所有的DOS符号设备去做对比,应该不止A-Z盘,其他的也可以出来了,不过没有测试过,不知道效果怎么样啊。
Ring0有Rtl(Io)VolumeDeviceToDosName可以用,Ring3下只能倒过来使,比较累
另外SEC_IMAGE也试了,如果加上这个判断,nls文件就不会出来了(发的程序中这个判断注释掉了)。
2009-4-11 21:57
0
雪    币: 635
活跃值: (101)
能力值: ( LV12,RANK:420 )
在线值:
发帖
回帖
粉丝
18
VolumeDeviceToDosName并不好用

另外你这个网络路径和VMWARE的共享目录也不行
2009-4-11 22:36
0
雪    币: 722
活跃值: (123)
能力值: ( LV12,RANK:300 )
在线值:
发帖
回帖
粉丝
19
看了wrk,IoVolumeDeviceToDosName的原理好像是向Device发IRP的,我不太了解这个,不太清楚适用范围有多大。
网络路径和VMWARE共享目录我还是不明白怎么做的
2009-4-11 22:40
0
雪    币: 148
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
给楼主推荐个VC++源码.有空翻译成masm32.呵呵
上传的附件:
2009-4-11 23:06
0
游客
登录 | 注册 方可回帖
返回
//