一、概述
在当前的网络安全领域中,逆向工程和接口分析已成为重要的技术手段,而模拟和复现运行环境则是实现这些技术的关键一步。在实际工作中,我们经常需要面对复杂的加密逻辑或接口签名校验,这些校验通常依赖于特定的运行环境。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进入目标函数简单分析下代码 主要逻辑分解
参数检查 if (!a3 || (a2 && !getSha1(a1))) return 0; 检查 a3 是否为空。如果为空,直接返回 0(表示失败)。 如果 a2 不为零,还检查 getSha1(a1) 的结果。如果失败,同样返回 0。
获取 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)。
获取集合的迭代器 v13 = (*a1)->GetMethodID(a1, v12, "iterator", "()Ljava/util/Iterator;"); v14 = _JNIEnv::CallObjectMethod(a1, v9, v13); 获取集合的迭代器方法 iterator 的 ID。 调用该方法获取迭代器对象(v14)。
遍历集合,处理键值对 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 获取集合中的一个元素(键值对)。 获取键对象并转换为字节数组形式,存储并处理。
签名的生成 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 方法)。
将摘要结果转换为字符串 for (j = 0; j < v39; ++j) { v42[2 * j] = a0123456789abcd_0[v40[j] >> 4]; v44[1] = a0123456789abcd_0[v45 & 0xF]; } v42[2 * v39] = 0; 将字节数组转换为十六进制字符串。
返回结果 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);
}
运行结果:
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2025-1-6 22:12
被复活甲编辑
,原因: 修改错字和样式