首页
论坛
课程
招聘
某视频app(V15.7)及web分析记录
2021-9-24 15:16 21933

某视频app(V15.7)及web分析记录

2021-9-24 15:16
21933

某视频app(V15.7)web分析记录


 

   初衷是刷抖音太多,发现不能在点赞过的视频列表中直接搜索,就想自己实现下,把这个过程做了下记录,当学习笔记了,纯技术交流用。

 

一、软硬件环境:

 

抖音android (V15.7,应用宝渠道)

抖音web (V16.1)

IDA 7.5

Frida 14.2.2

Gda3.86

JEB

jadx-gui

unidbg

LineageOs 17.1 (android 10)

小米8

 

二、流水账 (直接按时间顺序,有坑写坑)

 

1.  App X-Gorgon算法定位

 

开始也是直接网上搜了下,有下面这个:

https://blog.csdn.net/weixin_48271161/article/details/108544446

在这个V15.7版本里面没找到相关的so(后来发现我看的这个版本对应的是libmetasec_ml.so)。

还有其它一些,比如搜索X-Gorgon,hook HashMap,发现对这个版本都不好使了,那就从头开始了,自己找这个加密关键点。

 

先上frida,输出jni函数:

现在回过头看,发现当时就输出了libmetasec_ml.so jni记录(虽然不是直接的加密函数),但是当时可能是这个记录太多了,竟然没注意到,估计当时看到了也没留意,因为开始也不知道这个so是做什么的。

看到这个Cronet,搜了下:

Cronet网络库系列(一):用例与原理实现详解

https://segmentfault.com/a/1190000021095757

 

https://blog.csdn.net/u010983881/article/details/97770544/

【Android】移动端接入Cronet实践

 

根据登录url找到下面代码:

然后根据调用关系翻代码,找Cronet相关调用,浏览代码,翻到com.ttnet.org.chromium.net.impl.CronetUrlRequest也没

发现有设置X-Gorgon头的地方(其它header倒是有设置),中间也尝试通过send反找,调用线太长,还是决定换个方式来做。

直接下了个cronet代码:

https://github.com/hanpfei/chromium-net

 

然后就是对着代码,确定认为会相关的几个函数,上IDA调试:

nativeCreateRequestAdapter

nativeAddRequestHeader

 

最后找到这个点

 

到这里就跟libmetasec_ml.so关联起来了。

知道了调用点,就想直接调用libmetasec_ml.so方便调试,写了个程序来加载这个so,发现会异常,考虑到可能是上下文环境不全,就打算按正常流程来加载so,也调用JNI_OnLoad:

void* hMetasecSo = dlopen("/data/local/tmp/libmetasec_ml.so", RTLD_LAZY);

    typedef jint(*FUN)(JavaVM* vm, void* res);

    FUN func_onload = (FUN)dlsym(hMetasecSo, "JNI_OnLoad");

 

这个调用是需要JavaVM参数的,就准备加载libart来调用JNI_CreateJavaVM创建JavaVM,参考网上资料设置好参数:

    JavaVMInitArgs vm_args;

    JavaVMOption options[2];

    options[0].optionString = "-Djava.class.path=.";

    vm_args.version = JNI_VERSION_1_6;

    vm_args.options = options;

    vm_args.nOptions = 2;

    vm_args.ignoreUnrecognized = JNI_TRUE;

编译运行后,直接崩了,查看日志,提示没有设置NoSigChain,

 

然后查看android源码,找到对应地方看了下,是在检查创建VM的选项参数,应该是没有no-sig-chain这个参数,网上搜了下,没有找到怎么设置这个参数的,然后根据代码,结合其它属性改了下代码,增加了一条:

options[1].optionString = "-Xno-sig-chain";

android源码里面也跳过了这个相关的,编译运行,之前的错误就没有了,但是后面还是异常退出了,后来查了下,找到下面信息:

从Android N开始(SDK >= 24),通过dlopen打开系统私有库,或者lib库中依赖系统私有库,都会产生异常,甚至可能导致app崩溃。

应用可以调用/vendor/etc/public.libraries.txt和/system/etc/public.libraries.txt里面的所有so库,

所以往这个文件写入自己希望被调用的so,这个库就变成共用的了,任意应用就可以找到这个so库了

 

