胡凯莉 发表于 2023-6-26 16:56

小红书APP Unidbg shield 分享知识点

# shield 分享知识点

## 1、hook NewStringUTF

- 找到在so层 c语言字符串转化为jni中的jstring类型 使用的函数`NewStringUTF(env,c中字符串)`

- ```js
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrNewStringUTF = null;
for (var i = 0; i < symbols.length; i++) {
      var symbol = symbols;

      if (symbol.name.indexOf("NewStringUTF") >= 0 && symbol.name.indexOf("CheckJNI") < 0) {
          addrNewStringUTF = symbol.address;
          console.log("NewStringUTF is at ", symbol.address, symbol.name);
      }
}

if (addrNewStringUTF != null) {
      Interceptor.attach(addrNewStringUTF, {
          onEnter: function (args) {
            var c_string = args;
            var dataString = c_string.readCString();

            if (dataString.indexOf("XYAAAAAQAAAAEAAAB") != -1) {
                  console.log(dataString);
                  console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');
                  console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
            }

          }
      });
}

// frida -UF-l1.so_utfstring.js -o token.txt
```

## 2、hook 所有的interceptor

- ```java
public class XhsHttpInterceptor implements Interceptor {
//在so中创建请求头的值,并添加到request对象中,在继续执行下一个拦截器
public native Response intercept(Interceptor.Chain chain, long j2) throws IOException;

}
```

- 拦截器中做的事情:

- `public native Response intercept(Interceptor.Chain chain, long j2)`
- 1、获取request对象request = chain.request()
- 2、使用request对象添加请求头
- 3、执行下一个拦截器 chain.proceed(request)

- ```js
//hook所有拦截器
Java.perform(function () {
      var Builder = Java.use('okhttp3.OkHttpClient$Builder');

      Builder.addInterceptor.implementation = function (inter) {
          //console.log("实例化:");

          console.log(JSON.stringify(inter) );
          //console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
          return this.addInterceptor(inter);
      };
})

//frida -U -f com.xingin.xhs-l 6.all_inter.js

```

```
"<instance: okhttp3.Interceptor, $className: p.d0.g1.j.b>"
"<instance: okhttp3.Interceptor, $className: p.d0.g1.j.a>"
"<instance: okhttp3.Interceptor, $className: com.xingin.shield.http.XhsHttpInterceptor>"
"<instance: okhttp3.Interceptor, $className: p.d0.v1.e0.n0.h>"
"<instance: okhttp3.Interceptor, $className: p.d0.g1.k.a>"
"<instance: okhttp3.Interceptor, $className: p.d0.v1.e0.r0.k>"
```



## 3、验证前后两个拦截器的内容 是否是在某个拦截器中生成的参数

- 验证1:`com.xingin.shield.http.XhsHttpInterceptor`

- ```js
Java.perform(function () {
      var XhsHttpInterceptor = Java.use('com.xxx.shield.http.XhsHttpInterceptor');
      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 8.next_request.js
```



- 验证2 : `p.d0.v1.e0.n0.h`

- ```js
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 8.next_request.js
```



## 4、逆向

- 判断so方法是静态方法 还是 实例方法

- 静态方法 :

    - 方法格式如: `publicstatic native Response intercept()`   注意static
    -直接`类.方法`

- 实例方法:

    - 格式 : `public native Response intercept(Interceptor.Chain chain, long j2)`

    - 需要实例化类对象:

      - 1 、 XhsHttpInterceptori1=    newXhsHttpInterceptor()
      - 2 、 Builder.addInterceptor(i1)
      - 3、i1.intercept(chain, 10)

    - 注意:

      - 1static中的内容要补全
      - 2构造方法中的事情要补全

   

   

    - ```java
       static {
            initializeNative();
          }
      
      1 public static native void initializeNative();
      
      
      public XhsHttpInterceptor(String str, a<Request> aVar) {
            this.cPtr = initialize(str);
            this.predicate = aVar;
          }
      
      
      2 public native long initialize(String str);
      
      
      3 public native Response intercept(Interceptor.Chain chain, long j2) throws IOException;
      
      
      
      ```

- 注意: shield 请求头的值不是通过返回值获取的,在intercept内部

- request= chain.request()
- request.addHeader("shield","zzzxxx")
- 补环境时遇到request对象和addHeader 方法时注意看一下参数 是否是shield

## 5、 补环境 initializeNative

- 签名调用

```java
DvmClass cls = vm.resolveClass("com/xxx/shield/http/XhsHttpInterceptor");
      // 调用方法 StringObject---unidbg中
      cls.callStaticJniMethodObject(
                emulator,
                "initializeNative()V",
      );
```

- 偏移地址调用

- hook 函数地址的偏移

- ```js
    var symbols = Module.enumerateSymbolsSync("libart.so");
    var addrRegisterNatives = null;
    for (var i = 0; i < symbols.length; i++) {
      var symbol = symbols;
      if (symbol.name.indexOf("art") >= 0 &&
            symbol.name.indexOf("JNI") >= 0 &&
            symbol.name.indexOf("RegisterNatives") >= 0 &&
            symbol.name.indexOf("CheckJNI") < 0) {
            addrRegisterNatives = symbol.address;
            // console.log("RegisterNatives is at ", symbol.address, symbol.name);
      }
    }
    // console.log("addrRegisterNatives=", addrRegisterNatives);
   
    if (addrRegisterNatives != null) {
      Interceptor.attach(addrRegisterNatives, {
            onEnter: function (args) {
                var env = args;
                var java_class = args;
                var class_name = Java.vm.tryGetEnv().getClassName(java_class);
                // 只有类名为com.xxx.nativelibrary.LibBili,才打印输出
                console.log(class_name);
                var taget_class = "com.xxx.shield.http.XhsHttpInterceptor";
   
                if (class_name === taget_class) {
                  // console.log("\n method_count:", args);
                  var methods_ptr = ptr(args);
                  var method_count = parseInt(args);
   
                  for (var i = 0; i < method_count; i++) {
                        // Java中函数名字的
                        var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
                        // 参数和返回值类型
                        var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
                        // C中的函数指针
                        var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
   
                        var name = Memory.readCString(name_ptr); // 读取java中函数名
                        var sig = Memory.readCString(sig_ptr); // 参数和返回值类型
                        var find_module = Process.findModuleByAddress(fnPtr_ptr); // 根据C中函数指针获取模块
   
                        var offset = ptr(fnPtr_ptr).sub(find_module.base) // fnPtr_ptr - 模块基地址
                        // console.log(" java_class:", class_name);
                        console.log("name:", name, "sig:", sig, "module_name:", find_module.name, "offset:", offset);
                        //console.log("name:", name, "module_name:", find_module.name, "offset:", offset);
   
                  }
                }
            }
      });
    }
    -----------------------------------或者下面----------------------------------------
   
    function find_RegisterNatives(params) {
      var symbols = Module.enumerateSymbolsSync("libart.so");
      var addrRegisterNatives = null;
      for (var i = 0; i < symbols.length; i++) {
            var symbol = symbols;
            
            //_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
            if (symbol.name.indexOf("art") >= 0 &&
                  symbol.name.indexOf("JNI") >= 0 &&
                  symbol.name.indexOf("RegisterNatives") >= 0 &&
                  symbol.name.indexOf("CheckJNI") < 0) {
                addrRegisterNatives = symbol.address;
                console.log("RegisterNatives is at ", symbol.address, symbol.name);
                hook_RegisterNatives(addrRegisterNatives)
            }
      }
   
    }
   
    function hook_RegisterNatives(addrRegisterNatives) {
   
      if (addrRegisterNatives != null) {
            Interceptor.attach(addrRegisterNatives, {
                onEnter: function (args) {
                  console.log(" method_count:", args);
                  var java_class = args;
                  var class_name = Java.vm.tryGetEnv().getClassName(java_class);
                  //console.log(class_name);
   
                  var methods_ptr = ptr(args);
   
                  var method_count = parseInt(args);
                  for (var i = 0; i < method_count; i++) {
                        var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
                        var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
                        var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
   
                        var name = Memory.readCString(name_ptr);
                        var sig = Memory.readCString(sig_ptr);
                        var symbol = DebugSymbol.fromAddress(fnPtr_ptr)
                                          //console.log(" java_class:", class_name)
                                          var target_ClassName = "com.xxx.shield.http.XhsHttpInterceptor";
                                          if(class_name === target_ClassName){
                                                   console.log(" java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr," fnOffset:", symbol, " callee:", DebugSymbol.fromAddress(this.returnAddress));
   
                           //console.log("name:", name, "sig:", sig, "module_name:", find_module.name, "offset:", offset);
                                          }
                  }
                }
            });
      }
    }
   
    setImmediate(find_RegisterNatives);
    ```

   

- ```java
ArrayList<Object> args = new ArrayList<>(10);
args.add(vm.getJNIEnv());   args.add(vm.addLocalObject(vm.resolveClass("com/xxx/shield/http/XhsHttpInterceptor")));
module.callFunction(
      emulator,
      0x94289,
      args.toArray()
          );

--------------------------------------------------------------
module.callFunction(emulator,
                   0x94289,
                   vm.getJNIEnv(),
      vm.addLocalObject(vm.resolveClass("com/xxx/shield/http/XhsHttpInterceptor")) //jclass
                  );
```

- 补环境

- ```java
java.lang.UnsupportedOperationException: java/nio/charset/Charset->defaultCharset()Ljava/nio/charset/Charset;
          at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:503)
```

- ```java
      @Override
      public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
            if (signature.equals("java/nio/charset/Charset->defaultCharset()Ljava/nio/charset/Charset;")){
                return vm.resolveClass("java/nio/charset/Charset").newObject(Charset.defaultCharset());
            }
            return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
      }
    ```

