精品连载丨安卓 App 逆向课程之四 frida 注入 Okhttp 抓包中篇
本篇文章接上篇。
2. Okhttp3 自吐抓包
我们将一次请求的request大致结构罗列如下。
•请求方法 GET、POST、PUT、DELETE、HEAD 等•URL•使用的协议版本 HTTP/1/1.1/2•多个请求 Header•回车、换行符•请求 Body 数据
如果通过Hook的方式实现另类的“抓包”,我们的需求是保留URL,请求Body,以及headers。至于协议版本等可有可无。目前国内应用使用的 HTTP 协议版本大多数都是 HTTP/1.1,HTTP/2 虽然出来有段时间了,但国内使用的还比较少,这些字段对我们无帮助。
2.1 Hook request的时机
先不考虑response如何获取,单纯想一下如何hook request,即使不深入了解Okhttp框架,通过断点调试一步步走,大概也能找到几个看着不错的点。
2.1.1 requests 构建过程
// 构造request
Request request = new Request.Builder().url(url) // Hook url()方法,此处只能得到单纯url,不妥.header(key, value) // Hook header()方法,一个request会多次调用此方法,会造成干扰,不妥。.header(key, value)....build();// Hook build()方法,一个request只可能调用一次,且它是构造完成时调用,不会遗漏信息。
DEBUG断点调试
DEBUG url()
可以得到url,其余均获取不到。
DEBUG header()
大家会发现,header会被调用数次,尽管我们并没有配置header,但Okhttp会检测并帮我们填补许多header字段,因此这个点也行不通。
诸如此类,DEBUG build()方法,会发现每次请求,build方法都会被调用两次,同样不适合。
上述问题是由Okhttp的拦截器机制造成,后续会有篇幅讲解,除此之外,构建一个request并不代表发送一次请求,这不是一个概念,因此我们要继续往下找。
查看OkHttpClient.newCall(request)方法源码
@Override public Call newCall(Request request) {return RealCall.newRealCall(this, request, false /* for web socket */);}
直接Hook newCall方法行不行,当然是可以的。
直接上frida脚本
Java.perform(function () {var OkHttpClient = Java.use("okhttp3.OkHttpClient");OkHttpClient.newCall.implementation = function (request) {var result = this.newCall(request);console.log(request.toString());return result;};});
启动Frida server
C:\Users\Lenovo>adb shell
root@OXF-AN10:/ # cd /data/local/tmp
root@OXF-AN10:/data/local/tmp # ./frida-server
Hook结果如下:
C:\Users\Lenovo>frida -U com.r0ysue.learnokhttp explore -l C:\Users\Lenovo\Desktop\抓包\teach\1.js____/ _ | Frida 12.8.14 - A world-class dynamic instrumentation toolkit| (_| |> _ | Commands:/_/ |_| help -> Displays the help system. . . . object? -> Display information about 'object'. . . . exit/quit -> Exit. . . .. . . . More info at https://www.frida.re/docs/home/[OXF AN10::com.r0ysue.learnokhttp]->
Request{method=GET, url=http://www.kuaidi100.com/query?type=yuantong&postid=11111111111, tags={}}
Request{method=GET, url=http://www.kuaidi100.com/query?type=yuantong&postid=11111111111, tags={}}
Request{method=GET, url=http://www.kuaidi100.com/query?type=yuantong&postid=11111111111, tags={}}
Request{method=GET, url=http://www.kuaidi100.com/query?type=yuantong&postid=11111111111, tags={}}
Request{method=GET, url=http://www.kuaidi100.com/query?type=yuantong&postid=11111111111, tags={}}
实际上,Hook request
的问题远没有完美解决,此Hook
点同样可能遗漏或多出部分请求,因为存在"call"后没有发出实际请求的情况。
newCall(Request)
方法调用了RealCall.newRealCall()
方法:
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
在RealCall.newRealCall()中,创建了一个新的RealCall对象,RealCall对象是Okhttp3.Call接口的一个实现,也是Okhttp3中Call的唯一实现。它表示一个等待执行的请求,它只能被执行一次,但实际上,到这一步,请求依然可以被取消。因此只有Hook 了execute()和enqueue(new Callback())才能真正保证每个从Okhttp出去的请求都能被Hook到,不多也不少。
除此之外,割裂开请求和相应,分开去找Hook点的想法是有问题的,只能看到request,但无法同时看到返回的相应,无法对爬虫工程师或协议分析过程产生有益的帮助。
2.2 Okhttp拦截器
拦截器是Okhttp中重要的一个概念,Okhttp通过Interceptor来完成监控管理、重写和重试请求。Okhttp本身存在五大拦截器,每个网络罗请求,不管是GET还是PUT/POST或者其他,都必须经过这五大拦截器。拦截器可以对request做出一定修改,同时对返回的Response做出一定修改,因此Interceptor是一个绝佳的Hook点,可以同时打印输出请求和相应。
为了帮助大家理解Interceptor机制,我们从三个方面去窥其一角。
2.2.1 拦截器整体概览
拦截器可以对request做出修改,在数据返回时,再对response做出修改,这种说法可能会让人不知所云,引用刘望舒的演示:
引用yuashuai[1] 所举的例子,他表述的生动且贴切。
老张有很多干面条,但是他想吃汤面,可是自己又不会做,但是碰巧村里大郎会做,于是老张拿一包干面条让大郎做成了汤面。但是老张发现他做面不好吃,盐都没放,连个青菜叶子都没有。
这时候老张正好碰到隔壁老王,老王说了这东西我也会做,比他做的好吃多了。于是老张又拿着一包干面条给了老王,老王说老张你等着,我马上回家给你做,做好了就给你送过去。但是老王回家并没有做,而是去家里拿了一包盐,然后去找隔壁老李了,原来老王并不会做面,但是他知道隔壁老李会做,而且做得比较好吃。于是他把干面条和盐都交给了老李。老李对老王说你回去等着吧,做好了马上给你送过去。可是老李同样不会做,但是他知道村里的大郎会做,这时老李首先回厨房拿了两个生菜叶子,然后带着老王给的干面条和盐去找大郎了,对大郎说,生菜叶子,盐,面条都给你了,你快给我做一碗面。大郎对老李说好嘞,3分钟就好了,3分钟后,老李拿着做好了的放了盐和生菜叶子的一碗面回去了。本来打算直接给老王,但是一想,自己放了两个生菜叶子,不吃点这个面吃不是有点亏,于是老李偷偷了吃了几根面。然后老李去找老王说你的面做好了并把面交给了老王。老王一看这面只有两个青菜叶子,营养是不是不够呀!于是老王又买了半斤熟牛肉,切切放了进去。然后老王去找老张说你的面做好了,还说道这么大一碗你也吃不完吧,让小张也吃点。最后老张吃着老王送来的红烧牛肉面感动的肉牛满面。
这里的干面条就可以看做一个最原始的request,到老王哪里被加了点盐,到老李哪里被加了生菜叶子,于是大郎才能把这个request做成放了盐和生菜叶子的response,这个response回到老李那里又被啃了几口,到老王那又被放了点牛肉。于是最后回到老张哪里收到的response就是被啃了几口并且加了牛肉的response。这样整个链条是不是就清楚了!
在Okhttp代码中,由getResponseWithInterceptorChain方法展示这个过程:
Response getResponseWithInterceptorChain() throws IOException {// 空的拦截器容器List<Interceptor> interceptors = new ArrayList<>();// 添加用户自定义的应用拦截器集合(可能0,1或多个)interceptors.addAll(client.interceptors());// 添加retryAndFollowUpInterceptor拦截器,该拦截器用于取消、失败重试、重定向interceptors.add(retryAndFollowUpInterceptor);// 添加BridgeInterceptor拦截器,对于Request而言,该拦截器把用户请求转换为 HTTP 请求;对于Resposne,把 HTTP 响应转换为用户友好的响应interceptors.add(new BridgeInterceptor(client.cookieJar()));// 添加CacheInterceptor拦截器,该拦截器读写缓存、根据策略决定是否使用interceptors.add(new CacheInterceptor(client.internalCache()));//该拦截器实现和服务器建立连接interceptors.add(new ConnectInterceptor(client));//如果不是WebSocket请求,添加用户自定义的网络拦截器集合(可能0,1或多个)if (!forWebSocket) {interceptors.addAll(client.networkInterceptors());}// 添加真正发起网络请求的Interceptorinterceptors.add(new CallServerInterceptor(forWebSocket));Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,originalRequest, this, eventListener, client.connectTimeoutMillis(),client.readTimeoutMillis(), client.writeTimeoutMillis());return chain.proceed(originalRequest);}
}
参照下图理解整体理解拦截器链:
2.2.2 动手添加用户拦截器
新建LoggingInterceptor类,实现Interceptor接口,这代表它是一个拦截器,接下来实现intercept方法,我们的拦截器会打印URL和请求headers,完整代码如下。
LoggingInterceptor.java
package com.r0ysue.learnokhttp;import android.util.Log;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;class LoggingInterceptor implements Interceptor {// TAG即为日志打印时的标签private static String TAG = "learnokhttp";@Override public Response intercept(Interceptor.Chain chain) throws IOException {Request request = chain.request();Log.i(TAG, "请求URL:"+String.valueOf(request.url())+"\n");Log.i(TAG, "请求headers:"+"\n"+String.valueOf(request.headers())+"\n");Response response = chain.proceed(request);return response;}}
接下来需要将我们自定义的LoggingInterceptor拦截器添加到拦截器链中,这部分需要顺着1.3.1 中的OkhttpClient对象继续讲。用户自定义的拦截器就是在此处添加。
用户拦截器有两种
•应用拦截器 Application Interceptors•网络拦截器 Network Interceptors
具体区别可以看官网[2],对于我们的需求而言,两者都可,但网络拦截器更好,具体原因有心的读者可以去对照一下。看一下相应代码的修改:
在example.java中
// 新建一个Okhttp客户端
// OkHttpClient client = new OkHttpClient();// 新建一个拦截器
OkHttpClient client = new OkHttpClient.Builder().addNetworkInterceptor(new LoggingInterceptor()).build();
大家会发现,OKhttpclient的创建方式改变了,这里讲一下Okhttpclient三种创建方式,之所以存在这三种创建方式,和Okhttpclient本身的原则息息相关。
创建方法一
OkHttpClient client = new OkHttpClient();
其内部即默认配置大量参数
public OkHttpClient() {this(new Builder());}public Builder() {dispatcher = new Dispatcher();protocols = DEFAULT_PROTOCOLS;connectionSpecs = DEFAULT_CONNECTION_SPECS;eventListenerFactory = EventListener.factory(EventListener.NONE);proxySelector = ProxySelector.getDefault();cookieJar = CookieJar.NO_COOKIES;//...//...//...}
#此即Okhttp默认配置
OkHttpClient(Builder builder) {this.dispatcher = builder.dispatcher;this.proxy = builder.proxy;this.protocols = builder.protocols;this.connectionSpecs = builder.connectionSpecs;this.interceptors = Util.immutableList(builder.interceptors);this.networkInterceptors = Util.immutableList(builder.networkInterceptors);this.eventListenerFactory = builder.eventListenerFactory;this.proxySelector = builder.proxySelector;//...//...//...}
Okhttp框架帮我们默认所有配置,因此无法自定义添加用户拦截器。
创建方法二
OkHttpClient client = new OkHttpClient.Builder().addNetworkInterceptor(new LoggingInterceptor()).build();
此即采用建造者(Builder)模式,可以自定义配置所有参数,我们在这儿添加了一个拦截器。
我们使用方式二验证一下拦截器是否生效,然后再讲解为什么需要第三种创建方式。
输出内容有三行,第一行是URL,第二行是headers,需要注意,第三行的response并非由我们拦截器中打印出来,而是之前代码的作用,之所以不在拦截器中顺带打印response,是因为在此时,response对象的打印稍有不便,我们在后续提它。
接下来讨论第三种client创建方式,它会在原先的client基础上创建一个新的okhttp客户端。
创建方式三
// 此为原先的client
OkHttpClient client = new OkHttpClient();// 基于原先的client创建新的client
OkHttpClient newClient = client.newBuilder().addNetworkInterceptor(new LoggingInterceptor()).build();
newclient和client共享连接池、线程池,且newclient继承client原先的配置。为什么我们需要它呢?
———通过这种方式,我们可以配置出一个用于处理某一类特定需求的client。
打个比方,一个新闻客户端,主要提供如下三个接口:
•/xxx/xxx/news ——浏览新闻•/xxx/xxx/comments ——查看评论•/xxx/xxx/login ——登录
可以将Okhttpclient想象成网络通信工厂,根据需求不断生产这三类请求,但登录接口由于涉及到用户信息,我们需要额外的防护,最好通过一个拦截器给request增加一个sign验证。在这种情况下,我们就可以使用第三种方式后创建loginClient,此工厂会为每个请求加上sign验证。那为什么不重新创建一个全新的client?一是因为每个App的网络通信都有许多默认配置,比如host,固定的sign,或者延迟等待时间等等。如果重新创建一个client,同样需要设置和添加这些配置,比较繁琐。二是为了提升性能,减少内存消耗。通过newBuilder方式创建的新client,和原client共享连接池、线程池等“基础设施”。
你可能会有疑问,为什么要在意这一点消耗呢?从我们的DEMO代码可以发现,我们每次点击“发送请求”按钮,都会创建一个新的client,既然每点击一次都创建一个新的客户端,何必在意newbuilder省下来的那点性能呢?
其实不然,在演示DEMO时,我们忽略了性能的问题,其实Okhttpclient应该被设置为单例模式,即App全局只使用一个共享的OkHttpClient 实例,将所有的网络请求都通过这个实例处理。因为每个OkHttpClient 实例都有自己的连接池和线程池,重用这个实例能降低延时,减少内存消耗,而重复创建新实例则会浪费资源。
换而言之,我们的DEMO每次点击都创建一个新的实例,相当于每个生产车间只生产一个布娃娃,因此会造成极大的浪费,甚至会造成内存的溢出。
Okhttp官方并没有在框架中强制OkhttpClient全局单例(可能是出于让开发者更灵活和自由的缘故),但强烈建议非必要的情况下,全局共享一个OkHttpclient(网络访问框架一般都需要单例模式)。
接下来我们使用Objection来验证,DEMO中是否存在滥创Okhttpclient的现象。(显而易见,我们单纯熟悉一下Objection操作)。首先关闭并重新打开App,不做任何操作,创造一个干净的环境。
objection是一个基于Frida开发的命令行工具,它可以很方便的Hook Java函数和类,并输出参数,调用栈,返回值。只需一行命令就可以完成Hook。Objection的简单使用可以看这篇[3]。
开启server,安装objection后,使用Objection Hook 我们的DEMO app:
C:\Users\Lenovo>objection -g com.r0ysue.learnokhttp explore
正常情况展示如下:
C:\Users\Lenovo>objection -g com.r0ysue.learnokhttp exploreA newer version of objection is available!
You have v1.8.4 and v1.9.2 is ready for download.Upgrade with: pip3 install objection --upgrade
For more information, please see: https://github.com/sensepost/objection/wiki/UpdatingUsing USB device `OXF AN10`
Agent injected and responds ok!_ _ _ ____| |_|_|___ ___| |_|_|___ ___
| . | . | | -_| _| _| | . | |
|___|___| |___|___|_| |_|___|_|_||___|(object)inject(ion) v1.8.4Runtime Mobile Explorationby: @leonjza from @sensepost[tab] for command suggestions
com.r0ysue.learnokhttp on (HUAWEI: 5.1.1) [usb] #
使用Objection搜索堆中okhttp3.OkHttpClient实例,命令如下:
android heap search instances okhttp3.OkHttpClient
com.r0ysue.learnokhttp on (HUAWEI: 5.1.1) [usb] # android heap search instances okhttp3.OkHttpClient
Class instance enumeration complete for [32mokhttp3.OkHttpClient[39m
结果为空,点击数次“发送请求”按钮后,再次尝试
每次click后,堆中就会多出一个新的client实例,这是极大的浪费,除此之外,如果读者显示结果有误差,堆中实例并无增加,添加 --fresh参数重试。
2.2.3 BridgeInterceptor拦截器讲解
以BridgeInterceptor
为例,截取一段代码:
if (userRequest.header("Host") == null) {requestBuilder.header("Host", hostHeader(userRequest.url(), false));}if (userRequest.header("Connection") == null) {requestBuilder.header("Connection", "Keep-Alive");}// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing// the transfer stream.boolean transparentGzip = false;if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {transparentGzip = true;requestBuilder.header("Accept-Encoding", "gzip");}List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());if (!cookies.isEmpty()) {requestBuilder.header("Cookie", cookieHeader(cookies));}if (userRequest.header("User-Agent") == null) {requestBuilder.header("User-Agent", Version.userAgent());}
如果我们并不对header中的相应字段做设置,Bridge拦截器会为其添加一些默认值,从之前的抓包对比也可以看出,当我们没有添加user-Agent、Host,Accept-Encoding等字段时,Okhttp会为我们自动添加这些信息。
本篇文章先到这里,下一篇我们会介绍另外两个拦截器—— Yang Okhttp 拦截器和天外飞仙拦截器的实现,敬请期待。
References
[1]
yuashuai: https://blog.csdn.net/qq_16445551/article/details/79008433
[2]
官网: https://square.github.io/okhttp/interceptors/#application-interceptors
[3]
这篇: https://www.anquanke.com/post/id/197657
怎么样?如果大家对安卓逆向感兴趣,想学到更多的知识,或者想与肉丝姐进一步交流的话,欢迎加入肉丝姐的星球来学习。
这里我跟肉丝姐还申请到了专属的半价(原价 50 元)优惠,一杯咖啡的钱大家就能学到更多关于安卓逆向的知识,感兴趣的朋友来扫码加入吧。
精品连载丨安卓 App 逆向课程之四 frida 注入 Okhttp 抓包中篇相关推荐
- 精品连载丨安卓 App 逆向课程之五 frida 注入 Okhttp 抓包下篇
本篇内容是「肉丝姐教你安卓逆向之 frida 注入 Okhttp 抓包系列的第三篇,建议配合前两篇一起阅读,效果更佳. 精品连载丨安卓 App 逆向课程之三 frida 注入 Okhttp 抓包上篇 ...
- 精品连载丨安卓 App 逆向课程之二逆向神器 frida 的介绍
" 阅读本文大概需要 8 分钟. " 前面我们介绍了精品连载丨安卓 App 逆向课程一之环境配置,下面我们来接着介绍一个安卓 App 逆向大杀器-- frida. 前阵子受< ...
- 《APP逆向学习》课程介绍和什么是安卓app逆向?
来源[小肩膀 零基础一站式安卓app逆向安全(2021版)]:aHR0cHM6Ly93d3cuYmlsaWJpbGkuY29tL3ZpZGVvL0JWMUFWNDExSjd6aw== 1.课程介绍 安 ...
- 知物由学 | 干货!一文了解安卓APP逆向分析与保护机制
"知物由学"是网易云易盾打造的一个品牌栏目,词语出自汉·王充<论衡·实知>.人,能力有高下之分,学习才知道事物的道理,而后才有智慧,不去求问就不会知道."知物 ...
- 安卓APP破解利器之FRIDA
本文讲的是安卓APP破解利器之FRIDA,在我去年参加RadareCon大会的时候,我了解到了一个动态的二进制插桩框架--Frida.起初我觉得它似乎只有一丁点趣味,后来经过实践才发现它原来是如此的有 ...
- APP渗透—MobSF安全评估、frida、r0capture抓包
APP渗透-MobSF安全评估.frida.r0capture抓包 1. 前言 2. 补充AppInfoScanner相关内容 3. MobSF安全评估 3.1. 下载MobSF 3.2. 安装Doc ...
- fiddler设置中文版本_突破安卓7.0以上版本WX小程序抓包篇
工作上经常需要使用brupsuite抓取APP和WX小程序的包,所以会在安卓上安装burp的证书.但是你会发现安卓7.0之后有了network-security-config这个选项,可以让app只信 ...
- Android安卓进阶之——一文带你了解抓包和反抓包
今天主要跟大家介绍一下Android的抓包和防止抓包 介绍两款抓包工具,Profiter和Charles. 工具环境: Android Studio 4.2.2 手机Google Pixel 3XL ...
- 安卓APP逆向百集完整版
课程介绍 Android逆向这玩意在日常工作中用的比较少,但是作为一个Android开发者,还是要学会的,比如我们可以破解反编译别人的APP,然后进行学习分析,甚至还可以反编译后进行修改再次打包等等, ...
最新文章
- php怎么看数据化,3.2.11 查看和判断数据类型
- IOS SEL (@selector) 原理及使用总结(一)
- bzoj2006 NOI2010 数据结构+堆维护区间和最大
- 微软Exchange Server 2013 CU11更新已发布
- H3C 三种生成树协议特性的比较
- PHP实现中文字符串截取无乱码
- 强力推荐几种多媒体播放器方案(jQuery、Flash、HTML5)
- linux dd 光标在闪,linux dd详解
- Cannot start process,the working directory 'F:\hello\hello'does not exit 问题解决
- golang-go mod版本等相关内容:
- 算法设计与分析基础 课后习题答案(第一章)
- 改进YOLOv7系列:26.CVPR2022. ConvNeXt结合YOLOv7 | 基于ConvNeXt结构 构建 CNeB 模块
- QQ被盗恢复原来QQ的好友有妙招
- ACM与IEEE双Fellow、华人女计算机科学家周以真:可信 AI,未来可期
- 微信分享网页链接自定义图片和文字描述
- Tensorflow2.*教程之使用Tensorflow Hub 对IMDB电影评论数据集进行文本分类(2)
- 台式您想使用系统还原计算机吗,系统还原功能已关闭。如果继续安装,将不能使用系统还原随您的计算机运行的Windows操作系统提供的Windows - Microsoft Community...
- 国密SM1、SM2、SM3、SM4算法资料大全
- u盘中的android文件夹图标不显示了,u盘里的文件夹不显示,u盘文件夹不见了
- 搜狗输入法5.0_马化腾又出手!将全资收购搜狗,后者盘前疯涨超45%
热门文章
- HTML5七夕情人节表白网页(新年倒计时-红色雪花) HTML+CSS+JS 求婚 html生日快乐祝福代码网页 520情人节告白代码 程序员表白源码 抖音3D旋转相册 js烟花代码 css爱
- 数据结构 作业答案 第1章 绪论
- 计算机函数公式的使用VLOOKUP,EXCEL函数公式大全使用VLOOKUP函数IF函数数据验证来自动获取价格...
- 幼师计算机能力自我评价,幼师工作能力自我评价
- HTML5+CSS大作业——明星个人主页(15页) 创作主页
- 程序员读《马云全转》
- python爬虫实战之爬取知乎帖子
- docker构建hadoop镜像、docker-compose启动hdfs
- 请问我这表该如何遍历呢?
- STP理论04-RSTP相对STP的改进