tools/ida_scripts/vtable_dump.py
author David Anderson <dvander@alliedmods.net>
Mon Aug 12 00:22:41 2013 +0100 (2013-08-12)
changeset 3989 b73dae36a0da
parent 3604 f29093434099
permissions -rw-r--r--
Eliminate InfoVars::rval (bug 5844 part 6, r=ds).
asherkin@3590
     1
"""vtable_dump.py: IDAPython script to dump a linux vtable (and a reconstructed windows one) from a binary."""
asherkin@3590
     2
asherkin@3590
     3
"""
asherkin@3590
     4
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
asherkin@3590
     5
asherkin@3590
     6
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
asherkin@3590
     7
asherkin@3590
     8
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
asherkin@3590
     9
asherkin@3590
    10
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
asherkin@3590
    11
asherkin@3590
    12
3. This notice may not be removed or altered from any source distribution.
asherkin@3590
    13
"""
asherkin@3590
    14
asherkin@3590
    15
__author__ = "Asher Baker"
asherkin@3590
    16
__copyright__ = "Copyright 2012, Asher Baker"
asherkin@3590
    17
__license__ = "zlib/libpng"
asherkin@3590
    18
asherkin@3590
    19
import re
asherkin@3590
    20
asherkin@3599
    21
"""
asherkin@3599
    22
Output Format:"
asherkin@3599
    23
    VTable for <1>: (<2>, <3>)
asherkin@3599
    24
    Lin    Win Function
asherkin@3599
    25
    <4><5> <6> <7>
asherkin@3599
    26
    <4><5> <6> <7>
asherkin@3599
    27
    ...
asherkin@3599
    28
    <4><5> <6> <7>
asherkin@3599
    29
asherkin@3599
    30
1: Classname
asherkin@3599
    31
2: VTable Offset
asherkin@3599
    32
3: Linux This Pointer Offset
asherkin@3599
    33
4: "T" if the function is a MI thunk
asherkin@3599
    34
5: Linux VTable Index
asherkin@3599
    35
6: Windows VTable Index
asherkin@3599
    36
7: Function Signature
asherkin@3599
    37
"""
asherkin@3599
    38
asherkin@3599
    39
catchclass = False
asherkin@3599
    40
innerclass = ""
asherkin@3599
    41
asherkin@3599
    42
classname = None
asherkin@3599
    43
offsetdata = {}
asherkin@3599
    44
asherkin@3599
    45
def ExtractTypeInfo(ea, level = 0):
asherkin@3599
    46
	global catchclass
asherkin@3599
    47
	global innerclass
asherkin@3599
    48
	global classname
asherkin@3599
    49
	global offsetdata
asherkin@3599
    50
	
asherkin@3599
    51
	# Param needed to support old IDAPython versions
asherkin@3604
    52
	end = NextNotTail(ea)
asherkin@3599
    53
	
asherkin@3599
    54
	# Skip vtable
asherkin@3599
    55
	ea += 4
asherkin@3599
    56
	
asherkin@3599
    57
	# Get type name
asherkin@3599
    58
	name = Demangle("_Z" + GetString(Dword(ea)), GetLongPrm(INF_LONG_DN))
asherkin@3599
    59
	ea += 4
asherkin@3599
    60
	
asherkin@3599
    61
	if classname is None and level == 0:
asherkin@3599
    62
		classname = name
asherkin@3599
    63
	
asherkin@3599
    64
	if catchclass:
asherkin@3599
    65
		innerclass = name
asherkin@3599
    66
		catchclass = False
asherkin@3599
    67
	
asherkin@3604
    68
	print " %*s%s" % (level, "", name)
asherkin@3599
    69
	
asherkin@3599
    70
	if not ea < end: # Base Type
asherkin@3599
    71
		pass
asherkin@3599
    72
	elif isData(GetFlags(Dword(ea))): # Single Inheritance
asherkin@3599
    73
		ExtractTypeInfo(Dword(ea), level + 1)
