某视频app的学习记录
一、软硬件环境
IDA 7.5
Frida 14.2.2
Gda3.86
JEB
jadx-gui
unidbg
LineageOs 17.1 (android 10)
小米8
二、坑(时间顺序)
1、截包相关
这个看见有人说有个版本开始不能截了,我这边一直都是换证书的,没感觉有影响,估计我下的是盗版,碰到再看了。
2、之前看到so都是32位的,后来都换成64位的了。
3、网络请求相关
之前就看到有使用Cronet模块,想看下实现过程,下过源码(https://github.com/hanpfei/chromium-net),一个是发现跟app使用的并不完全相同,再一个还要自己编译模块,就想到直接使用抖音的模块。
先新建一个自己的测试app,接着的工作就是搬代码了,直接导出所有的反编译代码,结合源码,首先就是这个包:
com.ttnet.org.chromium.net
这里插下搬代码中的一些问题:
首先就是反编译代码,几个工具(jeb ,jadx, GDA)各有优劣,要结合使用, jadx得到的代码可读性更好,但是很多函数识别不出来,比如这种:
GDA擅长处理疑难杂症,其它2个识别不了的代码,它这个都能识别,不过可读性不好,基本不能直接编译通过,需要看清楚逻辑后修复。
再就是反编译代码中变量顺序问题,也会影响编译(初始化的变量放在后面了):
代码修复中有些类型的转换,比如Bundle类型变量为null的,可能被还原成了int i=0; 然后条件判断的时候,这个也会出现编译错误。
再就是Map.Entry这种,需要先用object遍历,再转换类型。
if (!StringsKt.contains$default((CharSequence) name, (CharSequence) "__MACOSX", false, 2, (Object) null)) {
kotlin相关也要修改:
StringsKt.contains((CharSequence) str, (CharSequence) "?", false))
还有下面这种继承的:
public final class FrontMethodFragment$onCreateFailed$1 extends Lambda implements Function0 { Lambda要带上Lambda
参考这个 cannot be inherited with different type arguments_PhilsphyPrgram的博客-CSDN博客。
还有一些逻辑上的,少了break之类,就不是很好发现了,这个只有遇到后调试修复了。
最后还有麻烦的就是缺少异常处理,java编译是强制要求处理异常的,找过资料,想编译时候忽略异常处理,发现是JAVA规范强制的,屏蔽不了,加这个也花了不少时间。
总之,3个工具要结合使用,怎么方便怎么来。
搬过来后,直接调用测试:
// 初始化引擎 CronetEngine.Builder myBuilder = new CronetEngine.Builder(getApplicationContext()); CronetEngine cronetEngine = myBuilder.build(); // 创建请求线程 Executor executor = Executors.newSingleThreadExecutor(); // 创建UrlRequest strUrl="http://10.0.0.217/about"; UrlRequest.Builder requestBuilder = cronetEngine.newUrlRequestBuilder( strUrl, new MyUrlRequestCallback(), executor); requestBuilder.addHeader("testHeader","testValue"); UrlRequest request = requestBuilder.build(); // 发起请求 request.start(); class MyUrlRequestCallback extends UrlRequest.Callback { private static final String TAG = "ttttt MyUrlRequestCallback"; @Override public void onRedirectReceived(UrlRequest request, UrlResponseInfo info, String newLocationUrl) { android.util.Log.i(TAG, "onRedirectReceived method called."); // You should call the request.followRedirect() method to continue // processing the request. request.followRedirect(); } @Override public void onResponseStarted(UrlRequest request, UrlResponseInfo info) { //这个函数只会调用一次 android.util.Log.i(TAG, "onResponseStarted method called."); // You should call the request.read() method before the request can be // further processed. The following instruction provides a ByteBuffer object // with a capacity of 102400 bytes to the read() method. request.read(ByteBuffer.allocateDirect(102400)); } @Override public void onFailed(UrlRequest urlRequest, UrlResponseInfo urlResponseInfo, CronetException cronetException) { } @Override public void onReadCompleted(UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) { //这个会调用多次 android.util.Log.i(TAG, "onReadCompleted method called."); // You should keep reading the request until there's no more data. request.read(ByteBuffer.allocateDirect(102400)); } @Override public void onSucceeded(UrlRequest request, UrlResponseInfo info, String str) { android.util.Log.i(TAG, "onSucceeded method called."); }}
测试发现本地服务器正常收到请求了,测试app也能正常收到回调:
不过请求地址改成本地https的时候,发现崩溃,根据日志定位报错线程:
发现是throw new UnsupportedOperationException("Method not decompiled造成的,那就是jadx代码不全的问题,这个参考GDA补全。
不过这里也看出是证书校验问题,本地的证书不通过。
// Certificate is not trusted due to non-trusted root of the certificate// chain.static int NO_TRUSTED_ROOT = -2;
这里又熟悉了下https,查了下资料:HTTPS请求的整个过程的详细分析_研究生生活、学习记录-CSDN博客_https过程
最后,由于非对称加密的公钥可以在网络中传输,如何保证公钥传送到给正确的一方,这个时候使用了证书来验证。证书不是保证公钥的安全性,而是验证正确的交互方。
从这个流程图来看,访问本地网站就是验证证书无效了。
本地web服务器的file.crt文件:
解密后:
Len:625
正好上面截图对应测试程序调试中的certChain。
搞清楚这个过程后,后面再进行干涉就有切入点了。
到这里后,虽然可以调用Cronet了,但是发现跟抖音自己的接口调用路径其实是不同的,比如某个接口的堆栈:
明显不是上面用的调用方式,那接着就是根据这个补代码了。
熟悉了上面这种注释语法:
最后找到下面这个:
其实之前也看到过这个url,但是关联不到怎么调用的,现在看是通过代理类方式使用的($Proxy84),这里是这个功能的类定义文件,单纯静态看确实不好看明白,通过动态调试才搞清楚整个过程。
看见有鸿蒙相关:
这里遇到个自己挖的坑,因为用了混淆的包名,导致hash这里跳过了,返回null了,会导致创建proxy类不成功:
private T getStaticServiceImplReal(Class cls) { PatchProxyResult proxy = PatchProxy.proxy(new Object[]{cls}, this, changeQuickRedirect, false, 95671); if (proxy.isSupported) { return (T) proxy.result; } int iHashCode=cls.getName().hashCode(); switch (iHashCode) {
补完各种代码后(中间确实各种报错,各种补文件,最后备份的时候,发现有3000+文件,不过有的模块,比如wx相关的基本拷过来就能用了,zfb的交叉太多了,花了不少时间修复,感觉这样下去,可以编译一个抖音出来了),使用下面方式调用接口:
FeedActionApi.f71204b.diggItem("7018000130007633191", "7018000130007633191", 1, 0).get();
运行调试:
看起来点赞操作的类是创建成功了,跟hook看到的堆栈类似了。
这里查了下代理类的资料:Java动态代理之InvocationHandler最简单的入门教程 - 简书 (jianshu.com)
创建代理类相关:
这个选择不同的网络模式,模拟器默认走第二种。
if (C9859c.m21219a()) { jSONObject.put("netClientType", "CronetClient"); } else { jSONObject.put("netClientType", "TTOkhttp3Client"); }
到这里后,终于在测试服务器收到请求了:
到这里的时候,发现极速版更新到18.2了。
Cront相关Native函数引入有变化:
输出jni函数的时候,发现有SPDY相关的:
顺便查了下SPDY相关资料:HTTP 的前世今生:一次性搞懂 HTTP、HTTPS、SPDY、HTT_请求 (sohu.com)
后面看来要看下这个了。
在16.6版本都测试程序上替换上18.2版本都so,测试报错:
看起来native函数引入混淆了:
包装了一层:
对应修改后,就可以正常编译运行了。
看到检查root相关代码:
算起来这次搬代码,最难的是开始时候,加进来的代码牵扯其它引用,一堆编译错误,加入引用,可能又会引入新的引用依赖,很容易耐心磨没了,这个时候就需要权衡取舍,不能把分支展得太开,还好坚持下来了,慢慢框架搭起来后,很多就是正向工作了。
三、学习总结
1、学习了JAV代理类使用,大厂设计模式。
2、熟悉了Cronet模块。
感觉主要工作都是正向的,正向逆向不分家,有了正向的工作,逆向切入点也会更多。
