Skip to content

Instantly share code, notes, and snippets.

@vngkv123
Last active November 15, 2020 11:04
Show Gist options
  • Save vngkv123/61ccc4f2a28e53fe4a6f74bd899c3b24 to your computer and use it in GitHub Desktop.
Save vngkv123/61ccc4f2a28e53fe4a6f74bd899c3b24 to your computer and use it in GitHub Desktop.
# Made by aSiagaming
# Only work with iOS research kernelcache
import ida_bytes
import ida_name
import ida_funcs
import idc
import idautils
import idaapi
import ida_struct
import ida_kernwin
import ida_bytes
import os
import sys
iometa_path = "/tmp/vtab.map"
error = 0xffffffffffffffff
def make_log(log_level, module):
"""Create a logging function."""
def log(level, *args):
if len(args) == 0:
return level <= log.level
if level <= log.level:
print(module + ': ' + args[0].format(*args[1:]))
log.level = log_level
return log
_log = make_log(0, __name__)
def find_segments():
"""Find all candidate __PRELINK_INFO segments (or sections).
We try to identify any IDA segments with __PRELINK_INFO in the name so that this function will
work both before and after automatic rename. A more reliable method would be parsing the
Mach-O.
"""
segments = []
# Gather a list of all the possible segments.
for seg in idautils.Segments():
name = idc.get_segm_name(seg)
if '__DATA_CONST' in name or name == '__const':
_log(0, "Found __DATA_CONST or __const")
segments.append(seg)
if len(segments) < 1:
_log(0, "Can't find any __DATA_CONST")
else:
_log(0, "Found {} const segments".format(len(segments)))
return segments
'''
__DATA_CONST:__const:FFFFFFF0079C7AE0 ; IOExternalMethodDispatch
__DATA_CONST:__const:FFFFFFF0079C7AE0 IOExternalMethodDispatch <__ZN22AppleBCMWLANUserClient15closeUserClientEPS_PvP25IOExternalMethodArguments,\ ; AppleBCMWLANUserClient::closeUserClient(AppleBCMWLANUserClient*,void *,IOExternalMethodArguments *)
__DATA_CONST:__const:FFFFFFF0079C7AE0 0, 0, 0, 0>
struct IOExternalMethodDispatch
{
IOExternalMethodAction function;
uint32_t checkScalarInputCount;
uint32_t checkStructureInputSize;
uint32_t checkScalarOutputCount;
uint32_t checkStructureOutputSize;
};
-> 0x18
struct IOExternalMethod {
IOService * object;
IOMethod func;
IOOptionBits flags;
IOByteCount count0;
IOByteCount count1;
};
-> 0x30
'''
def ScanDispatchTable():
segments = find_segments()
for segment in segments:
start = idc.get_segm_start(segment)
end = idc.get_segm_end(segment)
for i in range(int((end - start) / 8)):
ea = start + 8 * i
sym = idc.get_name(ea)
if sym.endswith("sMethods") or sym.endswith("sMethodsE"):
dispatch = True
methods = []
total = 0
for j in range(0x1000):
# IOExternalMethodDispatch
func_addr = ida_bytes.get_qword(ea + 0x18 * j)
if ida_bytes.is_func(ida_bytes.get_flags(func_addr)) == True:
func_name = idc.demangle_name(
idc.get_func_name(func_addr), 0)
methods.append(func_name)
idc.SetType(ea + 0x18 * j, "IOExternalMethodDispatch")
total += 1
else:
break
if total > 0:
_log(0, "[Total : {}] Found 'IOExternalMethodDispatch' region -> {}({})".format(
total, idc.demangle_name(sym, 0), hex(ea)))
for m in methods:
print("\t" + str(m))
continue
total = 0
methods = []
for j in range(0x1000):
# IOExternalMethod
func_addr = ida_bytes.get_qword(ea + 0x30 * j + 8)
if ida_bytes.is_func(ida_bytes.get_flags(func_addr)) == True:
func_name = idc.demangle_name(
idc.get_func_name(func_addr), 0)
methods.append(func_name)
idc.SetType(ea + 0x30 * j, "IOExternalMethod")
total += 1
else:
break
if total > 0:
_log(0, "[Total : {}] Found 'IOExternalMethod' region -> {}({})".format(
total, idc.demangle_name(sym, 0), hex(ea)))
for m in methods:
print("\t" + str(m))
if total == 0:
_log(
0, "Fuck What region???? -> {} - {}".format(idc.demangle_name(sym, 0), hex(ea)))
'''
ex) Convert sMethod(struct IOExternalDispatch)
'''
def SetType(st_type, ea, num):
sid = ida_struct.get_struc_id(st_type)
s_size = ida_struct.get_struc_size(sid)
for i in range(num):
idc.SetType(ea + s_size * i, st_type)
# "OSMetaClassBase::safeMetaCast" resolve
# 1. XREF safeMetaCast function
# 2. Parse second argument to get correct type information via backward slicing
# 3. By forward analysis, cast result variable as the above type
'''
provider = (IOUserClient *)OSMetaClassBase::safeMetaCast(base_provider, &AppleARMFabricTrace::gMetaClass);
if ( !provider )
return 3758097088LL;
switch ( (int)selector )
{
case 1:
if ( a3->scalarInputCount != 1 )
goto LABEL_26;
return ((__int64 (__fastcall *)(OSMetaClassBase *, bool))provider->externalMethod)(...)
idautils.XrefTypeName(typecode)
Convert cross-reference type codes to readable names
idautils.XrefsFrom(ea, flags=0)
Return all references from address 'ea'
idautils.XrefsTo(ea, flags=0)
Return all references to address 'ea'
'''
def OSMetaClassBase_vtab_resolve():
pass
'''
struct __cppobj AppleBCMWLANCore : IOUserClient
{
AppleBCMWLANCore_vtbl *__vftable /*VFT*/;
char AppleBCMWLANCore_member[4096];
};
-> symbol "__vftable" will be recognized as vftable
https://www.hex-rays.com/products/ida/support/idadoc/1691.shtml
https://bazad.github.io/2018/03/ida-kernelcache-class-reconstruction/
Class hierarchy
UserClient
-> IOUserClient
-> IOService
-> IORegistryEntry
-> OSObject
-> OSMetaClassBase
DeviceDriver
-> IOService
-> IORegistryEntry
-> OSObject
-> OSMetaClassBase
'''
# iometa -n -A [kernelcache] > /tmp/kernel.txt
'''
-> Vtable structure
struct AClass::vtable {
struct ASuperClass1::vmethods ASuperClass1;
struct ASuperClass2::vmethods ASuperClass2;
/* ... */
struct ASuperClassN::vmethods ASuperClassN;
struct AClass::vmethods AClass;
};
-> Field member structure
struct AClass {
struct AClass::vtable* vtable;
struct ASuperClass1::fields ASuperClass1;
struct ASuperClass2::fields ASuperClass2;
/* ... */
struct ASuperClassN::fields ASuperClassN;
struct AClass::fields AClass;
};
'''
def class_vtab_resolve():
_log(0, "IOKit class vtable resolver")
fd = open(iometa_path)
data = fd.readlines()
fd.close()
# Current class name
className = ""
sid = None
vtab_sid = -1
curType = 0
for line in data:
t = line[:-1].strip()
if "vtab" in t and "meta" in t and "UserClient" in t:
className = t.split(" ")[5]
sid = ida_struct.get_struc_id(className)
# newly define struct
if sid == error:
sid = idc.add_struc(-1, className, 0)
#sid = idc.add_struc(-1, "__cppobj {} : IOUserClient".format(className), 0)
vtab_sid = idc.add_struc(-1, className + "_vtable", 0)
# Set class vtab
idc.add_struc_member(sid, "vtab", 0, idc.FF_DATA, -1, 8)
idc.SetType(idc.get_member_id(sid, 0), "struct {}_vtable *".format(className))
# Set IOUserClient basic vtab
idc.add_struc_member(vtab_sid, "IOUserClientVtab", 0, idc.FF_DATA, -1, 1)
idc.SetType(idc.get_member_id(vtab_sid, 0), "struct IOUserClient_vtbl")
# base member
idc.add_struc_member(sid, "{}_BaseMember".format(
className), 8, idc.FF_DATA, -1, 0xd0)
# current member
idc.add_struc_member(sid, "{}_member".format(
className), -1, idc.FF_DATA, -1, 0x1000)
curType = 1
continue
# Not IOUserClient
if "vtab" in t and "meta" in t and not "UserClient" in t:
className = t.split(" ")[5]
sid = ida_struct.get_struc_id(className)
# newly define struct
if sid == error:
sid = idc.add_struc(-1, className, 0)
vtab_sid = idc.add_struc(-1, className + "_vtable", 0)
# Set class vtab
idc.add_struc_member(sid, "vtab", 0, idc.FF_DATA, -1, 8)
idc.SetType(idc.get_member_id(sid, 0),
"struct {}_vtable *".format(className))
# Set IOUserClient basic vtab
idc.add_struc_member(vtab_sid, "IOServiceVtab",
0, idc.FF_DATA, -1, 1)
idc.SetType(idc.get_member_id(vtab_sid, 0),
"struct IOService_vtbl")
# base member
idc.add_struc_member(sid, "{}_BaseMember".format(
className), 8, idc.FF_DATA, -1, 0xd0)
# current member
idc.add_struc_member(sid, "{}_member".format(
className), -1, idc.FF_DATA, -1, 0x1000)
curType = 2
continue
sOffset = int(t.split(" ")[0], 0)
# CASE IOService -> after offset 0x540
if sOffset >= 0x540 and curType == 2:
funcName = t.split(" ")[4].split("(")[0]
print("class -> {}, offset -> {}, name -> {}".format(className, hex(sOffset), funcName))
idc.add_struc_member(vtab_sid, funcName, sOffset, idc.FF_DATA, -1, 8)
# CASE IOUserClient -> after offset 0x5d0
if sOffset >= 0x5d0 and curType == 1:
funcName = t.split(" ")[4].split("(")[0]
args = t.split("::")[1]
#print("class -> {}, offset -> {}, name -> {}".format(className,
# hex(sOffset), funcName))
idc.add_struc_member(vtab_sid, funcName,
sOffset, idc.FF_DATA, -1, 8)
idc.SetType(idc.get_member_id(vtab_sid, sOffset),
"void *(*{}){}".format(funcName, args[args.index("("):]))
return True
if __name__ == "__main__":
iometa_path = ida_kernwin.ask_text(
0x100, "/tmp/vtab.map",
"Full path of 'iometa -nA research_kernel' result")
if not os.path.isfile(iometa_path):
_log(0, "[ERROR] No such file %s" % iometa_path)
sys.exit(-1)
# Check
if ida_struct.get_struc_id("IOUserClient_vtbl") == error:
_log(0, "[ERROR] Please make IOUserClient_vtbl struct")
sys.exit(-1);
if ida_struct.get_struc_id("IOService_vtbl") == error:
_log(0, "[ERROR] Please make IOService_vtbl struct")
sys.exit(-1)
if class_vtab_resolve() == True:
_log(0, "[log] All done")
_log(0, "[log] Running externalMethod related information leaker...")
ScanDispatchTable()
else:
_log(0, "[log] Fail")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment