├── IDA-VMware-GDB.py └── README.TXT /IDA-VMware-GDB.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # ****************************************************************************** 4 | # 5 | # Helper script for Windows kernel debugging with IDA Pro on VMware + GDB stub. 6 | # 7 | # By Oleksiuk Dmytro (aka Cr4sh) 8 | # http://twitter.com/d_olex 9 | # http://blog.cr4.sh 10 | # mailto:cr4sh0@gmail.com 11 | # 12 | # Features: 13 | # 14 | # - Enumerating loaded kernel modules and segments creation for them. 15 | # - Loading debug symbols for kernel modules. 16 | # 17 | # Based on original vmware_modules.py from Hex Blog article 18 | # (http://www.hexblog.com/?p=94). 19 | # 20 | # Changes: 21 | # 22 | # * Changed nt!PsLoadedModuleList finding algo, 'cause using FS segment base 23 | # for this -- is bad idea (FS not always points to the _KPCR). 24 | # * Added complete support of Windows x64. 25 | # * Fixed bugs in .PDB loading for mdules with the 'non-canonical' image path. 26 | # 27 | # Tested on IDA 6.1 with IDAPython v1.5.2 on Windows XP, Vista, 7 (x32 and x64) 28 | # as debug targets. 29 | # 30 | # ****************************************************************************** 31 | # 32 | 33 | # 34 | # Path to the folder, that contains files from the \SystemRoot\system32 35 | # of your debug target. 36 | # 37 | SYSTEM32_COPY_PATH = "E:\\VMware Virtual Machines\\Windows 7 RTM x86\\System32" 38 | 39 | # 40 | # Lave this list empty, if you want to load debug symbols for all modules. 41 | # Otherwise -- specify list of the module file names. 42 | # 43 | PDB_MODULES = [ "ntoskrnl.exe", "ntkrnlpa.exe", "ntkrnlmp.exe", "ntkrpamp.exe", "win32k.sys" ] 44 | 45 | def is_64bit(): 46 | 47 | # 48 | # Seems that idainfo.is_32bit() and idainfo.is_64bit() always 49 | # returns False (WTF?!) on my machines, so, I implemented a little hack 50 | # with the IDT location check on x86_64 canonical address. 51 | # 52 | 53 | idtr_str = Eval('SendGDBMonitor("r idtr")') 54 | idt = long(idtr_str[10 : 10 + 10], 16) 55 | 56 | return ((idt & 0xffffff00) == 0xfffff800) 57 | 58 | # def end 59 | 60 | if is_64bit(): 61 | 62 | print "[+] 64-bit target" 63 | 64 | Ptr = Qword 65 | 66 | # type argument for SegCreate() 67 | segment_type = 2 68 | 69 | LIST_ENTRY_Blink = 8 70 | 71 | UNICODE_STRING_Buffer = 8 72 | 73 | LDR_DATA_TABLE_ENTRY_BaseAddress = 0x30 74 | LDR_DATA_TABLE_ENTRY_EntryPoint = 0x38 75 | LDR_DATA_TABLE_ENTRY_SizeOfImage = 0x40 76 | LDR_DATA_TABLE_ENTRY_FullDllName = 0x48 77 | LDR_DATA_TABLE_ENTRY_BaseDllName = 0x58 78 | 79 | IMAGE_NT_HEADERS_OptionalHeader = 0x18 80 | IMAGE_OPTIONAL_HEADER_SizeOfImage = 0x38 81 | 82 | else: 83 | 84 | print "[+] 32-bit target" 85 | 86 | Ptr = Dword 87 | 88 | # type argument for SegCreate() 89 | segment_type = 1 90 | 91 | LIST_ENTRY_Blink = 4 92 | 93 | UNICODE_STRING_Buffer = 4 94 | 95 | LDR_DATA_TABLE_ENTRY_BaseAddress = 0x18 96 | LDR_DATA_TABLE_ENTRY_EntryPoint = 0x1c 97 | LDR_DATA_TABLE_ENTRY_SizeOfImage = 0x20 98 | LDR_DATA_TABLE_ENTRY_FullDllName = 0x24 99 | LDR_DATA_TABLE_ENTRY_BaseDllName = 0x2c 100 | 101 | IMAGE_NT_HEADERS_OptionalHeader = 0x18 102 | IMAGE_OPTIONAL_HEADER_SizeOfImage = 0x38 103 | 104 | 105 | def find_sign(addr, sign): 106 | 107 | IMAGE_DOS_HEADER_e_lfanew = 0x3c 108 | 109 | # get image size from NT headers 110 | e_lfanew = Dword(addr + IMAGE_DOS_HEADER_e_lfanew) 111 | SizeOfImage = Dword(addr + e_lfanew + \ 112 | IMAGE_NT_HEADERS_OptionalHeader + \ 113 | IMAGE_OPTIONAL_HEADER_SizeOfImage) 114 | 115 | l = 0 116 | while l < SizeOfImage: 117 | 118 | matched = True 119 | 120 | for i in range(0, len(sign)): 121 | 122 | b = Byte(addr + l + i) 123 | if sign[i] is not None and sign[i] != b: 124 | 125 | matched = False 126 | break 127 | 128 | if matched: 129 | 130 | return addr + l 131 | 132 | l += 1 133 | 134 | raise Exception("find_sign(): Unable to locate signature") 135 | 136 | # def end 137 | 138 | def get_interrupt_vector_64(number): 139 | 140 | # 141 | # get IDT base, GDB returns is as the following string: 142 | # idtr base=0xfffff80003400080 limit=0xfff 143 | # 144 | idtr_str = Eval('SendGDBMonitor("r idtr")') 145 | 146 | # extract and convert IDT base 147 | idt = long(idtr_str[10 : 10 + 18], 16) 148 | 149 | # go to the specified IDT descriptor 150 | idt += number * 16 151 | 152 | # build interrupt vector address 153 | descriptor_0 = Qword(idt) 154 | descriptor_1 = Qword(idt + 8) 155 | descriptor = ((descriptor_0 >> 32) & 0xffff0000) + (descriptor_0 & 0xffff) + (descriptor_1 << 32) 156 | 157 | return descriptor 158 | 159 | # def end 160 | 161 | def get_interrupt_vector_32(number): 162 | 163 | # 164 | # get IDT base, GDB returns is as the following string: 165 | # idtr base=0x80b95400 limit=0x7ff 166 | # 167 | idtr_str = Eval('SendGDBMonitor("r idtr")') 168 | 169 | # extract and convert IDT base 170 | idt = long(idtr_str[10 : 10 + 10], 16) 171 | 172 | # go to the specified IDT descriptor 173 | idt += number * 8 174 | 175 | # build interrupt vector address 176 | descriptor_0 = Qword(idt) 177 | descriptor = ((descriptor_0 >> 32) & 0xffff0000) + (descriptor_0 & 0xffff) 178 | 179 | return descriptor 180 | 181 | # def end 182 | 183 | def find_PsLoadedModuleList_64(addr): 184 | 185 | # 186 | # Find nt!PsLoadedModuleList on Windows x64 by 187 | # following signature from the nt!IoFillDumpHeader(): 188 | # 189 | sign = [ 190 | 0xC7, 0x43, 0x30, 0x64, 0x86, 0x00, 0x00, # mov dword ptr [rbx+30h], 8664h 191 | 0x89, 0x93, 0x98, 0x0F, 0x00, 0x00, # mov [rbx+0F98h], edx 192 | 0x48, 0x8B, 0x05, None, None, None, None, # mov rax, cs:MmPfnDatabase 193 | 0x48, 0x89, 0x43, 0x18, # mov [rbx+18h], rax 194 | 0x48, 0x8D, 0x05, None, None, None, None # lea rax, PsLoadedModuleList 195 | ] 196 | 197 | sign_offset = 24 198 | 199 | s = find_sign(addr, sign) 200 | 201 | return s + sign_offset + Dword(s + sign_offset + 3) + 7 202 | 203 | # def end 204 | 205 | def find_PsLoadedModuleList_32(addr): 206 | 207 | # 208 | # Find nt!PsLoadedModuleList on Windows x32 by 209 | # following signature from the nt!IoFillDumpHeader(): 210 | # 211 | sign = [ 212 | 0xA1, None, None, None, None, # mov eax, ds:_MmPfnDatabase 213 | 0x89, None, 0x14, # mov [esi+14h], eax 214 | 0xC7, None, 0x18, None, None, None, None # mov dword ptr [esi+18h], offset _PsLoadedModuleList 215 | ] 216 | 217 | sign_offset = 11 218 | 219 | s = find_sign(addr, sign) 220 | 221 | return Dword(s + sign_offset) 222 | 223 | # def end 224 | 225 | def get_unistr(addr): 226 | 227 | length = Word(addr) 228 | start = Ptr(addr + UNICODE_STRING_Buffer) 229 | 230 | if length > 1000: 231 | 232 | raise Exception("get_unistr(): String too long") 233 | 234 | res = u'' 235 | while length > 0: 236 | 237 | c = Word(start) 238 | 239 | if c == 0: 240 | 241 | break 242 | 243 | res += unichr(c) 244 | start += 2 245 | length -= 1 246 | 247 | return res 248 | 249 | # def end 250 | 251 | def walk_modulelist(list, callback): 252 | 253 | # get the first module 254 | cur_mod = Ptr(list) 255 | 256 | # loop until we come back to the beginning 257 | while cur_mod != list and cur_mod != BADADDR: 258 | 259 | BaseAddress = Ptr(cur_mod + LDR_DATA_TABLE_ENTRY_BaseAddress) 260 | EntryPoint = Ptr(cur_mod + LDR_DATA_TABLE_ENTRY_EntryPoint) 261 | SizeOfImage = Ptr(cur_mod + LDR_DATA_TABLE_ENTRY_SizeOfImage) 262 | FullDllName = get_unistr(cur_mod + LDR_DATA_TABLE_ENTRY_FullDllName).encode('utf-8') 263 | BaseDllName = get_unistr(cur_mod + LDR_DATA_TABLE_ENTRY_BaseDllName).encode('utf-8') 264 | 265 | # get next module (FLink) 266 | next_mod = Ptr(cur_mod) 267 | 268 | print " * %s %s" % (str(hex(BaseAddress)), FullDllName) 269 | 270 | if callback is not None: 271 | 272 | callback(BaseAddress, BaseDllName, FullDllName, SizeOfImage, EntryPoint) 273 | 274 | # check that BLink points to the previous structure 275 | if Ptr(next_mod + LIST_ENTRY_Blink) != cur_mod: 276 | 277 | raise Exception("walk_modulelist(): List error") 278 | 279 | cur_mod = next_mod 280 | 281 | # def end 282 | 283 | def get_module_base(addr): 284 | 285 | if is_64bit(): 286 | 287 | page_mask = 0xFFFFFFFFFFFFF000 288 | 289 | else: 290 | 291 | page_mask = 0xFFFFF000 292 | 293 | # align address by PAGE_SIZE 294 | addr &= page_mask 295 | 296 | # find module base by address inside it 297 | l = 0 298 | while l < 5 * 1024 * 1024: 299 | 300 | # check for the MZ signature 301 | w = Word(addr - l) 302 | if w == 0x5a4d: 303 | 304 | return addr - l 305 | 306 | l += 0x1000 307 | 308 | raise Exception("get_module_base(): Unable to locate DOS signature") 309 | 310 | # def end 311 | 312 | def add_segment_callback(BaseAddress, BaseDllName, FullDllName, SizeOfImage, EntryPoint): 313 | 314 | # do we already have a segment for this module? 315 | if SegStart(BaseAddress) != BaseAddress or \ 316 | SegEnd(BaseAddress) != BaseAddress + SizeOfImage: 317 | 318 | try: 319 | 320 | # if not, create one 321 | SegCreate(BaseAddress, BaseAddress + SizeOfImage, 0, segment_type, saRelByte, scPriv) 322 | SegRename(BaseAddress, BaseDllName) 323 | 324 | except: 325 | 326 | pass 327 | 328 | # def end 329 | 330 | def load_pdb_callback(BaseAddress, BaseDllName, FullDllName, SizeOfImage, EntryPoint): 331 | 332 | if len(PDB_MODULES) > 0 and BaseDllName.lower() not in PDB_MODULES: 333 | 334 | # skip this module 335 | return 336 | 337 | # fix the path, that starts with the windows folder name 338 | if FullDllName.lower().startswith("\\windows\\system32"): 339 | 340 | FullDllName = "\\SystemRoot\\system32" + FullDllName[17:] 341 | 342 | # fix the path, that contains file name only 343 | if FullDllName.find("\\") == -1: 344 | 345 | FullDllName = "\\SystemRoot\\system32\\DRIVERS\\" + FullDllName 346 | 347 | # load modules from the System32 only 348 | if FullDllName.lower().startswith("\\systemroot\\system32"): 349 | 350 | # translate into local filename 351 | filename = SYSTEM32_COPY_PATH + FullDllName[20:] 352 | 353 | if is_64bit(): 354 | 355 | val = 0xFFFFFFFFFFFFFFFE 356 | 357 | else: 358 | 359 | val = 0xFFFFFFFE 360 | 361 | penode = idaapi.netnode() 362 | penode.create("$ PE header") 363 | 364 | # save old values 365 | save_base = penode.altval(val) 366 | save_name = idaapi.get_input_file_path() 367 | 368 | # set parameters for PDB plugin 369 | penode.altset(val, BaseAddress) 370 | idaapi.set_root_filename(filename) 371 | 372 | # load symbols 373 | print "Trying to load symbols for %s from %s" % (BaseDllName, filename) 374 | RunPlugin("pdb", 3) # use 1 to get a confirmation prompt 375 | pdbnode = idaapi.netnode("$ pdb") 376 | ok = pdbnode.altval(0) 377 | if not ok: 378 | 379 | print "Could not load symbols for %s" % BaseDllName 380 | 381 | # restore previous values 382 | penode.altset(val, save_base) 383 | idaapi.set_root_filename(save_name) 384 | 385 | else: 386 | 387 | print "%s is not in System32 directory" % BaseDllName 388 | 389 | # def end 390 | 391 | if is_64bit(): 392 | 393 | get_interrupt_vector = get_interrupt_vector_64 394 | find_PsLoadedModuleList = find_PsLoadedModuleList_64 395 | 396 | else: 397 | 398 | get_interrupt_vector = get_interrupt_vector_32 399 | find_PsLoadedModuleList = find_PsLoadedModuleList_32 400 | 401 | addr = get_interrupt_vector(0) 402 | kernel_base = get_module_base(addr) 403 | 404 | print "Kernel base is %s" % str(hex(kernel_base)) 405 | 406 | PsLoadedModuleList = find_PsLoadedModuleList(kernel_base) 407 | 408 | print "nt!PsLoadedModuleList is at %s" % str(hex(PsLoadedModuleList)) 409 | 410 | walk_modulelist(PsLoadedModuleList, add_segment_callback) 411 | walk_modulelist(PsLoadedModuleList, load_pdb_callback) 412 | 413 | # 414 | # EoF 415 | # 416 | -------------------------------------------------------------------------------- /README.TXT: -------------------------------------------------------------------------------- 1 | 2 | ****************************************************************************** 3 | 4 | Helper script for Windows kernel debugging with IDA Pro on VMware + GDB stub. 5 | 6 | By Oleksiuk Dmytro (aka Cr4sh) 7 | http://twitter.com/d_olex 8 | http://blog.cr4.sh 9 | mailto:cr4sh0@gmail.com 10 | 11 | ****************************************************************************** 12 | 13 | Features: 14 | 15 | - Enumerating loaded kernel modules and segments creation for them. 16 | - Loading debug symbols for kernel modules. 17 | 18 | Based on original vmware_modules.py from Hex Blog article (http://www.hexblog.com/?p=94). 19 | 20 | Changes: 21 | 22 | * Changed nt!PsLoadedModuleList finding algo, 'cause using FS segment base 23 | for this -- is bad idea (FS not always points to the _KPCR). 24 | 25 | * Added complete support of Windows x64. 26 | 27 | * Fixed bugs in .PDB loading for mdules with the 'non-canonical' image path. 28 | 29 | Tested on IDA 6.1 with IDAPython v1.5.2 on Windows XP, Vista, 7 (x32 and x64) 30 | as debug targets. 31 | --------------------------------------------------------------------------------