Skip to content

Commit eb500cf

Browse files
committed
Several improvements
- Added BCA as array - set member types to pointers/RVAs to BCA, COL, TD and BCD
1 parent 7d18266 commit eb500cf

4 files changed

Lines changed: 157 additions & 63 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#### Yet Another RTTI Parsing IDA plugin ####
33
![PyClassInformer Icon](/pyclassinformer/pci_icon.png)
44

5-
PyClassInformer is an RTTI parser. Although there are several RTTI parsers such as Class Informer and SusanRTTI, and even IDA can also parse RTTI, I created this tool. It is because they cannot be used as libraries for parsing RTTI. IDA cannot show class hierarchies, either.
5+
PyClassInformer is an RTTI parser. Although there are several RTTI parsers such as Class Informer and SusanRTTI, and even IDA can also parse RTTI, I created this tool. It is because they cannot be used as libraries for parsing RTTI. IDA cannot easily manage class hierarchies such as checking them as a list and filtering the information, either.
66

77
PyClassInformer can parse RTTI for Windows on x86 and x64. Since it is written in IDAPython, you can run it on IDA for Mac OS and Linux as well as Windows. You can also use results of parsing RTTI in your python code by importing this tool as a library.
88

pyclassinformer/pci_chooser.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,28 +40,28 @@ def get_hierarychy(self, data, vftable_ea):
4040
col = data[vftable_ea]
4141
col_offs, curr_off = u.get_col_offs(col, data)
4242
result = "{}: ".format(col.name)
43-
if len(col.chd.bases) > 0:
43+
if len(col.chd.bca.bases) > 0:
4444
idx = 0
45-
if col.chd.bases[0].name == col.name:
45+
if col.chd.bca.bases[0].name == col.name:
4646
idx = 1
4747
# get the result related to the offset of the COL
48-
result += ", ".join([x.name for x in col.chd.bases[idx:] if u.does_bcd_append(col_offs, x, curr_off)]) + ";" if len(col.chd.bases) > 1 else ""
48+
result += ", ".join([x.name for x in col.chd.bca.bases[idx:] if u.does_bcd_append(col_offs, x, curr_off)]) + ";" if len(col.chd.bca.bases) > 1 else ""
4949
return result
5050

5151
def get_hierarychy_order(self, data, vftable_ea):
5252
col = data[vftable_ea]
5353
col_offs, curr_off = u.get_col_offs(col, data)
5454
result = []
55-
if len(col.chd.bases) > 0:
56-
for off in col.chd.paths:
55+
if len(col.chd.bca.bases) > 0:
56+
for off in col.chd.bca.paths:
5757
target_off = off
5858
if off not in col_offs:
5959
# sometimes, mdisp is not included in COLs.
6060
# in those cases, get the least offset in COLs and it is treated as the offset.
6161
target_off = 0
6262
if len(col_offs) > 0:
6363
target_off = sorted(col_offs)[0]
64-
for p in col.chd.paths[off]:
64+
for p in col.chd.bca.paths[off]:
6565
#if curr_off == target_off or col.chd.flags.find("V") >= 0 or len(list(filter(lambda x: x.pdisp >= 0, p))) > 0:
6666
if curr_off == target_off:
6767
result.append("{:#x}: ".format(off) + " -> ".join([x.name + " ({},{},{})".format(x.mdisp, x.pdisp, x.vdisp) for x in p]))

pyclassinformer/pci_utils.py

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@
1313
import ida_name
1414
import ida_ida
1515

16+
ida_9_or_later = False
1617
try:
18+
import ida_struct
1719
from ida_struct import get_member_by_name
1820
except ModuleNotFoundError:
1921
# for IDA 9.0
22+
ida_9_or_later = True
2023
def get_member_by_name(tif, name):
2124
if not tif.is_struct():
2225
return None
@@ -131,11 +134,44 @@ def get_refs_to_by_type_name(name):
131134
for xref in utils.get_refs_to(tif.get_tid()):
132135
yield xref
133136

