mirror of
https://github.com/alliedmodders/sourcemod.git
synced 2025-12-07 18:38:37 +00:00
Some checks failed
Continuous Integration / ${{ matrix.os_short }}-${{ matrix.compiler_cc }} (clang, clang++, ubuntu-latest, linux) (push) Has been cancelled
Continuous Integration / ${{ matrix.os_short }}-${{ matrix.compiler_cc }} (clang-14, clang++-14, ubuntu-22.04, linux) (push) Has been cancelled
Continuous Integration / ${{ matrix.os_short }}-${{ matrix.compiler_cc }} (msvc, windows-latest, win) (push) Has been cancelled
hl2sdk-mock tests / mock (push) Has been cancelled
SourcePawn scripting / build (ubuntu-latest, linux) (push) Has been cancelled
SourcePawn scripting / build (windows-latest, win) (push) Has been cancelled
282 lines
8.4 KiB
Python
282 lines
8.4 KiB
Python
#from __future__ import print_function
|
|
"""vtable_dump.py: IDAPython script to dump a linux vtable (and a reconstructed windows one) from a binary."""
|
|
|
|
"""
|
|
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.
|
|
|
|
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:
|
|
|
|
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.
|
|
|
|
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
|
|
|
3. This notice may not be removed or altered from any source distribution.
|
|
"""
|
|
|
|
__author__ = "Asher Baker"
|
|
__copyright__ = "Copyright 2014, Asher Baker"
|
|
__license__ = "zlib/libpng"
|
|
|
|
import re
|
|
|
|
"""
|
|
Output Format:"
|
|
VTable for <1>: (<2>, <3>)
|
|
Lin Win Function
|
|
<4><5> <6> <7>
|
|
<4><5> <6> <7>
|
|
...
|
|
<4><5> <6> <7>
|
|
|
|
1: Classname
|
|
2: VTable Offset
|
|
3: Linux This Pointer Offset
|
|
4: "T" if the function is a MI thunk
|
|
5: Linux VTable Index
|
|
6: Windows VTable Index
|
|
7: Function Signature
|
|
"""
|
|
|
|
catchclass = False
|
|
innerclass = ""
|
|
|
|
classname = None
|
|
offsetdata = {}
|
|
|
|
# Detect address size
|
|
__EA64__ = ida_idaapi.BADADDR == 0xFFFFFFFFFFFFFFFF
|
|
adr_size = 8 if __EA64__ else 4
|
|
|
|
def ExtractTypeInfo(ea, level = 0):
|
|
global catchclass
|
|
global innerclass
|
|
global classname
|
|
global offsetdata
|
|
|
|
end = ea + adr_size
|
|
|
|
while len(Name(end)) == 0:
|
|
end += adr_size
|
|
|
|
while (end - adr_size) == 0:
|
|
end -= adr_size
|
|
|
|
# Skip vtable
|
|
ea += adr_size
|
|
|
|
# Get type name
|
|
name = idc.demangle_name("_Z" + ida_bytes.get_strlit_contents(ea), ida_ida.inf_get_long_demnames())
|
|
ea += adr_size
|
|
|
|
if classname is None and level == 0:
|
|
classname = name
|
|
|
|
if catchclass:
|
|
innerclass = name
|
|
catchclass = False
|
|
|
|
print(" %*s%s" % (level, "", name))
|
|
|
|
if not ea < end: # Base Type
|
|
pass
|
|
elif ea != 0: #elif isData(GetFlags(ea)): # Single Inheritance
|
|
ExtractTypeInfo(ea, level + 1)
|
|
ea += adr_size
|
|
else: # Multiple Inheritance
|
|
ea += 8
|
|
while ea < end:
|
|
catchclass = True
|
|
ExtractTypeInfo(ea, level + 1)
|
|
ea += adr_size
|
|
offset = ea
|
|
ea += adr_size
|
|
#print "%*s Offset: 0x%06X" % (level, "", offset >> 8)
|
|
if (offset >> 8) != 0:
|
|
offsetdata[offset >> 8] = innerclass
|
|
|
|
# Source: http://stackoverflow.com/a/9147327
|
|
def twos_comp(val, bits):
|
|
"""compute the 2's compliment of int value val"""
|
|
if (val & (1 << (bits - 1))) != 0:
|
|
val = val - (1 << bits)
|
|
return val
|
|
|
|
def Analyze():
|
|
ida_auto.set_ida_state(IDA_STATUS_WORK)
|
|
|
|
if ida_ida.inf_get_cc_id() != COMP_GNU:
|
|
Warning("This script is for binaries compiled with GCC only.")
|
|
ida_auto.set_ida_state(IDA_STATUS_READY)
|
|
return
|
|
|
|
ea = idc.get_screen_ea()
|
|
|
|
end = ea + adr_size
|
|
while idc.demangle_name(idc.get_name(end, ida_name.GN_VISIBLE), ida_ida.inf_get_long_demnames()) is None:
|
|
end += adr_size
|
|
|
|
while (end - adr_size) == 0:
|
|
end -= adr_size
|
|
|
|
while idc.demangle_name(idc.get_name(ea, ida_name.GN_VISIBLE), ida_ida.inf_get_long_demnames()) is None:
|
|
ea -= adr_size
|
|
|
|
name = idc.demangle_name(idc.get_name(ea, ida_name.GN_VISIBLE), ida_ida.inf_get_long_demnames())
|
|
if ea == ida_idaapi.BADADDR or name is None or not re.search(r"vf?table(?: |'\{)for", name):
|
|
Warning("No vtable selected!\nSelect vtable block first.")
|
|
ida_auto.set_ida_state(IDA_STATUS_READY)
|
|
return
|
|
|
|
linux_vtable = []
|
|
temp_windows_vtable = []
|
|
|
|
other_linux_vtables = {}
|
|
other_thunk_linux_vtables = {}
|
|
temp_other_windows_vtables = {}
|
|
|
|
# Extract vtable
|
|
while ea < end:
|
|
# Read thisoffs
|
|
offset = -twos_comp(ea, 32)
|
|
#print "Offset: 0x%08X (%08X)" % (offset, ea)
|
|
ea += adr_size
|
|
|
|
# Read typeinfo address
|
|
typeinfo = ea
|
|
ea += adr_size
|
|
|
|
if offset == 0: # We only need to read this once
|
|
print("Inheritance Tree:")
|
|
ExtractTypeInfo(typeinfo)
|
|
|
|
while (ea < end) and (ida_bytes.is_code(ida_bytes.get_full_flags(ea)) or idc.get_name(ea, ida_name.GN_VISIBLE) == "___cxa_pure_virtual"):
|
|
name = idc.get_name(ea, ida_name.GN_VISIBLE)
|
|
demangled = idc.demangle_name(name, ida_ida.inf_get_long_demnames())
|
|
#print "Name: %s, idc.demangle_named: %s" % (name, demangled)
|
|
|
|
name = demangled if demangled else name
|
|
|
|
if offset == 0:
|
|
linux_vtable.append(name)
|
|
temp_windows_vtable.append(name)
|
|
else:
|
|
if offset not in other_linux_vtables:
|
|
other_linux_vtables[offset] = []
|
|
temp_other_windows_vtables[offset] = []
|
|
other_thunk_linux_vtables[offset] = []
|
|
|
|
if "`non-virtual thunk to'" in name:
|
|
other_linux_vtables[offset].append(name[22:])
|
|
other_thunk_linux_vtables[offset].append(name[22:])
|
|
temp_other_windows_vtables[offset].append(name[22:])
|
|
else:
|
|
other_linux_vtables[offset].append(name)
|
|
temp_other_windows_vtables[offset].append(name)
|
|
|
|
# MI entry, strip "`non-virtual thunk to'" and remove from list
|
|
# But not if it's a dtor... what the hell is this.
|
|
if "`non-virtual thunk to'" in name and "::~" not in name:
|
|
name = name[22:]
|
|
#print "Stripping '%s' from windows vtable." % (name)
|
|
temp_windows_vtable.remove(name)
|
|
|
|
ea += adr_size
|
|
|
|
for i, v in enumerate(temp_windows_vtable):
|
|
if "::~" in v:
|
|
#print "Found destructor at index %d: %s" % (i, v)
|
|
del temp_windows_vtable[i]
|
|
break
|
|
|
|
windows_vtable = []
|
|
overload_stack = []
|
|
prev_function = ""
|
|
prev_symbol = ""
|
|
for v in temp_windows_vtable:
|
|
function = v.split("(", 1)[0]
|
|
|
|
if function == prev_function:
|
|
# If we don't have a stack, we need to push the last function on first
|
|
if len(overload_stack) == 0:
|
|
# We will have added this in the previous run, remove it again...
|
|
windows_vtable.pop()
|
|
#print "Storing '%s' (!)" % (prev_symbol)
|
|
overload_stack.append(prev_symbol)
|
|
#print "Storing '%s'" % (v)
|
|
overload_stack.append(v)
|
|
else:
|
|
# If we've moved onto something new, dump the stack first
|
|
while len(overload_stack) > 0:
|
|
windows_vtable.append(overload_stack.pop())
|
|
|
|
windows_vtable.append(v)
|
|
|
|
prev_function = function
|
|
prev_symbol = v
|
|
|
|
# If there is anything left in the stack, dump it
|
|
while len(overload_stack) > 0:
|
|
windows_vtable.append(overload_stack.pop())
|
|
|
|
print("\nVTable for %s: (0, 0)" % (classname))
|
|
print(" Lin Win Function")
|
|
for i, v in enumerate(linux_vtable):
|
|
if "__cxa_pure_virtual" in v:
|
|
print("P%3d" % (i))
|
|
continue
|
|
|
|
winindex = windows_vtable.index(v) if v in windows_vtable else None
|
|
if winindex is not None:
|
|
print("%4d %4d %s" % (i, winindex, v))
|
|
else:
|
|
print("%4d %s" % (i, v))
|
|
|
|
for k in temp_other_windows_vtables:
|
|
for i, v in enumerate(temp_other_windows_vtables[k]):
|
|
if v.find("::~") != -1:
|
|
#print "Found destructor at index %d: %s" % (i, v)
|
|
del temp_other_windows_vtables[k][i]
|
|
break
|
|
|
|
other_windows_vtables = {}
|
|
for k in temp_other_windows_vtables:
|
|
other_windows_vtables[k] = []
|
|
overload_stack = []
|
|
prev_function = ""
|
|
prev_symbol = ""
|
|
for v in temp_other_windows_vtables[k]:
|
|
function = v.split("(", 1)[0]
|
|
if function == prev_function:
|
|
if len(overload_stack) == 0:
|
|
other_windows_vtables[k].pop()
|
|
#print "Storing '%s' (!)" % (prev_symbol)
|
|
overload_stack.append(prev_symbol)
|
|
#print "Storing '%s'" % (v)
|
|
overload_stack.append(v)
|
|
else:
|
|
if len(overload_stack) > 0:
|
|
while len(overload_stack) > 0:
|
|
other_windows_vtables[k].append(overload_stack.pop())
|
|
other_windows_vtables[k].append(v)
|
|
prev_function = function
|
|
prev_symbol = v
|
|
|
|
for k in other_linux_vtables:
|
|
print("\nVTable for %s: (%d, %d)" % (offsetdata[k], offsetdata.keys().index(k) + 1, k))
|
|
print(" Lin Win Function")
|
|
for i, v in enumerate(other_linux_vtables[k]):
|
|
if "__cxa_pure_virtual" in v:
|
|
print("P%3d" % (i))
|
|
continue
|
|
|
|
winindex = other_windows_vtables[k].index(v)
|
|
if v not in other_thunk_linux_vtables[k]:
|
|
print("%4d %4d %s" % (i, winindex, v))
|
|
else:
|
|
print("T%3d %4d %s" % (i, winindex, v))
|
|
|
|
ida_auto.set_ida_state(IDA_STATUS_READY)
|
|
|
|
if __name__ == '__main__':
|
|
Analyze()
|