0%
Theme NexT works best with JavaScript enabled
说在前面
本文警告:此文仅限于技术交流,如果损害了App方利益,请发邮件xxoo@hotmail.com
,谢谢。
开发工具:
Reveal查看App界面
Frida跟踪方法调用过程
class-dump导出头文件
IDA分析Mach-O文件
theos开发tweak插件
restore-symbol恢复符号表
本文目标:去除开屏广告、下载广告、页内广告
事前准备
使用frida-tools和frida-ios-dump进行脱壳
Mach-O拖入IDA进行分析
restore-symbol恢复Mach-O符号表后替换原有Mach-O文件
theos新建一个tweak项目
开屏广告
App启动时会有5s的开屏广告,广告过后会直接进入App里面。这个时候迅速用Reveal查看,要不然就很快跳过去了。本人试了好几遍才捕捉到这个界面,发现广告是由SSReadingAdSplashCSJViewController
控制的。
使用frida-trace
追踪控制器的所有方法,找到哪里对这个控制器进行初始化。把初始化的条件破坏,就不会进行初始化了,也就没有所谓的广告了。-f选项加上App的BundleID表示重启App进行跟踪,1 frida-trace -U -f com.dragon.read -m "*[SSReadingAdSplashCSJViewController *]"
命令执行后,会打印如下内容,这个时候需要打印堆栈,查看哪里调用了init
方法1 2 3 4 Started tracing 45 functions. Press Ctrl+C to stop. /* TID 0x403 */ 2786 ms -[SSReadingAdSplashCSJViewController init] 2787 ms -[SSReadingAdSplashCSJViewController setDelegate:0x12e0e7ae0]
进入如下路径1 __handlers__文件夹 -> SSReadingAdSplashCSJViewController文件夹 -> init.js
找到init.js
文件,修改以下内容。按control + c
终止程序,执行以上跟踪方法,会重新进入App。1 2 3 4 onEnter (log, args, state ) { log(`-[SSReadingAdSplashCSJViewController init]` ); log(Thread.backtrace(this .context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n\t' )); },
调用堆栈确实打印出来了,一层一层好多调用。有load
和task
关键字的方法表示已经在调用广告了,这个应该不是我们需要的。SSReadingAdSplashService
表示跟开屏广告有关的服务,大概率这个跟是否开启广告有着密切的关系,我们需要从这个类入手。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Started tracing 45 functions. Press Ctrl+C to stop. /* TID 0x403 */ 2585 ms -[SSReadingAdSplashCSJViewController init] 2585 ms 0x101420158 Reading!-[SSReadingAdSplashCSJTask setupSplashController] 0x10141f934 Reading!-[SSReadingAdSplashCSJTask load] 0x10126b4a0 Reading!-[SSReadingAdSplashLoader flushQueue:] 0x10126ae70 Reading!-[SSReadingAdSplashLoader onTaskCompletion:error:] 0x10126ad54 Reading!0x5a2d54 0x1013fccac Reading!-[SSReadingAdSplashBrandTask onCompletionWithError:] 0x1013fc7a8 Reading!-[SSReadingAdSplashBrandTask onAdLoadFailureWithError:] 0x1013fb458 Reading!-[SSReadingAdSplashBrandTask load] 0x10126b4a0 Reading!-[SSReadingAdSplashLoader flushQueue:] 0x10126a328 Reading!-[SSReadingAdSplashLoader load] 0x10157eaf8 Reading!-[SSReadingAdSplashService loadSplashWithHotLaunch:] 0x10157ee84 Reading!-[SSReadingAdSplashService appLaunchSplash] 0x1014be6b8 Reading!-[SSReadingAppDelegate appWillFinishLaunchWithOptions:]
跟踪SSReadingAdSplashService
的所有方法,仔细查看发现shouldShowSplashAd
是否展示开屏广告,终于找到了,需要验证一下猜想1 2 3 4 5 6 7 8 9 frida-trace -U -f com.dragon.read -m "*[SSReadingAdSplashService *]" Started tracing 21 functions. Press Ctrl+C to stop. /* TID 0x403 */ 2344 ms -[SSReadingAdSplashService onServiceInit] 2345 ms | -[SSReadingAdSplashService addObservers] 2345 ms | -[SSReadingAdSplashService shouldShowSplashAd] .... 2349 ms -[SSReadingAdSplashService appLaunchSplash]
找到shouldShowSplashAd.js
,更改返回值为0,表示不展示广告,按control + c
终止程序,重新进执行以上跟踪方法,发现不会出现开屏广告1 2 3 onLeave (log, retval, state ) { retval.replace(0 ) }
大胆在Tweak.x中写入一下代码,从此告别了开屏广告,美滋滋呀,美滋滋。1 2 3 4 5 %hook SSReadingAdSplashService - (BOOL)shouldShowSplashAd { return NO; } %end
下载广告
随便找一本小说进入,然后后点击下载,会有如下弹框,让我们看完广告去下载。要不然每次点击下载都要看广告,这样也太烦了。
通过Reveal发现弹框属于SSNewKCAlertView
视图,跟踪SSNewKCAlertView
所有方法。1 frida-trace -U -m "*[SSNewKCAlertView *]" XX小说
当点击下载时,会首先执行如下方法进行初始化弹框操作。打印此方法的调用堆栈,找到哪里进行了广告弹框的初始化操作1 58574 ms -[SSNewKCAlertView initWithStyle:0x0 title:0x103320958 detail:0x0 actions:0x14e6b2990]
找到initWithStyle_title_detail_actions_.js
文件,在入口处添加如下代码,打印函数调用堆栈和模块在内存中的偏移地址1 2 3 4 5 6 7 8 onEnter(log, args, state) { let funcAddr = Module.findExportByName('dyld', '_dyld_get_image_vmaddr_slide') let funcBody = new NativeFunction(funcAddr, 'ulong', ['ulong']) let aslrAddr = funcBody(0).toString(16) log('ASLR偏移地址为:0x' + aslrAddr) log(`-[SSNewKCAlertView initWithStyle:${args[2]} title:${args[3]} detail:${args[4]} actions:${args[5]}]`); log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n\t')); },
中断方法执行,再次执行跟踪方法,重新点击下载,会打印如下内容1 2 3 4 5 6 7 3443 ms ASLR偏移地址为:0x288000 3443 ms -[SSNewKCAlertView initWithStyle:0x0 title:0x1024b0958 detail:0x0 actions:0x111b66a80] 3443 ms 0x100c87ef4 Reading!+[SSCommonMethod showAlertViewWithStyle:title:detail:action:darkMode:showCloseBtn:] 0x1009ce37c Reading!-[SSReaderManager inspire_showInspireAlertWithBookId:confirm:] 0x1004d9b3c Reading!-[SSReaderManager downloadBookAfterWatchInspire] 0x100511d28 Reading!-[SSReaderManager onDownloadBtnClick:] 0x100935fd0 Reading!-[SSReadingNavigationView onDownloadBtnClick:]
不难发现以下两个方法是已经在执行弹框操作了,不在研究范围之内,直接忽略掉。1 2 +[SSCommonMethod showAlertViewWithStyle:title:detail:action:darkMode:showCloseBtn:] -[SSReaderManager inspire_showInspireAlertWithBookId:confirm:]
下面方法是关注的重点,大概意思就是观看完广告视频才能下载,我们需要知道满足什么条件会执行这个方法,我们让它不满足这个条件,就会直接执行下载操作。1 -[SSReaderManager downloadBookAfterWatchInspire]
计算0x100511d28-0x288000(ASLR偏移地址)=0x100289D28
的结果,拿0x100289D28
放到IDA进行地址搜索,发现定位到如下位置1 2 3 4 5 6 7 8 9 void __cdecl -[SSReaderManager onDownloadBtnClick:](SSReaderManager *self, SEL a2, id a3) { ... // 此处为定位(光标)位置 -[SSReaderManager onReaderDownloadBtnClick](self, "onReaderDownloadBtnClick", a3); ... objc_msgSend(v8, "report_click_reader:content:bookType:", v10, CFSTR("download"), v14); ... }
打开onReaderDownloadBtnClick
方法实现的伪代码,发现快200行代码,这也太多了,怎么找呢?一行一行看,直到发现downloadBookAfterWatchInspire
方法。经过耐心寻找,一直找到最后才发现相关代码。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 v56 = +[SSABTestHelper buzz_book_download_inspire_type]( &OBJC_CLASS___SSABTestHelper, "buzz_book_download_inspire_type"); if ( v56 == (void *)2 ) { v57 = "downloadBookAfterWatchInspire"; goto LABEL_12; } if ( v56 == (void *)1 ) { v57 = "downloadBookDirectly"; goto LABEL_12; } if ( !v56 ) { LABEL_17: v57 = "downloadBookAfterBuyVip"; LABEL_12: objc_msgSend(v2, v57); }
通过上面代码分析,得出如下结论
下载方法有三种:downloadBookAfterWatchInspire
表示看完广告视频进行下载,downloadBookAfterBuyVip
表示购买完vip就行下载,downloadBookDirectly
表示直接进行下载
通过v56的值也就是buzz_book_download_inspire_type
的值判断进行哪种方式的下载
设置值为1,也就是+[SSABTestHelper buzz_book_download_inspire_type] = 1
时,可以直接进行下载
打开Tweak.x文件,写下如下代码。安装后,重新点击下载,发现可以直接下载,不用看任何广告。1 2 3 4 5 %hook SSABTestHelper + (long long)buzz_book_download_inspire_type { return 1; } %end
页内广告
随便找个小说翻几页就会出现一个广告,这样的阅读体验让人感到难受极了。出现广告的时候用Reveal查看发现属于SSReadingAdChapterMiddleContentViewController
,需要追踪这个控制的所有方法:1 frida-trace -U -m "*[SSReadingAdChapterMiddleContentViewController *]" XX小说
发现每出现一个广告,都会调用如下方法。1 187872 ms -[SSReadingAdChapterMiddleContentViewController initWithModel:0x157ceeb70]
找到initWithModel.js
,添加以下内容,打印调用堆栈1 2 3 4 onEnter (log, args, state ) { log(`-[SSReadingAdChapterMiddleContentViewController initWithModel:${args[2 ]} ]` ); log(Thread.backtrace(this .context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n\t' )); },
调用堆栈层级比较多,把重要的贴出来,如下。1 2 3 4 5 6 7 8 9 10 frida-trace -U -m "-[SSReadingAdChapterMiddleContentViewController initWithModel:]" XX小说 .... /* TID 0x303 */ 2802 ms -[SSReadingAdChapterMiddleContentViewController initWithModel:0x158c5c7f0] 2802 ms 0x10173b688 Reading!-[SSReadingAdManager chapterMiddleAdVCWithReaderModel:curPageContext:targetPageContext:pageChangeInfo:adATData:adCSJData:] 0x1013cbd34 Reading!-[SSReadingAdManager satiAdWithReaderModel:curPageContext:targetPageContext:pageChangeInfo:] 0x101509338 Reading!-[SSReadingAdManager getBusinessInsertedVCWithReadModel:curPageContext:targetPageContext:pageChangeInfo:] 0x1010a9e14 Reading!-[SSReaderManager requestInsertedVCWithReadModel:curPageContext:targetPageContext:pageChangeInfo:] 0x10195ecb4 Reading!-[BDReaderViewController tryGetInsertedVC:fromPageContext:toPageContext:] ....
可以清晰地发现SSReadingAdManager
是一个跟广告有关的管理器,顾名思义,出现了广告才会出现管理器。广告哪里来,肯定是从网络请求的,而SSReaderManager requestInsertedVCWithReadModel
就像是从网路请求广告数据。这是我们的猜测,在Tweak.x中写如下代码,验证一下。1 2 3 4 5 %hook SSReaderManager - (id)requestInsertedVCWithReadModel:(id)arg1 curPageContext:(id)arg2 targetPageContext:(id)arg3 pageChangeInfo:(id)arg4 { return nil; } %end
手机注销后,随便打开一个小说,随便翻看,发现再也没有广告了。