首页
论坛
课程
招聘
[原创]总结一下得到内核模块地址的方法,参考N多大牛
2011-8-14 21:57 16094

[原创]总结一下得到内核模块地址的方法,参考N多大牛

2011-8-14 21:57
16094
//参考:combojiang:   http://hi.baidu.com/combojiang/blog/item/bfa7a6d9f1c913ee38012f28.html
//      combojiang:   http://hi.baidu.com/hu3167343/blog/item/3fb1cc91e9582507d31b7023.html
//      combojiang:   http://bbs.pediy.com/showthread.php?t=58447
//      莫灰灰:       http://hi.baidu.com/hu3167343/blog/item/3fb1cc91e9582507d31b7023.html
//      还有一些,忘了出处了,一并感谢,此次只是做个总结

网上说的比较常见的4种方法:

1、通过DriverEntry传入的DriverObject参数的DriverSection成员指向LDR_DATA_TABLE_ENTRY结构,通过遍历这张表得到ntoskrnl的基址和大小

2、ZwQuerySystemInformation大法

3、搜索内存

4、利用KPCR结构

存在的问题:

1、第1种方法和第4种方法得到的结果比ZwQuerySystemInformation少一个

2、第1种方法如果输出BaseDllName是ntoskrnl.exe,如果输出FullDllName则是:\WINDOWS\system32\ntkrnlpa.exe,地址都是:804d8000,不明白为何

环境:虚拟机VMWare:WIN XP SP3  + WDK ---- WINXP Check方式编译

#include <ntddk.h>
//---------------------------------//
//下面的结构包含了一些重要信息。如:PsLoadedModuleList ,它是Windows加载的所有内核模块构成的链表的表头。
//PsLoadedModuleList就是如下这个结构体中InLoadOrderLinks。即为LDR_DATA_TABLE_ENTRY结构的第一项。
#pragma pack(push)//结构定义
#pragma pack(1)                  
typedef struct _LDR_DATA_TABLE_ENTRY
{
    LIST_ENTRY         InLoadOrderLinks;
    LIST_ENTRY         InMemoryOrderLinks;
    LIST_ENTRY         InInitializationOrderLinks;
    PVOID              DllBase;
    PVOID              EntryPoint;
    ULONG              SizeOfImage;
    UNICODE_STRING     FullDllName;
    UNICODE_STRING     BaseDllName;
    ULONG              Flags;
    USHORT             LoadCount;
    USHORT             TlsIndex;
    union
    {
        LIST_ENTRY     HashLinks;
        struct
        {
            PVOID      SectionPointer;
            ULONG      CheckSum;
        };
    };
    union
    {
        ULONG           TimeDateStamp;
        PVOID           LoadedImports;
    };
    PVOID               EntryPointActivationContext;
    PVOID               PatchInformation;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
#pragma pack(pop)
//---------------------------------------------------------------------------------------------------//函数声明
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING pRegistryPath);
NTSTATUS DriverUnload();
//Method3用到,指定当前线程运行在那个处理器
NTKERNELAPI VOID KeSetSystemAffinityThread ( KAFFINITY Affinity );
NTKERNELAPI VOID KeRevertToUserAffinityThread ( VOID );

NTKERNELAPI NTSTATUS ZwQuerySystemInformation(
	                                          IN ULONG SystemInformationClass,
                                              IN OUT PVOID SystemInformation,
                                              IN ULONG SystemInformationLength,
                                              IN PULONG ReturnLength OPTIONAL 
											  );

#pragma alloc_text(INIT, DriverEntry)
#pragma alloc_text(PAGE, DriverUnload)
//---------------------------------------------------------------------------------------------------//变量、常量、结构定义
UNICODE_STRING BaseName;

#define SystemModuleInformation 11  //Method2要用到11功能号

