APP逆向 day28 小红薯(XHS)shield

一.前言

1 小红书请求头字段: shield 字段  好多请求有这个
    -发送短信验证码(post)
    -验证短信验证码--->get请求
    -短信登录(post)
2 版本:v6.73.0

3 注意:只是以请求头中:shield 讲解如何破,为了研究unidbg,小红书后台风控很严格

4 学到的知识点
    1 jadx搜不到代码,c层代码分析
    2 延迟hook
    3 unidbg补环境--》通过hook得到值,补环境
    4 okhttp3的拦截器,Request对象源码分析

二.抓包分析

我们使用SocksDroid,进行转发,然后打开XHS点击登录

6f25f079ebb74debbb188f349050dc87.png

我们要今天就只破解这个sheild,url地址https://www.xiaohongshu.com/api/sns/v1/system_service/check_code 

三.找到加密位置

3.1 分析

我们把apk拖入jadx进行反编译,然后我们搜索一下shield

7734e234e5f84b0592865394bbd27b52.png

发现找不到任何有价值的,那我们就只能盲目的找了嘛

我们就猜测这个字段是在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

09430dba22d6489694cf5b474fb95157.png

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)

f7da77065e0a4eba94151939d13b9272.png

发现这个拦截器很奇怪,那我们就得知道拦截器是干了什么 

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对象(所有的请求头)

6e79387dccd04c0d886c41eb95942a33.png 那我们根据刚才的分析,我们是不是可以获取这个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

8ed8c2a8ad00411d84b8e7dd54f664af.png 发现请求头里确实没有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

 e02feea6719541ee973f07ccbab854dd.png

"<instance: okhttp3.Interceptor, $className: p.d0.v1.e0.n0.h>"

那我们一会就要找到这个拦截器来验证不是吗

fca77bd0b0784bc0b025928e9c305d14.png

这里给出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

7e2a3eab10f54730bb27cd832b23dae1.png 可以发现下一个拦截器的chain里面就有shield,所以猜想没错,就是在那个加密的,那我们现在就是重点读那个拦截器

四.分析加密代码

948068734f8e4a8db17b13afb3882a0b.png

使用拦截器流程
    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

我们先把初始化的内容写好

b8ed6b27af22403a88c9864a42f57f74.png

但是注意,我们这里要跑三个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)

那我们大致把模板重写写齐

83cf0521d43e422ab476be4ab1de74e5.png

我们把一个sign依次写成三个方法

5.1 初始化

f93075b5ca864122914d1de0713b51bd.png

我们这就给他初始化,然后我们开始运行

c327a18c6cb94b57a1081380843c16af.png

这就是要开始补环境了

5.2 补环境

8362e1ac938949469676ff3807cdbd15.png

这个是java中内部的方法,直接补然后包裹返回就好了

3a39d184791346338dc5a5f13ab2c772.png

再运行,发现报错,返回的是小红书内部的device_id,那我们去代码里面找找

aebda26e982c41dda4d13b548a5fa47c.png 我们也可以继续读逻辑,看看这个值到底是什么,我这里和大家说就是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到的结果 

43ab718ea7e3414b8aa11788823e2f42.png 

sAppId= -319115519
sDeviceId= 44bbd521-3c12-3028-8fa0-d7cec8a8fc03
sExperiment= true

b22ee0e18cad4bd8bf22eb8becb2c176.png
 我们直接这么写就好

4cda42e8cd6d40d884c9566fc92632b3.png

接着运行,发现他又要补appid,那我们刚刚hook出来直接用就好了

4df46dc7dff74cd2800d30f00ad2774d.png

我们接着运行

d852b94fe38d459488dfc5e649dc91eb.png 发现他要sExperiment,那我们接着写死刚刚的值,就好了

cd55395d17404b7392dc01abe5508338.png

13771f85b5e84eb693c9b83d02bed68b.png 至此我们搞定,开始补下一个

六.unidbg跑initialize

6.1 分析参数

这个的时候需要一个参数,那我们得hook一下看看参数是多少,但是这个是在so文件中加载的,所以我们需要延迟hook一下,这里给出延迟hook的代码

6cd6d49331b540ba9ca1b574231666b9.png

发现传入的字符串就是main,那我们把这里修改成main就好

631b14360a7a46099318d724ecd4eab9.png

6.2 初始化