asherkin@3599
    74
		ea += 4
asherkin@3599
    75
	else: # Multiple Inheritance
asherkin@3599
    76
		ea += 8
asherkin@3599
    77
		while ea < end:
asherkin@3599
    78
			catchclass = True
asherkin@3599
    79
			ExtractTypeInfo(Dword(ea), level + 1)
asherkin@3599
    80
			ea += 4
asherkin@3599
    81
			offset = Dword(ea)
asherkin@3599
    82
			ea += 4
asherkin@3599
    83
			#print "%*s Offset: 0x%06X" % (level, "", offset >> 8)
asherkin@3599
    84
			if (offset >> 8) != 0:
asherkin@3599
    85
				offsetdata[offset >> 8] = innerclass
asherkin@3599
    86
asherkin@3599
    87
# Source: http://stackoverflow.com/a/9147327
asherkin@3599
    88
def twos_comp(val, bits):
asherkin@3599
    89
	"""compute the 2's compliment of int value val"""
asherkin@3599
    90
	if (val & (1 << (bits - 1))) != 0:
asherkin@3599
    91
		val = val - (1 << bits)
asherkin@3599
    92
	return val
asherkin@3599
    93
asherkin@3590
    94
def Analyze():
asherkin@3590
    95
	SetStatus(IDA_STATUS_WORK)
asherkin@3590
    96
	
asherkin@3590
    97
	if GetLongPrm(INF_COMPILER).id != COMP_GNU:
asherkin@3590
    98
		Warning("This script is for binaries compiled with GCC only.")
asherkin@3590
    99
		SetStatus(IDA_STATUS_READY)
asherkin@3590
   100
		return
asherkin@3590
   101
	
asherkin@3590
   102
	ea = ScreenEA()
asherkin@3590
   103
	
asherkin@3590
   104
	if not isHead(GetFlags(ea)):
asherkin@3599
   105
		# Param needed to support old IDAPython versions
asherkin@3599
   106
		ea = PrevHead(ea, 0)
asherkin@3590
   107
	
asherkin@3604
   108
	end = NextNotTail(ea)
asherkin@3590
   109
	
asherkin@3590
   110
	name = Demangle(Name(ea), GetLongPrm(INF_LONG_DN))
asherkin@3590
   111
	if ea == BADADDR or name is None or not re.search(r"vf?table(?: |'\{)for", name):
asherkin@3590
   112
		Warning("No vtable selected!\nSelect vtable block first.")
asherkin@3590
   113
		SetStatus(IDA_STATUS_READY)
asherkin@3590
   114
		return
asherkin@3590
   115
	
asherkin@3590
   116
	linux_vtable = []
asherkin@3590
   117
	temp_windows_vtable = []
asherkin@3590
   118
	
asherkin@3599
   119
	other_linux_vtables = {}
asherkin@3599
   120
	other_thunk_linux_vtables = {}
asherkin@3599
   121
	temp_other_windows_vtables = {}
asherkin@3599
   122
	
asherkin@3590
   123
	# Extract vtable
asherkin@3590
   124
	while ea < end:
asherkin@3599
   125
		# Read thisoffs
asherkin@3599
   126
		offset = -twos_comp(Dword(ea), 32)
asherkin@3599
   127
		ea += 4
asherkin@3590
   128
		
asherkin@3599
   129
		# Read typeinfo address
asherkin@3599
   130
		typeinfo = Dword(ea)
asherkin@3599
   131
		ea += 4
asherkin@3590
   132
		
asherkin@3599
   133
		if offset == 0: # We only need to read this once
asherkin@3599
   134
			print "Inheritance Tree:"
asherkin@3599
   135
			ExtractTypeInfo(typeinfo)
asherkin@3590
   136
		
asherkin@3590
   137
		while ea < end and isCode(GetFlags(Dword(ea))):
asherkin@3604
   138
			name = Name(Dword(ea))
asherkin@3604
   139
			demangled = Demangle(name, GetLongPrm(INF_LONG_DN))
asherkin@3604
   140
			#print "Name: %s, Demangled: %s" % (name, demangled)
asherkin@3604
   141
			
asherkin@3604
   142
			name = demangled if demangled else name
asherkin@3590
   143
			
asherkin@3590
   144
			if offset == 0:
asherkin@3590
   145
				linux_vtable.append(name)
asherkin@3590
   146
				temp_windows_vtable.append(name)
asherkin@3590
   147
			else:
asherkin@3599
   148
				if offset not in other_linux_vtables:
asherkin@3599
   149
					other_linux_vtables[offset] = []
asherkin@3599
   150
					temp_other_windows_vtables[offset] = []
asherkin@3599
   151
					other_thunk_linux_vtables[offset] = []
asherkin@3599
   152
				
asherkin@3599
   153
				if "`non-virtual thunk to'" in name:
asherkin@3599
   154
					other_linux_vtables[offset].append(name[22:])
asherkin@3599
   155
					other_thunk_linux_vtables[offset].append(name[22:])
asherkin@3599
   156
					temp_other_windows_vtables[offset].append(name[22:])
asherkin@3599
   157
				else:
asherkin@3599
   158
					other_linux_vtables[offset].append(name)
asherkin@3599
   159
					temp_other_windows_vtables[offset].append(name)
asherkin@3599
   160
				
asherkin@3590
   161
				# MI entry, strip "`non-virtual thunk to'" and remove from list
asherkin@3590
   162
				#     But not if it's a dtor... what the hell is this.
asherkin@3599
   163
				if "`non-virtual thunk to'" in name and "::~" not in name:
asherkin@3590
   164
					name = name[22:]
asherkin@3590
   165
					#print "Stripping '%s' from windows vtable." % (name)
asherkin@3590
   166
					temp_windows_vtable.remove(name)
asherkin@3590
   167
			
asherkin@3590
   168
			ea += 4
asherkin@3590
   169
	
asherkin@3590
   170
	for i, v in enumerate(temp_windows_vtable):
asherkin@3599
   171
		if "::~" in v:
asherkin@3590
   172
			#print "Found destructor at index %d: %s" % (i, v)
asherkin@3590
   173
			del temp_windows_vtable[i]
asherkin@3590
   174
			break
asherkin@3590
   175
	
asherkin@3590
   176
	windows_vtable = []
asherkin@3590
   177
	overload_stack = []
asherkin@3590
   178
	prev_function = ""
asherkin@3590
   179
	prev_symbol = ""
asherkin@3590
   180
	for v in temp_windows_vtable:
asherkin@3590
   181
		function = v.split("(", 1)[0]
asherkin@3590
   182
		
asherkin@3590
   183
		if function == prev_function:
asherkin@3590
   184
			# If we don't have a stack, we need to push the last function on first
asherkin@3590
   185
			if len(overload_stack) == 0:
asherkin@3590
   186
				# We will have added this in the previous run, remove it again...
asherkin@3590
   187
				windows_vtable.pop()
asherkin@3590
   188
				#print "Storing '%s' (!)" % (prev_symbol)
asherkin@3590
   189
				overload_stack.append(prev_symbol)
asherkin@3590
   190
			#print "Storing '%s'" % (v)
asherkin@3590
   191
			overload_stack.append(v)
asherkin@3590
   192
		else:
asherkin@3590
   193
			# If we've moved onto something new, dump the stack first
asherkin@3599
   194
			while len(overload_stack) > 0:
asherkin@3599
   195
				windows_vtable.append(overload_stack.pop())
asherkin@3590
   196
			
asherkin@3590
   197
			windows_vtable.append(v)
asherkin@3590
   198
		
asherkin@3590
   199
		prev_function = function
asherkin@3590
   200
		prev_symbol = v
asherkin@3590
   201
	
asherkin@3599
   202
	# If there is anything left in the stack, dump it
asherkin@3599
   203
	while len(overload_stack) > 0:
