首页
社区
课程
招聘
[原创]unidbg补环境实战(某文学网站)
发表于: 2025-1-6 22:06 2015

[原创]unidbg补环境实战(某文学网站)

2025-1-6 22:06
2015

一、概述

在当前的网络安全领域中,逆向工程和接口分析已成为重要的技术手段,而模拟和复现运行环境则是实现这些技术的关键一步。在实际工作中,我们经常需要面对复杂的加密逻辑或接口签名校验,这些校验通常依赖于特定的运行环境。Unidbg 作为一款强大的 Android 动态调试工具,为我们提供了在本地模拟原生库运行的能力,大大降低了环境复现的难度。本文以某文学网站为例,结合实际案例,详细讲解如何通过 Unidbg 补齐运行环境,并成功模拟目标应用的关键加密逻辑。

二、案例样本逻辑分析

jadx反编译apk找到找到关键函数的签名入口。getSigValue(Context context, Map<String, String> map, int i)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import android.content.Context;
import com.umeng.analytics.pro.UContent;
import java.util.Map;
import p746h.p755d0.p757c.C9767h;
 
/* compiled from: Confusion.kt */
/* loaded from: classes2.dex */
public final class Confusion {
 
    /* renamed from: a */
    public static final Confusion f21521a = new Confusion();
 
    static {
        System.loadLibrary("conxx-lib");
    }
 
    private Confusion() {
    }
 
    /* renamed from: a */
    public final String m26215a(Context context, Map<String, String> map, int i) {
        C9767h.m1060e(context, UContent.f16415R);
        C9767h.m1060e(map, "map");
        String sigValue = getSigValue(context, map, i);
        return sigValue == null ? "" : sigValue;
    }
 
    public final native String getSigValue(Context context, Map<String, String> map, int i);
}

然后在IDA中分析conxx-lib.so,导出表中搜索getSigValue进入目标函数简单分析下代码
图片描述
主要逻辑分解

  1. 参数检查
    if (!a3 || (a2 && !getSha1(a1)))
    return 0;
    检查 a3 是否为空。如果为空,直接返回 0(表示失败)。
    如果 a2 不为零,还检查 getSha1(a1) 的结果。如果失败,同样返回 0。
  2. 获取 a3 对象的类信息并调用方法
    v7 = (*a1)->GetObjectClass(a1, a3);
    v8 = (*a1)->GetMethodID(a1, v7, "entrySet", "()Ljava/util/Set;");
    v9 = _JNIEnv::CallObjectMethod(a1, a3, v8);
    获取 a3 对象的类(v7)。
    获取 entrySet 方法的 ID(v8),该方法返回一个集合(Set)。
    调用 entrySet 方法,获取结果对象(v9)。
  3. 获取集合的迭代器
    v13 = (*a1)->GetMethodID(a1, v12, "iterator", "()Ljava/util/Iterator;");
    v14 = _JNIEnv::CallObjectMethod(a1, v9, v13);
    获取集合的迭代器方法 iterator 的 ID。
    调用该方法获取迭代器对象(v14)。
  4. 遍历集合,处理键值对
    while (_JNIEnv::CallBooleanMethod(a1, v14, v16))
    {
    v62 = _JNIEnv::CallObjectMethod(a1, v14, v59); // 获取当前项
    v63 = _JNIEnv::CallObjectMethod(a1, v62, v58); // 获取键
    v54 = _JNIEnv::CallObjectMethod(a1, v63, v64); // 获取键的字节数组
    jstringTostring(&v65, a1); // 转为 C++ 字符串
    }
    使用 hasNext 方法检查是否有下一项。
    调用 next 获取集合中的一个元素(键值对)。
    获取键对象并转换为字节数组形式,存储并处理。
  5. 签名的生成
    v33 = (*a1)->FindClass(a1, "java/security/MessageDigest");
    v34 = (*a1)->GetStaticMethodID(a1, v33, "getInstance", "(Ljava/lang/String;)Ljava/security/MessageDigest;");
    v35 = (*a1)->NewStringUTF(a1, "SHA224");
    v36 = _JNIEnv::CallStaticObjectMethod(a1, v33, v34, v35);
    v38 = _JNIEnv::CallObjectMethod(a1, v36, v37);
    使用 MessageDigest 类获取 SHA224 的实例。
    对数据进行摘要计算(digest 方法)。
  6. 将摘要结果转换为字符串
    for (j = 0; j < v39; ++j)
    {
    v42[2 * j] = a0123456789abcd_0[v40[j] >> 4];
    v44[1] = a0123456789abcd_0[v45 & 0xF];
    }
    v42[2 * v39] = 0;
    将字节数组转换为十六进制字符串。
  7. 返回结果
    v28 = (*a1)->NewStringUTF(a1, v42);
    return v28;
    最终,将生成的十六进制字符串创建为 jstring,并返回。

