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