Hex Blog
February 19th, 2009, 13:17
We have already published short tutorial ("http://www.hex-rays.com/idapro/debugger/gdb_vmware_winkernel.pdf") on Windows kernel debugging with IDA and VMWare on our site, but the debugging experience can still be improved.
VMWare's GDB stub is very basic, it doesn't know anything about processes or threads (for Windows guests), so for anything high-level we'll need to do some extra work. We will show how to get the loaded module list and load symbols for all them using IDAPython.
<H4>Preparing VM for debugging</H2> Let's assume that you already have a VM with Windows (32-bit) installed. Before starting the debugging, copy files for which you want to see symbols to the host. If you're not sure, copy nt*.exe and hal.dll from System32, and the whole System32\drivers directory.
Edit the VM's .vmx file to enable GDB debugger stub:
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_91f1c9c.png
Add these lines to the file:
Save the file.
In VMWare, click "Power on this virtual machine" or click the green Play button on the toolbar.
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_100c5d47.png
Wait until the VM boots.
<H4>Debugging in IDA</H2> Start IDA.
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_m20f96fb6.gif
If you get the welcome dialog, choose "Go".
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_519c4c2f.gif
Choose Debugger | Attach | Remote GDB debugger.
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_6b9e4a85.gif
Enter "localhost" for hostname and 8832 for the port number.
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_m104fe357.gif
Choose <attach to the process started on target> and click OK.
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_m15202098.png
The execution should stop somewhere in the kernel (address above 0x80000000). You can step through the code, but it's not very convenient without any names. Let's try to gather some more information.
<H4>Getting the module list</H2> The list of kernel modules is stored in the list pointed to by the symbol in the kernel. To find its address, we will use the so-called "KPCR trick". KPCR stands for Kernel Processor Control Region. It is used by the kernel to store various information about each processor. It is placed at the base of the segment pointed to by the register (similar to PEB in user mode). One of the fields in it is which points to a structure used by the kernel debugger. It, in turn, has various pointers to kernel structures, including .
Definition of the KPCR structure can be found in many places, including IDA's ntddk.til. Right now we just need to know that field is situated at offset 0x34 from the start of KPCR. It points to , which has pointer at offset 0x18.
Let's write a small Python function to find the value of that pointer. To retrieve the base of the segment pointed to by fs, we can use the VMWare's debug monitor "r" command. GDB debugger plugin registers an IDC function to send commands to the monitor, and we can use IDAPython's function to call it:
Returned string has the following format:
We need the address specified after "base":
Then get the value of :
And finally :
<H4>Walking the module list</H2> is declared as . is a structure which represents a member of a double-linked list:
So, we just need to follow the pointer until we come back to where we started. A single entry of the list has the following structure:
Now we can write a small function to walk this list and create a segment for each module:
<H4>Loading symbols</H2> Having the module list is nice, but not very useful without symbols. We can load the symbols manually for each module using File | Load File | PDB file... command, but it would be better to automate it.
For that we can use the PDB plugin. From looking at its sources (available in the SDK), we can see that it supports three "call codes":
Call code 2 looks just like what we need. However, current IDAPython includes a rather basic implementation of netnode class and it is not possible to set supvals from Python. However, if we look at handling of the other call codes, we can see that the plugin retrieves module base from netnode and module path using function. IDAPython's function does work, and we can use set_root_filename() to set the input file path. Also, if we pass a call code 3, we will avoid the "Do you want to load the symbols?" prompt.
However, we need to replace the kernel-mode path by the local path beforehand:
Now we can gather all pieces into a single script. Download it here ("http://hexblog.com/idapro/vmware_modules.py")
After running it, you should have a nice memory map:
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_m17b305f6.gif
...and name list:
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_65b4d822.gif
Looks much better now. Happy debugging!
http://hexblog.com/2009/02/advanced_windows_kernel_debugg_1.html
VMWare's GDB stub is very basic, it doesn't know anything about processes or threads (for Windows guests), so for anything high-level we'll need to do some extra work. We will show how to get the loaded module list and load symbols for all them using IDAPython.
<H4>Preparing VM for debugging</H2> Let's assume that you already have a VM with Windows (32-bit) installed. Before starting the debugging, copy files for which you want to see symbols to the host. If you're not sure, copy nt*.exe and hal.dll from System32, and the whole System32\drivers directory.
Edit the VM's .vmx file to enable GDB debugger stub:
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_91f1c9c.png
Add these lines to the file:
Code:
debugStub.listen.guest32 = "TRUE"
debugStub.hideBreakpoints= "TRUE"
In VMWare, click "Power on this virtual machine" or click the green Play button on the toolbar.
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_100c5d47.png
Wait until the VM boots.
<H4>Debugging in IDA</H2> Start IDA.
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_m20f96fb6.gif
If you get the welcome dialog, choose "Go".
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_519c4c2f.gif
Choose Debugger | Attach | Remote GDB debugger.
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_6b9e4a85.gif
Enter "localhost" for hostname and 8832 for the port number.
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_m104fe357.gif
Choose <attach to the process started on target> and click OK.
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_m15202098.png
The execution should stop somewhere in the kernel (address above 0x80000000). You can step through the code, but it's not very convenient without any names. Let's try to gather some more information.
<H4>Getting the module list</H2> The list of kernel modules is stored in the list pointed to by the
Code:
PsLoadedModuleList
Code:
fs
Code:
KdVersionBlock
Code:
PsLoadedModuleList
Definition of the KPCR structure can be found in many places, including IDA's ntddk.til. Right now we just need to know that
Code:
KdVersionBlock
Code:
DBGKD_GET_VERSION64
Code:
PsLoadedModuleList
Let's write a small Python function to find the value of that pointer. To retrieve the base of the segment pointed to by fs, we can use the VMWare's debug monitor "r" command. GDB debugger plugin registers an IDC function
Code:
SendGDBMonitor()
Code:
Eval()
Code:
fs_str = Eval('SendGDBMonitor("r fs"')
Code:
fs 0x30 base 0x82744a00 limit 0x00002008 type 0x3 s 1 dpl 0 p 1 db 1
Code:
kpcr = int(fs_str[13:23], 16) #extract and convert as base 16 (hexadecimal) number
Code:
KdVersionBlock
Code:
kdversionblock = Dword(kpcr+0x34)
Code:
PsLoadedModuleList
Code:
PsLoadedModuleList = Dword(kdversionblock+0x18)
<H4>Walking the module list</H2>
Code:
PsLoadedModuleList
Code:
PLIST_ENTRY
Code:
LIST_ENTRY
Code:
typedef struct _LIST_ENTRY
{
PLIST_ENTRY Flink;
PLIST_ENTRY Blink;
} LIST_ENTRY, *PLIST_ENTRY;
Code:
Flink
Code:
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;
};
Code:
#get the first module
cur_mod = Dword(PsLoadedModuleList)
while cur_mod != PsLoadedModuleList and cur_mod != BADADDR:
BaseAddress = Dword(cur_mod+0x18)
SizeOfImage = Dword(cur_mod+0x20)
FullDllName = get_unistr(cur_mod+0x24)
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)
For that we can use the PDB plugin. From looking at its sources (available in the SDK), we can see that it supports three "call codes":
Code:
//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)
Code:
"$ PE header"
Code:
get_input_file_path()
Code:
netnode.altset()
Code:
#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)
Code:
#path to the local copy of System32 directory
local_sys32 = r"D:\VmWareShared\w7\System32"
if FullDllName.lower().startswith(r"\systemroot\system32":
#translate into local filename
filename = local_sys32 + FullDllName[20:]
After running it, you should have a nice memory map:
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_m17b305f6.gif
...and name list:
http://www.hexblog.com/ida_pro/pix/gdb-vmware-winkernel2_htm_65b4d822.gif
Looks much better now. Happy debugging!
http://hexblog.com/2009/02/advanced_windows_kernel_debugg_1.html