内核模块列表保存在一个有PsLoadedModuleList符号指向的内核列表中。为了获取这个列表的地址就要用到KPCR的方法,KPCR的全称是Kernel Processor Control Region。内核用这个区域来存储每个处理器所包含的各种信息。它被放置在fs寄存器指向的区段中(类似于应用层中的TEB)。它有一个区域叫做KdVersionBlock,这个区域指向了内核调试使用的一个结构体。而这个结构体则包含了各种内存结构的指针,其中就包括PsLoadedModuleList。
KPRC的定义
这个结构体可以在很多地方找到,其中IDA的ntddk.til文件中也有这个结构的定义。现在我们只需要知道KdVersionBlock位于KPRC结构体的的0x34处,并且它指向了DBGKD_GET_VERSION64。在DBGKD_GET_VERSION64的偏移量0x18处则可以找到PsLoadedModuleList。
现在我们就可以写一个小的Python函数来找到这个指针的的值。为了得到fs指向的区段的基址我们可以使用VMWare的“r”调试命令。GDB 调试器插件注册了一个IDC函数,叫做SendGDBMonitor()来发送命令到监视器,所以我们可以使用IDAPython的Eval()函数来调用它。
fs_str = Eval(‘SendGDBMonitor("r fs")’)
返回的数据格式如下所示:
fs 0×30 base 0x82744a00 limit 0×00002008 type 0×3 s 1 dpl 0 p 1 db 1
我们需要的是在base标记之后的数值。
kpcr = int(fs_str[13:23], 16) #extract and convert as base 16 (hexadecimal) number
然后来获取KdVersionBlock的数值:
kdversionblock = Dword(kpcr+0×34)
最后我们来获取PsLoadedModuleList的地址:
PsLoadedModuleList = Dword(kdversionblock+0×18)
遍历内核模块
现在就可以根据上面的地址来遍历内核模块了,PsLoadedModuleList被声明为PLIST_ENTRY。PLIST_ENTRY的定义如下所示(双向链表):
typedef struct _LIST_ENTRY
{
PLIST_ENTRY Flink;
PLIST_ENTRY Blink;
} LIST_ENTRY, *PLIST_ENTRY;
所以要遍历所有的模块只需要跟随Flink指针,直到我们回到开始的地方就可以了。每一个模块的结构定义如下所示:
struct LDR_MODULE
{
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID BaseAddress;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;
};
现在我们可以来编写一个小函数遍历这个链表并且为每个模块创建一个区段。
#get the first module
cur_mod = Dword(PsLoadedModuleList)
while cur_mod != PsLoadedModuleList and cur_mod != BADADDR:
BaseAddress = Dword(cur_mod+0×18)
SizeOfImage = Dword(cur_mod+0×20)
FullDllName = get_unistr(cur_mod+0×24)
BaseDllName = get_unistr(cur_mod+0x2C)
#create a segment for the module
SegCreate(BaseAddress, BaseAddress+SizeOfImage, 0, 1, saRelByte, scPriv)
#set its name
SegRename(BaseAddress, BaseDllName)
#get next entry
cur_mod = Dword(cur_mod)
加载符号库
已经能够获取内核模块列表固然不错,但是如果不能加载符号库那么上面的工作也就没有多少用处。我们可以通过IDA的File->LoadFile->PDB file手动为每个模块加载符号库,但是这样做太蛋疼了。为什么不让它自动加载呢?
为了达到我们的目的这里就要用到PDB插件,通过查阅源代码(SDK)我们可以发现它支持下面的三个调用代码:
//call_code==0: user invoked ‘load pdb’ command, load pdb for the input file
//call_code==1: ida decided to call the plugin itself
//call_code==2: load pdb for an additional exe/dll
// load_addr: netnode("$ pdb").altval(0)
// dll_name: netnode("$ pdb").supstr(0)
第二个调用代码看起来正是我们需要的功能。但是,当前版本的IDAPython只包含一个非常基础的节点类,所以没有办法是用Python来实现这个功能。但是我们如果查看其它的调用代码可以看到这个插件重定义了模块的基址(通过$ PE header)和模块的路径(通过get_input_file_path()),并且我们可以通过set_root_filename()函数来设置输入文件的路径。同样如果我们使用第三个调用代码那么我们要避免出现“你是否想要加载符号库?”(”Do you want to load the symbols?”)的提示。
#new netnode instance
penode = idaapi.netnode()
#create netnode the in database if necessary
penode.create("$PE header")
#set the imagebase (-2 == 0xFFFFFFFE)
penode.altset(0xFFFFFFFE, BaseAddress)
#set the module filename
idaapi.set_root_filename(filename)
#run the plugin
RunPlugin("pdb",3)
但是我们需要用使用前面的文件路径(想要看到符号库的文件)来取代内核模块路径:
#path to the local copy of System32 directory
local_sys32 = r" E:\虚拟机系统\Windows 7\Shar4ed dll"
if FullDllName.lower().startswith(r"\systemroot\system32"):
#translate into local filename
filename = local_sys32 + FullDllName[20:]
也可以直接运行vmmodules.py脚本,在执行的过程中将会弹出如图09所示的提示窗口。
运行vmmodules.py出错:
The initial autoanalysis has been finished.
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "D:\tools\IDA\python\init.py", line 62, in runscript
execfile(script, globals())
File "C:/vmmodules.py", line 100, in <module>
list = get_PsLoadedModuleList()
File "C:/vmmodules.py", line 9, in get_PsLoadedModuleList
if not fs_str.startswith('fs '):
AttributeError: 'int' object has no attribute 'startswith'