0%

Unidbg

  • unidbg

项目地址:https://github.com/zhkl0228/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
//Android模拟器
private final AndroidEmulator emulator;
//虚拟机
private final VM vm;
//内存接口
private final Memory memory;
//加载进内存的Module对象
private final Module module;

public MainActivity(){
emulator = AndroidEmulatorBuilder
//32位处理器
.for32Bit()
//64位处理器
//.for64Bit()
//设置根路径,相当于Android中的根目录
//.setRootDir(new File("target/rootfs/default"))
//添加后端工厂
//.addBackendFactory(new DynarmicFactory(true))
//指定进程名,最好Android包名作为进程名
//.setProcessName("com.github.unidbg")
//生成实例
.build();

memory = emulator.getMemory();
//设置Android解析器SDK版本
memory.setLibraryResolver(new AndroidResolver(23));
//创建APK对应的vm
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/dta/lesson2/app-debug.apk"));
//加载so、调用init
DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/java/com/dta/lesson2/libnative-lib.so"), true);
//将so文件对应的Module存入成员变量
module = dalvikModule.getModule();
//动态注册初始化【模拟器实例,要执行的JNI_Onload函数模块】
vm.callJNI_OnLoad(emulator,module);
//设置是否输出JNI运行日志
//vm.setVerbose(true);
}

符号调用

1
2
3
4
5
6
7
8
9
10
public void callMd5(){
//生成代理对象
DvmObject obj = ProxyDvmObject.createObject(vm,this);
//设置输入参数
String data = "dta";
DvmObject dvmObject = obj.callJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", data);
//从Dvm对象获取值
String result = (String) dvmObject.getValue();
System.out.println("[symble] Call the so md5 function result is ==> "+ result);
}

地址调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void call_address() {
//构造函数参数
Pointer jniEnv = vm.getJNIEnv();
DvmObject obj = ProxyDvmObject.createObject(vm,this);
StringObject data = new StringObject(vm,"dta");
//转换为参数列表
List<Object> args = new ArrayList<>();
args.add(jniEnv);
args.add(vm.addLocalObject(obj));
args.add(vm.addLocalObject(data));
//调用so
Number[] numbers = new Number[]{module.callFunction(emulator, 0x8E81, args.toArray())};
//getvalue
DvmObject<?> object = vm.getObject(numbers[0].intValue());
String value = (String) object.getValue();
System.out.println("[addr] Call the so md5 function result is ==> "+ value);
}

Hook

unidbg支持的hook框架:HookZz、Dobby、xHook、whale

Unicorn Hook

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
emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
RegisterContext context = emulator.getContext();
//System.out.println(user);
//System.out.println(size);
if (address == module.base + 0x1FF4){
Pointer md5Ctx = context.getPointerArg(0);
Inspector.inspect(md5Ctx.getByteArray(0, 32), "md5Ctx");
Pointer plainText = context.getPointerArg(1);
int length = context.getIntArg(2);
Inspector.inspect(plainText.getByteArray(0, length), "plainText");
}else if (address == module.base + 0x2004){
Pointer cipherText = context.getPointerArg(1);
Inspector.inspect(cipherText.getByteArray(0, 16), "cipherText");
}

}
@Override
public void onAttach(UnHook unHook) {
}
@Override
public void detach() {
}
}, module.base + 0x1FE8, module.base + 0x2004, "xxxxzzzz");

HookZz

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
public void hook(){
//获取HookZz实例
HookZz hook = HookZz.getInstance(emulator);
//replace(首地址[thumb指令集要加一],ReplaceCallback回调)
hook.replace(module.base + 0x3DC + 1, new ReplaceCallback() {
/*ReplaceCallback回调三个参数:
* 1. onCall方法在函数执行之前执行【获取和修改函数参数】
* 2. postCall方法在函数执行结束之后执行【借助后端工厂对寄存器来写入,修改返回值】
* 3. postCall方法默认不执行,需要配置第三个参数enablePostCall为true
* */
@Override
/*onCall的两个可重写方法:有无参数context[用于读取寄存器相关内容]
*
* */
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
//打印参数
System.out.println(String.format("R2: %d, R3: %d",context.getIntArg(2),context.getIntArg(3)));
//修改输入参数
emulator.getBackend().reg_write(Unicorn.UC_ARM_REG_R3,5);
return super.onCall(emulator, context, originFunction);
}

@Override
public void postCall(Emulator<?> emulator, HookContext context) {
//后端工厂reg_write(寄存器number,value)
emulator.getBackend().reg_write(Unicorn.UC_ARM_REG_R0,10);
super.postCall(emulator, context);
}
}, true);
}

