在当前的网络安全领域中,逆向工程和接口分析已成为重要的技术手段,而模拟和复现运行环境则是实现这些技术的关键一步。在实际工作中,我们经常需要面对复杂的加密逻辑或接口签名校验,这些校验通常依赖于特定的运行环境。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,并返回。
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);
}
运行结果:
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2025-1-6 22:12
被西贝巴巴编辑
,原因: 修改错字和样式