typedef struct _SYSTEM_MODULE_INFORMATION_ENTRY
{
	ULONG   Unknow1;
	ULONG   Unknow2;
	#ifdef  _WIN64
	ULONG   Unknow3;
	ULONG   Unknow4:
	#endif
	PVOID   Base;
	ULONG   Size;
	ULONG   Flags;
	USHORT  Index;
	USHORT  NameLength;
	USHORT  LoadCount;
	USHORT  ModuleNameOffset;
	char    ImageName[256];
}SYSTEM_MODULE_INFORMATION_ENTRY, *PSYSTEM_MODULE_INFORMATION_ENTRY;

typedef struct _SYSTEM_MODULE_INFORMATION
{
   ULONG Count;//内核中以加载的模块的个数
   SYSTEM_MODULE_INFORMATION_ENTRY Module[1];
}SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;


//---------------------------------------------------------------------------------------------------//
/*
    用到了DriverObject域的InLoadOrderLinks链表

注意:
   下面的代码会用到一个宏:
---------------------------------------------------------------------------------------------------------------------
CONTAINING_RECORD 这样的一个宏,它的定义如下:
#define CONTAINING_RECORD(address, type, field) ((type *)( (PCHAR)(address) - (ULONG_PTR)(&((type*)0)->field)))

根据网上资料:就是address -(field在type中的偏移)
----------------------------------------------------------------------------------------------------------------------
*/
VOID Method1(IN PDRIVER_OBJECT DriverObject)//遍历链表
{
	ULONG Base=0;//模块基地址
	LDR_DATA_TABLE_ENTRY* SectionBase=NULL;
	LIST_ENTRY* Entry=NULL;
    LIST_ENTRY InLoadOrderLinks;
    ULONG num=0;
	Entry=((LIST_ENTRY*)DriverObject->DriverSection)->Flink;

	do
	{
		SectionBase=CONTAINING_RECORD(Entry,LDR_DATA_TABLE_ENTRY,InLoadOrderLinks);//得到这个Entry所属的Section的地址,此方法经过验证可行

	    if (SectionBase->EntryPoint && 
            SectionBase->BaseDllName.Buffer &&
            SectionBase->FullDllName.Buffer && 
            SectionBase->LoadCount
            )
		{
			DbgPrint("方法一遍历模块名称:%wZ,地址:%x\n",&(SectionBase->FullDllName),SectionBase->DllBase);
			//DbgPrint("方法一遍历模块名称:%wZ,地址:%8X\n",&(SectionBase->BaseDllName),SectionBase->DllBase);
			num++;
			/*if(!RtlCompareUnicodeString(&(SectionBase->BaseDllName),&BaseName,FALSE))
			{
				DbgPrint("方法一模块名称:%wZ,地址:%x\n",&(SectionBase->BaseDllName),SectionBase->DllBase);
			}*/
		}
		Entry=Entry->Flink;
		
	}while(Entry!=((LIST_ENTRY*)DriverObject->DriverSection)->Flink);//直到遍历回来
	DbgPrint("方法一得到模块总数:%d\n",num);
}

