Last active
November 15, 2020 11:04
-
-
Save vngkv123/61ccc4f2a28e53fe4a6f74bd899c3b24 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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