试了下上面的方法,包括把libart.so及相关的库放到其它用户目录下,还是不行,考虑到本来就是想还原运行环境的,就想直接上APK吧,还能省去自己创建VM,就写了个测试APK调用这个so:

编译好apk后导入到手机运行:

Didn't find class "com.bytedance.mobsec.metasec.ml.MS"

直接参考抖音补上对应的包路径:

现在能运行了,但是调用JNI_OnLoad会异常,准备上IDA调试,看了下流程图:

 

带了llvm,看so文件尾部记录的是Apple LLVM version 10.0.1 (clang-1001.0.46.3)

跟了下,一些跳转都做了处理,不是很好分析,准备上unidbg (https://github.com/dqzg12300/unidbg_tools,可以用这个大佬整理的)trace代码下来分析。

 

 

根据trace得到的指令流,发现有这种访问"/proc/self/exe"路径返回-1的情况,我是直接修改这个函数,直接返回1了,这个函数只是用来取e_machine字段的:

 

 

 

 

 

后面继续trace,然后结合动态调试,发现有代码校验的地方:

 

上面都处理后,测试apk就可以正常跑完JNI_OnLoad了,后面就是主动调用加密函数测试了,直接在jni接口中调用libmetasec_ml.so:

 

 

 

 

 

 

下面插个分支,到这里的时候,在一个群里看到信息说抖音开了web,直接去看了下.

 

2.  抖音web请求参数_signature算法分析

 

直接访问www.douyin.com看访问参数多了个_signature,这种格式:

&_signature=_02B4Z6wo00901qf0GiQAAIDAwkLkeQfbXMKn9B6AAMkm74

多拿几个比较,发现前面一段(_02B4Z6wo00901)是前缀,后面初步判断是base64格式.

直接网上搜了下,找到下面这篇:

网络爬虫-今日头条_signature参数逆向(第一弹)_井蛙不可语于海的博客-CSDN博客_byted_acrawler

https://blog.csdn.net/qq_39802740/article/details/104911315

主要加密算法在acrawler.js,参考这个用node跑起来了,里面的实现是一个js 虚拟机,关于虚拟机还找到这篇:

StriveMario/jsvm: "某音"js虚拟机写一个编译器 (github.com)

不过目前版本都看起来跟这个不同了,并且本地调试算法其实也用不上,但是还是可以学习下的。


除了文章提到的,还有下面的几个参数要改下:

然后就可以调用global.byted_acrawler.init及global.byted_acrawler.sign参数了(这个后面发现这样调用的算法流程跟浏览器跑的其实是不同的).

用浏览器调试也是确定有init ,sign函数的:

 

后面就是直接用node调试,因为原代码的格式都是这样的:

都是一句话写完逻辑,不方便调试,先整理了下代码,比如这种

opCode = 3 & initCode;         //  initCode % 4

那>2的分支就是==3了,长的代码就分割下.

整理后也方便加日志,输出中间数据.

调试中把vm的基础操作整理出来(vm_xor,vm_and等等),特别字符串连接的指令,可以加上日志,方便定位。

Vm中会检查运行环境,包括下面这些:

domDetect

debuggerDetect

nodeDetect

phantomDetect

webdriverDetect

incognitoDetect

hookDetect

locationDetect

检查结果会参与加密,作为其中一段的因子。

 

当时看了几个签名数据,就有个疑问的,相同的数据,_signature总是不同的,当时想到应该有个变量因子的,但是看https提交的参数没发现这个变量,那这样就是直接记录在_signature中了。

直接base64解密排除前缀后的数据,类似这种:

<Buffer 7c 7e 62 a4 00 00 20 30 83 81 9d 5b 28 d0 87 e9 7c 76 e3 80 00 07 26 f8>

比较多个后也没发现明显的变量因子,猜到可能是时间戳,但是没找到哪个数据段是标识的时间,那就直接开始看流程了。

调试过程发现有getTime的调用,直接改为固定的,最后得到的_signature就不会变了:

 

 

那就是确定跟时间戳相关了,也就是服务器可以通过这个得到时间戳或者转换过的值,来验证客户端上报的_signature是否正确了。

后面算法中,主要就是参数字符串(location,user_agent,param)的处理(xor,or,and等)得到各个hash值,然后类似base64加密得到加密字符串(这里不是直接得到完整明文,再最后一次base64的方式):

 

处理完后,最后2个字符是附加的校验字符,是对前面数据得到一个DWORD值的低字节:

_02B4Z6wo00f014U9W.wAAIDB4IuloLYVYYOFPV9AAIGw

2260354722 '86ba46a2'

_02B4Z6wo00f014U9W.wAAIDB4IuloLYVYYOFPV9AAIGwa2

 

本以为搞完了,直接跟浏览器访问的一比较,悲剧了,竟然加密结果不同,直接上浏览器调试。

前面已经整理了代码的,浏览器访问的时候js是直接下载的,直接修改DNS,把这个js定向到本地服务器:

127.0.0.1 sf1-ttcdn-tos.pstatp.com

 

这样浏览器访问的时候也是用的整理后的js了,有了前面的调试经历,这个也很快搞清楚了流程,

 

 

 

 

 

 

 

 

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAQCAYAAABQrvyxAAACfElEQVRIS9WVO2hUURCGv20Eg1bapVC0sFNJJahFRPDRWFgoRokgQbAQLFJJdNFKFAVBNMROI1oYsBITsPDRpAgkNkmjEoggqIWNIILyywwMs3MxhevjwLLs3XPnzP+a0+L/XZuAE63U/wBwNz0bBWYAfcd1DrjYJfxrgHFgT9HLGeALUAIYAS6kl54A+lxNz3cAL7sEoCq7HdgVSCsBVMiPWrWszJ8EsBK4BjwEDgInrafRbKEIQJa5BawDViVrzQKHgAUrJOsdM3ZkrdP2n1sy203sPQC2eCOAW6NiX3U2JMuWCuTCKlYpIADDwBQgaV+ERpSXPgN/BLgHXAqgnCTlRxZ0dp+b7zMANb/TAPakbHQoMARMAB8BZ6/JQsqF9uw1dp7ayfJpL/AZeAR8AK4HABGwKxObVEC1HKhAVsOiQwFnQv66b0UO2/RRM2I8B1o5mA/M6aAbwHlgH3AWWJsAqLQOj6AyAA0TAdVzkVmtDgDR/xnAehtpss5kAKPfYqxtJ+hQHX4T2GzM5Wa11fd5g5UC3nRla7f2eA6xj1GpIB/rWzK/thDHO0FqSBkBup0AvAXumMcjgEWbJqrrFnS7us/dQhVQB+VuWaoAyL+PzQIRgCykMfbebKUGtgZl/EAFOjYXAWjPfmDalPqVAiJU2aruG9VtRwAxA1EBsf4MGATehFt5DvgE9AeDekDj2GxSQMNBt61Wk4VUT5mKI1v7SwWUAdlmm7HsFrpsDOgu0Cx2O30DTgFjDSH7XY+bMvDzIs0WWs6hq80i8r6HdznvdWVPBWAF8LXhNEl9xTLw15tXjxWAA8Bxu4DeAd+BjcBu4JVNEYX8n1g/AAC2uh6gEsDjAAAAAElFTkSuQmCC

 

多了对这个图片数据的参与,最后整理测试:


整体来看,web上虽然用了JS虚拟机,跟二进制的VM比起来还是弱些了,调试环境弄好后,执行流程就都比较清晰了。

这里web签名就基本完成了,继续app分析

 

3.  继续APP算法分析

 

被上个分支中断了下,思路都断了,又重新熟悉了下,继续开始跟这个加密算法。

在测试apk中调用libmetasec_ml.so加密函数后,返回的是NULL,调试后发现会从native调用java的情况:

 

然后参考抖音补全需要的包,里面有热更新保护相关的代码,屏蔽掉,让测试工程能跑起来就行:

//import com.bytedance.JProtect;
//import com.bytedance.covode.number.Covode;
//import com.meituan.robust.ChangeQuickRedirect;
//import com.meituan.robust.PatchProxy;
//import com.meituan.robust.PatchProxyResult;

 

 

反正就是各种补,模拟全之前的调用环境。

 

看到一些检测root相关的字符串:

 

 

通过分析trace日志,过滤掉一些跳转计算流程后,发现可能的关键调用,用unidbg跑的时候返回是null:

下面就是调试抖音进行验证了,修改对应指令为循环点,附加后循环处下断点:

那就确定是下面函数返回的签名字符串了:

 

 

 

 


 

对于自己APK调用时候,可以直接设置首尾地址:

//修改so的起始地址
*(unsigned  int *)(dwContextAddr+0x10)=0x100000;
//修改结束地址
*(unsigned  int *)(dwContextAddr+0x10)=0xFFFFFFFF;


跳过这个检查。

 

检查trace代码,发现有对代码指令的检查

根据调试流程,整理调用链,补全初始化调用:

 

 

测试app可以直接跑出结果了:

 

 

现在是自己写的程序可以跑了,后面是用app中提供签名服务,还是撸算法出来,都方便很多了。

搞完测试:

 

  整体感觉流程的分析难度不如 wegame.

 

  这次分析过程中学习的技能点总结:

1.   开发测试APK模拟目标的调用环境.

2.   Unidbg模拟调用so中任意地址

3.   RSA验签过程,其它工作涉及的,之前只是使用API(

//1.明文计算sha256

//2.RSA解密密文(一般是base64解密后的字节流)

//3.比较尾部的串是否跟1的一致(因为解密后的结果是包含这个hash串,不是等于)

 




[注意] 欢迎加入看雪团队!base上海,招聘安全工程师、逆向工程师多个坑位等你投递!

收藏
点赞3
打赏
分享
最新回复 (12)
雪    币: 170
活跃值: 活跃值 (62)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
东北小贰爷 活跃值 2021-9-24 17:29
2
0
小米8 好评
雪    币: 231
活跃值: 活跃值 (27)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小雪地 活跃值 2021-9-25 17:43
3
0
源码分享一下?
雪    币: 249
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
@=llfly 活跃值 2021-9-28 11:25
4
0
感谢大佬分享!
雪    币: 146
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_hgrbqfun 活跃值 2021-9-28 12:40
5
0
大佬
雪    币: 1488
活跃值: 活跃值 (558)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
my1988 活跃值 1 2021-9-30 10:24
6
0
他没反调试?
雪    币: 903
活跃值: 活跃值 (975)
能力值: ( LV12,RANK:340 )
在线值:
发帖
回帖
粉丝
xwtwho 活跃值 1 2021-9-30 10:50
7
0
my1988 他没反调试?
我这个是自己刷的系统,以前搞NProtect的,那个反调试多,所以这里有的也过掉了,能附加能调试,要说麻烦的,那就是它启动就会有很多网络请求,对调试单个请求有干扰,后面搞到自己APP里面了,想怎么调就怎么调了。
雪    币: 18
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
Kare-chen 活跃值 2021-10-11 13:45
8
0
大佬 方便加个联系方式吗
雪    币: 903
活跃值: 活跃值 (975)
能力值: ( LV12,RANK:340 )
在线值:
发帖
回帖
粉丝
xwtwho 活跃值 1 2021-10-11 16:25
9
0
Kare-chen 大佬 方便加个联系方式吗
q: 602466143
雪    币: 204
活跃值: 活跃值 (204)
能力值: ( LV3,RANK:28 )
在线值:
发帖
回帖
粉丝
capser 活跃值 2021-10-13 14:07
10
0
怪不得新版加密了Cronet的so库……
雪    币: 903
活跃值: 活跃值 (975)
能力值: ( LV12,RANK:340 )
在线值:
发帖
回帖
粉丝
xwtwho 活跃值 1 2021-10-13 14:33
11
0
capser 怪不得新版加密了Cronet的so库……
没看其它的,但是调用签名部分关键点没感觉什么大不同,还是可以这样找,整个流程对最新的17.9还是适用的
雪    币: 204
活跃值: 活跃值 (204)
能力值: ( LV3,RANK:28 )
在线值:
发帖
回帖
粉丝
capser 活跃值 2021-10-13 15:05
12
0
xwtwho 没看其它的,但是调用签名部分关键点没感觉什么大不同,还是可以这样找,整个流程对最新的17.9还是适用的
签名算法和以前libcms.so中的一样,算法内没啥可变的了,只能增加外围的难度吧。你这么正大光明的,小心请喝茶
雪    币: 903
活跃值: 活跃值 (975)
能力值: ( LV12,RANK:340 )
在线值:
发帖
回帖
粉丝
xwtwho 活跃值 1 2021-10-13 16:12
13
0
capser 签名算法和以前libcms.so中的一样,算法内没啥可变的了,只能增加外围的难度吧。你这么正大光明的,小心请喝茶[em_90]
纯技术贴,又没做啥,真正搞的人不会出来说的,说的不会搞
游客
登录 | 注册 方可回帖
返回