一.前言
1 小红书请求头字段: shield 字段 好多请求有这个
-发送短信验证码(post)
-验证短信验证码--->get请求
-短信登录(post)
2 版本:v6.73.03 注意:只是以请求头中:shield 讲解如何破,为了研究unidbg,小红书后台风控很严格
4 学到的知识点
1 jadx搜不到代码,c层代码分析
2 延迟hook
3 unidbg补环境--》通过hook得到值,补环境
4 okhttp3的拦截器,Request对象源码分析
二.抓包分析
我们使用SocksDroid,进行转发,然后打开XHS点击登录
我们要今天就只破解这个sheild,url地址https://www.xiaohongshu.com/api/sns/v1/system_service/check_code
三.找到加密位置
3.1 分析
我们把apk拖入jadx进行反编译,然后我们搜索一下shield
发现找不到任何有价值的,那我们就只能盲目的找了嘛
我们就猜测这个字段是在so中生成的,之前学过通用hook--NewStringUTF 方法,这个方法是c中返回个java字符串的通用方法 ,之前通过字符串长度来限制,现在我们可以通过字符串的开头,发现shield都包含XYAAAAA这一段字符串,当然,我们同时还得hook一下调用堆栈
// 后期任何app,都可以使用这个代码--》hook--so返回的字符串
//1 加载安卓手机底层包,系统自带的库,我们hook的NewStringUTF在这个包中
var symbols = Module.enumerateSymbolsSync("libart.so");
//2 定义一个变量,用来接收一会找到的NewStringUTF的地址
var addrNewStringUTF = null;
//3 循环找出libart.so中所有成员,匹配是NewStringUTF的函数,取出地址,赋值给上面的变量
for (var i = 0; i < symbols.length; i++) {
//3.1 取出libart.so的一个个方法对象
var symbol = symbols[i];
//3.2 判断方法对象的名字是不是包含 NewStringUTF和CheckJNI---》因为在真正底层,函数名不叫NewStringUTF,前后有别的字符串
// 实际它真正的名字:asdfa_NewStringUTF_dadsfasfd
if (symbol.name.indexOf("NewStringUTF") >= 0 && symbol.name.indexOf("CheckJNI") < 0) {
// 3.3 找到后,把地址赋值个上面的变量
addrNewStringUTF = symbol.address;
// 3.4 控制台打印一下
console.log("NewStringUTF is at ", symbol.address, symbol.name);
break
}
}
// 4 如果不为空,我们开始hook它(通过地址hook,有onEnter和onExit,所有的参数都给了args,通过位置取到每个参数)
if (addrNewStringUTF != null) {
Interceptor.attach(addrNewStringUTF, {
onEnter: function (args) {
// 4.1 取出NewStringUTF传入的第一个参数
var c_string = args[1];
// 4.2 第一个参数是c的字符串,我们把它转一下,变成真正的字符串
var dataString = c_string.readCString();
// 4.3 改字符串不为空,且长度为32,我们输出一下,并且打印出它的调用栈
if (dataString) {
// 只需要改这里后期
if (dataString.indexOf("XYAAAAAQAAAAEAAAB") !== -1) {
console.log(dataString);
// 4.4 读取当前在执行那个so文件,及so文件中的地址
console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');
// 4.5 打印调用栈
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
}
}
}
});
}
// attach 方案: frida -UF -l 15.通用hook脚本-获取so返回的字符串.js
// spawn 方案: frida -U -f 包名 -l 15.通用hook脚本-获取so返回的字符串.js
so文件是:libshield.so
调用栈:我们要看:com.xingin.shield.http.XhsHttpInterceptor.intercept
java.lang.Throwable
at com.xingin.shield.http.XhsHttpInterceptor.intercept(Native Method)
at com.xingin.shield.http.XhsHttpInterceptor.intercept(XhsHttpInterceptor.java:5)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:10)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:1)
at p.d0.g1.j.a.intercept(UserAgentInterceptor.kt:4)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:10)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:1)
at p.d0.g1.j.b.intercept(ValueRewriteInterceptor.kt:6)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:10)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:1)
at p.d0.v1.e0.b.intercept(ExceptionWithUrlInterceptor.kt:3)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:10)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:1)
at p.d0.g1.k.a.intercept(XYFixOkhttpInterceptor.kt:1)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:10)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:1)
at p.d0.v1.e0.r0.k.intercept(XhsNetTrackInterceptor.kt:8)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:10)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:1)
at p.d0.v1.e0.c0.intercept(XhsSavingResponseInterceptor.kt:1)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:10)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:1)
at p.d0.v1.e0.y.intercept(UnicomKingInterceptor.kt:5)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:10)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:1)
at p.d0.v1.b0.d.a.intercept(LoginInterceptor.kt:2)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:10)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:1)
at p.d0.v1.j.b.intercept(AntiSpamNativeInterceptor.kt:2)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:10)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:1)
at p.d0.v1.e0.d0.b.intercept(CustomHeadersInterceptor.kt:7)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:10)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:1)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:13)
at okhttp3.RealCall.execute(RealCall.java:8)
at d0.m.execute(OkHttpCall.java:8)
at p.d0.g1.b.b.b(XYRxJava2CallAdapterFactory.kt:5)
at s.a.r.a(Observable.java:153)
at p.d0.g1.b.a.b(XYRxJava2CallAdapterFactory.kt:1)
at s.a.r.a(Observable.java:153)
at s.a.j0.e.e.s.b(ObservableDoOnEach.java:1)
at s.a.j0.e.e.a1$b.run(ObservableSubscribeOn.java:1)
at p.d0.g1.i.a.run(SkynetScheduler.kt:4)
at p.d0.l0.b.f.a$a.run(XhsNetExecutor.kt:14)
at p.d0.q1.i.k.a$b$a.invoke(LightHelper.kt:2)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
at p.d0.q1.i.k.i.d$b.run(LightBaseThreadFactory.kt:2)
发现这个拦截器很奇怪,那我们就得知道拦截器是干了什么
3.2 回顾okhttp3拦截器
如何创建拦截器
方式一:写个类,实现Interceptor接口,重写intercept方法
拦截器1 = new XhsHttpInterceptor()--》重写intercept
方式二:直接实例化得到拦截对象
拦截器2 = new Interceptor(){
public Response intercept(Interceptor.Chain chain, long j2) {
...
}
}
使用拦截器
通过addInterceptor,传入拦截器
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
可以addInterceptor 多个
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor1).addInterceptor(interceptor2).addInterceptor(interceptor3)build();
在拦截器类中实现 intercept方法
public class CommonInterceptor implements Interceptor {
public Response intercept(Chain chain) throws IOException {
# 1 chain.request() 拿到Request对象--》请求对象
# 2 向请求对象中添加请求头 addHeader
Request request=chain.request().newBuilder().addHeader("ctime",ctime).addHeader("sign",sign).build();
# 3 执行下一个拦截器
Response response=chain.proceed(request);
# 4 发送请求响应回来的数据
return response;
}
}猜想:shield在so中生成--》jni的方法:intercept---》肯定在so内部,执行了addHeader 把 shield加入到请求头中的
-一定会有:c调用java代码
-用unidbg运行--》一定要补环境:addHeader--》有两个参数--》第一个参数是字符串:shield-提前告诉大家:补环境:不是补addHeader----》补的是header
chain.request().newBuilder().header('shield','asdfas')
chain.request().newBuilder().addHeader('shield','asdfas')
-通过读okhttp源码知道了这个
3.3 okhttp3的对象
1 chain 对象是哪个类
public native Response intercept(Interceptor.Chain chain, long j2)
看:Interceptor.Chain--》实际上是个接口
public interface Chain {}## 1.1 我们要确定chain是具体哪个类的对象--》hook得到
具体类型是:okhttp3.internal.http.RealInterceptorChain
## 1.2 chain.request()--》看RealInterceptorChain源码,看返回
public final class RealInterceptorChain implements Interceptor.Chain {
public final Request request;
public Request request() {
return this.request;
}
}
所以:chain.request() 返回的就是请求对象--》类比python的requests模块的request对象
里面会有:请求地址,请求头,请求参数。。。
# 2 读OKHttp3的Request类的源码
## 2.1 摘了部分源码--》request对象可以 .header .body 调用
public final class Request {
public final RequestBody body;
public volatile CacheControl cacheControl;
public final Headers headers;
public final String method;
public final Map<Class<?>, Object> tags;
public final HttpUrl url;
# 有个内部类:Builder
# 构造方法
public Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tags = Util.immutableMap(builder.tags);
}
# 其他方法:
# 请求体内容
public RequestBody body() {
return this.body;
}
# 请求头:传入请求头的key,拿到value
public String header(String str) {
return this.headers.get(str);
}
# 返回所有请求头
public Headers headers() {
return this.headers;
}
# 是否是https请求
public boolean isHttps() {
return this.url.isHttps();
}
# 请求方式是什么
public String method() {
return this.method;
}
# 打印request对象时,控制台显示的样式
public String toString() {
return "Request{method=" + this.method + ", url=" + this.url + ", tags=" + this.tags + '}';
}
}## 2.2 Request类内部有个Builder 内部类,实质上,我们在实例化得到Request对象时需要通过如下方式
方式一:Request request =new Request(builder的对象)
方式二:
builder对象 = new Request.Builder();# 创建空包裹
builder对象.addHeader(key,value)# 往包裹里放数据
builder对象.url('请求地址')
builder对象.removeHeader()
Request obj = new Request(builder对象);# 构建出request对象
方式三:开发常用
Request request = chain.request().newBuilder().url(请求地址).addHeader("ts", "15655352001").addHeader("sign", "xxxx").build();
最终调用:Builder类的对象调用build()方法--》返回request对象
public Request build() {
if (this.url != null) {
return new Request(this);
}
throw new IllegalStateException("url == null");
}## 2.3 Request类中有很多属性和方法
request对象.method(); # GET POST PUT ...
request对象.url().toString(); HttpUrl对象("http://xxxxx.com?v1=123&v2=5467")
request对象.headers() Headers对象(所有的请求头)
那我们根据刚才的分析,我们是不是可以获取这个chain的request对象,获取他的headers等内
容,然后呢,再看看下一个调用栈的requests的内容,我们就能判断是不是在这一步执行的了
这里给出hook代码
Java.perform(function () {
var XhsHttpInterceptor = Java.use('com.xingin.shield.http.XhsHttpInterceptor');
var Buffer = Java.use("okio.Buffer");
var Charset = Java.use("java.nio.charset.Charset");
XhsHttpInterceptor.intercept.overload('okhttp3.Interceptor$Chain', 'long').implementation = function (chain, j2) {
console.log('\n-----------------请求来了-------------------');
var request = chain.request(); // 我们主动调用chain.request() 返回okhttp的request对象
console.log("网址:")
console.log(request.url().toString())
console.log("\n请求头:")
console.log(request.headers().toString());
var requestBody = request.body();
if (requestBody) {
var buffer = Buffer.$new();
requestBody.writeTo(buffer);
console.log("请求体:")
console.log(buffer.readString(Charset.forName("utf8")));
}
var res = this.intercept(chain, j2);
return res;
};
})
//frida -UF -l 25 hoot拦截器中的请求头请求体等.js
发现请求头里确实没有shieldid,那我们接下来就要hook下一个拦截器里的,那我们是不是得知道他下一个拦截器是什么呢,那我们就来hook一下所有的拦截器,这里给出hook代码
Java.perform(function () {
var Builder = Java.use('okhttp3.OkHttpClient$Builder');
Builder.addInterceptor.implementation = function (inter) {
console.log(JSON.stringify(inter) );
return this.addInterceptor(inter);
};
})
//frida -Uf com.xingin.xhs -l 10.hook--拦截器.js
// -o all_interceptor3.txt
"<instance: okhttp3.Interceptor, $className: p.d0.v1.e0.n0.h>"
那我们一会就要找到这个拦截器来验证不是吗
这里给出hook代码,和之前那个代码特别像
Java.perform(function () {
var XhsHttpInterceptor = Java.use('p.d0.v1.e0.n0.h');
var Buffer = Java.use("okio.Buffer");
var Charset = Java.use("java.nio.charset.Charset");
XhsHttpInterceptor.intercept.overload('okhttp3.Interceptor$Chain').implementation = function (chain, j2) {
console.log('\n--------------------请求来了--------------------');
var request = chain.request();
var urlString = request.url().toString();
console.log("网址:")
console.log(urlString)
console.log("\n请求头:")
console.log(request.headers().toString());
var requestBody = request.body();
if (requestBody) {
var buffer = Buffer.$new();
requestBody.writeTo(buffer);
console.log("请求体:")
console.log(buffer.readString(Charset.forName("utf8")));
}
var res = this.intercept(chain);
return res;
};
})
// frida -UF -l 7-hook-下一个拦截器.js
可以发现下一个拦截器的chain里面就有shield,所以猜想没错,就是在那个加密的,那我们现在就是重点读那个拦截器
四.分析加密代码
使用拦截器流程
1 拦截器对象 = new XhsHttpInterceptor() # 调用它的构造方法
2 通过addInterceptor(拦截器对象) # 放到请求中
3 当执行到这,就会触发XhsHttpInterceptor的intercept 完成操作
根据上面的那个逻辑,我们可以知道,一共是执行
1 public static native void initializeNative();
2 public native long initialize(String str);
3 public native Response intercept(Interceptor.Chain chain, long j2)
这三个jni的方法,那我们就要依次写执行这三个的方法了,so文件时libshield.so
五.unidbg跑initializeNative
我们先把初始化的内容写好
但是注意,我们这里要跑三个so文件中的方法,所以我们不能直接写一个sign,我们得写三个函数才行
1 public static native void initializeNative();
2 public native long initialize(String str);
3 public native Response intercept(Interceptor.Chain chain, long j2)
那我们大致把模板重写写齐
我们把一个sign依次写成三个方法
5.1 初始化
我们这就给他初始化,然后我们开始运行
这就是要开始补环境了
5.2 补环境
这个是java中内部的方法,直接补然后包裹返回就好了
再运行,发现报错,返回的是小红书内部的device_id,那我们去代码里面找找
我们也可以继续读逻辑,看看这个值到底是什么,我这里和大家说就是uuid,我们为了方便直接hook一下
Java.perform(function () {
var ContextHolder = Java.use('com.xingin.shield.http.ContextHolder');
console.log('sAppId=',ContextHolder.sAppId.value);
console.log('sDeviceId=',ContextHolder.sDeviceId.value);
console.log('sExperiment=',ContextHolder.sExperiment.value);
})
// frida -UF -l hook_deviceid.js
hook到的结果
sAppId= -319115519
sDeviceId= 44bbd521-3c12-3028-8fa0-d7cec8a8fc03
sExperiment= true
我们直接这么写就好
接着运行,发现他又要补appid,那我们刚刚hook出来直接用就好了
我们接着运行
发现他要sExperiment,那我们接着写死刚刚的值,就好了
至此我们搞定,开始补下一个
六.unidbg跑initialize
6.1 分析参数
这个的时候需要一个参数,那我们得hook一下看看参数是多少,但是这个是在so文件中加载的,所以我们需要延迟hook一下,这里给出延迟hook的代码
发现传入的字符串就是main,那我们把这里修改成main就好
6.2 初始化
这样就是初始化好了,那我们运行一下
那我们就要开始补环境了
6.3 补环境
getSharedPreferences不就是去xml中拿吗
我们直接补,然后顺面打印出xml的文件名
这个报错呢,我们直接搜索
那我们就开始补,我们得先找到这个key和默认值,那我们先写
发现key是main,默认值是空,那我们现在就去xml中去寻找,那我么就要执行
cd /data/data/com.xingin.xhs/shared_prefs (后期只需要改包名)
在cat s.xml
发现没有main,那我们返回空
我们此时也把main_hmac写进去
报错了,接着补asd,发现是小红书内部的base64,我们理论上要去源码看看是不是魔改了的,到时候直接扣这个方法就好,或者验证一下是否魔改,但是这里直接告诉大家是没有魔改的,我们直接写
这样就好了,我们把打印都删一下,开始补下一个
七.unidbg跑intercept
Response intercept(Interceptor.Chain chain, long j2)
第一个参数是chain,无法伪造,我们传个空进去就行,第二个参数是long,这个是上一个传进来的
看这里可以看到,当时忘记给大家看了,那我们就开始改一下代码
7.1 初始化
okhttp3/Interceptor$Chain
中间用$,是因为这个是内部类,大家可以去搜一下
我们现在就要开始补环境了
7.2 导入okhttp3
发现这个是okhttp3的,这个不是java和安卓的环境,需要我们导入第三方的jar包,这里教大家导入
在这里导入,然后刷新
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.4.2</version>
</dependency>
这样我们就有了,发现这个就是返回一个request
因为后续使用的request,所以我们定义一个全局的
在main里面把这个request创建了,这样就ok了
7.3 补环境
再运行,我们接着补
然后后面需要补十几个环境,我这里就不和大家说了,因为实在是太多了,我最后就直接给大家学习下补环境代码
其中重点就是在这个方法里面,这个方法传入了shield,我们把这个打印出来就ok,这里给出大家补环境的代码
@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
if(signature.equals("java/nio/charset/Charset->defaultCharset()Ljava/nio/charset/Charset;")){
Charset cs=Charset.defaultCharset();
//return vm.resolveClass("java.nio.charset.Charset").newObject(cs) 这么写也可以 但是没必要
return ProxyDvmObject.createObject(vm,cs);
}
else if(signature.equals("com/xingin/shield/http/Base64Helper->decode(Ljava/lang/String;)[B")){
String input = (String) vaList.getObjectArg(0).getValue();
byte[] result = Base64.decodeBase64(input);
return new ByteArray(vm, result);
}
return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}
@Override
public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
if(signature.equals("com/xingin/shield/http/ContextHolder->sDeviceId:Ljava/lang/String;")){
return ProxyDvmObject.createObject(vm,"44bbd521-3c12-3028-8fa0-d7cec8a8fc03");
}
return super.getStaticObjectField(vm, dvmClass, signature);
}
@Override
public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
if(signature.equals("com/xingin/shield/http/ContextHolder->sAppId:I")){
return -319115519;
}
return super.getStaticIntField(vm, dvmClass, signature);
}
@Override
public boolean getStaticBooleanField(BaseVM vm, DvmClass dvmClass, String signature) {
if(signature.equals("com/xingin/shield/http/ContextHolder->sExperiment:Z")){
return true;
}
return super.getStaticBooleanField(vm, dvmClass, signature);
}
@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
if (signature.equals("android/content/Context->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;")) {
// 第一个参数是要读取的 xml文件名
String xmlName = (String) vaList.getObjectArg(0).getValue();
// System.out.println("XML文件名:" + xmlName);
//return vm.resolveClass("android/content/SharedPreferences").newObject(vaList.getObjectArg(0)); 理论上这么写 但是这么难写,不如一会直接返回字符串
return vm.resolveClass("android/content/SharedPreferences").newObject(null);
}
else if(signature.equals("android/content/SharedPreferences->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;")){
String key = (String) vaList.getObjectArg(0).getValue();
String defaultValue = (String) vaList.getObjectArg(1).getValue();
// System.out.println("键:" + key + " 默认值:" + defaultValue);
if(key.equals("main")){
return new StringObject(vm, defaultValue);
}else if(key.equals("main_hmac")){
return new StringObject(vm,"9i70Ve9zA+3GoZzHNU1gq+YoKge0Q1+nMU4wKF3nrx5pqFT6SXw3Oa260/YjpqwbXS0/FhvWowuSlvj1zzSstZJw4wPI+1/lappMYCm+PLHudYwvROATiL8PwcVzqPFI");
}
}
else if(signature.equals("okhttp3/Interceptor$Chain->request()Lokhttp3/Request;")){
// 执行 chain.request() 获取request请求相关的对象
return vm.resolveClass("okhttp3/Request").newObject(request);
}
else if(signature.equals("okhttp3/Request->url()Lokhttp3/HttpUrl;")){
Request request = (Request) dvmObject.getValue();
return vm.resolveClass("okhttp3/HttpUrl").newObject(request.url());
}
else if (signature.equals("okhttp3/HttpUrl->encodedPath()Ljava/lang/String;")) {
HttpUrl httpUrl = (HttpUrl) dvmObject.getValue();
return new StringObject(vm, httpUrl.encodedPath());
}
else if (signature.equals("okhttp3/HttpUrl->encodedQuery()Ljava/lang/String;")) {
HttpUrl httpUrl = (HttpUrl) dvmObject.getValue();
if (httpUrl.encodedQuery() != null) {
return new StringObject(vm, httpUrl.encodedQuery());
}
return new StringObject(vm, "");
}
else if (signature.equals("okhttp3/Request->body()Lokhttp3/RequestBody;")) {
Request request = (Request) dvmObject.getValue();
return vm.resolveClass("okhttp3/RequestBody").newObject(request.body());
}
else if (signature.equals("okhttp3/Request->headers()Lokhttp3/Headers;")) {
Request request = (Request) dvmObject.getValue();
return vm.resolveClass("okhttp3/Headers").newObject(request.headers());
}
else if (signature.equals("okio/Buffer->writeString(Ljava/lang/String;Ljava/nio/charset/Charset;)Lokio/Buffer;")) {
Buffer buffer = (Buffer) dvmObject.getValue();
buffer.writeString(vaList.getObjectArg(0).getValue().toString(), (Charset) vaList.getObjectArg(1).getValue());
return dvmObject;
}
else if (signature.equals("okhttp3/Headers->name(I)Ljava/lang/String;")) {
Headers headers = (Headers) dvmObject.getValue();
return new StringObject(vm, headers.name(vaList.getIntArg(0)));
}
else if (signature.equals("okhttp3/Headers->value(I)Ljava/lang/String;")) {
Headers headers = (Headers) dvmObject.getValue();
return new StringObject(vm, headers.value(vaList.getIntArg(0)));
}
if (signature.equals("okio/Buffer->clone()Lokio/Buffer;")) {
Buffer buffer = (Buffer) dvmObject.getValue();
return vm.resolveClass("okio/Buffer").newObject(buffer.clone());
}
if (signature.equals("okhttp3/Request->newBuilder()Lokhttp3/Request$Builder;")) {
Request request = (Request) dvmObject.getValue();
return vm.resolveClass("okhttp3/Request$Builder").newObject(request.newBuilder());
}
if (signature.equals("okhttp3/Request$Builder->header(Ljava/lang/String;Ljava/lang/String;)Lokhttp3/Request$Builder;")) {
Request.Builder builder = (Request.Builder) dvmObject.getValue();
builder.header(vaList.getObjectArg(0).getValue().toString(), vaList.getObjectArg(1).getValue().toString());
if ("shield".equals(vaList.getObjectArg(0).getValue().toString())) {
String shield = vaList.getObjectArg(1).getValue().toString();
System.out.println("shield=" + shield);
}
return dvmObject;
}
if (signature.equals("okhttp3/Request$Builder->build()Lokhttp3/Request;")) {
Request.Builder builder = (Request.Builder) dvmObject.getValue();
Request request = builder.build();
return vm.resolveClass("okhttp3/Request").newObject(request);
}
if (signature.equals("okhttp3/Interceptor$Chain->proceed(Lokhttp3/Request;)Lokhttp3/Response;")) {
return vm.resolveClass("okhttp3/Response").newObject(null);
}
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}
@Override
public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
if (signature.equals("okio/Buffer-><init>()V")) {
return dvmClass.newObject(new Buffer());
}
return super.newObjectV(vm, dvmClass, signature, vaList);
}
public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
if (signature.equals("okhttp3/Headers->size()I")) {
Headers headers = (Headers) dvmObject.getValue();
return headers.size();
}
else if (signature.equals("okio/Buffer->read([B)I")) {
Buffer buffer = (Buffer) dvmObject.getValue();
return buffer.read((byte[]) vaList.getObjectArg(0).getValue());
}
else if(signature.equals("okhttp3/Response->code()I")){
return 200;
}
return super.callIntMethodV(vm, dvmObject, signature, vaList);
}
@Override
public void callVoidMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
if (signature.equals("okhttp3/RequestBody->writeTo(Lokio/BufferedSink;)V")) {
BufferedSink bufferedSink = (BufferedSink) vaList.getObjectArg(0).getValue();
RequestBody requestBody = (RequestBody) dvmObject.getValue();
if (requestBody != null) {
try {
requestBody.writeTo(bufferedSink);
} catch (IOException e) {
System.out.println("错误了" + e);
}
}
return;
}
super.callVoidMethodV(vm, dvmObject, signature, vaList);
}
非常的全,这里插入一个小广告,有需求且需要源代码dd我,大家看我名字应该也知道
八.参数化以及打包
8.1 参数化
我们不要把请求头和请求体写死,这样就ok了
String method = args[0];
String url = args[1];
String commonParams = args[2];
String content = args[3]; // 请求体
if (method.equalsIgnoreCase("post")) {
MediaType TEXT = MediaType.parse("text/plan;charset=utf-8");
RequestBody body = RequestBody.create(TEXT, content);
request = new Request.Builder()
.url(url)
.addHeader("xy-common-params", commonParams)
.addHeader("content-type", "application/x-www-form-urlencoded")
.method("post", body)
.build();
} else {
request = new Request.Builder()
.url(url)
.addHeader("xy-common-params", commonParams)
.build();
}
8.2 打包
这个前几天才讲的,就不用我多说了吧,当然既然是最后一课,那当然得是最全的
先配置好,不记得去看前面的,记得点赞关注加收藏
构建好了我们把apk复制进去
然后把这个复制到pycham里面,就ok了
8.3 python调用
import subprocess
method = "get"
url = "https://www.xiaohongshu.com/api/sns/v1/system_service/check_code?zone=86&phone=18630099999&code=112233"
common_params = "fid=166893364910401a044595fd44d95587c504e09275d9&device_fingerprint=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&device_fingerprint1=2022112007045266bb5eba5505d24d7b1d35dec1975913018dc5b30ffa5ab5&launch_id=1673279778&tz=Asia%2FHong_Kong&channel=YingYongBao&versionName=6.73.0&deviceId=d7a8fa4f-98a2-398a-a7a5-417bf5e0b971&platform=android&sid=session.1668933583120053844884&identifier_flag=0&t=1673279684&project_id=ECFAAF&build=6730157&x_trace_page_current=login_full_screen_sms_page&lang=zh-Hans&app_id=ECFAAF01&uis=light"
body = "null"
cmd = f'java -jar unidbg-0.9.7.jar {method} "{url}" "{common_params}" "{body}"'
signature = subprocess.check_output(cmd, shell=True, cwd="unidbg_0_9_7_jar")
data_string = signature.strip().decode('utf-8').split("\n")[-1]
print(data_string)
这就ok了
九.总结
app逆向的基础部分也就告一段落了,我后续可能会更新其他的内容,也可能会更新进阶点的app逆向,希望我们都能成为生活中的高手,希望大家好好加油,往日情怀酿作酒,共勉。