90b68a09cc6a412aa6c9f0e49d1e7e18.png

这样就是初始化好了,那我们运行一下

c5153fa426bf4c13ad1379c6e6274243.png

那我们就要开始补环境了 

6.3 补环境 

getSharedPreferences不就是去xml中拿吗

ac0908a2dba346068b4af0ec79408467.png

 我们直接补,然后顺面打印出xml的文件名

83aa0b3673a1491597ad0f2ba30cad3f.png

这个报错呢,我们直接搜索

81e2d3e4d99f4950bffb9deb444b4de5.png

那我们就开始补,我们得先找到这个key和默认值,那我们先写

5380fd4932d045b1a933db59aada310a.png

发现key是main,默认值是空,那我们现在就去xml中去寻找,那我么就要执行

cd /data/data/com.xingin.xhs/shared_prefs (后期只需要改包名)

在cat s.xml

4055a83852d64e2d99827462b14579c4.png

发现没有main,那我们返回空

0a1ddc5b0ea847ea8cc16c9b31d41638.png

我们此时也把main_hmac写进去

61f343911cd1454ebb4139eaf1585fa9.png

报错了,接着补asd,发现是小红书内部的base64,我们理论上要去源码看看是不是魔改了的,到时候直接扣这个方法就好,或者验证一下是否魔改,但是这里直接告诉大家是没有魔改的,我们直接写

3376725d005443b885e46c13fb748a47.png

9510625409ce44e1aaba56b7bd7cfec4.png 

这样就好了,我们把打印都删一下,开始补下一个

七.unidbg跑intercept

Response intercept(Interceptor.Chain chain, long j2)

第一个参数是chain,无法伪造,我们传个空进去就行,第二个参数是long,这个是上一个传进来的

99bec490325f4ba1b56d1b1fce3affb2.png

看这里可以看到,当时忘记给大家看了,那我们就开始改一下代码

ec3a9252061348c8b0a79bd068a3c95c.png

7.1 初始化

24a8c38c7dec424f808018aad487ea60.png okhttp3/Interceptor$Chain

中间用$,是因为这个是内部类,大家可以去搜一下

97d31cd65712441d8c63d42596dd6086.png

我们现在就要开始补环境了

7.2 导入okhttp3 

发现这个是okhttp3的,这个不是java和安卓的环境,需要我们导入第三方的jar包,这里教大家导入

9d685cce0abe4b7db2f993928fb16a23.png 在这里导入,然后刷新

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>3.4.2</version>
</dependency>

这样我们就有了,发现这个就是返回一个request

d0049109876a45ae952ed1d1dd3eb284.png

因为后续使用的request,所以我们定义一个全局的

c658140423b84676ab80508120cb10fe.png 

ede0d000650d4f39b21f81f9418250fe.png 

在main里面把这个request创建了,这样就ok了

7.3 补环境

76f2fc138d694698861e247f23265252.png

再运行,我们接着补

4c19560d3d7e43eeb0bcab519ad2fad9.png 

然后后面需要补十几个环境,我这里就不和大家说了,因为实在是太多了,我最后就直接给大家学习下补环境代码

e746a242638e4b06a46df5c751c7fb84.png

其中重点就是在这个方法里面,这个方法传入了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我,大家看我名字应该也知道

48958ec219f64078a355b08b7610e7f2.png 

 

八.参数化以及打包

8.1 参数化

609727c2ca3c4c9abcdf055b3db1747f.png

 我们不要把请求头和请求体写死,这样就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 打包

这个前几天才讲的,就不用我多说了吧,当然既然是最后一课,那当然得是最全的

98d326dcc6944e7d980324cdbaf55a93.png

先配置好,不记得去看前面的,记得点赞关注加收藏

8b1c5e409d874375a0f26e023d7b1c0f.png

526c76e86e17456d8dd013e9e8c26774.png 构建好了我们把apk复制进去

cb89db1e86404978be91e713470e3b4e.png

 然后把这个复制到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)

57ee4aa5e23e4ced91a5374ad17bfba3.png 

这就ok了 

九.总结

app逆向的基础部分也就告一段落了,我后续可能会更新其他的内容,也可能会更新进阶点的app逆向,希望我们都能成为生活中的高手,希望大家好好加油,往日情怀酿作酒,共勉。

 

 

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

往日情怀酿做酒 V1763929638

往日情怀酿作酒 感谢你的支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值