三、unidbg初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.xx.reader;
 
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
 
import java.io.File;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
 
public class MainActivity2 extends AbstractJni {
 
    private final AndroidEmulator emulator;
 
    private final Module module;
 
    private final Memory memory;
 
    private final VM vm;
 
    MainActivity2() {
        emulator = AndroidEmulatorBuilder.for32Bit().build();
 
        memory = emulator.getMemory();
 
        memory.setLibraryResolver(new AndroidResolver(23));
 
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/xx/reader/zz.apk"));
 
        vm.setVerbose(true);
 
        vm.setJni(this);
 
        DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xx/reader/libconxx-lib.so"), false);
 
        module = dalvikModule.getModule();
 
        vm.callJNI_OnLoad(emulator, module);
    }
 
    public static void main(String[] args) {
        MainActivity2 mainActivity = new MainActivity2();
 
    }
     
}

四、补环境基本要素

● 基本类型直接传递。 byte、short、int、long、double、float、boolean、char,只需要直接传递即可。
● 字符串、字节数组等基本对象直接传递,其内部会做封装,也可以自己调用new StringObject(vm, str)、new ByteArray(vm, value)等。
● JDK 标准库对象,如 HashMap、JSONObject 等,使用ProxyDvmObject.createObject(vm, value)处理。
● 非 JDK 标准库对象,如 Android Context、SharedPreference 等,使用vm.resolveClass(vm,className).newObject(value)处理。

五、发起调用

模拟getSigValue函数的调用,参数有三个 Context context, Map<String, String> map, int i,getSigValue的地址:0xfD92

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void sign() {
       ArrayList<Object> args = new ArrayList<>();
       // 第一个参数
       args.add(vm.getJNIEnv());
       // 第二个参数
       args.add(0);
       // 第三个参数 实际第一个
       DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
       args.add(vm.addLocalObject(context));
       // map 第四个参数 实际第二个
       TreeMap<String, String> keyMap = new TreeMap<String, String>();
       keyMap.put("device", "5555");
       DvmClass map = vm.resolveClass("java/util/Map");
       DvmClass abstractMap = vm.resolveClass("java/util/AbstractMap", map);
       DvmObject<?> argsMpa = vm.resolveClass("java/util/TreeMap", abstractMap).newObject(keyMap);
       args.add(vm.addLocalObject(argsMpa));
       // 实际第三个
       args.add(602);
       Number number = module.callFunction(emulator, 0xfD92 + 1, args.toArray());
       System.out.println("[addr] number is ==> " + number.intValue());
       DvmObject<?> object = vm.getObject(number.intValue());
       System.out.println("[addr] Call the so md5 function result is ==> " + object.getValue());
   }

执行结果:
图片描述
抛出异常,提示缺少的环境,java/util/TreeMap->entrySet()Ljava/util/Set; 表示模拟的代码中调用了 TreeMap 的 entrySet() 方法,但在 Unidbg 中未正确处理。
java.lang.UnsupportedOperationException: java/util/TreeMap->entrySet()Ljava/util/Set;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:416)
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)

dvmObject.getValue()获取对象的值,java/util/Set 是JDK 标准库对象,使用ProxyDvmObject.createObject(vm, value)处理,所以我们根据知识点三的要求,就很好补了。

1
2
3
4
5
6
7
8
9
10
@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
     if (signature.equals("java/util/TreeMap->entrySet()Ljava/util/Set;")) {
        TreeMap<String, String> treeMap = (TreeMap<String, String>) dvmObject.getValue();
        Set<Map.Entry<String, String>> setE = treeMap.entrySet();
        DvmObject<?> set =ProxyDvmObject.createObject(vm,setE);
        return set;
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

再次运行后,得到一个新的异常,说之前的环境补充是正确的。新的异常:
java.lang.UnsupportedOperationException: java/util/TreeMap$EntrySet->iterator()Ljava/util/Iterator;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:416)
at com.zongheng.reader.MainActivity2.callObjectMethodV(MainActivity2.java:114)

java/util/TreeMap$EntrySet->iterator()Ljava/util/Iterator; 也是JDK 标准库的,返回值用ProxyDvmObject.createObject

1
2
3
4
5
6
7
8
9
10
@Override
public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
     if (signature.equals("java/util/TreeMap$EntrySet->iterator()Ljava/util/Iterator;")) {
        Set<Map.Entry<Object,Object>> set = (Set<Map.Entry<Object,Object>>) dvmObject.getValue();
        Iterator<Map.Entry<Object, Object>> iterator = set.iterator();
        DvmObject<?> iteratorO =ProxyDvmObject.createObject(vm,iterator);
        return iteratorO;
    }
    return super.callIntMethodV(vm, dvmObject, signature, vaList);
}

运行后,得到一个新的异常:
java.lang.UnsupportedOperationException: java/util/TreeMap$EntryIterator->hasNext()Z
at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:624)
at com.github.unidbg.linux.android.dvm.AbstractJni.callBooleanMethodV(AbstractJni.java:602)
at com.github.unidbg.linux.android.dvm.DvmMethod.callBooleanMethodV(DvmMethod.java:119)

1.方法签名检查:if (signature.equals("java/util/TreeMap$EntryIterator->hasNext()Z")): 检查传入的方法签名是否匹配 TreeMap.EntryIterator 的 hasNext() 方法。Z 表示返回值是一个布尔类型。
2.提取迭代器对象:Iterator<Object> iterator = (Iterator<Object>) dvmObject.getValue();: 从 dvmObject 中提取实际的 Iterator 对象。dvmObject 是 unidbg 对 Java 对象的抽象表示。
3.检查是否有下一个元素:boolean b = iterator.hasNext();: 调用 Iterator 的 hasNext() 方法,返回一个布尔值,指示迭代器是否有更多的元素。
4.调用父类方法:return super.callBooleanMethodV(vm, dvmObject, signature, vaList);: 如果传入的方法签名不匹配 TreeMap.EntryIterator.hasNext(),则调用父类的 callBooleanMethodV 方法处理其他情况。

1
2
3
4
5
6
7
8
9
10
@Override
public boolean callBooleanMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    if (signature.equals("java/util/TreeMap$EntryIterator->hasNext()Z")) {
        Iterator<Object> iterator = (Iterator<Object>)dvmObject.getValue();
        boolean b = iterator.hasNext();
        return b;
    }
 
    return super.callBooleanMethodV(vm, dvmObject, signature, vaList);
}

运行后,得到一个新的异常:
java.lang.UnsupportedOperationException: java/util/TreeMap$EntryIterator->next()Ljava/lang/Object;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:416)
at com.zongheng.reader.MainActivity2.callObjectMethodV(MainActivity2.java:121)

1.方法签名检查:if (signature.equals("java/util/TreeMap$EntryIterator->next()Ljava/lang/Object;")): 检查传入的方法签名是否匹配 TreeMap.EntryIterator 的 next() 方法。该方法的签名表示它返回一个 Object。
2.提取迭代器对象:Iterator<?> iterator = (Iterator<?>) dvmObject.getValue();: 从 dvmObject 中提取实际的 Iterator 对象。dvmObject 是 unidbg 中对 Java 对象的封装。
3.获取下一个元素:Object next = iterator.next();: 调用 Iterator 的 next() 方法获取迭代器的下一个元素。

1
2
3
4
5
6
7
8
9
    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
       if (signature.equals("java/util/TreeMap$EntryIterator->next()Ljava/lang/Object;")) {
            Iterator<?> iterator = (Iterator<?>) dvmObject.getValue();
            Object next = iterator.next();
            DvmObject<?> nextO =ProxyDvmObject.createObject(vm,next);
            return nextO;
        }
}

运行后,得到一个新的异常:
java.lang.UnsupportedOperationException: java/util/Map$Entry->getKey()Ljava/lang/Object;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:416)
at com.zongheng.reader.MainActivity2.callObjectMethodV(MainActivity2.java:126)

1.方法签名检查:if (signature.equals("java/util/TreeMap$EntryIterator->next()Ljava/lang/Object;")): 这一行检查传入的签名是否匹配 TreeMap.EntryIterator 的 next() 方法。这个签名表示方法返回一个 Object。
2.获取迭代器:Iterator<?> iterator = (Iterator<?>) dvmObject.getValue();: 这里从 dvmObject 中提取实际的 Java Iterator 对象。dvmObject 是 unidbg 中对 Java 对象的抽象封装。
3.调用 next() 方法,Object next = iterator.next();: 调用 Iterator 的 next() 方法获取下一个元素。
4.包装返回值:DvmObject<?> nextO = ProxyDvmObject.createObject(vm, next);: 使用 unidbg 的 ProxyDvmObject.createObject 方法将原生 Java 对象 next 转换为 DvmObject,以便 unidbg 能正确地处理和返回这个对象。

1
2
3
4
5
6
7
8
    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        if (signature.equals("java/util/Map$Entry->getKey()Ljava/lang/Object;")) {
            Map.Entry entry = (Map.Entry) dvmObject.getValue();
            String key = (String) entry.getKey();
            return new StringObject(vm, key);
        }
}

运行后,得到一个新的异常:
java.lang.UnsupportedOperationException: java/util/Map$Entry->getValue()Ljava/lang/Object;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:416)
at com.zongheng.reader.MainActivity2.callObjectMethodV(MainActivity2.java:126)

1.方法签名检查:if (signature.equals("java/util/Map$Entry->getValue()Ljava/lang/Object;")): 这行代码检查传入的签名是否匹配 Map.Entry 接口的 getValue() 方法,该方法返回一个 Object 类型的值。
2.提取 Map.Entry 对象:Map.Entry entry = (Map.Entry) dvmObject.getValue();: 通过 dvmObject.getValue() 提取实际的 Map.Entry 对象。这意味着 dvmObject 包装了一个 Map.Entry 实例。
3.获取值:String value = (String) entry.getValue();: 调用 Map.Entry 的 getValue() 方法来获取存储的值,并将其强制转换为 String 类型。这里假设 getValue() 返回的是一个 String。
4.封装返回值:return new StringObject(vm, value);: 使用 unidbg 提供的 StringObject 类将 String 值封装起来,并返回一个 DvmObject。StringObject 是 unidbg 中对 Java 字符串对象的封装,使得框架可以处理和返回这个字符串。

1
2
3
4
5
6
7
8
    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        if (signature.equals("java/util/Map$Entry->getValue()Ljava/lang/Object;")) {
            Map.Entry entry = (Map.Entry) dvmObject.getValue();
            String value = (String) entry.getValue();
            return new StringObject(vm, value);
        }
}

运行后,得到一个新的异常:
java.lang.UnsupportedOperationException: java/util/TreeMap->size()I
at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:562)
at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethodV(AbstractJni.java:528)

1.方法签名检查:if (signature.equals("java/util/TreeMap->size()I")): 这行代码检查传入的方法签名是否匹配 TreeMap 的 size() 方法。I 表示返回值是一个整数。
2.获取 TreeMap 对象:TreeMap<String, String> treeMap = (TreeMap<String, String>) dvmObject.getValue();: 从 dvmObject 中提取实际的 TreeMap 对象。dvmObject 是 unidbg 中对 Java 对象的抽象表示,这里将其强制转换为 TreeMap 类型。
3.获取 TreeMap 大小:int size = treeMap.size();: 调用 TreeMap 的 size() 方法获取映射的大小,即键值对的数量。

1
2
3
4
5
6
7
8
9
@Override
public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    if (signature.equals("java/util/TreeMap->size()I")) {
        TreeMap<String, String> treeMap = (TreeMap<String, String>) dvmObject.getValue();
        int size = treeMap.size();
        return size;
    }
    return super.callIntMethodV(vm, dvmObject, signature, vaList);
}

运行后,得到一个新的异常:
java.lang.IllegalStateException: java.security.NoSuchAlgorithmException: SHA224 MessageDigest not available
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:488)
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:437)

1.方法签名检查:if (signature.equals("java/security/MessageDigest->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;")): 这行代码检查传入的方法签名是否匹配 MessageDigest 类的 getInstance 方法,该方法接受一个 String 参数并返回一个 MessageDigest 实例。
2.获取算法名称:StringObject type = vaList.getObjectArg(0);: 从 vaList 参数列表中获取第一个参数,这是一个表示算法名称的 StringObject。
3.判断算法类型:if (type.getValue().equals("SHA224")): 检查传入的算法名称是否是 SHA224。
4.模拟返回 SHA-224 实例:使用 vm.resolveClass 和 newObject 方法来创建一个 MessageDigest 的模拟对象,模拟返回一个 SHA-224 的实例。
5.实际获取 SHA-224 实例:eturn messageDigestClass.newObject(MessageDigest.getInstance("SHA-224"));: 使用 Java 标准库的 MessageDigest.getInstance("SHA-224") 方法实际获取一个 SHA-224 实例,并将其封装为 DvmObject 返回。
6.处理其他签名:return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);: 如果传入的方法签名不是 SHA-224 的 getInstance,则调用父类的 callStaticObjectMethodV 方法处理其他情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
  public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
      if (signature.equals("java/security/MessageDigest->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;")) {
          StringObject type = vaList.getObjectArg(0);
          if (type.getValue().equals("SHA224")) {
              // 模拟返回一个 SHA-224 的实例
              DvmClass messageDigestClass = vm.resolveClass("java/security/MessageDigest");
              DvmObject<?> messageDigestInstance = messageDigestClass.newObject(type.getValue());
              DvmObject<?> algorithmObj = vaList.getObjectArg(0);
              String algorithm = (String) algorithmObj.getValue();
              DvmObject<?> messageDigestInstance1 = messageDigestClass.newObject(algorithm);
              System.out.println("Returning SHA-224 MessageDigest instance");
              try {
                  return messageDigestClass.newObject(MessageDigest.getInstance("SHA-224"));
              } catch (NoSuchAlgorithmException e) {
                  throw new IllegalStateException(e);
              }
          }
      }
      return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
  }

运行结果:
图片描述


[注意]APP应用上架合规检测服务,协助应用顺利上架!

最后于 2025-1-6 22:12 被西贝巴巴编辑 ,原因: 修改错字和样式
收藏
免费 1
支持
分享
最新回复 (1)
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2025-1-6 22:31
0
游客
登录 | 注册 方可回帖
返回