Created
April 8, 2012 14:50
-
-
Save marpie/2337717 to your computer and use it in GitHub Desktop.
convKDdef takes a file of KD/WinDBG "Define Type" (dt) command output and tries to convert them to C structs.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
""" convKDdef | |
convKDdef takes a file of KD/WinDBG "Define Type" (dt) command output | |
and tries to convert them to C structs. | |
It uses a two pass approch to parse and resolv all dependencies. After | |
reading the file content and parsing it to Type() objects the | |
Types().parse() function gets calls to invoke the two pass algo: | |
1. Parse the raw WinDBG output and create Field() objects for every | |
field of a Type() object. | |
2. Iterate over all Type() objects and check if every Type() is | |
recognized as either a build-in type (TYPES) or a parsed Type(). | |
Then the script searches for "gaps" in the definition (shadowed fields) | |
and prints the C structs. | |
Author: marpie (marpie@a12d404.net) | |
Last Update: 20120408 | |
Created: 20120407 | |
""" | |
from sys import exit, argv | |
from os.path import isfile | |
# Version Information | |
__version__ = "0.0.1" | |
__program__ = "convKDdef v" + __version__ | |
__author__ = "marpie" | |
__email__ = "marpie+convKDdef@a12d404.net" | |
__license__ = "BSD License" | |
__copyright__ = "Copyright 2012, a12d404.net" | |
__status__ = "Prototype" # ("Prototype", "Development", "Testing", "Production") | |
#SCRIPT_PATH = os.path.dirname( os.path.realpath( __file__ ) ) | |
EOE = -1 | |
TYPES = { | |
"UChar": ("UCHAR", 1,), | |
"Uint2B": ("WORD", 2,), | |
"Uint4B": ("DWORD", 4,), | |
"Int4B": ("INT32", 4,), | |
"Ptr32": ("", 4,), | |
"Ptr32 Char": ("PSTR", 4,), | |
"Ptr32 Uint2B": ("PWSTR", 4,), | |
"Ptr32 Uint4B": ("PUINT32", 8,), | |
"Ptr32 Void": ("PVOID", 4,), | |
"Ptr32 Ptr32 Void": ("PVOID *", 4,), | |
"Uint8B": ("UINT64", 8,), | |
"Int8B": ("INT64", 8,), | |
"Ptr64": ("", 8,), | |
"Ptr64 Char": ("PSTR", 8,), | |
"Ptr64 Uint2B": ("PWSTR", 8,), | |
"Ptr64 Uint4B": ("PUINT64", 8,), | |
"Ptr64 Void": ("PVOID64", 8,), | |
"Ptr64 Ptr64 Void": ("PVOID64 *", 8,), | |
# special | |
"_LARGE_INTEGER": ("LARGE_INTEGER", 64,), | |
"_ULARGE_INTEGER": ("ULARGE_INTEGER", 64,), | |
} | |
class Error(object): | |
def __init__(self, description): | |
self.description = description | |
def __str__(self): | |
return self.description | |
class Field(object): | |
def __init__(self, rawData): | |
self.fName = None # str | |
self.fType = None # str | |
self.fArrayLen = None # int | |
self.fSupposedAddr = None # int | |
self.parsed = False | |
self.rawData = rawData | |
def Name(self): | |
return self.fName | |
def Type(self, undecorate = False): | |
""" | |
Type returns type name. If undecorate is True the plain type | |
without Ptr* is returned. | |
""" | |
if not undecorate: | |
return self.fType | |
if self.fType.startswith("Ptr") and (" " in self.fType): | |
rest, name = self.fType.split(" ", 1) | |
return name | |
return self.fType | |
def SupposedAddr(self): | |
if self.fSupposedAddr: | |
return int(self.fSupposedAddr, 0) | |
return 0 | |
def __repr__(self): | |
return self.__str__() | |
def __str__(self): | |
if self.parsed: | |
arrStr = "" | |
if self.fArrayLen: | |
arrStr = "[%d]" % self.fArrayLen | |
return "\t%s %s%s" % (self.fType, self.fName, arrStr) | |
else: | |
return "*not parsed*" | |
def parse(self): | |
""" parse returns None on success or a Error() otherwise. """ | |
try: | |
self.fSupposedAddr, rest = self.rawData.split(" ", 1) | |
except: | |
return Error("Couldn't parse 'Address in Structure'") | |
try: | |
self.fName, self.fType = [entry.strip() for entry in rest.split(": ", 2)] | |
except: | |
return Error("Couldn't parse Name or Type") | |
if self.fType.startswith("["): | |
# array found | |
idx = self.fType.index("]") | |
self.fArrayLen = int(self.fType[1:idx]) | |
self.fType = self.fType[idx+2:] | |
self.parsed = True | |
return None | |
class Type(object): | |
def __init__(self, entryName, rawData): | |
self.name = entryName | |
self.rawData = rawData | |
self.parsed = False | |
self.fields = [] | |
def Name(self): | |
return self.name | |
def Size(self, entries, resolverPath=""): | |
""" Size returns the in memory size of this object. """ | |
lastField = self.fields[-1] | |
size = lastField.SupposedAddr() | |
# get the size of the last field | |
resolverPath += self.name + ";" | |
fieldType = lastField.Type(False) | |
try: | |
_, fieldSize = TYPES[fieldType] | |
except KeyError: | |
# not a build-in type | |
if fieldType.startswith("Ptr") and (" " in fieldType): | |
# pointer found | |
ptrType, _ = fieldType.split(" ", 1) | |
try: | |
_, fieldSize = TYPES[ptrType] | |
except KeyError: | |
print("Unknown pointer type: " + ptrType) | |
exit(1) | |
else: | |
tmpStr = fieldType + ";" | |
if tmpStr in resolverPath: | |
print("[" + self.name + "Recursion detected for field: " + fieldType) | |
exit(1) | |
entry = entries[fieldType] | |
fieldSize = entry.Size(entries, resolverPath) | |
size += fieldSize | |
return size | |
def __repr__(self): | |
return self.__str__() | |
def __str__(self): | |
return "[" + self.name + "]\n\t" + '\n\t'.join([str(field) for field in self.fields]) | |
def parse(self): | |
""" parse returns None on success or a Error() otherwise. """ | |
for line in self.rawData: | |
field = Field(line) | |
if field.parse(): | |
# error occured | |
return Error("[" + self.name + "] Error while parsing line: " + line) | |
self.fields.append(field) | |
if len(self.fields) < 1: | |
return Error("[" + self.name + "] Empty type definition!") | |
self.parsed = True | |
return None | |
def parseTitle(rawTitle): | |
""" parseTitle returns None on success or Error() otherwise. """ | |
if "!" in rawTitle: | |
return rawTitle.split("!", 1)[1] | |
return rawTitle | |
def parseFile(fileName): | |
""" parseFile returns a tuple of (Entries{}, Error()) """ | |
if not isfile(fileName): | |
return False | |
with open(fileName, 'r') as f: | |
content = [line.strip() for line in f.readlines()] | |
content.append("") | |
entries = {} | |
lineCounter = 0 | |
start = EOE | |
for line in content: | |
if line == "": | |
if start == EOE: | |
# empty line | |
continue | |
else: | |
# EoE (end of entry) reached | |
end = lineCounter | |
title = parseTitle(content[start]) | |
entry = Type(title, content[start+1:end]) | |
try: | |
entries[title] | |
return None, Error("Duplicate Entry '%s' on line: %d" % (title, start+1,)) | |
except KeyError: | |
entries[title] = entry | |
start = EOE | |
elif start == EOE: | |
start = lineCounter | |
lineCounter += 1 | |
return entries, None | |
def processEntries(entries): | |
""" | |
processEntries creates a parsing "tree" and resolves all | |
"dependencies". | |
returns: None on success or Error() otherwise. | |
""" | |
# pass 1 - parse fields | |
for entry in entries.itervalues(): | |
err = entry.parse() | |
if err: | |
return err | |
# pass 2 - check dependencies | |
for entry in entries.itervalues(): | |
for field in entry.fields: | |
tName = field.Type(True) | |
tNameUn = field.Type(False) | |
if (tName in TYPES) or (tName in entries) or (tNameUn in TYPES): | |
continue | |
return Error("[" + entry.Name() + "] [Field: " + field.Name() + "] '" + tNameUn + "' unknown!") | |
return None | |
def convertToC(entries): | |
""" convertToC converts the Types() hash to C structs. """ | |
for t in entries.itervalues(): | |
print("typedef struct " + t.Name() + " {") | |
size = 0 | |
nextAddr = 0 | |
reservedCounter = 1 | |
for field in t.fields: | |
# check for "hidden" fields | |
if field.SupposedAddr() <> nextAddr: | |
hiddenSize = field.SupposedAddr()-nextAddr | |
print("\t/* +0x%04x */ BYTE Reserved%d[%d];" % (nextAddr, reservedCounter, hiddenSize)) | |
reservedCounter += 1 | |
nextAddr += hiddenSize | |
fieldType = field.Type(False) | |
try: | |
fieldType, fieldSize = TYPES[fieldType] | |
except KeyError: | |
# not a build-in type | |
# check if it's a pointer | |
if fieldType.startswith("Ptr") and (" " in fieldType): | |
# pointer found | |
ptrType, fieldType = fieldType.split(" ", 1) | |
fieldType = "P" + fieldType[1:] | |
try: | |
_, fieldSize = TYPES[ptrType] | |
except KeyError: | |
return Error("Unknown pointer type: " + ptrType) | |
else: | |
fieldSize = entries[fieldType].Size(entries) | |
fieldType = fieldType[1:] | |
arrStr = "" | |
if field.fArrayLen: | |
arrStr = "[%d]" % field.fArrayLen | |
fieldSize *= field.fArrayLen | |
print("\t/* +0x%04x */ %s %s%s;" % (nextAddr, fieldType, field.Name(), arrStr,)) | |
nextAddr += fieldSize | |
tName = t.Name()[1:] | |
print("} " + tName + ", *P" + tName + "; /* struct size: " + str(t.Size(entries)) + " bytes */ \n") | |
# Main | |
def main(argv): | |
#ptr64 = False | |
if len(argv) < 2: | |
print( __doc__ ) | |
print("convKDdef.py [input-file]") | |
return False | |
entries, err = parseFile(argv[1]) | |
if err: | |
print("Error: " + str(err)) | |
return False | |
err = processEntries(entries) | |
if err: | |
print(err) | |
return False | |
err = convertToC(entries) | |
if err: | |
print(err) | |
return False | |
#if ptr64: | |
# print("\n\nBe aware that it's possbile that the pointer types 'Ptr32'/'Ptr64' should be encoded as PVOID instead of PVOID32/PVOID64 to work with 32bit and 64bit environments.") | |
return True | |
if __name__ == "__main__": | |
exit( not main( argv ) ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment