http://zhuoyue360.com/crack/75.html
什么是unidbg就不多说了,大家自行百度。
写个简单的加减法来给unidbg调用。
public native String add(int a,int b);
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_lesson2_MainActivity_add(JNIEnv *env, jobject thiz, jint a, jint b) {
// TODO: implement add()
if(a<0){
a = -a;
}
if(b<0){
b=-b;
}
return a+b;
}
unidbg的基础调用代码
package com.example.lesson2;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.DynarmicFactory;
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.List;
public class MainActivity {
// 1. 环境初始化
private final AndroidEmulator emulator;
private final VM vm;
private final Memory memory;
private final Module module;
public MainActivity(){
// 2.1 创建模拟机
emulator = AndroidEmulatorBuilder
.for64Bit()
.addBackendFactory(new DynarmicFactory(true))
.build();
// 2.2 创建memory
memory = emulator.getMemory();
// 2.2 创建Android sdk版本. 支持19和23版
memory.setLibraryResolver(new AndroidResolver(23));
// 2.3 创建vm
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/example/lesson2/app-debug.apk"));
// 载入so文件 且 初始化,返回的说so加载到虚拟机的一个对象
DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/java/com/example/lesson2/liblesson2.so"), true);
module = dalvikModule.getModule();
vm.callJNI_OnLoad(emulator,module);
}
public void callAdd(int a,int b){
DvmObject obj = ProxyDvmObject.createObject(vm,this);
int result = obj.callJniMethodInt(emulator,"add(II)I",a,b);
System.out.println(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");
mainActivity.callAdd(1,8);
}
}
public void hook(){
HookZz hook = HookZz.getInstance(emulator);
hook.replace(module.base + 0xfdd4, new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long originFunction) {
System.out.println("onCall no context");
return super.onCall(emulator, originFunction);
}
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
System.out.println("onCall");
return super.onCall(emulator, context, originFunction);
}
@Override
public void postCall(Emulator<?> emulator, HookContext context) {
System.out.println("postCall");
super.postCall(emulator, context);
}
},true);
}
postCall需要在hook.replace
的参数3要为true的时候,才会执行。
onCall
:调用的时候 可以在此处修改参数等.
onPost
:离开的时候 可以在此处修改返回值
论证一下,调试看看执行的顺序, 从日志中, 可以看到是 onCall -> onCall(no context) -> postCall
load the vm 527ms
onCall
onCall no context
postCall
3
在开始进行真正的hook时,需要先说明一下ARM汇编子程序参数传递的调用约定相关内容
一.被调用模块的寄存器使用
1.调用模块和被调用模块通过R0-R3传递参数,因此参数少于四个时可以随意
使用剩余的而不必保存和恢复
2.使用R4-R11之前一定要先在堆栈中保存起来,退出时再恢复
3.可以使用堆栈,但一定要保证堆栈指针(R13)在进入时和退出时相等
4.R14用于保存返回地址,使用前一定要备份
二.被调用模块的堆栈使用
ATPCS规则规定堆栈是满递减(fD)型的,因此使用STMFD/LDMFD指令操作,
注意保证进入和退出时堆栈指针相等
三.参数传递
当少于四个时,按从左到右的顺序依次放在r0,r1,r2,r3中;
当多于四个时,前四个放在r0,r1,r2,r3中,剩余的放在堆栈中,最后一个
参数先入栈,第五个参数最后入栈,即从右到左入栈
四.返回值
结果为32位时,通过R0返回
结果为64位时,r0放低32位,r1放高32位
如果不涉及Thumb指令集,全部规则就这几条,
寄存器
在arm64汇编中寄存器是64bit的,使用X[n]
表示,低32位以w[n]
表示
在64
位架构中有31
个64
位的通用寄存器。
可以通过register read
查看
(lldb) register read
General Purpose Registers:
x0 = 0x0000000000000001
x1 = 0x00000002805d8a20
x2 = 0x00000002837e7980
x3 = 0x00000002805d8a20
x4 = 0x0000000000000001
x5 = 0x0000000000000001
x6 = 0x0000000100d54000
x7 = 0x0000000000000000
x8 = 0x0000200000000000
x9 = 0x000161a1d63f7945 (0x00000001d63f7945) (void *)0x01d63f7cb0000001
x10 = 0x0000000000000000
x11 = 0x000000000000006d
x12 = 0x000000000000006d
x13 = 0x0000000000000000
x14 = 0x0000000000000000
x15 = 0x00000001ca634e6d "touchesBegan:withEvent:"
x16 = 0x000000019c4cd47c libobjc.A.dylib`objc_storeStrong
x17 = 0x0000000000000000
x18 = 0x0000000000000000
x19 = 0x0000000100f17810
x20 = 0x00000002837e7920
x21 = 0x00000002805d8a20
x22 = 0x00000001ca634e6d "touchesBegan:withEvent:"
x23 = 0x0000000100e11a30
x24 = 0x00000002837e7980
x25 = 0x0000000100e11a30
x26 = 0x00000002805d8a20
x27 = 0x00000001ca62d900 "countByEnumeratingWithState:objects:count:"
x28 = 0x00000002805d8a20
fp = 0x000000016f47d730 // x29
lr = 0x00000001009866dc ArmAssembly`-[ViewController touchesBegan:withEvent:] + 84 at ViewController.m:38 // x30
sp = 0x000000016f47d720
pc = 0x0000000100986720 ArmAssembly`foo1 + 16 at ViewController.m:46
cpsr = 0x80000000
(lldb)
x0~x7
:一般是函数的参数,大于8个的会通过堆栈传参。
x0
:一般表示返回值
pc
:表示当前执行的指令的地址
lr
:链接寄存器,存放着函数的返回地址。lr也就是x30,这个里面存放着函数的返回地址
更多关于arm64汇编的内容可以看看下面的文档
https://www.jianshu.com/p/99067af33f14
经过上面的简单介绍,我们现在要如何去修改参数呢?看下面的代码。
Java_com_example_lesson2_MainActivity_add(JNIEnv *env, jobject thiz, jint a, jint b) {
在32位的情况下
上面说了,如果小于等于四个参数的话,可以直接放在r0-r3.我们这边刚刚好.
r0是env,r1是jobject,r2是a,r3是b。写个代码打印r2,r3试试看
在64位的情况下
只要参数的个数小于8个,都会放在x0-7中。
x0表示返回值。
由于我的so的64位的,所以应该打印是x2,和x3
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
System.out.println(String.format("X2:%d, X3:%d",context.getIntArg(2),context.getIntArg(3)));
return super.onCall(emulator, context, originFunction);
}
可以看到,这个结果和我们传入的参数是一样的
load the vm 669ms
R2:1, R3:8
onCall no context
9
通过emulator.getBackend().reg_write
进行值的修改。
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
System.out.println(String.format("X2:%d, X3:%d",context.getIntArg(2),context.getIntArg(3)));
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_X3,2);
return super.onCall(emulator, context, originFunction);
}
public void postCall(Emulator<?> emulator, HookContext context) {
System.out.println("postCall");
// 获取X0的内容
System.out.println(String.format("X0:%d",emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X0)));
// 修改返回值
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_X0,6666);
super.postCall(emulator, context);
}
什么是Patch?
在线arm汇编指令与HEX转换网站:https://armconverter.com/
我需要把这个ADD改成SUB,那么就需要上面的这个网站
.text:000000000000FDD4 ; =============== S U B R O U T I N E =======================================
.text:000000000000FDD4
.text:000000000000FDD4
.text:000000000000FDD4 EXPORT Java_com_example_lesson2_MainActivity_add
.text:000000000000FDD4 Java_com_example_lesson2_MainActivity_add
.text:000000000000FDD4 ; DATA XREF: LOAD:00000000000038A8↑o
.text:000000000000FDD4
.text:000000000000FDD4 var_18 = -0x18
.text:000000000000FDD4 var_14 = -0x14
.text:000000000000FDD4 var_10 = -0x10
.text:000000000000FDD4 var_8 = -8
.text:000000000000FDD4
.text:000000000000FDD4 ; __unwind {
.text:000000000000FDD4 FF 83 00 D1 SUB SP, SP, #0x20
.text:000000000000FDD8 E0 0F 00 F9 STR X0, [SP,#0x20+var_8]
.text:000000000000FDDC E1 0B 00 F9 STR X1, [SP,#0x20+var_10]
.text:000000000000FDE0 E2 0F 00 B9 STR W2, [SP,#0x20+var_14]
.text:000000000000FDE4 E3 0B 00 B9 STR W3, [SP,#0x20+var_18]
.text:000000000000FDE8 E8 0F 40 B9 LDR W8, [SP,#0x20+var_14]
.text:000000000000FDEC 1F 01 00 71 CMP W8, #0
.text:000000000000FDF0 E8 B7 9F 1A CSET W8, GE
.text:000000000000FDF4 A8 00 00 37 TBNZ W8, #0, loc_FE08
.text:000000000000FDF8 E8 0F 40 B9 LDR W8, [SP,#0x20+var_14]
.text:000000000000FDFC 09 00 80 52 MOV W9, #0
.text:000000000000FE00 28 01 08 6B SUBS W8, W9, W8
.text:000000000000FE04 E8 0F 00 B9 STR W8, [SP,#0x20+var_14]
.text:000000000000FE08
.text:000000000000FE08 loc_FE08 ; CODE XREF: Java_com_example_lesson2_MainActivity_add+20↑j
.text:000000000000FE08 E8 0B 40 B9 LDR W8, [SP,#0x20+var_18]
.text:000000000000FE0C 1F 01 00 71 CMP W8, #0
.text:000000000000FE10 E8 B7 9F 1A CSET W8, GE
.text:000000000000FE14 A8 00 00 37 TBNZ W8, #0, loc_FE28
.text:000000000000FE18 E8 0B 40 B9 LDR W8, [SP,#0x20+var_18]
.text:000000000000FE1C 09 00 80 52 MOV W9, #0
.text:000000000000FE20 28 01 08 6B SUBS W8, W9, W8
.text:000000000000FE24 E8 0B 00 B9 STR W8, [SP,#0x20+var_18]
.text:000000000000FE28
.text:000000000000FE28 loc_FE28 ; CODE XREF: Java_com_example_lesson2_MainActivity_add+40↑j
.text:000000000000FE28 E8 0F 40 B9 LDR W8, [SP,#0x20+var_14]
.text:000000000000FE2C E9 0B 40 B9 LDR W9, [SP,#0x20+var_18]
.text:000000000000FE30 00 01 09 0B ADD W0, W8, W9 // 我们要操作这里
.text:000000000000FE34 FF 83 00 91 ADD SP, SP, #0x20
.text:000000000000FE38 C0 03 5F D6 RET
.text:000000000000FE38 ; } // starts at FDD4
.text:000000000000FE38 ; End of function Java_com_example_lesson2_MainActivity_add
.text:000000000000FE38
.text:000000000000FE3C
.text:000000000000FE3C ; =============== S U B R O U T I N E =======================================
.text:000000000000FE3C
.text:000000000000FE3C ; Attributes: bp-based frame
把命令输入到网站后,右边生成了HEX的代码
此时就可以写代码了。
public void patch() {
UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0xFE30);
byte[] code = new byte[]{0x00,0x01,0x09,0x4B};
pointer.write(code);
}
看看结果:
load the vm 728ms
-7
使用keystore生成
public void patch2() {
UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0xFE30);
Keystone keystone = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian);
String s = "sub w0, w8, w9";
byte[] machineCode = keystone.assemble(s).getMachineCode();
// System.out.println(Integer.toHexString(machineCode[3]));
pointer.write(machineCode);
}
看看结果:
load the vm 728ms
-7
package com.example.lesson2;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.arm.backend.DynarmicFactory;
import com.github.unidbg.arm.backend.unicorn.Unicorn;
import com.github.unidbg.hook.HookContext;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.hookzz.HookZz;
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.github.unidbg.pointer.UnidbgPointer;
import com.sun.jna.Pointer;
import keystone.Keystone;
import keystone.KeystoneArchitecture;
import keystone.KeystoneMode;
import unicorn.Arm64Const;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class MainActivity {
// 1. 环境初始化
private final AndroidEmulator emulator;
private final VM vm;
private final Memory memory;
private final Module module;
public MainActivity(){
// 2.1 创建模拟机
emulator = AndroidEmulatorBuilder
.for64Bit()
.addBackendFactory(new DynarmicFactory(true))
.build();
// 2.2 创建memory
memory = emulator.getMemory();
// 2.2 创建Android sdk版本. 支持19和23版
memory.setLibraryResolver(new AndroidResolver(23));
// 2.3 创建vm
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/example/lesson2/app-debug.apk"));
// 载入so文件 且 初始化,返回的说so加载到虚拟机的一个对象
DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/java/com/example/lesson2/liblesson2.so"), true);
module = dalvikModule.getModule();
vm.callJNI_OnLoad(emulator,module);
}
public void callMD5(){
DvmObject obj = ProxyDvmObject.createObject(vm,this);
String data = "123456";
DvmObject dvmObject = obj.callJniMethodObject(emulator,"md5(Ljava/lang/String;)Ljava/lang/String;",data);
String result = (String) dvmObject.getValue();
// System.out.println("call the md5 function result is ->" + result);
}
public void callAdd(int a,int b){
DvmObject obj = ProxyDvmObject.createObject(vm,this);
int result = obj.callJniMethodInt(emulator,"add(II)I",a,b);
System.out.println(result);
}
public void hook(){
HookZz hook = HookZz.getInstance(emulator);
hook.replace(module.base + 0xfdd4, new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long originFunction) {
System.out.println("onCall no context");
return super.onCall(emulator, originFunction);
}
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
System.out.println(String.format("X2:%d, X3:%d",context.getIntArg(2),context.getIntArg(3)));
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_X3,2);
return super.onCall(emulator, context, originFunction);
}
@Override
public void postCall(Emulator<?> emulator, HookContext context) {
System.out.println("postCall");
// 获取X0的内容
System.out.println(String.format("X0:%d",emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X0)));
// 修改返回值
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_X0,6666);
super.postCall(emulator, context);
}
},true);
}
private void call_address() {
Pointer jniEnv = vm.getJNIEnv();
DvmObject obj = ProxyDvmObject.createObject(vm,this);
String data = "dta";
List<Object> args = new ArrayList<>();
args.add(jniEnv);
args.add(vm.addLocalObject(obj));
args.add(vm.addLocalObject(new StringObject(vm,data)));
Number[] numbers = module.callFunction(emulator, 0x8F00+ 1, args.toArray());
String value = (String) vm.getObject(numbers[0].intValue()).getValue();
System.out.println("[addr] call the md5 function result is ->" + value);
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
MainActivity mainActivity = new MainActivity();
System.out.println("load the vm " + (System.currentTimeMillis() - start) + "ms");
// mainActivity.hook();
mainActivity.patch2();
mainActivity.callAdd(1,8);
}
public void patch() {
UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0xFE30);
byte[] code = new byte[]{0x00,0x01,0x09,0x4B};
pointer.write(code);
}
public void patch2() {
UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0xFE30);
Keystone keystone = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian);
String s = "sub w0, w8, w9";
byte[] machineCode = keystone.assemble(s).getMachineCode();
// System.out.println(Integer.toHexString(machineCode[3]));
pointer.write(machineCode);
}
}