void Method2()//ZwQuerySystemInformation大法
{
	PVOID pBuffer=0;//缓冲区
	NTSTATUS Result;//查询结果
	ULONG NeedSize;
	PSYSTEM_MODULE_INFORMATION pSystemModuleInformation;//将结果强制转换为该类型
	ULONG BufferSize = 0x5000;//初始分配内存大小,没有采用查询再分配的循环方法
	ULONG ModuleCount;//模块总数
	ULONG i;
	do
	{
		pBuffer=ExAllocatePool(NonPagedPool,BufferSize);
		if(pBuffer==NULL)
		{
			DbgPrint("分配内存失败!\n");
			return FALSE;
		}
		Result=ZwQuerySystemInformation(SystemModuleInformation,pBuffer,BufferSize,&NeedSize);
		if(Result==STATUS_INFO_LENGTH_MISMATCH )//分配不够
		{
			ExFreePool(pBuffer);
			//大小乘以2,重新分配
			BufferSize*=2;
		}
		else if(!NT_SUCCESS(Result))//失败,放弃吧
		{
			DbgPrint( "查询失败,错误码:%8X\n", Result );
			ExFreePool(pBuffer);
			return FALSE;
		}
	 }while( Result == STATUS_INFO_LENGTH_MISMATCH );

	pSystemModuleInformation = (PSYSTEM_MODULE_INFORMATION)pBuffer;//类型转换
	ModuleCount=pSystemModuleInformation->Count;//模块总数
	for(i=0;i<ModuleCount;i++)
	{
		DbgPrint( "方法二遍历模块名称:%s,地址:%8X\n", pSystemModuleInformation->Module[i].ImageName, pSystemModuleInformation->Module[i].Base );
	}
	DbgPrint("方法二得到模块总数:%d\n",ModuleCount);
	ExFreePool(pBuffer);
	return TRUE;
}
VOID Method3(ULONG Base)//搜索内存,从0x80000000-----0xa0000000
{
	;
}
//内核中FS寄存器指向KPCR结构,每个处理器都有一个,使用第一个处理器即可其中比较重要的是KdVersionBlock这个指针, 它指向一个DBGKD_GET_VERSION64这个结构.

//这个结构体里面包含了一些重要信息。如:PsLoadedModuleList ,它是Windows加载的所有内核模块构成的链表的表头

//两个处理器对应的KPCR结构是有区别的, 只有第一个处理器的KPCR域KdVersionBlock才指向DBGKD_GET_VERSION64这个结构.

//-------------------------------------仔细观察定义会发现,这个跟使用DriverObject方法达到的链表示一样的!
void Method4()                              
{
	ULONG Addr;//内核地址

	LIST_ENTRY* Entry=NULL;
    LIST_ENTRY InLoadOrderLinks;
	LDR_DATA_TABLE_ENTRY* SectionBase=NULL;//LdrData->DllBase,LdrData->FullDllNme

    ULONG num=0;
    //-----------------------------------------------------------------------------//在莫灰灰基础上修改一小部分
    KeSetSystemAffinityThread(1);//使当前线程运行在第一个处理器上
	_asm
	{
		push  eax
		mov   eax,FS:[0x34]                     ;指向KdVersionBlock的指针
		add   eax,18h                           ;得到指向PsLoadedModuleList的地址,即该指针的地址,指针里存有PsLoadedModuleList的地址
		mov   eax,[eax]                         ;得到PsLoadedModuleList的地址
        mov   eax,[eax]                         ;得到PsLoadedModuleList的内容
		//mov   eax,[eax+18h]                     ;取出DllBase, 即ntoskrnl.exe的基地址
        mov   Addr,eax
        pop  eax
	}
	
	KeRevertToUserAffinityThread();//恢复线程运行的处理器
	//----------------------------------------------------------------------// 以下跟方法一重复

    Entry=(LIST_ENTRY*)Addr;

	do
	{
		SectionBase=CONTAINING_RECORD(Entry,LDR_DATA_TABLE_ENTRY,InLoadOrderLinks);//得到这个Entry所属的Section的地址,此方法经过验证可行

	    if (SectionBase->EntryPoint && 
            SectionBase->BaseDllName.Buffer &&
            SectionBase->FullDllName.Buffer &&
            SectionBase->LoadCount
            )
		{
			DbgPrint("方法四遍历模块名称:%wZ,地址:%8X\n",&(SectionBase->FullDllName),SectionBase->DllBase);
			num++;
		}
		Entry=Entry->Flink;
		
	}while(Entry!=(LIST_ENTRY*)Addr);//直到遍历回来
	DbgPrint("方法四得到模块总数:%d\n",num);
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING pRegistryPath)
{
	ULONG EntryAddr;
	_asm
	{
		push ecx;
		lea ecx,[ebp][4];//得到DriverEntry返回地址
		mov EntryAddr,ecx;
		pop ecx;
	}
	EntryAddr=*(ULONG*)EntryAddr;
	DbgPrint("驱动返回地址:%8X\n",EntryAddr);
	RtlInitUnicodeString(&BaseName,L"ntoskrnl.exe");
	DbgPrint("驱动加载成功!\n");
	//-------------------------------//
	Method1(pDriverObject);
	//-------------------------------//
	Method2();
       //-------------------------------//
	Method3();
        //-------------------------------//
	Method4();
	//-------------------------------//
	pDriverObject->DriverUnload=DriverUnload;
	return STATUS_SUCCESS;
}
NTSTATUS DriverUnload()
{
	DbgPrint("驱动卸载成功\n");
}