patch

arm –> 机器码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//直接写机器码
public void patch(){
UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0x3E8);
byte[] code = new byte[]{(byte) 0xd0, 0x1a};
pointer.write(code);
}
//keystone转化
public void patch2(){
UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0x3E8);
Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb);
String s = "subs r0, r2, r3";
byte[] machineCode = keystone.assemble(s).getMachineCode();
//byte[] code = ;
pointer.write(machineCode);
}

打印调用栈

1
emulator.getUnwinder().unwind();

BreakPoint

1
2
3
4
5
6
7
emulator.attach().addBreakPoint(module.base + 0xB1A, new BreakPointCallback() {

@Override
public boolean onHit(Emulator<?> emulator, long address) {
return false;
}
});

监控内存

文件输出流

1
2
3
4
5
6
7
String traceFile = "myMonitorFile";
PrintStream traceStream = null;
try {
traceStream = new PrintStream(new FileOutputStream(traceFile), true);
} catch (FileNotFoundException e) {
e.printStackTrace();
}

内存读

1
emulator.traceRead(module.base, module.base + module.size).setRedirect(traceStream);

内存写

1
emulator.traceWrite(module.base, module.base + module.size).setRedirect(traceStream);

TraceCode

traceCode

  • traceCode在分析llvm中非常有用,可以将程序运行后实际用到的汇编代码输出到指定文件中,便于下一步分析
  • 相当于做一个执行流的trace,通常与上面的traceread、tracewrite一起用
