-
-
Save aemmitt-ns/457f44bccac1eefc32e77e812fe27aff to your computer and use it in GitHub Desktop.
const typeMap = { | |
"c": "char", | |
"i": "int", | |
"s": "short", | |
"l": "long", | |
"q": "long long", | |
"C": "unsigned char", | |
"I": "unsigned int", | |
"S": "unsigned short", | |
"L": "unsigned long", | |
"Q": "unsigned long long", | |
"f": "float", | |
"d": "double", | |
"B": "bool", | |
"v": "void", | |
"*": "char *", | |
"@": "id", | |
"#": "Class", | |
":": "SEL", | |
"[": "Array", | |
"{": "struct", | |
"(": "union", | |
"b": "Bitfield", | |
"^": "*", | |
"r": "char *", | |
"?": "void *" // just so it works | |
}; | |
const descMap = { | |
"NSXPCConnection": (obj) => { | |
return "service name: " + obj.serviceName(); | |
}, | |
"Protocol": (obj) => { | |
return obj.description() + " " + object.name(); | |
}, | |
"NSString": (obj) => { | |
return '@"' + obj.description() + '"'; | |
} | |
}; | |
const descCache = {}; | |
function getClassName(obj) { | |
const object = new ObjC.Object(obj); | |
if (object.$methods.indexOf("- className") != -1) { | |
return object.className(); | |
} else { | |
return "id" | |
} | |
} | |
function getDescription(object) { | |
const klass = object.class(); | |
const name = "" + object.className(); | |
if (!descCache[name]) { | |
const klasses = Object.keys(descMap); | |
for(let i = 0; i < klasses.length; i++) { | |
let k = klasses[i]; | |
if (klass["+ isSubclassOfClass:"](ObjC.classes[k])) { | |
return descMap[k](object); | |
} | |
} | |
} | |
descCache[name] = 1; | |
if (object.$methods.indexOf("- description") != -1) { | |
return "/* " + object.description() + " */ " + ptr(object); | |
} else { | |
return "" + ptr(object); | |
} | |
} | |
function typeDescription(t, obj) { | |
if (t != "@") { | |
let p = ""; | |
let nt = t; | |
if (t[0] == "^") { | |
nt = t.substring(1); | |
p = " *"; | |
} | |
return typeMap[nt[0]] + p; | |
} else { | |
return getClassName(obj) + " *"; | |
} | |
} | |
function objectDescription(t, obj) { | |
if (t == "@") { | |
const object = new ObjC.Object(obj); | |
return getDescription(object); | |
} else if (t == "#") { | |
const object = new ObjC.Object(obj); | |
return "/* " + obj + " */ " + object.description(); | |
} else if (t == ":") { | |
// const object = new ObjC.Object(obj); | |
const description = "" + obj.readCString(); | |
return "/* " + description + " */ " + obj; | |
} else if (t == "*" || t == "r*") { | |
return '"' + obj.readCString() + '"'; | |
} else if ("ilsILS".indexOf(t) != -1) { | |
return "" + obj.toInt32(); | |
} else { | |
return "" + obj; | |
} | |
} | |
const hookMethods = (selector) => { | |
if(ObjC.available) { | |
const resolver = new ApiResolver('objc'); | |
const matches = resolver.enumerateMatches(selector); | |
matches.forEach(m => { | |
// console.log(JSON.stringify(element)); | |
const name = m.name; | |
const t = name[0]; | |
const klass = name.substring(2, name.length-1).split(" ")[0]; | |
const method = name.substring(2, name.length-1).split(" ")[1]; | |
const mparts = method.split(":"); | |
try { | |
Interceptor.attach(m.address, { | |
onEnter(args) { | |
const obj = new ObjC.Object(args[0]); | |
const sel = args[1]; | |
const sig = obj["- methodSignatureForSelector:"](sel); | |
this.invocation = null; | |
if (sig !== null) { | |
this.invocation = { | |
"targetType": t, | |
"targetClass": klass, | |
"targetMethod": method, | |
"args": [] | |
}; | |
const nargs = sig["- numberOfArguments"](); | |
this.invocation.returnType = sig["- methodReturnType"](); | |
for(let i = 0; i < nargs; i++) { | |
// console.log(sig["- getArgumentTypeAtIndex:"](i)); | |
const argtype = sig["- getArgumentTypeAtIndex:"](i); | |
this.invocation.args.push({ | |
"typeString": argtype, | |
"typeDescription": typeDescription(argtype, args[i]), | |
"object": args[i], | |
"objectDescription": objectDescription(argtype, args[i]) | |
}); | |
} | |
} | |
}, | |
onLeave(ret) { | |
if (this.invocation !== null) { | |
this.invocation.retTypeDescription = typeDescription(this.invocation.returnType, ret); | |
this.invocation.returnDescription = objectDescription(this.invocation.returnType, ret); | |
send(JSON.stringify(this.invocation)); | |
} | |
} | |
}); | |
} catch (err) { | |
// sometimes it cant hook copyWithZone? dunno but its not good to hook it anyway. | |
if (method != "copyWithZone:") { | |
console.log(`Could not hook [${klass} ${method}] : ${err}`); | |
} | |
} | |
}); | |
} | |
} | |
rpc.exports.hook = hookMethods; |
from __future__ import print_function | |
from rich.console import Console | |
from rich.syntax import Syntax | |
console = Console() | |
import frida | |
import sys | |
import argparse | |
import json | |
import time | |
parser = argparse.ArgumentParser(description='Process some integers.') | |
parser.add_argument('-s', '--spawn', action='store_true', help='spawn process') | |
parser.add_argument('-U', '--usb', action='store_true', help='use usb device') | |
parser.add_argument('-n', '--name', required=True, type=str, help='name of process') | |
parser.add_argument('methods', metavar='SEL', type=str, nargs='+', | |
help='a method selector like "*[NSMutable* initWith*]"') | |
args = parser.parse_args() | |
device = frida.get_local_device() | |
if args.usb: | |
device = frida.get_usb_device() | |
name = args.name | |
if name.isdigit(): | |
name = int(name) | |
if args.spawn: | |
session = device.spawn(name) | |
else: | |
session = device.attach(name) | |
print(f"Attached to {name} on {device}") | |
try: | |
js = open("funtime.js").read() | |
script = session.create_script(js) | |
def format_call(info): | |
obj = info["args"][0] | |
parts = info["targetMethod"].split(":") | |
objstr = f"""({obj["typeDescription"]})( {obj["objectDescription"]} )""" | |
if info["targetType"] == "+": | |
objstr = obj["typeDescription"].split(" ")[0] | |
formatted = "\n\t" + f"""{info["targetType"]}[{objstr} """.replace("\n", "\n\t") | |
if len(parts) == 1: | |
formatted += "\n\t\t" + parts[0] | |
else: | |
for i, arg in enumerate(info["args"][2:]): | |
formatted += ("\n\t\t" + f"""{parts[i]}: ({arg["typeDescription"]})""" + | |
f"""( {arg["objectDescription"]} )""".replace("\n", "\n\t\t")) | |
formatted += "];\n\t" | |
if info["retTypeDescription"] != "void": | |
formatted += (f"""return ({info["retTypeDescription"]})""" + | |
f"""( {info["returnDescription"]} );""".replace("\n", "\n\t\t")) | |
return formatted + f" // time: {time.time()}" | |
def on_message(message, data): | |
if "payload" in message: | |
payload = json.loads(message["payload"]) | |
formatted = format_call(payload) | |
console.print(Syntax(formatted, "objc", background_color="black")) | |
script.on('message', on_message) | |
script.load() | |
for method in args.methods: | |
try: | |
script.exports.hook(method) | |
except Exception as e: | |
print("error", e) | |
sys.stdin.read() | |
except KeyboardInterrupt: | |
pass | |
# script.unload() | |
session.detach() |
I'm running on an OLD checkra1n-ed iPod9,1 running 14.4.1 also
awesome that you are integrating this! i love ipsw, amazing tool. it appears its an issue with calling - methodSignatureForSelector:
, what output do you get when adding a console.log(obj) right before this call on line 125?
awesome that you are integrating this! i love ipsw, amazing tool.
❤️
it appears its an issue with calling - methodSignatureForSelector:, what output do you get when adding a console.log(obj) right before this call on line 125?
• Received 'send':
- NSRegularExpression(
SMSApplication * (/* <SMSApplication: 0x12341234> */ 0x12341234),
SEL (/* release */ 0x12341234)
) -> undefined (0x12341234)
• Received 'log' - <SMSApplication: 0x12341234>
• Received 'log' - <SMSApplication: 0x12341234>
• Received 'log' - nil
⨯ Received 'error' - TypeError: not a function column=1 line=126
⨯ Received 'error' - TypeError: cannot read property 'returnType' of undefined column=1 line=154
• Received 'log' - nil
⨯ Received 'error' - TypeError: not a function column=1 line=126
⨯ Received 'error' - TypeError: cannot read property 'returnType' of undefined column=1 line=154
• Received 'log' - <_UIBoxedAutoreleasePoolMark: 0x12341234>
• Received 'log' - <_UIBoxedAutoreleasePoolMark: 0x12341234>
• Received 'log' - <_UIWeakReference: 0x12341234>
• Received 'log' - <_NSCopyOnWriteCalendarWrapper: 0x12341234>
• Received 'log' - <__NSCFCalendar: 0x12341234>
• Received 'log' - <_NSRefcountedPthreadMutex: 0x12341234>
• Received 'log' - ChatRegistry
• Received 'log' - IMService[
• Received 'log' - ]
⨯ Received 'error' - ReferenceError: 'object' is not defined column=1 line=35
• Received 'log' - <__NSMallocBlock__: 0x12341234>
• Received 'log' - [IMRemoteObject] Port Name: (null) Pid: 0 Process: (null)
• Received 'send':
- NSRegularExpression(
__NSCFConstantString * (@"]"),
SEL (/* retain */ 0x12341234)
) -> __NSCFConstantString * (@"]")
I guess I could just check if obj is nil and bail out? methodSignatureForSelector
prob shouldn't be failing though right?
no it shouldn't but it looks like something worse is going on because all the objects are weird. i think the release and retain methods of NSObject have been hooked so its getting hit for objects of every class. do you get weird results for '-[NSRegularExpression initWithPattern*]' ?
No errors with that input:
• Received 'log' - obj: <NSRegularExpression: 0x12341234> (null) 0x0, sel: 0x12341234
• Received 'send':
- NSRegularExpression(
NSRegularExpression * (/* <NSRegularExpression: 0x12341234> (null) 0x0 */ 0x12341234),
SEL (/* initWithPattern:options:error: */ 0x12341234),
__NSCFConstantString * (@"[⺀-鿿]"),
unsigned long long (0x1),
id * (0x0)
) -> NSRegularExpression * (/* <NSRegularExpression: 0x12341234> [⺀-鿿] 0x1 */ 0x12341234)
• Received 'log' - obj: <NSRegularExpression: 0x12341234> (null) 0x0, sel: 0x12341234
• Received 'send':
- NSRegularExpression(
NSRegularExpression * (/* <NSRegularExpression: 0x12341234> (null) 0x0 */ 0x12341234),
SEL (/* initWithPattern:options:error: */ 0x12341234),
__NSCFConstantString * (@"[⺀-鿿]"),
unsigned long long (0x1),
id * (0x0)
) -> NSRegularExpression * (/* <NSRegularExpression: 0x12341234> [⺀-鿿] 0x1 */ 0x12341234)
Hi @aemmitt-ns I integrated this script into
ipsw
as aipsw frida
cmd, however, I'm getting a lot of errors. Please forgive my frida ignorance as I'm certain this is an issue on my side, but thought you might know exactly what's wrong: