Full Code of nccgroup/SusanRTTI for AI

master a0baf99937cd cached
8 files
26.3 KB
7.7k tokens
59 symbols
1 requests
Download .txt
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] = []
Download .txt
gitextract_r9sq1w8f/

├── .gitignore
├── LICENSE.txt
├── classdiagram.py
├── classinformer.py
├── gcc.py
├── msvc.py
├── readme.md
└── utils.py
Download .txt
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.

Copied to clipboard!