1
2
3
4
5
6
7
8
9
String traceFile = "myTraceCodeFile";
PrintStream traceStream = null;
try {
traceStream = new PrintStream(new FileOutputStream(traceFile), true);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//追踪目标库的汇编代码,并将信息输出定向到指定文件中
emulator.traceCode(module.base, module.base + module.size).setRedirect(traceStream);

TraceBlock

完整案例

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package com.dta.lesson2;

import capstone.api.Instruction;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.backend.BlockHook;
import com.github.unidbg.arm.backend.CodeHook;
import com.github.unidbg.arm.backend.UnHook;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmObject;
import com.github.unidbg.linux.android.dvm.StringObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;
import com.sun.jna.Pointer;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MainActivity {
//Android模拟器
private final AndroidEmulator emulator;
//虚拟机
private final VM vm;
//内存接口
private final Memory memory;
//加载进内存的Module对象
private final Module module;

// 保存需要用 Frida Hook 的 Block 的地址
public static Map<Integer, Integer> subTraceMap = new HashMap<>();
// 保存命中的 Block 地址的次数,命中次数太多的就忽略掉
public static Map<Integer, Integer> calcMap = new HashMap<>();

private void traceBlock(final long baseAddr, final long startAddr, final long endAddr) {
emulator.getBackend().hook_add_new(new BlockHook() {
@Override
public void hookBlock(Backend backend, long address, int size, Object user) {
// 代码块需要大于 20 个字节,块太小影响 Frida 的 Hook
if (size > 20) {
// 反汇编当前地址的指令
Instruction[] insns = emulator.disassemble(address, 4, 0);
int iSize = insns[0].getSize();
int iUseAddr = 0;

// 根据模式计算偏移地址
if (iSize == 4) {
// ARM 模式 4 字节
iUseAddr = (int) (address - baseAddr);
} else {
// THUMB 模式 2 字节,hook 的时候需要 +1
iUseAddr = (int) (address - baseAddr) + 1;
}

// 检查命中次数
if (calcMap.containsKey(iUseAddr)) {
// 保存命中次数
int iValue = calcMap.get(iUseAddr);
calcMap.put(iUseAddr, iValue + 1);

// 4 次以上的调用就不显示,也不用 Frida Trace 了
if (iValue > 3) {
subTraceMap.remove(iUseAddr);
} else {
System.out.println("sub_" + Integer.toHexString(iUseAddr));
}
} else {
// 第一次命中,初始化次数
calcMap.put(iUseAddr, 1);
subTraceMap.put(iUseAddr, 1);
System.out.println("sub_" + Integer.toHexString(iUseAddr));
}
}
}

@Override
public void onAttach(UnHook unHook) {
// 可选:附加时的回调
}

@Override
public void detach() {
// 可选:分离时的回调
}
}, startAddr, endAddr, 0);
}

public MainActivity(){
emulator = AndroidEmulatorBuilder
.for32Bit()
//.setRootDir(new File("target/rootfs/default"))
//.addBackendFactory(new DynarmicFactory(true))
.build();

memory = emulator.getMemory();
//设置Android解析器SDK版本
memory.setLibraryResolver(new AndroidResolver(23));
//创建APK对应的vm
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/dta/lesson2/app-debug.apk"));
//加载so、调用init
DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/java/com/dta/lesson2/libnative-lib.so"), true);
//将so文件对应的Module存入成员变量
module = dalvikModule.getModule();
//动态注册初始化
vm.callJNI_OnLoad(emulator,module);


// 4. 确定基地址和需要 Trace 的范围
long baseAddr = module.base; // so 文件的基地址
long startAddr = baseAddr + 0x8E70; // Trace 开始地址
long endAddr = baseAddr + 0x8E90; // Trace 结束地址


// 5. 调用 traceBlock 方法进行跟踪
this.traceBlock(baseAddr, startAddr, endAddr);

this.callMd5();

// 7. 输出保存的 Trace 结果
System.out.println("Sub Trace Map: " + subTraceMap);
System.out.println("Calc Map: " + calcMap);


}

//符号调用
public void callMd5(){
//生成代理对象
DvmObject obj = ProxyDvmObject.createObject(vm,this);
//设置输入参数
String data = "dta";
DvmObject dvmObject = obj.callJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", data);
//从Dvm对象获取值
String result = (String) dvmObject.getValue();
System.out.println("[symble] Call the so md5 function result is ==> "+ result);
}

public static void main(String[] args) {
long start = System.currentTimeMillis();
MainActivity mainActivity = new MainActivity();
System.out.println("load the vm "+( System.currentTimeMillis() - start )+ "ms");

}

}

输出

1
2
3
4
5
sub_8e81
[symble] Call the so md5 function result is ==> 36072180305f072a2e2c7ea96eedf034
Sub Trace Map: {36481=1}
Calc Map: {36481=1}
load the vm 5769ms

codeHook & blockHook

二者在实际业务中是用的比较多的,一个是指令级别的hook,一个是代码块级别的hook,具体使用方案的话这里就不介绍了,方法重写什么的idea会给提示的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
emulator.getBackend().hook_add_new(new BlockHook() {
@Override
public void hookBlock(Backend backend, long address, int size, Object user) {
}

@Override
public void onAttach(UnHook unHook) {

}

@Override
public void detach() {

}
},startAddress,endAddress,null);

cmake编译so

build.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cmake \
# 配置ninja的目录
-H./ \
# 配置生成的架构
-B./ninja \
# arm64-v8a
-DANDROID_ABI=armeabi-v7a \
# 配置安卓平台版本 android-16
-DANDROID_PLATFORM=android-23 \
# 配置NDK目录
-DANDROID_NDK=/root/Android/Sdk/ndk/22.1.7171670 \
# 配置工具链android.toolchain.cmake文件的位置【NDK】
-DCMAKE_TOOLCHAIN_FILE=/root/Android/Sdk/ndk/22.1.7171670/build/cmake/android.toolchain.cmake \
-G Ninja
# 使用ninja编译so
ninja -C./ninja

CMakeLists.txt

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
cmake_minimum_required(VERSION 3.10.2)

# Declares and names the project.

project("lesson5")
# 配置生成so的目录位置
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../)
# 配置构建类型
set(CMAKE_BUILD_TYPE "Release")
# 指定C和C++不输出调试信息
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -s")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s")

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
native-lib

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
native-lib.cpp )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
log-lib

# Specifies the name of the NDK library that
# you want CMake to locate.
log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
native-lib

# Links the target library to the log library
# included in the NDK.
${log-lib} )

参考致谢: