事情的起因是本人需要使用到“拍照取自”这款软件,但是这款软件识别后导出文档需要付费,想着也就使用几次付费就有点浪费了,于是乎就找到了“Pro助手”这款插件,这款插件支持破解的App挺多,还挺方便的; 但是,不清楚啥原因这个插件会检测某两个源,只要你使用了这两个源,他就会弹窗提示你“珍爱手机,远离垃圾源”,如果仅仅是提示一下并且破解功能不生效,那么也就不会有接下来的事了,但是这个插件不仅提示还调用exit让App直接退出,这就很过分了,所以接下我就逆向了这个插件,分析了下他检测的原理并编写了绕过检测的插件,下面为整个分析过程:
- 首先我们知道的是,他会调用exit函数,所以我们lldb挂上,然后断点exit函数
br s -n exit
br s -n _exit
c
- 进程继续后我们等待插件检测到我们安装了某两个源后就会弹出“珍爱手机,远离垃圾源”的提示,并且5秒后就会调用exit函数并且跳转到Safari,由于我们断住了exit,所以App还没有退出,接下来我们使用bt命令来看看调用栈
(lldb) sbt
==========================================xia0LLDB===========================================
BlockSymbolFile Not Set The Block Symbol Json File, Try 'sbt -f'
=============================================================================================
frame #0: [file:0x1800e1484 mem:0x1a66c1484] libsystem_c.dylib`exit + 0
frame #1: [file:0x3dd2c mem:0x106261d2c] prozs.dylib`___lldb_unnamed_symbol5961$$prozs.dylib + 1172
frame #2: [file:0xa404 mem:0x10622e404] prozs.dylib`___lldb_unnamed_symbol625$$prozs.dylib + 424
frame #3: [file:0x5974 mem:0x106229974] prozs.dylib`___lldb_unnamed_symbol369$$prozs.dylib + 72
frame #4: [file:0x5a1c mem:0x106229a1c] prozs.dylib`___lldb_unnamed_symbol370$$prozs.dylib + 156
frame #5: [file:0x180865c48 mem:0x1a6e45c48] Foundation`__NSFireTimer + 68
frame #6: [file:0x1803e8190 mem:0x1a69c8190] CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 32
frame #7: [file:0x1803e7ea8 mem:0x1a69c7ea8] CoreFoundation`__CFRunLoopDoTimer + 936
frame #8: [file:0x1803e751c mem:0x1a69c751c] CoreFoundation`__CFRunLoopDoTimers + 280
frame #9: [file:0x1803e2274 mem:0x1a69c2274] CoreFoundation`__CFRunLoopRun + 1624
frame #10: [file:0x1803e18f4 mem:0x1a69c18f4] CoreFoundation`CFRunLoopRunSpecific + 480
frame #11: [file:0x18a7f8604 mem:0x1b0dd8604] GraphicsServices`GSEventRunModal + 164
frame #12: [file:0x1845b5358 mem:0x1aab95358] UIKitCore`UIApplicationMain + 1944
frame #13: [file:0x100365bb4 mem:0x100dd1bb4] OssIOSDemo`main + 88
frame #14: [file:0x18025d2dc mem:0x1a683d2dc] libdyld.dylib`start + 4
我们把prozs.dylib拖到Hopper里,并且对file后面的地址一个个跳入进去看看这个调用方是谁,于是我找到了一个比较可疑的调用
frame #2: [file:0xa404 mem:0x10622e404] prozs.dylib`___lldb_unnamed_symbol625$$prozs.dylib + 424
在Hopper里显示他的伪代码为
/* @class SsSsSsSsSs */
-(void)buttonTapped:(void *)arg2;
根据弹窗页面猜测,可能这就是最底下的“朕知道啦!”按钮的事件,那么我们就Hook这个函数来看看他是不是,于是编写代码如下
%hook SsSsSsSsSs
- (void)buttonTapped:(id)arg1 {
NSLog(@"iOSRE: BUTTON TAP");
}
%end
然后我们把插件编译安装到手机上(需注意的是要保证我们的插件比他先加载否则Hook不生效),依然是打开App、等待检测、弹窗,等待5秒过后,发现控制台输出了“iOSRE: BUTTON TAP”,然后App也没有退出,证实了这就是这个按钮的点击事件,此时我在想,我如果把这个弹窗关闭了是不是就能正常使用了呢,由于这个弹窗本人使用过,可以十分确定他是“SCLAlertView”,而这个名为“SsSsSsSsSs”的类很可能就是混淆后的“SCLAlertView”,于是到GitHub上查看该库的源码,发现一个"- [SCLAlertView hideView]"的方法,于是代码变为如下
%hook SsSsSsSsSs
- (void)buttonTapped:(id)arg1 {
NSLog(@"iOSRE: BUTTON TAP");
[self hideView];
}
%end
然后再次编译安装、打开App、等待检测、弹窗,等待5秒后,弹窗自动消失,目的达成!
但是,弹窗是消失了,但是功能并没有生效,也就是说他在检测到你安装了某两个源之后就不启用功能了,所以没办法,继续往下分析。
- 由于我们在第二部确认了该库就是“SCLAlertView”,那么接下来的事情就简单了,我们只要找到他的“showView”方法的调用方即可,由于dylib文件混淆了代码,直接快捷键x并不会显示调用方,于是我们还是只能通过lldb进行分析,首先我们对“拍照识图”这个App进行砸壳,然后拖入Hopper,lldb挂上
然后这时我们在宿主App的“-[MainNoteVC viewDidLoad]”方法处下断
lldb挂上后我们拿到宿主App的ASLR:0x0000000000e8c000
在Hopper中找到“-[MainNoteVC viewDidLoad]”方法的地址:0000000100264a78
于是我们在0x0000000000e8c000 + 0000000100264a78的地方下断
xbr -a 0x1010F0A78
c
继续运行后到达“-[MainNoteVC viewDidLoad]”断点,此时我们再次下断
image list -o -f
搜索“prozs”找到
[505] 0x0000000104dc4000 /Library/MobileSubstrate/DynamicLibraries/prozs.dylib(0x0000000104dc4000)
然后在通过分析“SCLAlertView”的源码我们分析到他可能调用的方法为
-[SsSsSsSsSs showNotice:title:subTitle:closeButtonTitle:duration:]
于是我们在Hopper中找到该方法的地址: 000000000000ba48
于是我们在0x0000000104dc4000 + 000000000000ba48处下断
xbr -a 0x104DCFA48
c
继续运行后我们程序再次到达我们的断点处,使用bt命令查看调用栈
(lldb) sbt
error: libarclite_iphoneos.a(arclite.o) failed to load objfile for /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphoneos.a
==========================================xia0LLDB===========================================
BlockSymbolFile Not Set The Block Symbol Json File, Try 'sbt -f'
=============================================================================================
frame #0: [file:0xba48 mem:0x104dcfa48] prozs.dylib`___lldb_unnamed_symbol633$$prozs.dylib + 0
frame #1: [file:0x3d51c mem:0x104e0151c] prozs.dylib`___lldb_unnamed_symbol5960$$prozs.dylib + 1444
frame #2: [file:0x18010c33c mem:0x1a66ec33c] libdispatch.dylib`_dispatch_client_callout + 20
frame #3: [file:0x18010eaf8 mem:0x1a66eeaf8] libdispatch.dylib`_dispatch_continuation_pop + 408
frame #4: [file:0x18011f624 mem:0x1a66ff624] libdispatch.dylib`_dispatch_source_invoke + 1224
frame #5: [file:0x1801184f0 mem:0x1a66f84f0] libdispatch.dylib`_dispatch_main_queue_callback_4CF + 560
frame #6: [file:0x1803e76b0 mem:0x1a69c76b0] CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16
frame #7: [file:0x1803e22c8 mem:0x1a69c22c8] CoreFoundation`__CFRunLoopRun + 1708
frame #8: [file:0x1803e18f4 mem:0x1a69c18f4] CoreFoundation`CFRunLoopRunSpecific + 480
frame #9: [file:0x18a7f8604 mem:0x1b0dd8604] GraphicsServices`GSEventRunModal + 164
frame #10: [file:0x1845b5358 mem:0x1aab95358] UIKitCore`UIApplicationMain + 1944
frame #11: [file:0x100365bb4 mem:0x1011f1bb4] OssIOSDemo`main + 88
frame #12: [file:0x18025d2dc mem:0x1a683d2dc] libdyld.dylib`start + 4
由于调用栈是后入的在上,于是我们着重分析 #1 这个调用方
frame #1: [file:0x3d51c mem:0x104e0151c] prozs.dylib`___lldb_unnamed_symbol5960$$prozs.dylib + 1444
我们在Hopper里按G输入地址0x3d51c跳进去, 然后查看一下伪代码
int sub_3cf78(int arg0) {
var_20 = r22;
stack[-40] = r21;
r31 = r31 + 0xffffffffffffffd0;
var_10 = r20;
stack[-24] = r19;
saved_fp = r29;
stack[-8] = r30;
r29 = &saved_fp;
r19 = arg0;
asm { ldapr w9, [x8] };
if (r9 == 0x0) {
*(int128_t *)0x8a320 = *0x44210;
*(int128_t *)0x8a330 = *0x44220;
*0x8a340 = *0x8a340;
*(int128_t *)0x8a350 = *0x44230;
*(int128_t *)0x8a360 = *0x44240;
*(int128_t *)0x8a370 = *0x44250;
*(int128_t *)0x8a380 = *0x44260;
*(int128_t *)0x8a390 = *0x44270;
*(int128_t *)0x8a3a0 = *0x44280;
*(int128_t *)0x8a3b0 = *0x44290;
*(int128_t *)0x8a3c0 = *0x442a0;
*(int128_t *)0x8a3d0 = *0x442b0;
*(int128_t *)0x8a3e0 = *0x442c0;
*(int128_t *)0x8a3f0 = *(int128_t *)0x8a3f0;
*(int128_t *)0x8a270 = *0x442e0;
*(int128_t *)0x8a280 = *0x442f0;
*(int128_t *)0x8a290 = *0x44300;
*(int128_t *)0x8a2a0 = *0x44310;
*(int128_t *)0x8a2b0 = *0x44320;
*(int128_t *)0x8a2c0 = *0x44330;
*(int128_t *)0x8a2d0 = *0x44340;
*(int128_t *)0x8a2e0 = *0x44350;
*(int128_t *)0x8a2f0 = *0x44360;
*(int128_t *)0x8a300 = *0x44370;
*0x8a310 = *0x8a310;
*0x8a250 = *0x8a250;
*0x8a260 = *0x8a260;
*(int128_t *)0x8a200 = *0x44380;
*(int128_t *)0x8a210 = *0x44390;
*(int128_t *)0x8a220 = *0x443a0;
*(int128_t *)0x8a230 = *0x443b0;
*0x8a243 = *0x8a243;
*(int128_t *)0x8a1b0 = *0x443c0;
*(int128_t *)0x8a1c0 = *0x443d0;
*(int128_t *)0x8a1d0 = *0x443e0;
*(int128_t *)0x8a1e0 = *0x443f0;
*0x8a1f0 = *0x8a1f0;
*(int8_t *)0x8a348 = *(int8_t *)0x8a348 ^ 0x94;
*(int8_t *)0x8a349 = *(int8_t *)0x8a349 ^ 0xa1;
*(int8_t *)0x8a34a = *(int8_t *)0x8a34a ^ 0xfffffffffffffff7;
*(int8_t *)0x8a34b = *(int8_t *)0x8a34b ^ 0x1d;
*(int16_t *)0x8a400 = *(int16_t *)0x8a400 ^ 0x2196;
*(int16_t *)0x8a318 = *(int16_t *)0x8a318 ^ 0x25b3;
*(int16_t *)0x8a31a = *(int16_t *)0x8a31a ^ 0x3af1;
*(int16_t *)0x8a31c = *(int16_t *)0x8a31c ^ 0xb9b9;
*(int16_t *)0x8a258 = *(int16_t *)0x8a258 ^ 0xd38;
*(int16_t *)0x8a25a = *(int16_t *)0x8a25a ^ 0x1546;
*(int16_t *)0x8a268 = *(int16_t *)0x8a268 ^ 0x9f3e;
*(int8_t *)0x8a240 = *(int8_t *)0x8a240 ^ 0xae;
*(int8_t *)0x8a241 = *(int8_t *)0x8a241 ^ 0x8c;
*(int8_t *)0x8a24b = *(int8_t *)0x8a24b ^ 0x1;
*(int8_t *)0x8a24c = *(int8_t *)0x8a24c ^ 0xa6;
*(int8_t *)0x8a1f8 = *(int8_t *)0x8a1f8 ^ 0x16;
*(int8_t *)0x8a1f9 = *(int8_t *)0x8a1f9 ^ 0xfffffffffffffff1;
*(int8_t *)0x8a1fa = *(int8_t *)0x8a1fa ^ 0x3b;
*(int128_t *)0x8a410 = *0x44400;
*(int128_t *)0x8a420 = *0x44410;
*0x8a430 = *0x8a430;
*(int8_t *)0x8a438 = *(int8_t *)0x8a438 ^ 0xc4;
*(int8_t *)0x8a439 = *(int8_t *)0x8a439 ^ 0xffffffffffffffe3;
*(int8_t *)0x8a43a = *(int8_t *)0x8a43a ^ 0x1a;
*(int8_t *)0x8a43b = *(int8_t *)0x8a43b ^ 0xffffffffffffff87;
*(int8_t *)0x8a43c = *(int8_t *)0x8a43c ^ 0xffffffff88888888;
*(int8_t *)0x8a43d = *(int8_t *)0x8a43d ^ 0x6;
}
asm { stlr w9, [x8] };
r20 = r31 - 0x90;
if (stat(0x8a1b0, r20) == 0x0) {
r0 = [SsSsSsSsSs alloc];
r0 = [r0 initWithNewWindow];
r21 = r0;
r0 = [r0 addButton:0x8a440 actionBlock:0x79850];
r29 = r29;
[[r0 retain] release];
[r21 setCustomViewColor:[UIColor colorWithRed:0x8a440 green:0x79850 blue:r4 alpha:r5]];
[r21 addTimerToButtonIndex:0x0 reverse:0x1];
r4 = 0x8a480;
r5 = 0x0;
[r21 showNotice:*(r19 + 0x20) title:0x8a460 subTitle:r4 closeButtonTitle:r5 duration:r6];
[r21 release];
}
if (stat(0x8a320, r20) == 0x0) {
r0 = [SsSsSsSsSs alloc];
r0 = [r0 initWithNewWindow];
r21 = r0;
[[[r0 addButton:0x8a440 actionBlock:0x79890] retain] release];
r0 = [UIColor colorWithRed:0x8a440 green:0x79890 blue:r4 alpha:r5];
r29 = r29;
r22 = [r0 retain];
[r21 setCustomViewColor:r22];
[r22 release];
[r21 addTimerToButtonIndex:0x0 reverse:0x1];
r4 = 0x8a480;
r5 = 0x0;
[r21 showNotice:*(r19 + 0x20) title:0x8a460 subTitle:r4 closeButtonTitle:r5 duration:r6];
[r21 release];
}
if (stat(0x8a200, r20) == 0x0) {
r0 = [SsSsSsSsSs alloc];
r0 = [r0 initWithNewWindow];
r21 = r0;
[[[r0 addButton:0x8a440 actionBlock:0x798d0] retain] release];
r0 = [UIColor colorWithRed:0x8a440 green:0x798d0 blue:r4 alpha:r5];
r29 = r29;
r22 = [r0 retain];
[r21 setCustomViewColor:r22];
[r22 release];
[r21 addTimerToButtonIndex:0x0 reverse:0x1];
r4 = 0x8a4a0;
r5 = 0x0;
[r21 showNotice:*(r19 + 0x20) title:0x8a460 subTitle:r4 closeButtonTitle:r5 duration:r6];
[r21 release];
}
r0 = stat(0x8a410, r20);
if (r0 == 0x0) {
r0 = [SsSsSsSsSs alloc];
r0 = [r0 initWithNewWindow];
r20 = r0;
[[[r0 addButton:0x8a440 actionBlock:0x79910] retain] release];
r0 = [UIColor colorWithRed:0x8a440 green:0x79910 blue:r4 alpha:r5];
r29 = r29;
r21 = [r0 retain];
[r20 setCustomViewColor:r21];
[r21 release];
[r20 addTimerToButtonIndex:0x0 reverse:0x1];
[r20 showNotice:*(r19 + 0x20) title:0x8a460 subTitle:0x8a4a0 closeButtonTitle:0x0 duration:r6];
r0 = [r20 release];
}
return r0;
}
于是发现了4个比较可疑的“if”判断,“stat”函数也经常作用于越狱检测,难道这就是我们要找的判断方法吗?于是我们在原先的Hook代码上对“stat”函数进行Hook
static int (*orig_stat)(char *c, struct stat *s);
static int new_stat(char *c, struct stat *s){
NSLog(@"iOSRE: STAT: %s", c);
return orig_stat(c,s);
}
%ctor {
MSHookFunction((void *)stat,(void *)new_stat,(void **)&orig_stat);
}
编写如上代码后我们编译插件安装到手机,然后查看控制台的输出内容,在控制台输出的一堆内容中我们找到了4个比较可疑的路径
/var/mobile/Library/Caches/com.saurik.Cydia/lists/某某源1._Release
/var/lib/apt/lists/某某源1._Release
/var/lib/apt/lists/某某源2._Release
/var/mobile/Library/Caches/com.saurik.Cydia/lists/某某源2._Release
刚好4条路径,对应那4个“if”判断,于是我们改变一下Hook代码来进行验证
static int new_stat(char *c, struct stat *s) {
if( strcmp(c, "/var/lib/apt/lists/某某源1._Release") == 0
|| strcmp(c, "/var/mobile/Library/Caches/com.saurik.Cydia/lists/某某源1._Release") == 0
|| strcmp(c, "/var/lib/apt/lists/某某源2._Release") == 0
|| strcmp(c, "/var/mobile/Library/Caches/com.saurik.Cydia/lists/某某源2._Release") == 0 ) {
NSLog(@"iOSRE: STAT: R -1");
return -1;
}
return orig_stat(c,s);
}
再次编译安装到手机,然后启动App,插件正常载入,并且控制台输出了4个“iOSRE: STAT: R -1”,并且也不弹窗了,再看看“Pro助手”的功能完全正常使用,到此完成该插件的破解。
需要注意的是:我们编译的插件需要比逆向分析的插件优先注入,然后在他加载后进行init才能Hook到。
最终成品插件下载地址:https://wwa.lanzous.com/iVgg4fosbyh