看雪论坛2020激励机制:能力值、活跃值和雪币体系!会员积分、权限和会员发帖、回帖活跃程度关联!

收藏
点赞0
打赏
分享
最新回复 (16)
雪    币: 189
活跃值: 活跃值 (11)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
BiffoLee 活跃值 2011-8-15 07:12
2
0
ULONG EnrtyAddr;//拼写错误
EntryAddr=(ULONG*);->EntryAddr=(ULONG*)DriverEntry;
雪    币: 0
活跃值: 活跃值 (10)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
tihty 活跃值 2 2011-8-15 08:44
3
0
学习一下..
雪    币: 1392
活跃值: 活跃值 (10)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
futosky 活跃值 3 2011-8-15 16:50
4
0
谢谢提醒,这里本来是要注释掉的,忘记了,感谢补全!
雪    币: 1140
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
reinharddm 活跃值 2011-8-15 18:56
5
0
挺好的知识普及帖,感谢了!
雪    币: 321
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
pandaforum 活跃值 2011-8-16 08:52
6
0
学习一下,总结是非常必要的。
雪    币: 5972
活跃值: 活跃值 (106)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
傷遺忘 活跃值 2011-8-16 09:07
7
0
搜内存原理是这样的。
在DriverEntry内取得返回地址。
(为什么可以取返回地址,想一想驱动的加载过程具体说就nt!IopLoadDriver内那句Call dword ptr [edi+2c])而这个返回地址就在内核文件内部。然后以返回地址为起点,按页对齐(对齐只是为了加快搜索速度,不对齐也行)向前搜。直到匹配了PE特征。而第一个匹配PE特征的地址就是内核文件基地址。

明白了原理相信C代码就很容易了。我顺带贴一个。

BOOLEAN
FindBaseAndSize(
        IN PVOID SomePtr,
        OUT PVOID *BaseAddress OPTIONAL,
        OUT ULONG *ImageSize OPTIONAL
        )
{
        ULONG SomeAddress = (ULONG) SomePtr;

        for ( SomeAddress &= 0xFFFFF000 ; ; SomeAddress -= PAGE_SIZE )
        {
                if(MmIsAddressValid ((PVOID)SomeAddress) && *(USHORT*)SomeAddress == IMAGE_DOS_SIGNATURE ) // MZ signature?
                {
                        PVOID NtHeader = RtlImageNtHeader ((PVOID)SomeAddress);// + ((IMAGE_DOS_HEADER*)SomeAddress)->e_lfanew;

                        if (MmIsAddressValid ((PVOID)NtHeader) && *(ULONG*)NtHeader == IMAGE_NT_SIGNATURE) // PE signature?
                        {
                                if (ARGUMENT_PRESENT (BaseAddress))
                                        *BaseAddress = (PVOID)SomeAddress;

                                if (ARGUMENT_PRESENT (ImageSize))
                                        *ImageSize = ((IMAGE_NT_HEADERS*)NtHeader)->OptionalHeader.SizeOfImage;

                                return TRUE;
                        }
                }
        }

        return FALSE;
}

在DriverEntry内调用的时候FindBaseAndSize(DriverEntry返回地址,XXX,XXX)。

备注:
一、实际上也不一定非取得返回地址,内核内任意一个函数的地址都可以作为起点。
二、我顺手贴的这个代码不支持X64.
雪    币: 240
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
爱吃肥肉 活跃值 2011-8-16 12:28
8
0
很好的基础知识,学习了。
雪    币: 136
活跃值: 活跃值 (10)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
robey 活跃值 1 2011-8-16 16:03
9
0
学习写驱动。标记慢慢看。。
雪    币: 1392
活跃值: 活跃值 (10)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
futosky 活跃值 3 2011-8-16 18:47
10
0
[QUOTE=傷遺忘;991319]搜内存原理是这样的。
在DriverEntry内取得返回地址。
(为什么可以取返回地址,想一想驱动的加载过程具体说就nt!IopLoadDriver内那句Call dword ptr [edi+2c])而这个返回地址就在内核文件内部。然后以返回地址为起点,按页对齐(对齐只是为了加快搜索速度,不对...[/QUOTE]

感谢讲解

我这样试了试,估计有错误,指针什么的
void Method3(ULONG Base)//,OUT PVOID* BaseAddress,OUT PVOID* ImageSize)//搜索内存,从0x80000000-----0xa0000000
{
	for(Base&=0xfffff000;;Base-=PAGE_SIZE)
	{
		if(MmIsAddressValid((PVOID)Base)&&*(USHORT*)Base==IMAGE_DOS_SIGNATURE )  //MZ标识
		{
			PVOID NTHeader=RtlImageNtHeader((PVOID)Base);//获得NT头指针 
            if(MmIsAddressValid((PVOID)NTHeader)&&*(ULONG*)NTHeader==IMAGE_NT_SIGNATURE)     //PE文件标识
			{
				//MSDN:The ARGUMENT_PRESENT macro takes an argument pointer and returns FALSE if the pointer is NULL, TRUE otherwise.
				/*if (ARGUMENT_PRESENT (BaseAddress))
					*BaseAddress = (PVOID)SomeAddress;

                if (ARGUMENT_PRESENT (ImageSize))
                    *ImageSize = ((IMAGE_NT_HEADERS*)NtHeader)->OptionalHeader.SizeOfImage;*/
				DbgPrint("地址:%8X\n",Base);
			}
		}
		DbgPrint("搜索%8X中...\n",Base);
		if(Base<0x80000000)
		{
			DbgPrint("搜索失败!\n");
				break;
		}
			
	}
}


结果WinDbg看输出都一个小时了才到  搜索E88C4000中...
雪    币: 1392
活跃值: 活跃值 (10)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
futosky 活跃值 3 2011-8-18 23:53
11
0
能不能说说上面的代码有哪些问题
雪    币: 107
活跃值: 活跃值 (35)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yangya 活跃值 2011-8-24 22:54
12
0
mark!!
雪    币: 238
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
foeyes 活跃值 2011-8-25 10:00
13
0
不错不错,学习~
雪    币: 1392
活跃值: 活跃值 (10)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
futosky 活跃值 3 2012-2-6 10:40
14
0
驱动的返回地址是这么得到的吗?好像不是啊,基础太差了
        EntryAddr=(ULONG)DriverEntry;
        DbgPrint("%8X\n",EntryAddr);
得到的是:  驱动返回地址:F8D14990
明显不在内核文件范围内,求指教
雪    币: 1392
活跃值: 活跃值 (10)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
futosky 活跃值 3 2012-2-7 09:01
15
0
方法3终于搞定了
雪    币: 244
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
BoyXiao 活跃值 2012-2-29 15:49
16
0
楼主灰常辛苦
雪    币: 328
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
丫丫journey 活跃值 2012-9-9 12:14
17
0
学习学习~
游客
登录 | 注册 方可回帖
返回