134-
def add_ptr_or_rva_member(self, sid, name):
137+
def add_ptr_or_rva_member(self, sid, mname, mtype_name, array=False, idx=-1):
138+
sname = idc.get_struc_name(sid)
139+
140+
# if idx is not specified, insert the member at the end of the structure
141+
if idx < 0:
142+
idx = idc.get_member_qty(sid)
143+
144+
idc.add_struc_member(sid, mname, ida_idaapi.BADADDR, ida_bytes.FF_DATA|ida_bytes.FF_DWORD|ida_bytes.FF_0OFF, -1, 4)
145+
self.set_ptr_or_rva_member(sid, mname, mtype_name, array, idx)
146+
147+
def set_ptr_or_rva_member(self, sid, mname, mtype_name, array=False, idx=-1):
148+
sname = idc.get_struc_name(sid)
149+
150+
# if idx is not specified, modifie the last member
151+
if idx < 0:
152+
idx = idc.get_member_qty(sid) - 1
153+
154+
r = None
135155
if self.x64:
136-
idc.add_struc_member(sid, name, ida_idaapi.BADADDR, ida_bytes.FF_DATA|ida_bytes.FF_DWORD|ida_bytes.FF_0OFF, self.mt_rva().tid, 4, reftype=ida_nalt.REFINFO_RVAOFF|self.REF_OFF)
156+
reftype = ida_nalt.REFINFO_RVAOFF|self.REF_OFF
157+
mtif = get_ptr_type(mtype_name, ptr_size=ida_typeinf.TAPTR_PTR32, array=array)
158+
if ida_9_or_later:
159+
r = get_val_repr(ida_typeinf.FRB_OFFSET, reftype)
137160
else:
138-
idc.add_struc_member(sid, name, ida_idaapi.BADADDR, ida_bytes.FF_DATA|ida_bytes.FF_DWORD|ida_bytes.FF_0OFF, self.mt_address().tid, 4)
161+
reftype = self.REF_OFF
162+
mtif = get_ptr_type(mtype_name, ptr_size=0, array=array)
163+
164+
if ida_9_or_later:
165+
tif = ida_typeinf.tinfo_t()
166+
tif.get_named_type(None, sname)
167+
udt = ida_typeinf.udt_type_data_t()
168+
if tif.get_udt_details(udt):
169+
tif.set_udm_type(idx, mtif, 0, r)
170+
tif.get_udt_details(udt)
171+
else:
172+
s = ida_struct.get_struc(sid)
173+
ida_struct.set_member_tinfo(s, s.get_member(idx), 0, mtif, 0)
174+
idc.set_member_type(sid, idc.get_member_offset(sid, mname), ida_bytes.FF_DATA|ida_bytes.FF_DWORD|ida_bytes.FF_0OFF, -1, 1, reftype=reftype)
139175

140176
@staticmethod
141177
def get_moff_by_name(struc, name):
@@ -227,10 +263,21 @@ def create_ptr_attr(data_type=ida_typeinf.BTF_INT, attr=ida_typeinf.TAPTR_PTR32)
227263
return mtif
228264

229265

230-
def get_ptr_type(type_name, ptr_size=ida_typeinf.TAPTR_PTR32):
266+
def get_ptr_type(type_name, ptr_size=ida_typeinf.TAPTR_PTR32, array=False):
231267
mtif = ida_typeinf.tinfo_t()
232268
if mtif.get_named_type(None, type_name):
233-
mtif = create_ptr_attr(ida_typeinf.BTF_INT, ptr_size)
269+
# for 64-bit, the member stores an RVA.
270+
# create "*__ptr32" here.
271+
if ptr_size:
272+
mtif = create_ptr_attr(mtif, ptr_size)
273+
# for 32-bit, the member stores a pointer.
274+
# just create a pointer
275+
else:
276+
mtif.create_ptr(mtif)
277+
278+
# for BCA
279+
if array:
280+
mtif.create_array(mtif)
234281
return mtif
235282
return None
236283

pyclassinformer/pyclassinformer.py

Lines changed: 98 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def strip(name):
4444

4545

