Repository: nccgroup/SusanRTTI
Branch: master
Commit: a0baf99937cd
Files: 8
Total size: 26.3 KB
Directory structure:
gitextract_r9sq1w8f/
├── .gitignore
├── LICENSE.txt
├── classdiagram.py
├── classinformer.py
├── gcc.py
├── msvc.py
├── readme.md
└── utils.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.pyproj
*.sln
.vs/*
idaapi.py
.env
.vscode
*.pyc
.DS_Store
================================================
FILE: LICENSE.txt
================================================
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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: classdiagram.py
================================================
from idaapi import GraphViewer, ask_file
# The below will only be displayed as bases
ignore_namespaces = ("std", "type_info")
class ClassDiagram(GraphViewer):
def __init__(self, title, classes):
self.classes = self.transitive_reduction(classes)
GraphViewer.__init__(self, title)
def transitive_reduction(self, graph):
for u in graph.keys():
print('u node: ' + u + '(parents: ' + ', '.join([v for v in graph[u]]) + ')')
for v in graph[u]:
# Compute the dfs from v
print('DFS from v node: ' + v)
dfs = self.dfs_paths(graph, v)
for node in dfs:
print(' - DFS node: ' + str(node))
if v != node and node in graph[u]:
graph[u].remove(node)
print(' # Removed ' + u + ' -> ' + node)
return graph
def dfs_paths(self, graph, start, path=None):
if path is None:
path = [start]
# check for leaf
if start not in graph.keys() or len(graph[start])==0:
#print(' + ' + str(len(graph[start])))
#print(start + ': []')
for p in path:
yield p
else:
#print(start + ': [' + ', '.join([s for s in graph[start]]) + ']')
print(' + RECUR: ' + ', '.join(set(graph[start]) - set(path)))
for next in set(graph[start]) - set(path):
for p in self.dfs_paths(graph, next, path + [next]):
print(' + ' + p)
yield p
def add_node(self, class_name):
if class_name is None:
return
if not class_name.startswith(ignore_namespaces):
print("Adding node: " + class_name)
new_node = self.AddNode(class_name)
self.name_to_node[class_name] = new_node
def OnRefresh(self):
self.Clear()
self.name_to_node = {}
# Create nodes
for class_name in self.classes.keys():
# Skipping common namespaces
self.add_node(class_name)
# Create edges
for class_name in self.name_to_node.keys():
print("Adding edges for: " + class_name)
node = self.name_to_node[class_name]
print("bases: " + str(self.classes[class_name]))
for base_name in self.classes[class_name]:
if base_name not in self.name_to_node:
# Add originally skipped base node
self.add_node(base_name)
base = self.name_to_node.get(base_name)
if base is not None:
self.AddEdge(base, node)
return True
def OnGetText(self, node_id):
return self[node_id]
# dot file export modified from http://joxeankoret.com
def OnCommand(self, cmd_id):
if self.cmd_dot == cmd_id:
fname = ask_file(1, "*.dot", "Export DOT file")
if fname:
f = open(fname, "wt")
buf = "digraph G {\n graph [overlap=scale]; node [fontname=Courier]; rankdir=\"LR\";\n\n"
keyList = list(self.classes.keys())
for c in keyList:
n = keyList.index(c)
buf += ' a%s [shape=box, label = "%s", color="blue"]\n' % (n, c)
buf += "\n"
for c in keyList:
class_index = keyList.index(c)
for base in self.classes[c]:
if base in keyList:
base_index = keyList.index(base)
buf += ' a%s -> a%s [style = bold]\n' % (class_index, base_index)
buf += "}"
f.write(buf)
f.close()
def Show(self):
if not GraphViewer.Show(self):
return False
self.cmd_dot = self.AddCommand("Export DOT", "F2")
return True
================================================
FILE: classinformer.py
================================================
# ClassInformer python
# Nicolas Guigo / NCC Group
# Tyler Colgan / NCC Group
# 03/2017
import idaapi
from idc import *
from ida_search import find_text
idaapi.require("utils")
idaapi.require("msvc")
idaapi.require("gcc")
idaapi.require("classdiagram")
from idaapi import auto_is_ok
from msvc import run_msvc
from gcc import run_gcc
from classdiagram import ClassDiagram
def show_classes(classes):
c = ClassDiagram("Class Diagram", classes)
c.Show()
def isGcc():
gcc_info = find_text(0x0, 0, 0, "N10__cxxabiv117__class_type_infoE", SEARCH_CASE|SEARCH_DOWN)
return gcc_info != BADADDR
def main():
print("Starting ClassInformerPython")
if auto_is_ok():
classes = run_gcc() if isGcc() else run_msvc()
print(classes)
show_classes(classes)
else:
print("Take it easy, man")
print("Done")
if __name__ == '__main__':
main()
================================================
FILE: gcc.py
================================================
# Modified from GCC RTTI parsing code originally written by Igor Skochinsky.
# See http://www.hexblog.com/?p=704 for the original version of his code.
import idaapi
from idaapi import BADADDR
from idc import *
from utils import utils
u = utils()
all_classes = {}
ti_names = [
"St9type_info",
"N10__cxxabiv117__class_type_infoE",
"N10__cxxabiv120__si_class_type_infoE",
"N10__cxxabiv121__vmi_class_type_infoE",
]
TI_TINFO = 0
TI_CTINFO = 1
TI_SICTINFO = 2
TI_VMICTINFO = 3
class BaseClass:
def __init__(self, ti, offset, flags):
self.ti = ti
self.offset = offset
self.flags = flags
class ClassDescriptor:
def __init__(self, vtable, namestr):
self.vtable = vtable
self.namestr = namestr
self.bases = []
def add_base(self, base, offset=0, flags=0):
self.bases.append(BaseClass(base, offset, flags))
def tinfo2class(tiname):
s = demangle_name(tiname, 0)
if s is None:
return s
return s.replace("`typeinfo for'","")
def classname(namestr):
return tinfo2class("__ZTI" + namestr)
# dd `vtable for'std::type_info+8
# dd `typeinfo name for'std::type_info
def format_type_info(ea):
# get the class name string
tis = u.get_ptr(ea + u.PTR_SIZE)
if u.is_bad_addr(tis):
return BADADDR
bname = get_strlit_contents(tis)
if bname == None or len(bname) == 0:
return BADADDR
# looks good, let's do it
name = bname.decode('UTF-8')
ea2 = u.format_struct(ea, "vp")
u.force_name(tis, "__ZTS" + name)
u.force_name(ea, "__ZTI" + name)
# find our vtable
# 0 followed by ea
pat = u.ptr_to_bytes(0) + " " + u.ptr_to_bytes(ea)
vtb = find_binary(0, SEARCH_CASE|SEARCH_DOWN, pat)
if not u.is_bad_addr(vtb):
print("vtable for %s at %08X" % (name, vtb))
u.format_struct(vtb, "pp")
u.force_name(vtb, u.vtname(name))
else:
vtb = BADADDR
all_classes[ea] = ClassDescriptor(vtb, name)
return ea2
# dd `vtable for'__cxxabiv1::__si_class_type_info+8
# dd `typeinfo name for'MyClass
# dd `typeinfo for'BaseClass
def format_si_type_info(ea):
ea2 = format_type_info(ea)
pbase = u.get_ptr(ea2)
all_classes[ea].add_base(pbase)
ea2 = u.format_struct(ea2, "p")
return ea2
# dd `vtable for'__cxxabiv1::__si_class_type_info+8
# dd `typeinfo name for'MyClass
# dd flags
# dd base_count
# (base_type, offset_flags) x base_count
def format_vmi_type_info(ea):
ea2 = format_type_info(ea)
ea2 = u.format_struct(ea2, "ii")
base_count = create_data(ea2-4, FF_DWORD, 4, ida_idaapi.BADADDR)
clas = all_classes[ea]
if base_count > 100:
print("%08X: over 100 base classes?!" % ea)
return BADADDR
for i in range(base_count):
base_ti = u.get_ptr(ea2)
flags_off = u.get_ptr(ea2 + u.PTR_SIZE)
off = u.SIGNEXT(flags_off>>8, 24)
clas.add_base(base_ti, off, flags_off & 0xFF)
ea2 = u.format_struct(ea2, "pl")
return ea2
def find_type_info(idx):
name = ti_names[idx]
ea = u.find_string(name)
if ea != BADADDR:
xrefs = u.xref_or_find(ea)
if xrefs:
ti_start = xrefs[0] - u.PTR_SIZE
if not u.is_bad_addr(ti_start):
print("found %d at %08X" % (idx, ti_start))
ea2 = format_type_info(ti_start)
if idx >= TI_CTINFO:
u.format_struct(ea2, "p")
def handle_classes(idx, formatter):
name = u.vtname(ti_names[idx])
ea = get_name_ea_simple(name)
if ea == BADADDR:
# try single underscore
name = name[1:]
ea = get_name_ea_simple(name)
if ea == BADADDR:
print("Could not find vtable for %s" % ti_names[idx])
return
idx = 0
handled = set()
while ea != BADADDR:
print("Looking for refs to vtable %08X" % ea)
if idaapi.is_spec_ea(ea):
xrefs = u.xref_or_find(ea, True)
ea += u.PTR_SIZE*2
xrefs.extend(u.xref_or_find(ea, True))
else:
ea += u.PTR_SIZE*2
xrefs = u.xref_or_find(ea, True)
for x in xrefs:
if not u.is_bad_addr(x) and not x in handled:
print("found %s at %08X" % (name, x))
ea2 = formatter(x)
handled.add(x)
ea = get_name_ea_simple("%s_%d" % (name, idx))
idx += 1
def run_gcc():
classes = {}
# turn on GCC3 demangling
idaapi.cvar.inf.demnames |= idaapi.DEMNAM_GCC3
print("Looking for standard type info classes")
find_type_info(TI_TINFO)
find_type_info(TI_CTINFO)
find_type_info(TI_SICTINFO)
find_type_info(TI_VMICTINFO)
print("Looking for simple classes")
handle_classes(TI_CTINFO, format_type_info)
print("Looking for single-inheritance classes")
handle_classes(TI_SICTINFO, format_si_type_info)
print("Looking for multiple-inheritance classes")
handle_classes(TI_VMICTINFO, format_vmi_type_info)
for i in range(len(all_classes)):
tiaddr = u.num2key(all_classes)[i]
klass = all_classes[tiaddr]
name = classname(klass.namestr)
ti = "%08X" % tiaddr
vt = "%08X" % klass.vtable
basestr = []
for b in klass.bases:
if b.ti in all_classes:
bklass = all_classes[b.ti]
basename = classname(bklass.namestr)
elif idaapi.is_spec_ea(b.ti):
nm = get_name(b.ti, ida_name.GN_VISIBLE)
basename = tinfo2class(nm)
else:
print("Base %08X not found for class %08X!" % (b.ti, tiaddr))
basename = "ti_%08X" % b.ti
basestr.append(basename)
classes[name] = basestr
print("basestr: \"%s\"" % basestr)
return classes
================================================
FILE: msvc.py
================================================
from idaapi import get_struc_id, BADADDR, del_struc, get_struc, add_struc, add_struc_member, FF_DATA, FF_DWORD, FF_0OFF, get_struc_size, FF_STRLIT, del_items, DELIT_DELNAMES, create_struct, get_member_by_name, get_32bit, get_strlit_contents, demangle_name, create_dword, op_offset
from idc import *
from utils import utils
u = utils()
classes = {}
class RTTIStruc:
tid = 0
struc = 0
size = 0
def strip(name):
if name.startswith("class ") and name.endswith("`RTTI Type Descriptor'"):
return name[6:-23]
elif name.startswith("struct ") and name.endswith("`RTTI Type Descriptor'"):
return name[7:-23]
else:
return name
class RTTICompleteObjectLocator(RTTIStruc):
# Init class statics
msid = get_struc_id("RTTICompleteObjectLocator")
if msid != BADADDR:
del_struc(msid)
msid = add_struc(0xFFFFFFFF, "RTTICompleteObjectLocator", False)
add_struc_member(msid, "signature", BADADDR, FF_DATA|FF_DWORD, -1, 4)
add_struc_member(msid, "offset", BADADDR, FF_DATA|FF_DWORD, -1, 4)
add_struc_member(msid, "cdOffset", BADADDR, FF_DATA|FF_DWORD, -1, 4)
add_struc_member(msid, "pTypeDescriptor", BADADDR, FF_DATA|FF_DWORD|FF_0OFF, u.mt_rva().tid, 4)
add_struc_member(msid, "pClassDescriptor", BADADDR, FF_DATA|FF_DWORD|FF_0OFF, u.mt_rva().tid, 4)
if u.x64:
add_struc_member(msid, "pSelf", BADADDR, FF_DATA|FF_DWORD|FF_0OFF, u.mt_rva().tid, 4)
tid = msid
struc = get_struc(tid)
size = get_struc_size(tid)
print("Completed Registering RTTICompleteObjectLocator")
def __init__(self, ea, vtable):
del_items(ea, DELIT_DELNAMES, self.size)
if ida_bytes.create_struct(ea, self.size, self.tid):
# Get adress of type descriptor from CompleteLocator
print("Complete Object Locator at: 0x%x" % ea)
offset = get_member_by_name(self.struc, "pTypeDescriptor").soff
typeDescriptor = get_32bit(ea+offset) + u.x64_imagebase()
print("Looking for type Descriptor at: 0x%x" % typeDescriptor)
rtd = RTTITypeDescriptor(typeDescriptor)
if rtd.class_name:
print("Type Descriptor at: 0x%x" % typeDescriptor)
offset = get_member_by_name(self.struc, "pClassDescriptor").soff
classHierarchyDes = get_32bit(ea+offset) + u.x64_imagebase()
rchd = RTTIClassHierarchyDescriptor(classHierarchyDes)
# filter out None entries
rchd.bases = filter(lambda x: x, rchd.bases)
classes[strip(rtd.class_name)] = [strip(b) for b in rchd.bases]
set_name(vtable, "vtable__" + strip(rtd.class_name), SN_NOWARN)
else:
# if the RTTITypeDescriptor doesn't have a valid name for us to
# read, then this wasn't a valid RTTICompleteObjectLocator
del_items(ea, self.size, DELIT_SIMPLE)
class RTTITypeDescriptor(RTTIStruc):
class_name = None
msid = get_struc_id("RTTITypeDescriptor")
if msid != BADADDR:
del_struc(msid)
msid = add_struc(0xFFFFFFFF, "RTTITypeDescriptor", False)
add_struc_member(msid, "pVFTable", BADADDR, FF_DATA|u.PTR_TYPE|FF_0OFF, u.mt_address().tid, u.PTR_SIZE)
add_struc_member(msid, "spare", BADADDR, FF_DATA|u.PTR_TYPE, -1, u.PTR_SIZE)
add_struc_member(msid, "name", BADADDR, FF_DATA|FF_STRLIT, u.mt_ascii().tid, 0)
tid = msid
struc = get_struc(tid)
size = get_struc_size(tid)
print("Completed Registering RTTITypeDescriptor")
def __init__(self, ea):
name = ea + get_member_by_name(get_struc(self.tid), "name").soff
strlen = u.get_strlen(name)
if strlen is None:
# not a real vtable
return
self.size = self.size + strlen
bmangled = get_strlit_contents(name, strlen, 0)
if bmangled is None:
# not a real function name
return
mangled = bmangled.decode('UTF-8')
print("Mangled: " + mangled)
demangled = demangle_name('??_R0' + mangled[1:] , 0)
if demangled:
del_items(ea, DELIT_DELNAMES, self.size)
if ida_bytes.create_struct(ea, self.size, self.tid):
print(" Made td at 0x%x: %s" % (ea, demangled))
self.class_name = demangled
return
print(" FAIL :(")
return
class RTTIClassHierarchyDescriptor(RTTIStruc):
bases = None
msid = get_struc_id("RTTIClassHierarchyDescriptor")
if msid != BADADDR:
del_struc(msid)
msid = add_struc(0xFFFFFFFF, "RTTIClassHierarchyDescriptor", False)
add_struc_member(msid, "signature", BADADDR, FF_DWORD|FF_DATA, -1, 4)
add_struc_member(msid, "attribute", BADADDR, FF_DWORD|FF_DATA, -1, 4)
add_struc_member(msid, "numBaseClasses", BADADDR, FF_DWORD|FF_DATA, -1, 4)
add_struc_member(msid, "pBaseClassArray", BADADDR, FF_DATA|FF_DWORD|FF_0OFF, u.mt_rva().tid, 4)
tid = msid
struc = get_struc(tid)
print("Completed Registering RTTIClassHierarchyDescriptor")
def __init__(self, ea):
print("Processing Class Hierarchy Descriptor at 0x%x" % ea)
del_items(ea, DELIT_DELNAMES, get_struc_size(self.tid))
if ida_bytes.create_struct(ea, get_struc_size(self.tid), self.tid):
baseClasses = get_32bit(ea+get_member_by_name(get_struc(self.tid), "pBaseClassArray").soff) + u.x64_imagebase()
nb_classes = get_32bit(ea+get_member_by_name(get_struc(self.tid), "numBaseClasses").soff)
print("Baseclasses array at 0x%x" % baseClasses)
# Skip the first base class as it is itself (could check)
self.bases = []
for i in range(1, nb_classes):
baseClass = get_32bit(baseClasses+i*4) + u.x64_imagebase()
print("base class 0x%x" % baseClass)
ida_bytes.create_dword(baseClasses+i*4, 4)
op_offset(baseClasses+i*4, -1, u.REF_OFF|REFINFO_RVA, -1, 0, 0)
ida_bytes.create_struct(baseClass, RTTIBaseClassDescriptor.size, RTTIBaseClassDescriptor.tid)
typeDescriptor = get_32bit(baseClass) + u.x64_imagebase()
self.bases.append(RTTITypeDescriptor(typeDescriptor).class_name)
class RTTIBaseClassDescriptor(RTTIStruc):
msid = get_struc_id("RTTIBaseClassDescriptor")
if msid != BADADDR:
del_struc(msid)
msid = add_struc(0xFFFFFFFF, "RTTIBaseClassDescriptor", False)
add_struc_member(msid, "pTypeDescriptor", BADADDR, FF_DATA|FF_DWORD|FF_0OFF, u.mt_rva().tid, 4)
add_struc_member(msid, "numContainerBases", BADADDR, FF_DWORD|FF_DATA, -1, 4)
add_struc_member(msid, "PMD", BADADDR, FF_DATA|FF_DWORD|FF_0OFF, u.mt_rva().tid, 4)
add_struc_member(msid, "attributes", BADADDR, FF_DWORD|FF_DATA, -1, 4)
tid = msid
struc = get_struc(tid)
size = get_struc_size(tid)
print("Completed Registering RTTIBaseClassDescriptor")
def run_msvc():
start = u.rdata.start_ea
end = u.rdata.end_ea
rdata_size = end-start
for offset in range(0, rdata_size-u.PTR_SIZE, u.PTR_SIZE):
vtable = start+offset
if u.isVtable(vtable):
print("vtable at : " + hex(vtable))
col = u.get_ptr(vtable-u.PTR_SIZE)
if u.within(col, u.valid_ranges):
rcol = RTTICompleteObjectLocator(col, vtable)
u.add_missing_classes(classes)
return classes
================================================
FILE: readme.md
================================================
# SusanRTTI #
#### Another RTTI Parsing IDA plugin ####
### Features ###
* All ida-python
* Class based design, error logging
* RTTI parsing algorithm scanning for vtables first (instead of
bruteforcing the entire rdata/data sections)
* Graphing of class hierarchy (using transitive reduction for clarity)
* Export functionality to GraphViz (.dot) format
* Handles RTTI and C++ name demangling for:
* X86 GCC
* X86 MSVC
* X64 GCC
* X64 MSVC
### Usage ###
Simple: First load your binary in IDA. Then run script `classinformerpython.py`
To export the dot file, either right click on the class diagram and select
`export` or just hit F2.
### References ###
GraphView reference: http://www.graphviz.org/
Online viewer: http://www.webgraphviz.com/
================================================
FILE: utils.py
================================================
import struct
import idaapi
from idc import *
from idaapi import get_segm_by_name, has_xref, get_full_flags, opinfo_t, refinfo_t,\
get_32bit, get_64bit, get_imagebase, get_byte
import idautils
from idautils import DataRefsTo
# Segments
within = lambda x, rl: any([True for r in rl if r[0]<=x<=r[1]])
class utils(object):
text = 0
data = 0
rdata = 0
valid_ranges = []
within = lambda self, x, rl: any([True for r in rl if r[0]<=x<=r[1]])
REF_OFF = 0
x64 = 0
PTR_TYPE = 0
PTR_SIZE = 0
def __init__(self):
self.text = get_segm_by_name(".text")
self.data = get_segm_by_name(".data")
self.rdata = get_segm_by_name(".rdata")
# try to use rdata if there actually is an rdata segment, otherwise just use data
if self.rdata is not None:
self.valid_ranges = [(self.rdata.start_ea, self.rdata.end_ea), (self.data.start_ea, self.data.end_ea)]
else:
self.valid_ranges = [(self.data.start_ea, self.data.end_ea)]
self.x64 = (idaapi.getseg(here()).bitness == 2)
if self.x64:
self.PTR_TYPE = FF_QWORD
self.REF_OFF = REF_OFF64
self.PTR_SIZE = 8
self.get_ptr = get_64bit
else:
self.PTR_TYPE = FF_DWORD
self.REF_OFF = REF_OFF32
self.PTR_SIZE = 4
self.get_ptr = get_32bit
# for 32-bit binaries, the RTTI structs contain absolute addresses, but for
# 64-bit binaries, they're offsets from the image base.
def x64_imagebase(self):
if self.x64:
return get_imagebase()
else:
return 0
def mt_rva(self):
ri = refinfo_t()
ri.flags = self.REF_OFF
ri.target = 0
mt = opinfo_t()
mt.ri = ri
return mt
def mt_address(self):
ri = refinfo_t()
ri.flags = self.REF_OFF
ri.target = 0
mt = opinfo_t()
mt.ri = ri
return mt
def mt_ascii(self):
ri = refinfo_t()
ri.flags = STRTYPE_C
ri.target = -1
mt = opinfo_t()
mt.ri = ri
return mt
def get_strlen(self, addr):
strlen = 0
while get_byte(addr+strlen) != 0x0 and strlen < 50:
strlen+=1
#assume no names will ever be longer than 50 bytes
if strlen == 50:
return None
return strlen
def isVtable(self, addr):
function = self.get_ptr(addr)
# Check if vtable has ref and its first pointer lies within code segment
if has_xref(get_full_flags(addr)) and function >= self.text.start_ea and function <= self.text.end_ea:
return True
return False
# helper for bin search
def ptr_to_bytes(self, val):
if self.x64:
sv = struct.pack("<Q", val)
else:
sv = struct.pack("<I", val)
return " ".join("%02X" % ord(c) for c in sv)
def ptrfirst(self, val):
return find_binary(0, SEARCH_CASE|SEARCH_DOWN, self.ptr_to_bytes(val))
def ptrnext(self, val, ref):
return find_binary(ref+1, SEARCH_CASE|SEARCH_DOWN, self.ptr_to_bytes(val))
def xref_or_find(self, addr, allow_many = False):
lrefs = list(DataRefsTo(addr))
if len(lrefs) == 0:
lrefs = list(idautils.refs(addr, self.ptrfirst, self.ptrnext))
if len(lrefs) > 1 and not allow_many:
print("too many xrefs to %08X" % addr)
return []
lrefs = [r for r in lrefs if not is_code(get_full_flags(r))]
return lrefs
def find_string(self, s, afrom=0):
print("searching for %s" % s)
ea = find_binary(afrom, SEARCH_CASE|SEARCH_DOWN, '"' + s + '"')
if ea != BADADDR:
print("Found at %08X" % ea)
return ea
def ForceDword(self, ea):
if ea != BADADDR and ea != 0:
if not is_dword(get_full_flags(ea)):
del_items(ea, 4, DELIT_SIMPLE)
create_data(ea, FF_DWORD, 4, BADADDR)
if is_off0(get_full_flags(ea)) and get_fixup_target_type(ea) == -1:
# remove the offset
op_hex(ea, 0)
def ForceQword(self, ea):
if ea != BADADDR and ea != 0:
if not is_qword(get_full_flags(ea)):
del_items(ea, 8, DELIT_SIMPLE)
create_data(ea, FF_QWORD, 8, BADADDR)
if is_off0(get_full_flags(ea)) and get_fixup_target_type(ea) == -1:
# remove the offset
op_hex(ea, 0)
def ForcePtr(self, ea, delta = 0):
if self.x64:
self.ForceQword(ea)
else:
self.ForceDword(ea)
if get_fixup_target_type(ea) != -1 and is_off0(get_full_flags(ea)):
# don't touch fixups
return
pv = self.get_ptr(ea)
if pv != 0 and pv != BADADDR:
# apply offset again
if idaapi.is_spec_ea(pv):
delta = 0
op_offset(ea, 0, [REF_OFF32, REF_OFF64][self.x64], -1, 0, delta)
# p pointer
# v vtable pointer (delta ptrsize*2)
# i integer (32-bit)
# l integer (32 or 64-bit)
def format_struct(self, ea, fmt):
for f in fmt:
if f in ['p', 'v']:
if f == 'v':
delta = self.PTR_SIZE*2
else:
delta = 0
self.ForcePtr(ea, delta)
ea += self.PTR_SIZE
elif f == 'i':
self.ForceDword(ea)
ea += 4
elif f == 'l':
if self.x64:
self.ForceQword(ea)
ea += 8
else:
self.ForceDword(ea)
ea += 4
return ea
def force_name(self, ea, name):
if is_tail(get_full_flags(ea)):
del_items(ea, 1, DELIT_SIMPLE)
set_name(ea, name, SN_NOWARN)
def is_bad_addr(self, ea):
return ea == 0 or ea == BADADDR or idaapi.is_spec_ea(ea) or not is_loaded(ea)
def vtname(self, name):
return "__ZTV" + name
# sign extend b low bits in x
# from "Bit Twiddling Hacks"
def SIGNEXT(self, x, b):
m = 1 << (b - 1)
x = x & ((1 << b) - 1)
return (x ^ m) - m
def xref_or_find(self, addr, allow_many = False):
lrefs = list(DataRefsTo(addr))
if len(lrefs) == 0:
lrefs = list(idautils.refs(addr, self.ptrfirst, self.ptrnext))
if len(lrefs) > 1 and not allow_many:
print("too many xrefs to %08X" % addr)
return []
lrefs = [r for r in lrefs if not is_code(get_full_flags(r))]
return lrefs
def num2key(self, all_classes):
return [k for k in all_classes]
def add_missing_classes(self, classes):
missing = []
for c, parents in classes.items():
for parent in parents:
if parent not in classes.keys():
missing.append(parent)
for m in missing:
classes[m] = []
gitextract_r9sq1w8f/ ├── .gitignore ├── LICENSE.txt ├── classdiagram.py ├── classinformer.py ├── gcc.py ├── msvc.py ├── readme.md └── utils.py
SYMBOL INDEX (59 symbols across 5 files)
FILE: classdiagram.py
class ClassDiagram (line 6) | class ClassDiagram(GraphViewer):
method __init__ (line 8) | def __init__(self, title, classes):
method transitive_reduction (line 12) | def transitive_reduction(self, graph):
method dfs_paths (line 26) | def dfs_paths(self, graph, start, path=None):
method add_node (line 43) | def add_node(self, class_name):
method OnRefresh (line 51) | def OnRefresh(self):
method OnGetText (line 72) | def OnGetText(self, node_id):
method OnCommand (line 76) | def OnCommand(self, cmd_id):
method Show (line 97) | def Show(self):
FILE: classinformer.py
function show_classes (line 19) | def show_classes(classes):
function isGcc (line 23) | def isGcc():
function main (line 27) | def main():
FILE: gcc.py
class BaseClass (line 24) | class BaseClass:
method __init__ (line 25) | def __init__(self, ti, offset, flags):
class ClassDescriptor (line 30) | class ClassDescriptor:
method __init__ (line 31) | def __init__(self, vtable, namestr):
method add_base (line 36) | def add_base(self, base, offset=0, flags=0):
function tinfo2class (line 39) | def tinfo2class(tiname):
function classname (line 45) | def classname(namestr):
function format_type_info (line 50) | def format_type_info(ea):
function format_si_type_info (line 79) | def format_si_type_info(ea):
function format_vmi_type_info (line 91) | def format_vmi_type_info(ea):
function find_type_info (line 107) | def find_type_info(idx):
function handle_classes (line 120) | def handle_classes(idx, formatter):
function run_gcc (line 149) | def run_gcc():
FILE: msvc.py
class RTTIStruc (line 8) | class RTTIStruc:
function strip (line 13) | def strip(name):
class RTTICompleteObjectLocator (line 21) | class RTTICompleteObjectLocator(RTTIStruc):
method __init__ (line 40) | def __init__(self, ea, vtable):
class RTTITypeDescriptor (line 63) | class RTTITypeDescriptor(RTTIStruc):
method __init__ (line 78) | def __init__(self, ea):
class RTTIClassHierarchyDescriptor (line 101) | class RTTIClassHierarchyDescriptor(RTTIStruc):
method __init__ (line 116) | def __init__(self, ea):
class RTTIBaseClassDescriptor (line 134) | class RTTIBaseClassDescriptor(RTTIStruc):
function run_msvc (line 148) | def run_msvc():
FILE: utils.py
class utils (line 12) | class utils(object):
method __init__ (line 24) | def __init__(self):
method x64_imagebase (line 48) | def x64_imagebase(self):
method mt_rva (line 54) | def mt_rva(self):
method mt_address (line 62) | def mt_address(self):
method mt_ascii (line 70) | def mt_ascii(self):
method get_strlen (line 78) | def get_strlen(self, addr):
method isVtable (line 87) | def isVtable(self, addr):
method ptr_to_bytes (line 95) | def ptr_to_bytes(self, val):
method ptrfirst (line 102) | def ptrfirst(self, val):
method ptrnext (line 105) | def ptrnext(self, val, ref):
method xref_or_find (line 108) | def xref_or_find(self, addr, allow_many = False):
method find_string (line 118) | def find_string(self, s, afrom=0):
method ForceDword (line 125) | def ForceDword(self, ea):
method ForceQword (line 134) | def ForceQword(self, ea):
method ForcePtr (line 143) | def ForcePtr(self, ea, delta = 0):
method format_struct (line 162) | def format_struct(self, ea, fmt):
method force_name (line 183) | def force_name(self, ea, name):
method is_bad_addr (line 188) | def is_bad_addr(self, ea):
method vtname (line 191) | def vtname(self, name):
method SIGNEXT (line 196) | def SIGNEXT(self, x, b):
method xref_or_find (line 201) | def xref_or_find(self, addr, allow_many = False):
method num2key (line 211) | def num2key(self, all_classes):
method add_missing_classes (line 214) | def add_missing_classes(self, classes):
Condensed preview — 8 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (28K chars).
[
{
"path": ".gitignore",
"chars": 60,
"preview": "*.pyproj\n*.sln\n.vs/*\nidaapi.py\n.env\n.vscode\n*.pyc\n.DS_Store\n"
},
{
"path": "LICENSE.txt",
"chars": 1570,
"preview": "This software is provided 'as-is', without any express or implied\nwarranty. In no event will the authors be held liable "
},
{
"path": "classdiagram.py",
"chars": 3960,
"preview": "from idaapi import GraphViewer, ask_file\n\n# The below will only be displayed as bases\nignore_namespaces = (\"std\", \"type"
},
{
"path": "classinformer.py",
"chars": 890,
"preview": "# ClassInformer python\n# Nicolas Guigo / NCC Group\n# Tyler Colgan / NCC Group\n# 03/2017\n\nimport idaapi\nfrom idc import *"
},
{
"path": "gcc.py",
"chars": 5485,
"preview": "# Modified from GCC RTTI parsing code originally written by Igor Skochinsky.\n# See http://www.hexblog.com/?p=704 for the"
},
{
"path": "msvc.py",
"chars": 7438,
"preview": "from idaapi import get_struc_id, BADADDR, del_struc, get_struc, add_struc, add_struc_member, FF_DATA, FF_DWORD, FF_0OFF,"
},
{
"path": "readme.md",
"chars": 754,
"preview": "# SusanRTTI #\n#### Another RTTI Parsing IDA plugin ####\n\n### Features ###\n* All ida-python\n* Class based design, error l"
},
{
"path": "utils.py",
"chars": 6741,
"preview": "import struct\nimport idaapi\nfrom idc import *\nfrom idaapi import get_segm_by_name, has_xref, get_full_flags, opinfo_t, r"
}
]
About this extraction
This page contains the full source code of the nccgroup/SusanRTTI GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 8 files (26.3 KB), approximately 7.7k tokens, and a symbol index with 59 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.