iOS逆向系列(二) 反调试与绕过案例实战
zsk Lv4

项目地址:https://github.com/cherubstar/iOSEnvDetection
根据项目使用Xcode构建app安装到手机上
iPhone:iPhone se2(越狱),iOS 14.4.1

Frida检测

端口检测

image
上来先用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)

文件检测

image
既然是文件检测,那肯定涉及到文件的读取,查资料把文件操作相关的类都用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)
// ret.replace(new NativePointer(0x0)); // 修改返回值
}
})
} 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行去掉注释,再次运行就全过掉了。
image

越狱检测

image
其中文件/目录检测,插件APP检测也都是同上面frida文件检测,也是同样过掉了。

代理检测

代理APP检测

image
我设备下载还打开了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 ; "HttpCatcher.app"
__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 ; "Kitsunebi.app"
__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 ; "Potatso.app"
__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 ; "Quantumult.app"
__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 ; "Quantumult X.app"
__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 ; "Shadowrocket.app"
__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 ; "Surge-iOS.app"
__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 ; "Thor.app"

按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);
// var emptyNSArray = Objc.classes.NSArray['new']();
// ret.replace(emptyNSArray);
}
})
}

调用栈里找到了 -[AgentDetection checkAgentAppIsInstalled],这个返回值是NSArray,返回一份不包含任何关键app的NSArray给它即可。为了通用,直接创建一个空的NSArray返回给fetchApps,Objc.classes.NSArray‘new’,去掉7,8行注释即可

常规代理检测

image
用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代理

调试检测

常规反调试函数

image

ptrace

用的是ptrace来检测,一个进程同时只能被另外一个进程调试,所以这里开了ptrace后,其它调试工具xcode,lldb就没法对其进行调试
没打开ptrace是可以调试上的
image
打开后调试则报错了
image
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
image
搜索结果
image
跟进查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__text:0000000100006028                               ; void __cdecl -[DebugDetection svc_ptrace](DebugDetection *self, SEL)
__text:0000000100006028 __DebugDetection_svc_ptrace_ ; DATA XREF: __objc_methlist:0000000100067FEC↓o
__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就可以了)
image
将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);
// oldImpl(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; // w8
void *__handle; // [xsp+10h] [xbp-30h]
void *v4; // [xsp+18h] [xbp-28h]
_BYTE *i; // [xsp+20h] [xbp-20h]
_DWORD __symbol[2]; // [xsp+28h] [xbp-18h] BYREF
SEL v7; // [xsp+30h] [xbp-10h]
DebugDetection *v8; // [xsp+38h] [xbp-8h]

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 {
//A 异或 B 等到 C,C 再异或 A 得到 B,隐藏 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 符号
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]; // [xsp+18h] [xbp-2C8h] BYREF
char v4[32]; // [xsp+30h] [xbp-2B0h] BYREF
int v5; // [xsp+50h] [xbp-290h]
int v6[4]; // [xsp+2B8h] [xbp-28h] BYREF

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都跟上面的也差不多

 评论