4646
class RTTITypeDescriptor(RTTIStruc):
47+
4748
# create structure
4849
msid = idc.get_struc_id("RTTITypeDescriptor")
4950
if msid != ida_idaapi.BADADDR:
@@ -98,7 +99,61 @@ def __init__(self, ea):
9899
return
99100

100101

102+
class RTTIClassHierarchyDescriptor(RTTIStruc):
103+
104+
CHD_MULTINH = 0x01 # Multiple inheritance
105+
CHD_VIRTINH = 0x02 # Virtual inheritance
106+
CHD_AMBIGUOUS = 0x04 # Ambiguous inheritance
107+
108+
# create structure
109+
msid = idc.get_struc_id("RTTIClassHierarchyDescriptor")
110+
if msid != ida_idaapi.BADADDR:
111+
idc.del_struc(msid)
112+
msid = idc.add_struc(0xFFFFFFFF, "RTTIClassHierarchyDescriptor", False)
113+
114+
# add members
115+
idc.add_struc_member(msid, "signature", ida_idaapi.BADADDR, ida_bytes.FF_DWORD|ida_bytes.FF_DATA, -1, 4)
116+
idc.add_struc_member(msid, "attribute", ida_idaapi.BADADDR, ida_bytes.FF_DWORD|ida_bytes.FF_DATA, -1, 4)
117+
idc.add_struc_member(msid, "numBaseClasses", ida_idaapi.BADADDR, ida_bytes.FF_DWORD|ida_bytes.FF_DATA, -1, 4)
118+
idc.add_struc_member(msid, "pBaseClassArray", ida_idaapi.BADADDR, ida_bytes.FF_DWORD|ida_bytes.FF_DATA, -1, 4) # for dummy. the correct type will be applied when the BCA class is created.
119+
120+
# get structure related info
121+
tid = msid
122+
struc = get_struc(tid)
123+
size = idc.get_struc_size(tid)
124+
print("Completed Registering RTTIClassHierarchyDescriptor")
125+
126+
def __init__(self, ea):
127+
self.ea = ea
128+
self.sig = 0
129+
self.bcaea = ida_idaapi.BADADDR
130+
self.nb_classes = 0
131+
self.flags = ""
132+
self.bca = None
133+
134+
# apply structure type to bytes
135+
ida_bytes.del_items(ea, ida_bytes.DELIT_DELNAMES, self.size)
136+
if ida_bytes.create_struct(ea, self.size, self.tid):
137+
# Get members of CHD
138+
self.sig = ida_bytes.get_32bit(ea+u.get_moff_by_name(self.struc, "signature"))
139+
self.bcaea = ida_bytes.get_32bit(ea+u.get_moff_by_name(self.struc, "pBaseClassArray")) + u.x64_imagebase()
140+
self.nb_classes = ida_bytes.get_32bit(ea+u.get_moff_by_name(self.struc, "numBaseClasses"))
141+
self.attribute = ida_bytes.get_32bit(ea+u.get_moff_by_name(self.struc, "attribute"))
142+
143+
self.bca = RTTIBaseClassArray(self.bcaea, self.nb_classes)
144+
145+
# parse flags
146+
if self.attribute & self.CHD_MULTINH:
147+
self.flags += "M"
148+
if self.attribute & self.CHD_VIRTINH:
149+
self.flags += "V"
150+
if self.attribute & self.CHD_AMBIGUOUS:
151+
self.flags += "A"
152+
#self.flags += " {:#x}".format(self.attribute)
153+
154+
101155
class RTTIBaseClassDescriptor(RTTIStruc):
156+
102157
BCD_NOTVISIBLE = 0x00000001
103158
BCD_AMBIGUOUS = 0x00000002
104159
BCD_PRIVORPROTBASE = 0x00000004
@@ -114,13 +169,13 @@ class RTTIBaseClassDescriptor(RTTIStruc):
114169
msid = idc.add_struc(0xFFFFFFFF, "RTTIBaseClassDescriptor", False)
115170

116171
# add members
117-
u.add_ptr_or_rva_member(msid, "pTypeDescriptor")
172+
u.add_ptr_or_rva_member(msid, "pTypeDescriptor", "RTTITypeDescriptor")
118173
idc.add_struc_member(msid, "numContainerBases", ida_idaapi.BADADDR, ida_bytes.FF_DWORD|ida_bytes.FF_DATA, -1, 4)
119174
idc.add_struc_member(msid, "mdisp", ida_idaapi.BADADDR, ida_bytes.FF_DATA|ida_bytes.FF_DWORD, -1, 4) # 00 PMD Vftable displacement inside class layout
120175
idc.add_struc_member(msid, "pdisp", ida_idaapi.BADADDR, ida_bytes.FF_DATA|ida_bytes.FF_DWORD, -1, 4) # 04 PMD Vbtable displacement
121176
idc.add_struc_member(msid, "vdisp", ida_idaapi.BADADDR, ida_bytes.FF_DATA|ida_bytes.FF_DWORD, -1, 4) # 08 PMD Vftable displacement inside vbtable
122177
idc.add_struc_member(msid, "attributes", ida_idaapi.BADADDR, ida_bytes.FF_DWORD|ida_bytes.FF_DATA, -1, 4)
123-
u.add_ptr_or_rva_member(msid, "pClassDescriptor")
178+
u.add_ptr_or_rva_member(msid, "pClassDescriptor", "RTTIClassHierarchyDescriptor")
124179

125180
# get structure related info
126181
tid = msid
@@ -140,74 +195,57 @@ def __init__(self, ea):
140195
self.pdisp = u.to_signed32(ida_bytes.get_32bit(ea+u.get_moff_by_name(self.struc, "pdisp")))
141196
self.vdisp = u.to_signed32(ida_bytes.get_32bit(ea+u.get_moff_by_name(self.struc, "vdisp")))
142197
self.attributes = ida_bytes.get_32bit(ea+u.get_moff_by_name(self.struc, "attributes"))
143-
self.rcd = ida_bytes.get_32bit(ea+u.get_moff_by_name(self.struc, "pClassDescriptor"))
198+
self.chdea = ida_bytes.get_32bit(ea+u.get_moff_by_name(self.struc, "pClassDescriptor")) + u.x64_imagebase()
144199

145200

146-
class RTTIClassHierarchyDescriptor(RTTIStruc):
147-
bases = None
148-
CHD_MULTINH = 0x01 # Multiple inheritance
149-
CHD_VIRTINH = 0x02 # Virtual inheritance
150-
CHD_AMBIGUOUS = 0x04 # Ambiguous inheritance
201+
class RTTIBaseClassArray(RTTIStruc):
151202

152203
# create structure
153-
msid = idc.get_struc_id("RTTIClassHierarchyDescriptor")
204+
msid = idc.get_struc_id("RTTIBaseClassArray")
154205
if msid != ida_idaapi.BADADDR:
155206
idc.del_struc(msid)
156-
msid = idc.add_struc(0xFFFFFFFF, "RTTIClassHierarchyDescriptor", False)
207+
msid = idc.add_struc(0xFFFFFFFF, "RTTIBaseClassArray", False)
157208

158209
# add members
159-
idc.add_struc_member(msid, "signature", ida_idaapi.BADADDR, ida_bytes.FF_DWORD|ida_bytes.FF_DATA, -1, 4)
160-
idc.add_struc_member(msid, "attribute", ida_idaapi.BADADDR, ida_bytes.FF_DWORD|ida_bytes.FF_DATA, -1, 4)
161-
idc.add_struc_member(msid, "numBaseClasses", ida_idaapi.BADADDR, ida_bytes.FF_DWORD|ida_bytes.FF_DATA, -1, 4)
162-
u.add_ptr_or_rva_member(msid, "pBaseClassArray")
210+
u.add_ptr_or_rva_member(msid, "arrayOfBaseClassDescriptors", "RTTIBaseClassDescriptor", array=True)
163211

164212
# get structure related info
165213
tid = msid
166214
struc = get_struc(tid)
167215
size = idc.get_struc_size(tid)
168-
print("Completed Registering RTTIClassHierarchyDescriptor")
216+
217+
# correct BCA's pBaseClassArray member type here.
218+
u.set_ptr_or_rva_member(RTTIClassHierarchyDescriptor.tid, "pBaseClassArray", "RTTIBaseClassArray")
219+
220+
print("Completed Registering RTTIBaseClassArray")
169221

170-
def __init__(self, ea):
222+
def __init__(self, ea, nb_classes):
171223
self.ea = ea
172-
self.sig = 0
173-
self.bcaea = ida_idaapi.BADADDR
174-
self.nb_classes = 0
175-
self.flags = ""
224+
# fix the size with the actual size by using nb_classes from CHD since the size depends on each BCA
225+
self.size = 4 * nb_classes
226+
176227
self.bases = []
177228
self.paths = {}
178229
self.depth = 0
179230

180231
# apply structure type to bytes
181232
ida_bytes.del_items(ea, ida_bytes.DELIT_DELNAMES, self.size)
182233
if ida_bytes.create_struct(ea, self.size, self.tid):
183-
# Get members of CHD
184-
self.sig = ida_bytes.get_32bit(ea+u.get_moff_by_name(self.struc, "signature"))
185-
self.bcaea = ida_bytes.get_32bit(ea+u.get_moff_by_name(self.struc, "pBaseClassArray")) + u.x64_imagebase()
186-
self.nb_classes = ida_bytes.get_32bit(ea+u.get_moff_by_name(self.struc, "numBaseClasses"))
187-
self.attribute = ida_bytes.get_32bit(ea+u.get_moff_by_name(self.struc, "attribute"))
188-
189-
# parse flags
190-
if self.attribute & self.CHD_MULTINH:
191-
self.flags += "M"
192-
if self.attribute & self.CHD_VIRTINH:
193-
self.flags += "V"
194-
if self.attribute & self.CHD_AMBIGUOUS:
195-
self.flags += "A"
196-
#self.flags += " {:#x}".format(self.attribute)
197-
198-
def parse_bca(self, ea):
234+
pass
235+
236+
def parse_bca(self, ea, nb_classes):
199237
result_paths = {}
200238
curr_path = []
201239
n_processed = {}
202-
for i in range(0, self.nb_classes):
203-
bcdoff = self.bcaea+i*4
240+
for i in range(0, nb_classes):
241+
bcdoff = ea+i*4
204242

205243
# apply data type to items in BCA
206-
ida_bytes.create_dword(bcdoff, 4)
207-
if u.x64:
208-
ida_offset.op_offset(bcdoff, ida_bytes.OPND_MASK, u.REF_OFF|ida_nalt.REFINFO_RVAOFF, -1, 0, 0)
209-
else:
210-
ida_offset.op_offset(bcdoff, ida_bytes.OPND_MASK, u.REF_OFF, -1, 0, 0)
244+
#ida_bytes.create_dword(bcdoff, 4)
245+
#if u.x64:
246+
# ida_offset.op_offset(bcdoff, ida_bytes.OPND_MASK, u.REF_OFF|ida_nalt.REFINFO_RVAOFF, -1, 0, 0)
247+
#else:
248+
# ida_offset.op_offset(bcdoff, ida_bytes.OPND_MASK, u.REF_OFF, -1, 0, 0)
211249

212250
# get relevant structures
213251
bcdea = ida_bytes.get_32bit(bcdoff) + u.x64_imagebase()
@@ -258,7 +296,7 @@ def parse_bca(self, ea):
258296

259297
#print({x:[[z.name for z in y] for y in result_paths[x]] for x in result_paths})
260298
self.paths = result_paths
261-
299+
262300

263301
class RTTICompleteObjectLocator(RTTIStruc):
264302

@@ -272,10 +310,11 @@ class RTTICompleteObjectLocator(RTTIStruc):
272310
idc.add_struc_member(msid, "signature", ida_idaapi.BADADDR, ida_bytes.FF_DATA|ida_bytes.FF_DWORD, -1, 4)
273311
idc.add_struc_member(msid, "offset", ida_idaapi.BADADDR, ida_bytes.FF_DATA|ida_bytes.FF_DWORD, -1, 4)
274312
idc.add_struc_member(msid, "cdOffset", ida_idaapi.BADADDR, ida_bytes.FF_DATA|ida_bytes.FF_DWORD, -1, 4)
275-
u.add_ptr_or_rva_member(msid, "pTypeDescriptor")
276-
u.add_ptr_or_rva_member(msid, "pClassDescriptor")
313+
u.add_ptr_or_rva_member(msid, "pTypeDescriptor", "RTTITypeDescriptor")
314+
u.add_ptr_or_rva_member(msid, "pClassDescriptor", "RTTIClassHierarchyDescriptor")
277315
if u.x64:
278-
idc.add_struc_member(msid, "pSelf", ida_idaapi.BADADDR, ida_bytes.FF_DATA|ida_bytes.FF_DWORD|ida_bytes.FF_0OFF, u.mt_rva().tid, 4, reftype=ida_nalt.REFINFO_RVAOFF|u.REF_OFF)
316+
u.add_ptr_or_rva_member(msid, "pSelf", "RTTICompleteObjectLocator")
317+
279318

280319
# get structure related info
281320
tid = msid
@@ -353,7 +392,7 @@ def parse_msvc_vftable():
353392
if len([xrea for xrea in u.get_refs_to(RTTIClassHierarchyDescriptor.tid)]) == 0:
354393
[ida_bytes.create_struct(result[x].chd.ea, RTTIClassHierarchyDescriptor.size, RTTIClassHierarchyDescriptor.tid, True) for x in result]
355394
if len([xrea for xrea in u.get_refs_to(RTTITypeDescriptor.tid)]) == 0:
356-
[ida_bytes.create_struct(result[x].td.ea, RTTITypeDescriptor.size, RTTITypeDescriptor.tid, True) for x in result]
395+
[ida_bytes.create_struct(result[x].td.ea, result[x].td.size, RTTITypeDescriptor.tid, True) for x in result]
357396

358397
# for refreshing xrefs to get xrefs from COLs to TDs
359398
ida_auto.auto_wait()
@@ -363,8 +402,16 @@ def parse_msvc_vftable():
363402
col = result[vtable]
364403

365404
# get BCDs
366-
for bcd, depth in col.chd.parse_bca(col.chd.bcaea):
405+
for bcd, depth in col.chd.bca.parse_bca(col.chd.bca.ea, col.chd.nb_classes):
367406
pass
407+
408+
# may be this is a bug on IDA.
409+
# ida fails to apply a structure type to bytes under some conditions, although create_struct returns True.
410+
# to avoid that, apply them again.
411+
ida_auto.auto_wait()
412+
if len([xrea for xrea in u.get_refs_to(RTTIBaseClassArray.tid)]) == 0:
413+
[ida_bytes.create_struct(result[x].chd.bca.ea, result[x].chd.bca.size, RTTIBaseClassArray.tid, True) for x in result]
414+
ida_auto.auto_wait()
368415

369416
return result
370417

@@ -377,8 +424,8 @@ def display_result(result):
377424
print(" CHD at {:#x}:".format(col.chd.ea), hex(col.chd.sig), hex(col.chd.bcaea), col.chd.nb_classes, col.chd.flags)
378425

379426
# get BCDs
380-
for bcd in col.chd.bases:
381-
print(" {}BCD at {:#x}:".format(" " *bcd.depth*2, bcd.ea), bcd.name, bcd.nb_cbs, bcd.mdisp, bcd.pdisp, bcd.vdisp, bcd.attributes)
427+
for bcd in col.chd.bca.bases:
428+
print(" {}BCD at {:#x}:".format(" " *bcd.depth*2, bcd.ea), bcd.name, bcd.nb_cbs, bcd.mdisp, bcd.pdisp, bcd.vdisp, bcd.attributes, hex(bcd.chdea))
382429

383430

384431
def run_pci(icon=-1):

0 commit comments

Comments
 (0)