Skip to content

Commit 61a24d7

Browse files
committed
Fixup vtable_structs and finalize vtable_io again
1 parent 2dd493c commit 61a24d7

4 files changed

Lines changed: 710 additions & 175 deletions

File tree

README.md

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@ Takes a SourceMod (or any) signature input and detects if it's unique or not.
2525

2626
Python translation of [makesig](https://github.com/alliedmodders/sourcemod/blob/master/tools/ida_scripts/makesig.idc).
2727

28+
Optionally, install pyperclip with `pip install pyperclip` to automatically copy any signatures to your clipboard when running.
29+
2830

2931
### makesigfromhere.py ###
3032

3133
Creates a signature from the cursor offset. Useful for byte patching.
3234

35+
3336
### nameresetter.py ###
3437

3538
Resets the name of every function in IDA's database. Does not include library or external functions.
@@ -71,9 +74,40 @@ This works well with the Signature Smasher. However to save you an hour or so, I
7174

7275
### vtable_io.py ###
7376

74-
Imports and exports virtual tables. Run it through a Linux binary to export to a file, then run it through a Windows binary to import those VTables into the database. This is similar to [Asherkin's VTable Dumper](https://asherkin.github.io/vtable/) but doesn't suffer from the pitfalls of multiple inheritance. Since it doesn't have those liabilities, it's function typing will almost always be perfect. 32-bit only for now.
75-
76-
Only works on libraries that have virtual thunks *after* the virtual table declaration such as in TF2. Fixing this is a TODO.
77+
Imports and exports virtual tables. Run it through a Linux binary to export to a file, then run it through a Windows binary to import those VTables into the database. This is similar to [Asherkin's VTable Dumper](https://asherkin.github.io/vtable/) but doesn't suffer from the pitfalls of multiple inheritance. Since it doesn't have those liabilities, its function typing will almost always be perfect.
78+
79+
#### Features ####
80+
This script is slightly heavy and has features that warrant explanation. Features can be freely enabled/disabled in the popup form that opens when you run the script. Desired features options are kept in the IDA registry and will persist.
81+
82+
**Parse type strings**
83+
- Sometimes IDA fails to properly analyze Windows RTTI Type Descriptor objects. Because of this, there won't be a reference from certain type descriptors to std::type_info, which is required for the script to work.
84+
- If this feature is enabled, then the string names of the type descriptor will be parsed in order to discover the unreferencing type descriptors. This will be done alongside the normal script function.
85+
- If you notice that there are multiple functions of the same name or classes that have virtual functions that aren't typed, consider enabling this.
86+
- It should be harmless to keep on regardless, but it is disabled by default.
87+
- This problem only seemed to be present in NMRiH.
88+
89+
**Skip vtable size mismatches**
90+
- The script is *almost* perfect. On rare occasion, it will fail to properly prepare a Windows translation of a Linux virtual table.
91+
- If this option is enabled, then any size mismatches will forego function typing.
92+
- Enabled by default.
93+
94+
**Comment reused functions**
95+
- Windows oftentimes optimizes shorter and simpler functions and reuses them across multiple virtual tables. This means that it would be redundant to rename these functions over and over again.
96+
- If enabled, virtual table declarations instead emplace a comment on the function's reference.
97+
- Enabled by default.
98+
99+
**Export options**
100+
- Should be self-explanatory, but the script is able to export the Linux and Windows virtual tables to a file.
101+
- This is is a .json file and is organized to be readable.
102+
- The format of the export file is as follows:
103+
```json
104+
"classname"
105+
{
106+
"[this-offset] vtable-offset function-name"
107+
}
108+
```
109+
- Linux offsets are denoted with `L` and Windows with `W`. If the function is not present in a certain OS, then that index is empty.
110+
- Exporting is optional, and if it is not enabled, then the export file path option can be safely ignored.
77111

78112
### vtable_structs.py ###
79113

sigsmasher.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ def main():
200200
f += ".yml"
201201

202202
with open(f, "w") as f:
203-
yaml.safe_dump(root, f, default_flow_style = False, width = 999999)
203+
yaml.safe_dump(root, f, default_flow_style=False, width=999999)
204204

205205
idaapi.hide_wait_box()
206206
print("Successfully generated {} signatures from {} functions".format(sigcount, funccount))

vtable_io.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,25 @@
55
import ctypes
66
import time
77
import re
8-
import os
98

10-
from sys import version_info
119
from dataclasses import dataclass
1210

1311
if idc.__EA64__:
1412
ea_t = ctypes.c_uint64
1513
ptr_t = ctypes.c_int64
1614
get_ptr = idaapi.get_qword
17-
# Calling this a lot so we'll speed up the invocations by manually implementing this here
18-
def is_ptr(f): return (f & idaapi.MS_CLS) == idaapi.FF_DATA and (f & idaapi.DT_TYPE) == idaapi.FF_QWORD
15+
FF_PTR = idaapi.FF_QWORD
1916
else:
2017
ea_t = ctypes.c_uint32
2118
ptr_t = ctypes.c_int32
2219
get_ptr = idaapi.get_dword
23-
def is_ptr(f): return (f & idaapi.MS_CLS) == idaapi.FF_DATA and (f & idaapi.DT_TYPE) == idaapi.FF_DWORD
20+
FF_PTR = idaapi.FF_DWORD
2421

25-
def is_off(f): return f & (idaapi.FF_0OFF|idaapi.FF_1OFF) != 0
22+
# Calling these a lot so we'll speed up the invocations by manually implementing them here
23+
def is_off(f): return (f & (idaapi.FF_0OFF|idaapi.FF_1OFF)) != 0
2624
def is_code(f): return (f & idaapi.MS_CLS) == idaapi.FF_CODE
2725
def has_any_name(f): return (f & idaapi.FF_ANYNAME) != 0
26+
def is_ptr(f): return (f & idaapi.MS_CLS) == idaapi.FF_DATA and (f & idaapi.DT_TYPE) == FF_PTR
2827

2928
# Let's go https://www.blackhat.com/presentations/bh-dc-07/Sabanal_Yason/Paper/bh-dc-07-Sabanal_Yason-WP.pdf
3029

@@ -245,16 +244,17 @@ class VFunc:
245244
postname: str
246245
sname: str
247246

248-
def make_vfunc(ea=idc.BADADDR, mangledname="", inheritid=-1, vaddr=idc.BADADDR):
249-
name = ""
250-
postname = ""
251-
sname = ""
252-
if mangledname:
253-
name = idaapi.demangle_name(mangledname, idaapi.MNG_LONG_FORM) or mangledname
254-
if name:
255-
postname = get_func_postname(name)
256-
sname = postname.split("(")[0]
257-
return VFunc(ea, vaddr, mangledname, inheritid, name, postname, sname)
247+
@staticmethod
248+
def create(ea=idc.BADADDR, mangledname="", inheritid=-1, vaddr=idc.BADADDR):
249+
name = ""
250+
postname = ""
251+
sname = ""
252+
if mangledname:
253+
name = idaapi.demangle_name(mangledname, idaapi.MNG_LONG_FORM) or mangledname
254+
if name:
255+
postname = get_func_postname(name)
256+
sname = postname.split("(")[0]
257+
return VFunc(ea, vaddr, mangledname, inheritid, name, postname, sname)
258258

259259
class VOptions(object):
260260
StringMethod = 1 << 0
@@ -412,7 +412,7 @@ def parse_vtable_addresses(ea):
412412
if not is_code(fflags):
413413
break
414414

415-
funcs.append(make_vfunc(ea=offs, vaddr=ea))
415+
funcs.append(VFunc.create(ea=offs, vaddr=ea))
416416

417417
ea = idaapi.next_head(ea, idc.BADADDR)
418418
return funcs
@@ -684,6 +684,8 @@ def is_thunk(thunkfunc, targetfuncs):
684684
return False
685685

686686
def build_export_table(linuxtables, wintables):
687+
# Table is built mainly for readability but having one that is actually parsable would
688+
# be a cool idea for the future
687689
exporttable = {}
688690
# Save Linux only tables for exporting too
689691
winless = {k: linuxtables[k] for k in linuxtables.keys() - wintables.keys()}
@@ -895,7 +897,7 @@ def fix_win_overloads(linuxitems, winitems, vclass, functable):
895897
currfuncs = linuxitems[i].funcs
896898
vfuncs = []
897899
for u in range(len(currfuncs)):
898-
f = make_vfunc(mangledname=currfuncs[u])
900+
f = VFunc.create(mangledname=currfuncs[u])
899901
for j, baseclass in enumerate(vclass.baseclasses.values()):
900902
if f.postname in baseclass.postnames:
901903
f.inheritid = j

0 commit comments

Comments
 (0)