asherkin@3599
   204
		windows_vtable.append(overload_stack.pop())
asherkin@3599
   205
	
asherkin@3599
   206
	print "\nVTable for %s: (0, 0)" % (classname)
asherkin@3604
   207
	print " Lin  Win Function"
asherkin@3590
   208
	for i, v in enumerate(linux_vtable):
asherkin@3604
   209
		if "__cxa_pure_virtual" in v:
asherkin@3604
   210
			print "P%3d" % (i)
asherkin@3604
   211
			continue
asherkin@3604
   212
		
asherkin@3590
   213
		winindex = windows_vtable.index(v) if v in windows_vtable else None
asherkin@3590
   214
		if winindex is not None:
asherkin@3604
   215
			print "%4d %4d %s" % (i, winindex, v)
asherkin@3590
   216
		else:
asherkin@3604
   217
			print "%4d      %s" % (i, v)
asherkin@3590
   218
	
asherkin@3599
   219
	for k in temp_other_windows_vtables:
asherkin@3599
   220
		for i, v in enumerate(temp_other_windows_vtables[k]):
asherkin@3599
   221
			if v.find("::~") != -1:
asherkin@3599
   222
				#print "Found destructor at index %d: %s" % (i, v)
asherkin@3599
   223
				del temp_other_windows_vtables[k][i]
asherkin@3599
   224
				break
asherkin@3599
   225
	
asherkin@3599
   226
	other_windows_vtables = {}
asherkin@3599
   227
	for k in temp_other_windows_vtables:
asherkin@3599
   228
		other_windows_vtables[k] = []
asherkin@3599
   229
		overload_stack = []
asherkin@3599
   230
		prev_function = ""
asherkin@3599
   231
		prev_symbol = ""
asherkin@3599
   232
		for v in temp_other_windows_vtables[k]:
asherkin@3599
   233
			function = v.split("(", 1)[0]
asherkin@3599
   234
			if function == prev_function:
asherkin@3599
   235
				if len(overload_stack) == 0:
asherkin@3599
   236
					other_windows_vtables[k].pop()
asherkin@3604
   237
					#print "Storing '%s' (!)" % (prev_symbol)
asherkin@3599
   238
					overload_stack.append(prev_symbol)
asherkin@3604
   239
				#print "Storing '%s'" % (v)
asherkin@3599
   240
				overload_stack.append(v)
asherkin@3599
   241
			else:
asherkin@3599
   242
				if len(overload_stack) > 0:
asherkin@3599
   243
					while len(overload_stack) > 0:
asherkin@3599
   244
						other_windows_vtables[k].append(overload_stack.pop())
asherkin@3599
   245
				other_windows_vtables[k].append(v)
asherkin@3599
   246
			prev_function = function
asherkin@3599
   247
			prev_symbol = v
asherkin@3599
   248
	
asherkin@3599
   249
	for k in other_linux_vtables:
asherkin@3599
   250
		print "\nVTable for %s: (%d, %d)" % (offsetdata[k], offsetdata.keys().index(k) + 1, k)
asherkin@3604
   251
		print " Lin  Win Function"
asherkin@3599
   252
		for i, v in enumerate(other_linux_vtables[k]):
asherkin@3604
   253
			if "__cxa_pure_virtual" in v:
asherkin@3604
   254
				print "P%3d" % (i)
asherkin@3604
   255
				continue
asherkin@3604
   256
			
asherkin@3599
   257
			winindex = other_windows_vtables[k].index(v)
asherkin@3599
   258
			if v not in other_thunk_linux_vtables[k]:
asherkin@3604
   259
				print "%4d %4d %s" % (i, winindex, v)
asherkin@3599
   260
			else:
asherkin@3604
   261
				print "T%3d %4d %s" % (i, winindex, v)
asherkin@3599
   262
	
asherkin@3590
   263
	SetStatus(IDA_STATUS_READY)
asherkin@3590
   264
asherkin@3590
   265
if __name__ == '__main__':
asherkin@3599
   266
	Analyze()