项目地址:https://github.com/cherubstar/iOSEnvDetection
根据项目使用Xcode构建app安装到手机上
iPhone:iPhone se2(越狱),iOS 14.4.1
Frida检测
端口检测
上来先用objection搜索有没有frida相关的类,可以看到其中FridaDetection比较符合,对其hook
1 2 3 4 5 6 7 8 9 10 11
| com.zsk.EnvDetection on (iPhone: 14.4.1) [usb] # ios hooking search classes frida EnvDetection.FridaDetectionViewModel _TtGC7SwiftUIP10$1a5ddeda09BoxVTableVGVS_11StateObjectC12EnvDetection23FridaDetectionViewModel_P10$1a5d8f8983Box_ _TtGC7SwiftUIP10$1a5ddeda09BoxVTableGVS_P10$1a5db422825ObservedObjectPropertyBoxC12EnvDetection23FridaDetectionViewModel__ FridaDetection
Found 4 classes com.zsk.EnvDetection on (iPhone: 14.4.1) [usb] # ios hooking list class_methods FridaDetection - checkListeningPort27042
Found 1 methods
|
只有 checkListeningPort27042 一个函数,hook查看调用栈返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| com.zsk.EnvDetection on (iPhone: 14.4.1) [usb] # ios hooking watch method "*[FridaDetection checkListeningPort27042]" --dump-args --dump-backtrace --dump-return (agent) Found selector at 0x102034000 as -[FridaDetection checkListeningPort27042] (agent) Registering job 246950. Type: watch-method for: *[FridaDetection checkListeningPort27042] com.zsk.EnvDetection on (iPhone: 14.4.1) [usb] # (agent) [246950] Called: *[FridaDetection checkListeningPort27042] 0 arguments(Kind: instance) (Super: NSObject) (agent) [246950] *[FridaDetection checkListeningPort27042] Backtrace: 0x102051240 EnvDetection!FridaDetectionViewModel.checkListeningPort27042() 0x1020865d4 EnvDetection!closure #2 in closure #1 in closure #1 in FridaDetectionView.body.getter 0x1a56f0e30 SwiftUI!partial apply for implicit closure #2 in implicit closure #1 in WrappedButtonStyle.Body.body.getter 0x1a5a07f28 SwiftUI!closure #1 in PressableGestureCallbacks.dispatch(phase:state:) 0x1a5771b98 SwiftUI!thunk for @escaping @callee_guaranteed () -> () 0x1a5771bc0 SwiftUI!thunk for @escaping @callee_guaranteed () -> (@out ()) 0x1a5771b98 SwiftUI!thunk for @escaping @callee_guaranteed () -> () 0x1a5760764 SwiftUI!static Update.end() 0x1a57b2e6c SwiftUI!EventBindingManager.send(_:) 0x1a5c3fe50 SwiftUI!specialized EventBindingBridge.send(_:source:) 0x1a5c3df28 SwiftUI!UIKitGestureRecognizer.send(touches:event:phase:) 0x1a5c3ebe0 SwiftUI!@objc UIKitGestureRecognizer.touchesBegan(_:with:) 0x1a11cad2c UIKitCore!-[UIGestureRecognizer _componentsEnded:withEvent:] 0x1a171325c UIKitCore!-[UITouchesEvent _sendEventToGestureRecognizer:] 0x1a11c0bd4 UIKitCore!__47-[UIGestureEnvironment _updateForEvent:window:]_block_invoke 0x1a11c08b4 UIKitCore!-[UIGestureEnvironment _updateForEvent:window:] (agent) [246950] Return Value: 0x1
|
返回了0x1,那是不是把函数返回值改为0x0,就不会被检测到
1
| com.zsk.EnvDetection on (iPhone: 14.4.1) [usb] # ios hooking set return_value "*[FridaDetection checkListeningPort27042]" 0x0
|
再点击检测则过掉了
frida代码
1 2 3 4 5 6 7 8 9 10
| function main(){ Interceptor.attach(ObjC.classes["FridaDetection"]["- checkListeningPort27042"].implementation, { onEnter: function(args){ }, onLeave: function(ret){ console.log("ret =>", ret); ret.replace(new NativePointer(0x0)) } }) } setImmediate(main)
|
文件检测
既然是文件检测,那肯定涉及到文件的读取,查资料把文件操作相关的类都用objection查找一遍,最终得到是 NSFileManager 类,进行hook
1
| com.zsk.EnvDetection on (iPhone: 14.4.1) [usb] # ios hooking watch class NSFileManager
|
手机Frida文件检测刷新一下
1 2 3 4 5 6
| com.zsk.EnvDetection on (iPhone: 14.4.1) [usb] # (agent) [703964] Called: [NSFileManager defaultManager] (Kind: class) (Super: NSObject) (agent) [703964] Called: [NSFileManager _registerForUbiquityAccountChangeNotifications] (Kind: instance) (Super: NSObject) (agent) [703964] Called: [NSFileManager defaultManager] (Kind: class) (Super: NSObject) (agent) [703964] Called: [NSFileManager _registerForUbiquityAccountChangeNotifications] (Kind: instance) (Super: NSObject) (agent) [703964] Called: [NSFileManager defaultManager] (Kind: class) (Super: NSObject) (agent) [703964] Called: [NSFileManager _registerForUbiquityAccountChangeNotifications] (Kind: instance) (Super: NSObject)
|
对这两个函数进行hook查看调用栈返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| com.zsk.EnvDetection on (iPhone: 14.4.1) [usb] # ios hooking watch method "*[NSFileManager _registerForUbiquityAccountChangeNotifications]" --dump-args --dump-backtrace --dump-return (agent) Found selector at 0x19feee6b0 as -[NSFileManager _registerForUbiquityAccountChangeNotifications] (agent) Registering job 536962. Type: watch-method for: *[NSFileManager _registerForUbiquityAccountChangeNotifications] com.zsk.EnvDetection on (iPhone: 14.4.1) [usb] # ios hooking watch method "*[NSFileManager defaultManager]" --dump-args --dump-backtrace --dump-return (agent) Found selector at 0x19feec534 as +[NSFileManager defaultManager] (agent) Registering job 371228. Type: watch-method for: *[NSFileManager defaultManager]
com.zsk.EnvDetection on (iPhone: 14.4.1) [usb] # (agent) [371228] Called: *[NSFileManager defaultManager] 0 arguments(Kind: class) (Super: NSObject) (agent) [371228] *[NSFileManager defaultManager] Backtrace: 0x1a000f3d4 Foundation!standardDefaultCenter 0x1a199dc5c UIKitCore!-[UITextEffectsWindow updateForOrientation:forceResetTransform:] 0x1a199e2f0 UIKitCore!+[UITextEffectsWindow _sharedTextEffectsWindowforWindowScene:allowHosted:matchesStatusBarOrientationOnAccess:shouldCreateIfNecessary:] 0x1a199e3fc UIKitCore!+[UITextEffectsWindow sharedTextEffectsWindowForWindowScene:] 0x1a10c640c UIKitCore!-[UIInputResponderController containerWindow] 0x1a10c651c UIKitCore!-[UIInputResponderController containerRootController] 0x1a149a1a8 UIKitCore!-[UIPeripheralHost(UIKitInternal) _isTransitioning] 0x1a0fa219c UIKitCore!-[_UINavigationInteractiveTransition _gestureRecognizer:shouldReceiveEvent:] 0x1a11cfd54 UIKitCore!-[UIGestureRecognizer _delegateShouldReceiveEvent:] 0x1a11cf3f4 UIKitCore!-[UIGestureRecognizer _shouldReceiveTouch:forEvent:recognizerView:] 0x1a171234c UIKitCore!__72-[UITouchesEvent _addGestureRecognizersForView:toTouch:forContinuation:]_block_invoke 0x1a1711cf8 UIKitCore!__62-[UITouchesEvent _collectGestureRecognizersForView:withBlock:]_block_invoke 0x1a1711784 UIKitCore!-[UITouchesEvent _collectGestureRecognizersForView:withBlock:] 0x1a17121b8 UIKitCore!-[UITouchesEvent _addGestureRecognizersForView:toTouch:forContinuation:] 0x1a1712074 UIKitCore!-[UITouchesEvent _addGestureRecognizersForView:toTouch:] 0x1a1712690 UIKitCore!-[UITouchesEvent _addTouch:forDelayedDelivery:] (agent) [371228] Return Value: 0x283a77540 (agent) [536962] Called: *[NSFileManager _registerForUbiquityAccountChangeNotifications] 0 arguments(Kind: instance) (Super: NSObject) (agent) [536962] *[NSFileManager _registerForUbiquityAccountChangeNotifications] Backtrace: 0x1a000f3e0 Foundation!standardDefaultCenter 0x1a199dc5c UIKitCore!-[UITextEffectsWindow updateForOrientation:forceResetTransform:] 0x1a199e2f0 UIKitCore!+[UITextEffectsWindow _sharedTextEffectsWindowforWindowScene:allowHosted:matchesStatusBarOrientationOnAccess:shouldCreateIfNecessary:] 0x1a199e3fc UIKitCore!+[UITextEffectsWindow sharedTextEffectsWindowForWindowScene:] 0x1a10c640c UIKitCore!-[UIInputResponderController containerWindow] 0x1a10c651c UIKitCore!-[UIInputResponderController containerRootController] 0x1a149a1a8 UIKitCore!-[UIPeripheralHost(UIKitInternal) _isTransitioning] 0x1a0fa219c UIKitCore!-[_UINavigationInteractiveTransition _gestureRecognizer:shouldReceiveEvent:] 0x1a11cfd54 UIKitCore!-[UIGestureRecognizer _delegateShouldReceiveEvent:] 0x1a11cf3f4 UIKitCore!-[UIGestureRecognizer _shouldReceiveTouch:forEvent:recognizerView:] 0x1a171234c UIKitCore!__72-[UITouchesEvent _addGestureRecognizersForView:toTouch:forContinuation:]_block_invoke 0x1a1711cf8 UIKitCore!__62-[UITouchesEvent _collectGestureRecognizersForView:withBlock:]_block_invoke 0x1a1711784 UIKitCore!-[UITouchesEvent _collectGestureRecognizersForView:withBlock:] 0x1a17121b8 UIKitCore!-[UITouchesEvent _addGestureRecognizersForView:toTouch:forContinuation:] 0x1a1712074 UIKitCore!-[UITouchesEvent _addGestureRecognizersForView:toTouch:] 0x1a1712690 UIKitCore!-[UITouchesEvent _addTouch:forDelayedDelivery:]
|
看了调用栈都是跟系统相关的,底层会去调用libc下的open函数,frida hook open函数
1 2 3 4 5 6 7 8 9 10 11
| function hook_libc_open(){ var openAddr = Module.findExportByName(null, "open"); Interceptor.attach(openAddr, { onEnter: function(args) { console.log("path =>", args[0].readCString()); console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n\t")) }, onLeave: function(retval){ console.log(retval) } }) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| [iOS Device::EnvDetection ]-> path => /usr/lib/frida/frida-server 0x100486284 EnvDetection!-[FileAndFolderPathDetection checkPathByOpen:] 0x1004d8978 EnvDetection!CommonUtils.checkPath(path:) 0x1004af724 EnvDetection!closure #1 in closure #1 in closure #1 in FridaFileDetectionView.body.getter 0x1a5c0a280 SwiftUI!HStack.init(alignment:spacing:content:) 0x1004af280 EnvDetection!closure #1 in closure #1 in FridaFileDetectionView.body.getter 0x1a5b0ee74 SwiftUI!ForEachChild.updateValue() 0x1a562d7fc SwiftUI!partial apply for implicit closure #2 in implicit closure #1 in closure #1 in closure #1 in Attribute.init<A>(_:) 0x1c8168a50 AttributeGraph!AG::Graph::UpdateStack::update() 0x1c8168e84 AttributeGraph!AG::Graph::update_attribute(AG::data::ptr<AG::Node>, bool) 0x1c8172088 AttributeGraph!AG::Subgraph::update(unsigned int) 0x1a5c80cdc SwiftUI!GraphHost.runTransaction() 0x1a5c83e1c SwiftUI!GraphHost.runTransaction(_:) 0x1a5c827a8 SwiftUI!GraphHost.flushTransactions() 0x1a5c83db4 SwiftUI!closure #1 in closure #1 in GraphHost.asyncTransaction<A>(_:mutation:style:) 0x1a5752168 SwiftUI!partial apply for closure #1 in ViewGraphDelegate.updateGraph<A>(body:) 0x1a5bd5e9c SwiftUI!closure #1 in ViewRendererHost.updateViewGraph<A>(body:) 0xffffffffffffffff
|
这里调用了open的只有 /usr/lib/frida/frida-server 未发现的文件,看它的调用栈上一级[FileAndFolderPathDetection checkPathByOpen:] 很像是检测路径的,objection hook一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| com.zsk.EnvDetection on (iPhone: 14.4.1) [usb] # ios hooking watch class FileAndFolderPathDetection (agent) Watching method: - checkPathByNSFileManager: (agent) Watching method: - checkPathByAccess: (agent) Watching method: - checkPathByStat: (agent) Watching method: - checkPathByLstat: (agent) Watching method: - checkPathByStatfs: (agent) Watching method: - checkPathByOpen: (agent) Watching method: - checkPathByFopen: (agent) Registering job 393195. Type: watch-class-methods for: FileAndFolderPathDetection
com.zsk.EnvDetection on (iPhone: 14.4.1) [usb] # (agent) [393195] Called: [FileAndFolderPathDetection checkPathByNSFileManager:] (Kind: instance) (Super: NSObject) (agent) [393195] Called: [FileAndFolderPathDetection checkPathByAccess:] (Kind: instance) (Super: NSObject) (agent) [393195] Called: [FileAndFolderPathDetection checkPathByNSFileManager:] (Kind: instance) (Super: NSObject) (agent) [393195] Called: [FileAndFolderPathDetection checkPathByAccess:] (Kind: instance) (Super: NSObject) (agent) [393195] Called: [FileAndFolderPathDetection checkPathByStat:] (Kind: instance) (Super: NSObject) (agent) [393195] Called: [FileAndFolderPathDetection checkPathByLstat:] (Kind: instance) (Super: NSObject) (agent) [393195] Called: [FileAndFolderPathDetection checkPathByStatfs:] (Kind: instance) (Super: NSObject) (agent) [393195] Called: [FileAndFolderPathDetection checkPathByOpen:] (Kind: instance) (Super: NSObject) (agent) [393195] Called: [FileAndFolderPathDetection checkPathByFopen:] (Kind: instance) (Super: NSObject) (agent) [393195] Called: [FileAndFolderPathDetection checkPathByNSFileManager:] (Kind: instance) (Super: NSObject) (agent) [393195] Called: [FileAndFolderPathDetection checkPathByNSFileManager:] (Kind: instance) (Super: NSObject) (agent) [393195] Called: [FileAndFolderPathDetection checkPathByNSFileManager:] (Kind: instance) (Super: NSObject) (agent) [393195] Called: [FileAndFolderPathDetection checkPathByNSFileManager:] (Kind: instance) (Super: NSObject) (agent) [393195] Called: [FileAndFolderPathDetection checkPathByNSFileManager:] (Kind: instance) (Super: NSObject) (agent) [393195] Called: [FileAndFolderPathDetection checkPathByNSFileManager:] (Kind: instance) (Super: NSObject)
|
这里使用frida对FileAndFolderPathDetection的所有函数进行hook,打印一下参数返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| function print_arguments(args) { var n = 100; var last_arg = ''; for (var i = 2; i < n; ++i) { var arg = (new ObjC.Object(args[i])).toString(); if (arg == 'nil' || arg == last_arg) { break; } last_arg = arg; return ' args' + (i-2) + ': ' + (new ObjC.Object(args[i])).toString() } }
function IosTraceClass(targetClass) { console.log("Entering ios hooking => " + targetClass) if (ObjC.classes.hasOwnProperty(targetClass)) { var methods = ObjC.classes[targetClass].$ownMethods; methods.forEach(function (method) { console.log("hooking " + method); try { Interceptor.attach(ObjC.classes[targetClass][method].implementation, { onEnter: function (args) { this.output = "" this.output = this.output.concat("[*] Detected call to: " + targetClass + " -> " + method) this.output = this.output.concat("\r\n") this.output = this.output.concat(print_arguments(args)) }, onLeave: function (ret) { this.output = this.output.concat("\r\nios return value => ", ret, "\r\n") console.log(this.output) } }) } catch (error) { console.log("ios hooking failed error is => ", error) } }) } }
function traceClass(targetClass) { if (ObjC.available) { IosTraceClass(targetClass) } }
function main() { traceClass("FileAndFolderPathDetection") }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| [iOS Device::EnvDetection ]-> [*] Detected call to: FileAndFolderPathDetection -> - checkPathByNSFileManager: args0: /usr/sbin/frida-server ios return value => 0x0
[*] Detected call to: FileAndFolderPathDetection -> - checkPathByAccess: args0: /usr/sbin/frida-server ios return value => 0x1 [*] Detected call to: FileAndFolderPathDetection -> - checkPathByNSFileManager: args0: /usr/lib/frida/frida-server ios return value => 0x0
[*] Detected call to: FileAndFolderPathDetection -> - checkPathByAccess: args0: /usr/lib/frida/frida-server ios return value => 0x0
[*] Detected call to: FileAndFolderPathDetection -> - checkPathByStat: args0: /usr/lib/frida/frida-server ios return value => 0x0
[*] Detected call to: FileAndFolderPathDetection -> - checkPathByLstat: args0: /usr/lib/frida/frida-server ios return value => 0x0
[*] Detected call to: FileAndFolderPathDetection -> - checkPathByStatfs: args0: /usr/lib/frida/frida-server ios return value => 0x0
[*] Detected call to: FileAndFolderPathDetection -> - checkPathByOpen: args0: /usr/lib/frida/frida-server ios return value => 0x0
[*] Detected call to: FileAndFolderPathDetection -> - checkPathByFopen: args0: /usr/lib/frida/frida-server ios return value => 0x0
[*] Detected call to: FileAndFolderPathDetection -> - checkPathByNSFileManager: args0: /usr/lib/frida/frida-agent.dylib ios return value => 0x1
[*] Detected call to: FileAndFolderPathDetection -> - checkPathByNSFileManager: args0: /Library/LaunchDaemons/re.frida.server.plist ios return value => 0x1
[*] Detected call to: FileAndFolderPathDetection -> - checkPathByNSFileManager: args0: /Library/dpkg/info/re.frida.server.extrainst_ ios return value => 0x1
[*] Detected call to: FileAndFolderPathDetection -> - checkPathByNSFileManager: args0: /Library/dpkg/info/re.frida.server.list ios return value => 0x1
[*] Detected call to: FileAndFolderPathDetection -> - checkPathByNSFileManager: args0: /Library/dpkg/info/re.frida.server.prerm ios return value => 0x1
[*] Detected call to: FileAndFolderPathDetection -> - checkPathByNSFileManager: args0: /Library/dpkg/info/re.frida.server.md5sums ios return value => 0x1
|
跟上面的图对照可以发现返回值是0x1的就是检测到的,所以修改所有函数的返回值为0x0,上面代码30行去掉注释,再次运行就全过掉了。
越狱检测
其中文件/目录检测,插件APP检测也都是同上面frida文件检测,也是同样过掉了。
代理检测
代理APP检测
我设备下载还打开了Shadowrocket,不知道为什么代理APP检测测出来。
也是objection查找类试了好几个关键词 proxy,network出来很多个类,既然它APP检测有Shadowrocket,直接ida打开搜索字符串
下载app压缩包:
https://github.com/cherubstar/iOSEnvDetection/releases/download/1.3/EnvDetection.app.zip
解压后用ida打开EnvDetection
菜单View->Open subviews->Strings 查找Shadowrocket会找到 Shadowrocket.app,按x查找引用,跳到引用的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| __text:0000000100005618 A2 03 00 B0 42 20 0E 91 ADRL X2, cfstr_HttpcatcherApp __text:0000000100005620 E8 03 00 91 MOV X8, SP __text:0000000100005624 E8 57 00 F9 STR X8, [SP,#0x1E0+var_138] __text:0000000100005628 A9 03 00 B0 29 A1 0E 91 ADRL X9, cfstr_KitsunebiApp __text:0000000100005630 09 01 00 F9 STR X9, [X8,#0x1E0+var_1E0] __text:0000000100005634 A9 03 00 B0 29 21 0F 91 ADRL X9, cfstr_PotatsoApp __text:000000010000563C 09 05 00 F9 STR X9, [X8,#0x1E0+var_1D8] __text:0000000100005640 A9 03 00 B0 29 A1 0F 91 ADRL X9, cfstr_QuantumultApp __text:0000000100005648 09 09 00 F9 STR X9, [X8,#0x1E0+var_1D0] __text:000000010000564C A9 03 00 B0 29 21 10 91 ADRL X9, cfstr_QuantumultXApp __text:0000000100005654 09 0D 00 F9 STR X9, [X8,#0x1E0+var_1C8] __text:0000000100005658 A9 03 00 B0 29 A1 10 91 ADRL X9, cfstr_ShadowrocketAp __text:0000000100005660 09 11 00 F9 STR X9, [X8,#0x1E0+var_1C0] __text:0000000100005664 A9 03 00 B0 29 21 11 91 ADRL X9, cfstr_SurgeIosApp __text:000000010000566C 09 15 00 F9 STR X9, [X8,#0x1E0+var_1B8] __text:0000000100005670 A9 03 00 B0 29 A1 11 91 ADRL X9, cfstr_ThorApp
|
按tab跳到代码页面,发现函数名是 -[AgentDetection checkAgentAppIsInstalled](AgentDetection *self, SEL a2),使用objection hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| com.zsk.EnvDetection on (iPhone: 14.4.1) [usb] # ios hooking watch method "-[AgentDetection checkAgentAppIsInstalled]" --dump-args --dump-backtrace --dump-return (agent) Found selector at 0x1026b8f34 as -[AgentDetection checkAgentAppIsInstalled] (agent) Registering job 473143. Type: watch-method for: -[AgentDetection checkAgentAppIsInstalled] com.zsk.EnvDetection on (iPhone: 14.4.1) [usb] # (agent) [473143] Called: -[AgentDetection checkAgentAppIsInstalled] 0 arguments(Kind: instance) (Super: NSObject) (agent) [473143] -[AgentDetection checkAgentAppIsInstalled] Backtrace: 0x1026da35c EnvDetection!AgentDetectionViewModel.isInstalled() 0x10270d8ac EnvDetection!closure #1 in AgentApplicationDetectionView.body.getter 0x197e23a60 SwiftUI!List<>.init(content:) 0x10270d3c0 EnvDetection!AgentApplicationDetectionView.body.getter 0x10270ef98 EnvDetection!protocol witness for View.body.getter in conformance AgentApplicationDetectionView 0x197b35b8c SwiftUI!partial apply for closure #1 in ViewBodyAccessor.updateBody(of:changed:) 0x197e8e248 SwiftUI!closure #1 in BodyAccessor.setBody(_:) 0x197b2f328 SwiftUI!ViewBodyAccessor.updateBody(of:changed:) 0x197e8e6d4 SwiftUI!DynamicBody.updateValue() 0x197b557fc SwiftUI!partial apply for implicit closure #2 in implicit closure #1 in closure #1 in closure #1 in Attribute.init<A>(_:) 0x1ba690a50 AttributeGraph!AG::Graph::UpdateStack::update() 0x1ba690e84 AttributeGraph!AG::Graph::update_attribute(AG::data::ptr<AG::Node>, bool) 0x1ba696588 AttributeGraph!AG::Graph::input_value_ref_slow(AG::data::ptr<AG::Node>, AG::AttributeID, unsigned int, AGSwiftMetadata const*, bool*, long) 0x1ba6a85bc AttributeGraph!AGGraphGetValue 0x197fa11c4 SwiftUI!EnvironmentReadingChild.updateValue() 0x197b557fc SwiftUI!partial apply for implicit closure #2 in implicit closure #1 in closure #1 in closure #1 in Attribute.init<A>(_:) (agent) [473143] Return Value: 0x281954120
|
没啥有用的信息,在ida的伪代码找到了调用 AppInfo的listInstalledApps
1 2
| v26 = (id)objc_alloc_init(&OBJC_CLASS___AppInfo); v5 = objc_msgSend(v26, "listInstalledApps");
|
直接函数列表过滤查看listInstalledApps的实现,查看代码找到,对其hook
1
| v2 = -[AppInfo fetchApps](self, "fetchApps");
|
1 2 3 4 5 6 7 8 9 10 11
| function hook_fetchApps(){ Interceptor.attach(ObjC.classes["AppInfo"]["- fetchApps"].implementation, { onEnter: function(args){ console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n\t")) }, onLeave: function(ret){ console.log("ret value is => ",ret, "=>", ObjC.Object(ret), "=>", ObjC.Object(ret).$className); } }) }
|
调用栈里找到了 -[AgentDetection checkAgentAppIsInstalled],这个返回值是NSArray,返回一份不包含任何关键app的NSArray给它即可。为了通用,直接创建一个空的NSArray返回给fetchApps,Objc.classes.NSArray‘new’,去掉7,8行注释即可
常规代理检测
用objection看一下AgentDetection函数
1 2 3 4 5
| com.zsk.EnvDetection on (iPhone: 14.4.1) [usb] # ios hooking list class_methods AgentDetection - checkAgentAppIsInstalled - checkProxyStatusByCFNetworkCopySystemProxySettings - getCFNetworkCopySystemProxySettingsDetails - setConnectionProxyDictionary
|
看输出的函数名后面3个检测代理的,其中常规代理检测会调用checkProxyStatusByCFNetworkCopySystemProxySettings,直接hook
1 2 3 4 5
| com.zsk.EnvDetection on (iPhone: 14.4.1) [usb] # (agent) [237829] Called: *[AgentDetection checkProxyStatusByCFNetworkCopySystemProxySettings] 0 arguments(Kind: instance) (Super: NSObject) (agent) [237829] Return Value: 0x0
com.zsk.EnvDetection on (iPhone: 14.4.1) [usb] # (agent) [237829] Called: *[AgentDetection checkProxyStatusByCFNetworkCopySystemProxySettings] 0 arguments(Kind: instance) (Super: NSObject) (agent) [237829] Return Value: 0x1
|
则直接修改其返回值为0x0即可
1 2 3 4 5 6 7 8
| function hook_checkSystemProxy(){ Interceptor.attach(ObjC.classes["AgentDetection"]["- checkProxyStatusByCFNetworkCopySystemProxySettings"].implementation, { onEnter: function(args){ }, onLeave: function(ret){ ret.replace(new NativePointer(0x0)) } }) }
|
设置代理检测变量
如果使用WiFi 代理抓包,会发现请求发不出去,charles也抓不到任何包
如果使用vpn代理的话,以Shadowrocket为例,Shadowrocket 的作用是代理转发,vpn代理是处于网络层的,可以抓到更多的数据包,也可以过掉代理检测。
一般抓包也推荐使用vpn代理
调试检测
常规反调试函数
ptrace
用的是ptrace来检测,一个进程同时只能被另外一个进程调试,所以这里开了ptrace后,其它调试工具xcode,lldb就没法对其进行调试
没打开ptrace是可以调试上的
打开后调试则报错了
ptrace 是系统函数,那直接hook
1
| int ptrace(int _request, pid _pid, caddr_t _addr, int _data);
|
1 2 3 4 5 6
| function hook_ptrace(){ var ptraceAddr = Module.findExportByName(null, 'ptrace'); Interceptor.replace(ptraceAddr, new NativeCallback(function(i1, i2, i3, i4){ console.log("ptrace flag is => ", i1); }, 'void', ['int', 'int', 'int', 'int'])) }
|
不让ptrace函数执行就行了,直接置空不做任何操作
再次打开ptrace用xcode附加调试则没有报错现象了
ptrace+svc
在 iOS 反动态调试中,常用 svc #0x80 汇编指令来实现对 ptrace 和 syscall 系统调用的直接调用。通过这种方式,可以规避标准库提供的 ptrace() 函数,直接触发系统调用,增加反调试的难度,使得 lldb 无法正常附加到 app 进程。
svc #0x80 指令等同于 syscall() 函数在 C 语言中的使用,都是直接触发系统调用,而不会走标准库函数的包装。
当开启ptrace+svc后上面的hook函数没触发,查看它的源码也可以看到
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| - (void)ori_ptrace { ptrace(PT_DENY_ATTACH, 0, 0, 0); }
- (void)svc_ptrace { #ifdef __arm64__ __asm__("mov X0, #31\n" "mov X1, #0\n" "mov X2, #0\n" "mov X3, #0\n" "mov w16, #26\n" "svc #0x80"); #endif }
|
也可以用直接ida打开搜索 SVC
搜索结果
跟进查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| __text:0000000100006028 __text:0000000100006028 __DebugDetection_svc_ptrace_ __text:0000000100006028 __text:0000000100006028 var_10= -0x10 __text:0000000100006028 var_8= -8 __text:0000000100006028 __text:0000000100006028 FF 43 00 D1 SUB SP, SP, #0x10 __text:000000010000602C E0 07 00 F9 STR X0, [SP,#0x10+var_8] __text:0000000100006030 E1 03 00 F9 STR X1, [SP,#0x10+var_10] __text:0000000100006034 E0 03 80 D2 MOV X0, #0x1F __text:0000000100006038 01 00 80 D2 MOV X1, #0 __text:000000010000603C 02 00 80 D2 MOV X2, #0 __text:0000000100006040 03 00 80 D2 MOV X3, #0 __text:0000000100006044 50 03 80 52 MOV W16, #0x1A __text:0000000100006048 01 10 00 D4 SVC 0x80 __text:000000010000604C FF 43 00 91 ADD SP, SP, #0x10 __text:0000000100006050 C0 03 5F D6 RET
|
汇编指令跟源码差不多
方式一可以使用patch的方式,修改 svc #0x80,在IDA View-A界面中选中svc那一行,点击工具栏Edit–>Patch program–>Change Bytes,弹出修改框,将01 10 00 D4修改为nop的byte码 1F 20 03 D5(如何知道nop的byte码,寻找一个nop,change byte就可以了)
将patch修改保存到程序里去,点击工具栏Edit–>Patch program–>Apply patches to input file
然后重签名打包安装就可以了
方式二直接用frida对 -[DebugDetection svc_ptrace]这个函数置空不执行,这是ObjC的函数,写法跟上面的不一样
1 2 3 4 5 6 7 8 9
| function hook_svc_ptrace(){ const DebugDetection = ObjC.classes.DebugDetection; const svc_ptrace = DebugDetection['- svc_ptrace'] const oldImpl = svc_ptrace.implementation; svc_ptrace.implementation = ObjC.implement(svc_ptrace, (handle, selector) => { console.log(handle, selector); }) }
|
只要不执行原函数就行了
到这里其实都是要找函数位置去hook返回值或者置空达到的反调试效果,那当代码混淆以及代码膨,使得想要快速定位svc 0x80调用并将其patch掉就变得难以实现。
可以查看大佬的文章:https://bbs.kanxue.com/thread-273796.htm
ptrace+xor
通过ida查找到对应函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| void __cdecl -[DebugDetection xor_ptrace](DebugDetection *self, SEL a2) { char v2; void *__handle; void *v4; _BYTE *i; _DWORD __symbol[2]; SEL v7; DebugDetection *v8;
v8 = self; v7 = a2; __symbol[0] = 268633345; *(_DWORD *)((char *)__symbol + 3) = 1897140752; for ( i = __symbol; ; ++i ) { v2 = *i ^ 0x71; *i = v2; if ( !v2 ) break; } __handle = dlopen(0LL, 10); v4 = dlsym(__handle, (const char *)__symbol); ((void (__fastcall *)(__int64, _QWORD, _QWORD))v4)(31LL, 0LL, 0LL); dlclose(__handle); }
|
这里使用了 dlsym() 函数从动态链接库中查找 ptrace 函数的符号地址,进行调用,用上面的hook就可以了
查看了它的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| - (void)xor_ptrace { unsigned char str[] = { ('q' ^ 'p'), ('q' ^ 't'), ('q' ^ 'r'), ('q' ^ 'a'), ('q' ^ 'c'), ('q' ^ 'e'), ('q' ^ '\0') }; unsigned char *p = str; while (((*p) ^= 'q') != '\0') p++; int (*ptrace_ptr)(int _request, pid_t _pid, caddr_t _addr, int _data); void *handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW); ptrace_ptr = dlsym(handle, (const char *)str); ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0); dlclose(handle); }
|
定义一个 str 数组, 拼接成ptrace字符串,最终会对 ptrace 函数进行调用
syscall
ida查看代码后去调用了系统函数syscall
1 2 3 4
| void __cdecl -[DebugDetection ori_syscall](DebugDetection *self, SEL a2) { syscall(26, 31LL, 0LL, 0LL, 0LL, a2, self); }
|
syscall的定义如下
1
| long syscall(long number, ...);
|
1 2 3 4 5 6
| function hook_syscall(){ var syscallAddr = Module.findExportByName(null, 'syscall'); Interceptor.replace(syscallAddr, new NativeCallback(function(i1){ console.log("syscall flag is => ", i1); }, 'void', ['int'])) }
|
syscall+svc
ptrace+svc 的hook改一下就可以
syscall+xor
同syscall
sysctl
同样找到代码位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| int __cdecl -[DebugDetection ori_sysctl](DebugDetection *self, SEL a2) { size_t v3[3]; char v4[32]; int v5; int v6[4];
v3[2] = (size_t)self; v3[1] = (size_t)a2; v6[0] = 1; v6[1] = 14; v6[2] = 1; v6[3] = getpid(); v3[0] = 648LL; v5 = 0; sysctl(v6, 4u, v4, v3, 0LL, 0LL); return (v5 & 0x800) != 0; }
|
1
| int sysctl(int *name, int namelen, void *oldval, size_t *oldlenp, void *newval, size_t newlen);
|
hook代码同上面的差不多
sysctl+svc和sysctl+xor都跟上面的也差不多