移除SIM卡弹框
前言
- 目的:如果iPhone没有插入SIM卡,每次重启或注销后,重新进入桌面都会弹出如下框。每次点击
好
,弹框才会消除,才能正常地使用iPhone,这样的体验也太差了。 - 环境:iPhone6、iOS11、unc0ver越狱
- 工具:
- Cycript可以用来探索、修改、调试正在运行的iOS APP
- IDA静态分析二进制文件
- LLDB与DebugServer动态调试
- Theos插件开发
从视图到控制器
- 注入SpringBoard,查看详细的层级结构。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21iPhoneName:~ root# cycript -p SpringBoard
UIApp.keyWindow.recursiveDescription().toString()
...省略很多...
| | <_UIAlertControllerView: 0x12c043600; frame = (52.5 281.5; 270 104.5); layer = <CALayer: 0x1c4a2ede0>>
| | | <UIView: 0x12fb85c90; frame = (0 0; 270 104.5); layer = <CALayer: 0x1c483bb40>>
| | | | <_UIAlertControllerInterfaceActionGroupView: 0x12fd3c4f0; frame = (0 0; 270 104.5); opaque = NO; gestureRecognizers = <NSArray: 0x1c56450a0>; layer = <CALayer: 0x1c4a2ee60>>
| | | | | <_UIDimmingKnockoutBackdropView: 0x12fd28600; frame = (0 0; 270 104.5); clipsToBounds = YES; layer = <CALayer: 0x1c4633e20>>
| | | | | | <UIView: 0x12fd2b5f0; frame = (0 0; 270 104.5); clipsToBounds = YES; layer = <CALayer: 0x1c4634040>>
| | | | | | <UIVisualEffectView: 0x12fd29330; frame = (0 0; 270 104.5); clipsToBounds = YES; layer = <CALayer: 0x1c4633d60>>
| | | | | | | <_UIVisualEffectBackdropView: 0x12fd2b3e0; frame = (0 0; 270 104.5); autoresize = W+H; userInteractionEnabled = NO; layer = <UICABackdropLayer: 0x1c4633dc0>>
| | | | | | | <_UIVisualEffectSubview: 0x12fd36f00; frame = (0 0; 270 104.5); autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x1c4634000>>
| | | | | <UIView: 0x12fd35cb0; frame = (0 0; 270 104.5); clipsToBounds = YES; layer = <CALayer: 0x1c4633800>>
| | | | | | <_UIInterfaceActionGroupHeaderScrollView: 0x12ca8c600; frame = (0 0; 270 60); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x1c1647da0>; layer = <CALayer: 0x1c0e3af20>; contentOffset: {0, 0}; contentSize: {270, 60}; adjustedContentInset: {0, 0, 0, 0}>
| | | | | | | <UIView: 0x12fcbc9e0; frame = (0 0; 270 60); layer = <CALayer: 0x1c0a3eea0>>
| | | | | | | | <UILabel: 0x12fcb98e0; frame = (16 20; 238 20.5); text = '\u672a\u5b89\u88c5 SIM \u5361'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x1c0a82f30>>
...省略很多...
```
* 从输入内容可以发现`\u672a\u5b89\u88c5 SIM \u5361`,对这个字符串进行Unicode解码,发现得到的内容和提示内容一致。
```shell
echo "\u672a\u5b89\u88c5 SIM \u5361"
未安装 SIM 卡 - 从上可知,
0x12fcb98e0
就是我们需要的目标视图,顺着它的响应链一直不停地往上寻找(一直调用nextResponder方法),就可以找到对应的控制器_SBAlertController
。1
2
3
4
5
6
7
8
9
10
11
12
13
14#0x12fcb98e0.nextResponder
"<UIView: 0x12fcbc9e0; frame = (0 0; 270 60); layer = <CALayer: 0x1c0a3eea0>>"
#0x12fcb98e0.nextResponder.nextResponder
"<_UIInterfaceActionGroupHeaderScrollView: 0x12ca8c600; frame = (0 0; 270 60); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x1c1647da0>; layer = <CALayer: 0x1c0e3af20>; contentOffset: {0, 0}; contentSize: {270, 60}; adjustedContentInset: {0, 0, 0, 0}>"
#0x12fcb98e0.nextResponder.nextResponder.nextResponder
"<UIView: 0x12fd35cb0; frame = (0 0; 270 104.5); clipsToBounds = YES; layer = <CALayer: 0x1c4633800>>"
#0x12fcb98e0.nextResponder.nextResponder.nextResponder.nextResponder
"<_UIAlertControllerInterfaceActionGroupView: 0x12fd3c4f0; frame = (0 0; 270 104.5); opaque = NO; gestureRecognizers = <NSArray: 0x1c56450a0>; layer = <CALayer: 0x1c4a2ee60>>"
#0x12fcb98e0.nextResponder.nextResponder.nextResponder.nextResponder.nextResponder
"<UIView: 0x12fb85c90; frame = (0 0; 270 104.5); layer = <CALayer: 0x1c483bb40>>"
#0x12fcb98e0.nextResponder.nextResponder.nextResponder.nextResponder.nextResponder.nextResponder
"<_UIAlertControllerView: 0x12c043600; frame = (52.5 281.5; 270 104.5); layer = <CALayer: 0x1c4a2ede0>>"
#0x12fcb98e0.nextResponder.nextResponder.nextResponder.nextResponder.nextResponder.nextResponder.nextResponder
"<_SBAlertController: 0x12cb8da00; title: \"\xe6\x9c\xaa\xe5\xae\x89\xe8\xa3\x85 SIM \xe5\x8d\xa1\">" _SBAlertController
就是我们寻找的目标控制器,通过打印发现继承于UIAlertController
控制器。1
2[#0x12cb8da00 superclass]
UIAlertController- 如果有iOS开发经验的同学就会知道这个弹框的伪代码大致如下所示。我们接下来就要寻找那里执行的这段代码,也就是说执行这段代码的条件。让条件不成立,就不会有弹框了
1
2
3
4
5UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"未安装SIM卡" message:nil preferredStyle: UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"好" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}];
[alert addAction: okAction];
[self presentViewController:alert animated:YES completion:nil];
定位核心代码
- iPhone上面开启debugserver
1
2killall -9 SpringBoard
debugserver 127.0.0.1:10011 -a SpringBoard - macOS上使用LLDB链接SpringBoard,并给
UIAlertController
所有方法断点- 连接:(lldb) process connect connect://127.0.0.1:10011
- 设置:(lldb) br set -r ‘[UIAlertController .*]‘
- 输入
c
继续程序,断点命中-[UIAlertController initWithNibName:bundle:]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15(lldb) c
Process 3303 resuming
Process 3303 stopped
thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.2
frame #0: 0x000000018a8ab9f8 UIKit` -[UIAlertController initWithNibName:bundle:]
UIKit`-[UIAlertController initWithNibName:bundle:]:
0x18a8ab9f8 <+0>: sub sp, sp, #0x40 ; =0x40
0x18a8ab9fc <+4>: stp x22, x21, [sp, #0x10]
0x18a8aba00 <+8>: stp x20, x19, [sp, #0x20]
0x18a8aba04 <+12>: stp x29, x30, [sp, #0x30]
0x18a8aba08 <+16>: add x29, sp, #0x30 ; =0x30
0x18a8aba0c <+20>: mov x19, x3
0x18a8aba10 <+24>: mov x20, x0
0x18a8aba14 <+28>: mov x0, x2
Target 0: (SpringBoard) stopped. - 使用
bt
查看调用堆栈。Foundation和CoreFoundation都属于系统框架,这样的调用先不管。选择SpringBoard调用最上层作为线索,开始追踪。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.2
* frame #0: 0x000000018a8ab9f8 UIKit` -[UIAlertController initWithNibName:bundle:]
frame #1: 0x0000000197180a00 SpringBoardUI` -[SBAlertItem _prepareNewAlertControllerWithLockedState:requirePasscodeForActions:] + 72
frame #2: 0x0000000100e3f428 SpringBoard` ___lldb_unnamed_symbol22251$$SpringBoard + 108
frame #3: 0x0000000100c66b10 SpringBoard` ___lldb_unnamed_symbol10676$$SpringBoard + 200
frame #4: 0x0000000100bc4bd4 SpringBoard` ___lldb_unnamed_symbol6876$$SpringBoard + 668
frame #5: 0x0000000100bc3960 SpringBoard` ___lldb_unnamed_symbol6858$$SpringBoard + 1484
frame #6: 0x0000000100bc337c SpringBoard` ___lldb_unnamed_symbol6857$$SpringBoard + 160
frame #7: 0x0000000100bc2c40 SpringBoard` ___lldb_unnamed_symbol6842$$SpringBoard + 292
frame #8: 0x0000000100bc8f6c SpringBoard` ___lldb_unnamed_symbol6958$$SpringBoard + 1872
frame #9: 0x000000018194b4c4 Foundation` __NSFireDelayedPerform + 412
frame #10: 0x0000000180f1a92c CoreFoundation` __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 28
frame #11: 0x0000000180f1a650 CoreFoundation` __CFRunLoopDoTimer + 864
frame #12: 0x0000000180f19e50 CoreFoundation` __CFRunLoopDoTimers + 248
frame #13: 0x0000000180f17a38 CoreFoundation` __CFRunLoopRun + 1928
frame #14: 0x0000000180e37fb8 CoreFoundation` CFRunLoopRunSpecific + 436
frame #15: 0x0000000182ccff84 GraphicsServices` GSEventRunModal + 100
frame #16: 0x000000018a40c2e8 UIKit` UIApplicationMain + 208
frame #17: 0x0000000196828570 FrontBoard` FBSystemAppMain + 1096
frame #18: 0x000000018095a56c libdyld.dylib` start + 4 - 找到SpringBoard的Mach-O文件,拖进去IDA,进行静态分析
1
2
3
4
5iPhoneName:~ root# ps -A | grep SpringBoard
3303 ?? 0:08.70 /System/Library/CoreServices/SpringBoard.app/SpringBoard
3305 ttys000 0:03.47 /usr/bin/debugserver 127.0.0.1:10011 -a SpringBoard
3318 ttys001 0:00.01 grep SpringBoard
iPhoneName:~ root# - 使用
image lookup -a
查看0x0000000100bc8f6c
在Macho-O文件中的位置。1
2
3(lldb) image lookup -a 0x0000000100bc8f6c
Address: SpringBoard[0x0000000100120f6c] (SpringBoard.__TEXT.__text + 1163848)
Summary: SpringBoard`___lldb_unnamed_symbol6958$$SpringBoard + 1872 - IDA中按
G
,输入0x0000000100120f6c
,定位到代码相应的位置,按Fn + F5
查看伪代码如下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...省略很多...
if ( !v62 && !((v49 | (v38 != 0)) & 1) && !v4->_lockEntryAlert )
{
v65 = v4->_status;
if ( (v65 | 2) != 2 )
{
v66 = ((id (__cdecl *)(SBSIMLockAlertItem_meta *, SEL, signed __int64))objc_msgSend)(
(SBSIMLockAlertItem_meta *)&OBJC_CLASS___SBSIMLockAlertItem,
"alertTitleForStatus:",
v65);
v67 = objc_retainAutoreleasedReturnValue(v66);
objc_release(v67);
if ( v67 )
{
v68 = (SBSIMLockAlertItem *)objc_msgSend(&OBJC_CLASS___SBSIMLockAlertItem, "alloc");
v69 = (SBSIMLockAlertItem *)-[SBSIMLockAlertItem initWithStatus:](v68, "initWithStatus:", v4->_status);
v70 = objc_msgSend(&OBJC_CLASS___SBAlertItemsController, v5);
v71 = (void *)objc_retainAutoreleasedReturnValue(v70);
objc_msgSend(v71, "activateAlertItem:", v69);
objc_release(v71);
v72 = objc_storeWeak(v90, v69);
v73 = sub_10048325C(v72);
v74 = objc_retainAutoreleasedReturnValue(v73);
if ( (unsigned int)os_log_type_enabled() )
{
HIWORD(v83) = 256;
v84 = 0;
v85 = 8;
v86 = v69;
_os_log_impl(&_mh_execute_header, v74, 1LL, aSlmPoppingAler, *(_QWORD *)((char *)&v83 + 2));
}
objc_release(v74);
objc_release(v69);
}
}
}
...省略很多...
分析核心代码
0x0000000100120f6c
实际定位到objc_release(v71);
行,往上看,发现三个if
判断条件:if ( v67 )
用来判断有没有获取到相应的提示文字。if ( (v65 | 2) != 2 )
判断_status
的取值,根据取值判断要不要执行下面的代码if ( !v62 && !((v49 | (v38 != 0)) & 1) && !v4->_lockEntryAlert )
判断条件太多,先不分析。
- 重点分析
_status
的取值- 上下文可以判断核心代码位于
-[SBSIMLockManager _updateToStatus:]
方法中,v4
的值为SBSIMLockManager
v65 = v4->_status;
等价于v65 = SBSIMLockManager->_status
。- 假设让
v4->_status = 0
,那么(v65 | 2) != 2
就不成立,不会执行下面的代码,就不会有弹框。
- 上下文可以判断核心代码位于
- 使用class-dump导出SpringBoard所有的头文件。打开
SBSIMLockManager.h
,发现1
2
3
4
5
6
7
8
9
10
11
12
13@interface SBSIMLockManager : NSObject
{
_Bool _isBrickedDevice;
long long _status;
SBSIMLockAlertItem *_weak_currentAlert;
SBSIMLockEntryAlert *_lockEntryAlert;
NSString *_languageCode;
_Bool _hasHadSIMWhileNotBricked;
_Bool _wasActivated;
_Bool _neededUIM;
}
...省略很多...
- (long long)status; // IMP=0x0000000100121a1c- 可以看出
status
只有getter方法和成员变量,没有setter方法。 status
只可读不可写,不能通过setter方法设置_status
的值,需要找到直接给_status
成员变量赋值的代码。
- 可以看出
- 光标移动到
v65 = v4->_status;
后面,按tab
键切换到汇编模式下,内容如下:1
0000000100120EE4 LDR X2, [X19,X27]
- LLDB中给
0000000100120EE4
下地址断点,命中断点后,打印x19和x27的值。从结果可以发现_status
的值是SBSIMLockManager
偏移了16个字节。1
2
3
4
5<SBSIMLockManager: 0x1c427eb00>
(lldb) po $x19
<SBSIMLockManager: 0x1c427eb00>
(lldb) po $x27
16 - 在
-[SBSIMLockManager _updateToStatus:]
最开始的地方设置地址断点。断点命中后,打印SBSIMLockManager
的地址,用这个地址加上16个字节,就是_status
的地址。1
2(lldb) po $x0
<SBSIMLockManager: 0x1c546af80> - 为
_status
设置一个观察点,观察点被命中的时候我们就知道_status
被改变了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18(lldb) watchpoint set expression (0x1c546af80+0x10)
Watchpoint created: Watchpoint 1: addr = 0x1c546af90 size = 8 state = enabled type = w
new value: 0
(lldb) c
Process 3864 resuming
Watchpoint 1 hit:
old value: 0
new value: 3
Process 3864 stopped
thread #1, queue = 'com.apple.main-thread', stop reason = watchpoint 1
frame #0: 0x0000000100aecb8c SpringBoard` ___lldb_unnamed_symbol6958$$SpringBoard + 880
SpringBoard`___lldb_unnamed_symbol6958$$SpringBoard:
0x100aecb8c <+880>: cmp x24, #0x4 ; =0x4
0x100aecb90 <+884>: b.hi 0x100aecc8c ; <+1136>
0x100aecb94 <+888>: cmp x24, #0x2 ; =0x2
...省略很多...
Target 0: (SpringBoard) stopped. - 内存观察点设置后,按c过掉断点,发现值被改了。断点停留在0x100aecb8c位置上,使用
image lookup -a
查找在IDA中的位置,如下。1
2
3(lldb) image lookup -a 0x100aecb8c
Address: SpringBoard[0x0000000100120b8c] (SpringBoard.__TEXT.__text + 1162856)
Summary: SpringBoard`___lldb_unnamed_symbol6958$$SpringBoard + 880 - IDA中按
G
,输入0x0000000100120b8c
,定位到如下位置,发现_status
是由v3赋值得到的。1
2
3v4->_isBrickedDevice = v12;
v4->_status = v3;
if ( v3 > 4 || v3 == 2 ) - 根据上下文可以得出:v3就是
-[SBSIMLockManager _updateToStatus:]
方法的第一个参数,所以改第一个参数就相当于更改了_status
的值。1
2
3
4
5void __cdecl -[SBSIMLockManager _updateToStatus:](SBSIMLockManager *self, SEL a2, signed __int64 a3) {
...省略很多...
v3 = a3;
...省略很多...
} - 利用Theos新建Tweak项目,
MobileSubstrate Bundle filter
填写com.apple.springboard
。新建完成后,在Tweak.x填写如下内容1
2
3
4
5%hook SBSIMLockManager
- (void)_updateToStatus:(long long)arg1 {
%orig(0);
}
%end - 进入Tweak项目目录,执行
make && make package && make install
安装插件,安装完成后,自动注销桌面。重新进入桌面后,发现弹框提示没有了,任务就完成了
想要更多
- 以上已经解决了移除SIM卡弹框的问题,但是想探究更多的方法,于是有了以下内容。
- 重新查看
SBSIMLockManager.h
,从名字和方法可以看出是个单例类,猜想是用来管理SIM卡的。可以看到跟status有关的方法。_updateToStatus
已经分析过了,这里着重分析_statusFromCT
和_CTToSBSIMStatus:
,1
2
3- (long long)_statusFromCT;
- (long long)_CTToSBSIMStatus:(struct __CFString *)arg1;
- (void)_updateToStatus:(long long)arg1 - 使用
br set -a
给- (long long)_CTToSBSIMStatus:(struct __CFString *)arg1;
下地址断点。- 断点命中后
po $x2
值为kCTSIMSupportSIMStatusNotInserted为没有插卡状态,所以才会有未插卡的弹框。 - 有开发经验的同学应该知道SIM卡有四种状态:
1
2
3
4extern NSString* const kCTSMSMessageReceivedNotification;
extern NSString* const kCTSMSMessageReplaceReceivedNotification;
extern NSString* const kCTSIMSupportSIMStatusNotInserted;
extern NSString* const kCTSIMSupportSIMStatusReady; - 其中CTSIMSupportSIMStatusReady为有卡时候的状态,为了不弹框,我们设置为有卡状态。
1
2
3
4
5%hook SBSIMLockManager
- (long long)_CTToSBSIMStatus:(NSString *)arg1 {
return %orig(@"kCTSIMSupportSIMStatusReady");
}
%end
- 断点命中后
- 同样地分析
- (long long)_statusFromCT;
- IDA打开加载SpringBoard二进制文件,查看方法实现的伪代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14signed __int64 __cdecl -[SBSIMLockManager _statusFromCT](SBSIMLockManager *self, SEL a2)
{
SBSIMLockManager *v2; // x19
struct objc_object *v3; // x0
void *v4; // x20
void *v5; // x21
v2 = self;
v3 = +[SBTelephonyManager sharedTelephonyManager](&OBJC_CLASS___SBTelephonyManager, "sharedTelephonyManager");
v4 = (void *)objc_retainAutoreleasedReturnValue(v3);
v5 = objc_msgSend(v4, "SIMStatus");
objc_release(v4);
return (signed __int64)-[SBSIMLockManager _CTToSBSIMStatus:](v2, "_CTToSBSIMStatus:", v5);
} - 还原以上伪代码如下
1
return [SBSIMLockManager _CTToSBSIMStatus: [[SBTelephonyManager sharedTelephonyManager] SIMStatus]
- 更改
SBTelephonyManager SIMStatus
的返回值就可以从源头改变状态值,代码如下1
2
3
4
5%hook SBTelephonyManager
- (id)SIMStatus {
return @"kCTSIMSupportSIMStatusReady";
}
%end
- IDA打开加载SpringBoard二进制文件,查看方法实现的伪代码
- 综上所述,上面三种方法选择其中一种就可以了。