- 需要hook一下参数   静态字段.value 获取值



- ```js
    Java.perform(function () {
      var ContextHolder = Java.use('com.xxxx.shield.http.ContextHolder');
      console.log('sAppId=',ContextHolder.sAppId.value);
      console.log('sDeviceId=',ContextHolder.sDeviceId.value);
    })
   
    ```

## 6、 补环境 initialize



- 注意返回值是long

- ```java
public long initialize (){
          Number number = module.callFunction(emulator,
                  0x937B1,
                  vm.getJNIEnv(),
                  vm.addLocalObject(vm.resolveClass("com/xingin/shield/http/XhsHttpInterceptor")), //jclass
                  vm.addLocalObject(new StringObject(vm,"main"))
          );
          return number.longValue();
      }
```

- hook时机: 在加载so文件后进行hook

- ```js
    function do_hook() {
      setTimeout(function () {
            Java.perform(function () {
                var XhsHttpInterceptor = Java.use('com.xxx.shield.http.XhsHttpInterceptor');
                XhsHttpInterceptor.initialize.implementation = function (str) {
                  console.log("str=", str);
                  return this.initialize(str);
                };
            })
      }, 40);
    }
   
    function load_so_and_hook() {
      var dlopen = Module.findExportByName(null, "dlopen");
      var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
   
      Interceptor.attach(dlopen, {
            onEnter: function (args) {
                var path_ptr = args;
                var path = ptr(path_ptr).readCString();
                // console.log("", path);
                this.path = path;
            }, onLeave: function (retval) {
                if (this.path.indexOf("libshield.so") !== -1) {
                  console.log("", this.path);
                  do_hook();
   
                }
            }
      });
   
      Interceptor.attach(android_dlopen_ext, {
            onEnter: function (args) {
                var path_ptr = args;
                var path = ptr(path_ptr).readCString();
   
                this.path = path;
            }, onLeave: function (retval) {
                if (this.path.indexOf("libshield.so") !== -1) {
                  console.log("\nandroid_dlopen_ext加载:", this.path);
                  do_hook();
   
                }
            }
      });
    }
   
    load_so_and_hook();
    ```

   

### getSharedPreferences

- 开发时 读取xml文件会使用这个api

- ```java
SharedPreferencessp= getSharedPreferences("s",0)//第一个参数是xml文件的名字,第二个参数是模式

Stringtoken = sp.getString("main","");//读取xml文件中内容
```

- ```java
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;")){
            String xmlName =vaList.getObjectArg(0).getValue().toString();
            System.out.println("xmlName : " + xmlName);
            return vm.resolveClass("android.content.SharedPreferences")
                      .newObject(null);
          }
          if (signature.equals("android/content/SharedPreferences->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;")){
            String arg1 = vaList.getObjectArg(0).getValue().toString();
            String arg2 = vaList.getObjectArg(1).getValue().toString();
            System.out.println("key : " + arg1);
            System.out.println(arg2);
          }
          return super.callObjectMethodV(vm, dvmObject, signature, vaList);
      }
```







- 没有mian的值 返回空即可

### Base64

- base64 补的是标准的base64   注意不要导包导错了

- ```java
import org.apache.commons.codec.binary.Base64;
byte[] decode = Base64.decodeBase64(arg1);
```

## 7、 补环境 intercept

```java
public native Response intercept(Interceptor.Chain chain, long j2) throws IOException;
```

- 如果要执行必须要传入参数

- chain 这个参数流程可以猜测如下

    - ```.
      1、request = chain.request
      2、读取request中的内容
      ```

    - 1 可以直接传空值然后看后面读取什么补什么

    - 2 在Unidbg中引用okhttp3构造一个真的chain对象

### 引用okhttp3

- ```xml
<dependency>
      <groupId>com.squareup.okhttp3</groupId>
      <artifactId>okhttp</artifactId>
      <version>3.10.0</version>
</dependency>
```

风未眠 发表于 2023-6-26 20:27

谢谢分享

moruye 发表于 2023-6-26 21:58

文章很详细,刚好可以研究研究

zjh889 发表于 2023-6-27 00:36

又有好东西让俺们学习了,谢谢!

你就是我的阳光 发表于 2023-6-27 08:29

感谢分享

a2504028411 发表于 2023-6-27 10:45

楼主是那个版本的app

rb30173504 发表于 2023-6-27 14:47


又有好东西让俺们学习了,谢谢!

djshitaincai 发表于 2023-6-28 10:30

文章专业详细,谢谢楼主

ningmng 发表于 2023-6-28 17:26

感谢分享!

xtvfhptei 发表于 2023-6-28 20:59

都不怎么用小红书app了
页: [1] 2 3 4 5 6 7
查看完整版本: 小红书APP Unidbg shield 分享知识点