使用Frida的python库
为什么要使用frida的python库捏
包名注入
import frida,sys
jscode="""
Java.perform(function(){
Java.use("com.dodonew.online.http.RequestUtil").encodeDesMap.overload('java.lang.String',"java.lang.String","java.lang.String").implementation=function(a,b,c){
console.log("deskey",b);
console.log("desIV",c);
console.log("data",a);
var retval=this.encodeDesMap(a,b,c);
console.log("retval",retval);
return retval;
}
Java.use('com.dodonew.online.util.Utils').md5.implementation=function(a){
console.log("Md5",a);
var retval=this.md5(a);
console.log("retval",retval);
return retval;
}
});
"""
process=frida.get_usb_device().attach("com.dodonew.online")//注意这里是附加
script=process.create_script(jscode)
script.load()
sys.stdin.read()
pid注入
只要将attach后面的包名改成pid就行
process=frida.get_usb_device().attach(PID)
spawn模式
device=frida.get_usb_device()
print("device",device)
pid=device.spawn(['com.dodonew.online'])
device.resume(pid)
print('pid:',pid)
jscode="""
Java.perform(function(){
Java.use("com.dodonew.online.http.RequestUtil").encodeDesMap.overload('java.lang.String',"java.lang.String","java.lang.String").implementation=function(a,b,c){
console.log("deskey",b);
console.log("desIV",c);
console.log("data",a);
var retval=this.encodeDesMap(a,b,c);
console.log("retval",retval);
return retval;
}
Java.use('com.dodonew.online.util.Utils').md5.implementation=function(a){
console.log("Md5",a);
var retval=this.md5(a);
console.log("retval",retval);
return retval;
}
});
"""
process=device.attach(pid)
print("process",process)
script=process.create_script(jscode)
script.load()
sys.stdin.read()
指定端口
启动frida-server的时候,如果加上指定参数-l 指定端口
那么就需要指定ip+端口去访问了
jscode="""
Java.perform(function(){
Java.use("com.dodonew.online.http.RequestUtil").encodeDesMap.overload('java.lang.String',"java.lang.String","java.lang.String").implementation=function(a,b,c){
console.log("deskey",b);
console.log("desIV",c);
console.log("data",a);
var retval=this.encodeDesMap(a,b,c);
console.log("retval",retval);
return retval;
}
Java.use('com.dodonew.online.util.Utils').md5.implementation=function(a){
console.log("Md5",a);
var retval=this.md5(a);
console.log("retval",retval);
return retval;
}
});
"""
process=frida.get_device_manager().add_remote_device("10.65.167.18:1145).attach('com.dodonew.online')
script = process.create_script(jscode)
script.load()
print("start")
sys.stdin.read()
send 与 console.log的区别
使用send发送 就要注册一个事件
jscode="""
Java.perform(function(){
Java.use("com.dodonew.online.http.RequestUtil").encodeDesMap.overload('java.lang.String',"java.lang.String","java.lang.String").implementation=function(a,b,c){
console.log("deskey",b);
console.log("desIV",c);
console.log("data",a);
var retval=this.encodeDesMap(a,b,c);
console.log("retval",retval);
return retval;
}
Java.use('com.dodonew.online.util.Utils').md5.implementation=function(a){
console.log("Md5",a);
var retval=this.md5(a);
send(retval);
return retval;
}
});
"""
def messageFunc(message,data):
if message["type"]=='send': #指定类型
print(u"[*]{0}".format(message['payload'])) #格式化输出
else:
print(message)
device=frida.get_usb_device()
process=device.attach('com.dodonew.online')
script = process.create_script(jscode)
script.on('message',messageFunc)
script.load()
print("start")
sys.stdin.read()
console.log只是单单的输出而已,而send 则是将js中的数据交给python去处理。
所以在messageFunc中可以对数据进行处理。
recv
如果我们想让js接受经过python处理后的数据,就要使用recv去接收
jscode="""
Java.perform(function(){
Java.use("com.dodonew.online.http.RequestUtil").encodeDesMap.overload('java.lang.String',"java.lang.String","java.lang.String").implementation=function(a,b,c){
console.log("deskey",b);
console.log("desIV",c);
console.log("data",a);
var retval=this.encodeDesMap(a,b,c);
console.log("retval",retval);
return retval;
}
Java.use('com.dodonew.online.util.Utils').md5.implementation=function(a){
console.log("Md5",a);
var retval=this.md5(a);
send(retval);
recv(function(obj){ //回调函数
console.log("python:",obj.data);
retval=obj.data; //替换返回值
}).wait();
return retval;
}
});
"""
def messageFunc(message,data):
if message["type"]=='send':
print(u"[*]{0}".format(message['payload']))
script.post({"data":"78b174c6c83e373e98929b070bf2ffc6"}) #通过post去传送数据
else:
print(message)
device=frida.get_usb_device()
process=device.attach('com.dodonew.online')
script = process.create_script(jscode)
script.on('message',messageFunc)
script.load()
print("start")
sys.stdin.read()
Rpc远程调用
jscode="""
Java.perform(function(){
Java.use("com.dodonew.online.http.RequestUtil").encodeDesMap.overload('java.lang.String',"java.lang.String","java.lang.String").implementation=function(a,b,c){
console.log("deskey",b);
console.log("desIV",c);
console.log("data",a);
var retval=this.encodeDesMap(a,b,c);
console.log("retval",retval);
return retval;
}
Java.use('com.dodonew.online.util.Utils').md5.implementation=function(a){
console.log("Md5",a);
var retval=this.md5(a);
console.log("retval",retval);
return retval;
}
});
function test(data){
var result="";
Java.perform(function(){
Java.use('com.dodonew.online.util.Utils').md5(data);
});
return result;
}
rpc.exports={
rpcfunc:test
};
"""
device = frida.get_usb_device()
print("device: ", device)
pid = device.spawn(["com.dodonew.online"]) # 以挂起方式创建进程
device.resume(pid) #恢复进程运行
print("pid: ", pid)
process = device.attach(pid)
print("process: ", process)
script = process.create_script(jscode)
script.load()
# device.resume(pid) # 加载完脚本,
result=script.exports.rpcfunc("equtype=ANDROID&loginImei=Android352678080375501&timeStamp=1688990595315&userPwd=3333&username=22222&key=sdlkjsdljf0j2fsjk")
# 注意,如果js中设置的函数名中带大写,那么在这里就要将每一个大写字母改成小写,然后在前面加上一个下划线 比如 如果js中是 rpcFunc:test 那么这里就要写成 .exports.rpc_func
print(result)
print("开始运行")
sys.stdin.read()
Unidbg
Unidbg是基于unicorn开发的框架
• 支持模拟JNI调用
• 支持模拟系统调用指令
• 支持ARM32和ARM64
• 支持Hookzz、Dobby、xHook、原生unicorn Hook等Hook方式
• 支持Android、iOS
好比是在CPU上搭建了一个系统,因此可以很方便地在PC端模拟运行so学习成本较低,不需要复现so算法,补环境后直接运行即可
下载解压Unidbg并分析官方给的样例
package com.bytedance.frameworks.core.encrypt;
import ...
public class TTEncrypt {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final DvmClass TTEncryptUtils;
private final boolean logging;
TTEncrypt(boolean logging) { //构造函数
this.logging = logging;
emulator = AndroidEmulatorBuilder.for32Bit()
.setProcessName("com.qidian.dldl.official") //设置进程名
.addBackendFactory(new Unicorn2Factory(true))
.build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
vm = emulator.createDalvikVM(); // 创建Android虚拟机
vm.setVerbose(logging); // 设置是否打印Jni调用细节
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/libttEncrypt.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数 注意设置好so库的路径 后面true与false是初始化,一般给false
dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数 dm其实就就相当于一个so了
module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块
TTEncryptUtils = vm.resolveClass("com/bytedance/frameworks/core/encrypt/TTEncryptUtils"); //加载类
}
void destroy() {
IOUtils.close(emulator);
if (logging) {
System.out.println("destroy");
}
}
public static void main(String[] args) throws Exception {
TTEncrypt test = new TTEncrypt(true);
byte[] data = test.ttEncrypt();
Inspector.inspect(data, "ttEncrypt"); Unidbg提供的接口,方便打印java中的byte数组
test.destroy();
}
byte[] ttEncrypt() {
if (logging) {
Symbol sbox0 = module.findSymbolByName("sbox0"); // 在libttEncrypt.so模块中查找sbox0导出符号
Symbol sbox1 = module.findSymbolByName("sbox1");
Inspector.inspect(sbox0.createPointer(emulator).getByteArray(0, 256), "sbox0"); // 打印sbox0导出符号在unicorn中的内存数据
Inspector.inspect(sbox1.createPointer(emulator).getByteArray(0, 256), "sbox1");
IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz,支持inline hook,文档看https://github.com/jmpews/HookZz
hookZz.enable_arm_arm64_b_branch(); // 测试enable_arm_arm64_b_branch,可有可无
hookZz.wrap(module.findSymbolByName("ss_encrypt"), new WrapCallback<RegisterContext>() { // inline wrap导出函数
@Override
public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
Pointer pointer = ctx.getPointerArg(2);
int length = ctx.getIntArg(3);
byte[] key = pointer.getByteArray(0, length);
Inspector.inspect(key, "ss_encrypt key");
}
@Override
public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
System.out.println("ss_encrypt.postCall R0=" + ctx.getLongArg(0));
}
});
hookZz.disable_arm_arm64_b_branch();
hookZz.instrument(module.base + 0x00000F5C + 1, new InstrumentCallback<Arm32RegisterContext>() {
@Override
public void dbiCall(Emulator<?> emulator, Arm32RegisterContext ctx, HookEntryInfo info) { // 通过base+offset inline wrap内部函数,在IDA看到为sub_xxx那些
System.out.println("R3=" + ctx.getLongArg(3) + ", R10=0x" + Long.toHexString(ctx.getR10Long()));
}
});
Dobby dobby = Dobby.getInstance(emulator);
dobby.replace(module.findSymbolByName("ss_encrypted_size"), new ReplaceCallback() { // 使用Dobby inline hook导出函数
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
System.out.println("ss_encrypted_size.onCall arg0=" + context.getIntArg(0) + ", originFunction=0x" + Long.toHexString(originFunction));
return HookStatus.RET(emulator, originFunction);
}
@Override
public void postCall(Emulator<?> emulator, HookContext context) {
System.out.println("ss_encrypted_size.postCall ret=" + context.getIntArg(0));
}
}, true);
IxHook xHook = XHookImpl.getInstance(emulator); // 加载xHook,支持Import hook,文档看https://github.com/iqiyi/xHook
xHook.register("libttEncrypt.so", "strlen", new ReplaceCallback() { // hook libttEncrypt.so的导入函数strlen
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
Pointer pointer = context.getPointerArg(0);
String str = pointer.getString(0);
System.out.println("strlen=" + str);
context.push(str);
return HookStatus.RET(emulator, originFunction);
}
@Override
public void postCall(Emulator<?> emulator, HookContext context) {
System.out.println("strlen=" + context.pop() + ", ret=" + context.getIntArg(0));
}
}, true);
xHook.register("libttEncrypt.so", "memmove", new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long originFunction) {
RegisterContext context = emulator.getContext();
Pointer dest = context.getPointerArg(0);
Pointer src = context.getPointerArg(1);
int length = context.getIntArg(2);
Inspector.inspect(src.getByteArray(0, length), "memmove dest=" + dest);
return HookStatus.RET(emulator, originFunction);
}
});
xHook.register("libttEncrypt.so", "memcpy", new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long originFunction) {
RegisterContext context = emulator.getContext();
Pointer dest = context.getPointerArg(0);
Pointer src = context.getPointerArg(1);
int length = context.getIntArg(2);
Inspector.inspect(src.getByteArray(0, length), "memcpy dest=" + dest);
return HookStatus.RET(emulator, originFunction);
}
});
xHook.refresh(); // 使Import hook生效
}
if (logging) {
emulator.attach(DebuggerType.ANDROID_SERVER_V7); // 附加IDA android_server,就可以单步地动态调试了,可输入c命令取消附加继续运行
}
byte[] data = new byte[16];
ByteArray array = TTEncryptUtils.callStaticJniMethodObject(emulator, "ttEncrypt([BI)[B", new ByteArray(vm, data), data.length); // 执行Jni方法 该函数比callFunction多封装了一些代码 所以就不用自己去寻找函数地址,不需要自己去包装参数了。 此例子中我们观察 函数签名 ttEncrypt([BI)[B 其中有两个参数 一个是Byte数组 一个是Int, 返回值是Int ,所以后面传入了一个使用Unidbg接口打包的byte数组(注意一定要用这个API打包后的byte,直接给byte类型是不对的)以及一个data的长度作为第二个参数。由于有返回值,所以使用callStaticJniMethodObject函数,这样就可以接受返回的对象。
return array.getValue();
}
}
一路查询callStaticJniMethodObject的实现,可以发现
nativeMap中已经放入了动态注册函数的函数名签名 以及对于的键值:函数地址。
当然,也不一样要用这个函数去获得函数地址,就像之前so库逆向一样,直接得到so库的基址,通过基址+偏移+ (thumb?1:0)
Unidbg主动调用案例
主动调用一个静态加载的JNI函数
package com.xiaojianbang.ndk;
import ...
public class Nativedemo {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final DvmClass NativeHelper;
private final boolean logging;
Nativedemo(boolean logging) {
this.logging = logging;
emulator = AndroidEmulatorBuilder.for64Bit()
.setProcessName("com.xiaojianbang.app")
.addBackendFactory(new Unicorn2Factory(true))
.build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
vm = emulator.createDalvikVM(); // 创建Android虚拟机
vm.setVerbose(logging); // 设置是否打印Jni调用细节
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xiaojianbang/ndk/libxiaojianbang.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
//dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块
NativeHelper = vm.resolveClass("com/xiaojianbang/ndk/NativeHelper");
}
void destroy() {
IOUtils.close(emulator);
if (logging) {
System.out.println("destroy");
}
}
public static void main(String[] args) throws Exception {
Nativedemo test = new Nativedemo(true);
int retval= test.callFunc();
System.out.println("retval: 0x"+Integer.toHexString(retval));
test.destroy();
}
int callFunc() {
byte[] data = new byte[16];
int retval = NativeHelper.callStaticJniMethodInt(emulator, "add(III)I",0x10,0x20,0x30); // 执行Jni方法
return retval;
}
}
可以发现确实执行so库,并且得到了正确得到值
主动调用一个动态加载的JNI函数
package com.xiaojianbang.ndk;
import ...
import java.io.File;
public class NativeDemo2 {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final DvmClass NativeHelper;
private final boolean logging;
NativeDemo2(boolean logging) {
this.logging = logging;
emulator = AndroidEmulatorBuilder.for64Bit()
.setProcessName("com.xiaojianbang.app")
.addBackendFactory(new Unicorn2Factory(true))
.build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
vm = emulator.createDalvikVM(); // 创建Android虚拟机
vm.setJni(new AbstractJni() { //使用Unidbg实现好的方法
});
vm.setVerbose(logging); // 设置是否打印Jni调用细节
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xiaojianbang/ndk/libxiaojianbang.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块
NativeHelper = vm.resolveClass("com/xiaojianbang/ndk/NativeHelper");
}
void destroy() {
IOUtils.close(emulator);
if (logging) {
System.out.println("destroy");
}
}
public static void main(String[] args) throws Exception {
NativeDemo2 test = new NativeDemo2(true);
test.callFunc();
test.destroy();
}
void callFunc() {
StringObject md5retval = NativeHelper.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;",new StringObject(vm,"Fup1p1")); // 执行Jni方法
System.out.println("md5result = :"+md5retval.getValue());
}
}
vm.setJni(new AbstractJni() { //使用Unidbg实现好的方法
});
然后发现一行报错
内存不够了???
加一行代码就行了
emulator.getSyscallHandler().setEnableThreadDispatcher(true);
最后能成功输出md5的值,并且我们在JNI_OnLoad创建的线程也能成功跑起来了。
一个so调用另一个so的函数
将另一个so也拖到目录下,然后加载一下就行
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xiaojianbang/ndk/libxiaojianbang.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
DalvikModule dm1 = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xiaojianbang/ndk/libxiaojianbangA.so"), false);
通过符号去调用函数
如果是返回的是基本数据类型,那就是返回值
Symbol symbol=module.findSymbolByName("Java_com_xiaojianbang_ndk_NativeHelper_add");
Number number= symbol.call(emulator,vm.getJNIEnv(),vm.addLocalObject(NativeHelper),0x40,0x20,0x10);
int result= number.intValue();
System.out.println("result "+result);
如果返回的是Java中的对象类型的话就要多些步骤了
Symbol symbol=module.findSymbolByName("_Z7_strcatP7_JNIEnvP7_jclass");
Number number= symbol.call(emulator,vm.getJNIEnv(),vm.addLocalObject(NativeHelper));
int result=number.intValue();
System.out.println("result "+vm.getObject(result).getValue()); //虚拟机的引用,result不是真正的返回值,更像是一种索引,然后要从dvmObj里面去捞数据。
DvmClass xxx= vm.resolveClass(path) 创建一个jclass
DvmObject xxxx=xxx.newObject(arg...) 根据jclass去实例化一个对象
Unidbg补环境
unidbg实现了大部分的JNI函数,如果有没有实现的,可以自己去实现
对于已经实现的,可以做一些覆写。
可以在 AbstractJni中覆写一些方法,比如下面这段就又再一次地修改了参数,使得最后调用的md5的时候,参数是Unidbg
vm.setJni(new AbstractJni() {
@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
System.out.println("signature"+signature);
if(signature.equals("java/lang/String->getBytes(Ljava/lang/String;)[B")){
String arg= (String) dvmObject.getValue();
System.out.println("arg "+arg);
byte[] strBytes="Unidbg".getBytes();
return new ByteArray(vm,strBytes);
}
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}
});
还有一种写法,就是直接继承AbstractJni这个类,然后直接写 vm.setJni(this)就行,然后要覆写的方法直接写就行。
Unidbg Hook
Unidbg支持dobby hookzz whale xhook
hookzz是dobby的前身,对32位的支持比较好,后者对64位支持比较好
unidbg是基于unicorn开发的,所以也支持unicorn的hook(指令级的hook、块级的hook、内存读写hook、异常hook)以及unidbg封装的console debugger(可以下多个断点,类似于gdb的调试)
hookzz
可以通过符号hook(本质上也是通过符号去取地址) 也可以通过地址hook
preCall和postCall有点像Xposed的 beforeHookedMethod和afterHookedMethod。
void callFunc() {
IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz,支持inline hook,文档看https://github.com/jmpews/HookZz
hookZz.wrap(module.findSymbolByName("_Z9MD5UpdateP7MD5_CTXPhj"), new WrapCallback<RegisterContext>() { // inline wrap导出函数
@Override
public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
md5_context = ctx.getPointerArg(0); //获取参数
Pointer plaintext= ctx.getPointerArg(1);
int length = ctx.getIntArg(2);
Inspector.inspect(md5_context.getByteArray(0,64),"preCall md5_context");
Inspector.inspect(plaintext.getByteArray(0,64), "Fup1p1");
}
@Override
public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
Inspector.inspect(md5_context.getByteArray(0,64),"postCall md5_context");
}
});
StringObject md5result=NativeHelper.callStaticJniMethodObject(emulator,"md5(Ljava/lang/String;)Ljava/lang/String;",new StringObject(vm,"Fup1p1"));
}
hook中注意参数的传递
hook一个jstring2cstr的函数,参数是jstring,返回值都是char*
IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz,支持inline hook,文档看https://github.com/jmpews/HookZz
hookZz.wrap(module.findSymbolByName("_Z12jstring2cstrP7_JNIEnvP8_jstring"), new WrapCallback<RegisterContext>() { // inline wrap导出函数
@Override
public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
StringObject obj=vm.getObject(ctx.getIntArg(1)); //参数是jstring,所以要通过getObject去vm虚拟机中捞数据
System.out.println(obj.getValue());
}
@Override
public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
byte[] bytes =ctx.getPointerArg(0).getByteArray(0,16); //返回的数据是char*,先转成UnidbgPointer然后再去取其byte数组
Inspector.inspect(bytes,"postcall:"+bytes);
}
});
Number number =module.callFunction(
emulator,
"_Z12jstring2cstrP7_JNIEnvP8_jstring",
vm.getJNIEnv(),
vm.addLocalObject(new StringObject(vm,"Fup1p1") ));//jstring 包装成 String Object 然后啊丢到vm虚拟机里面
long cstrAddr=number.longValue();
byte[] bytes=emulator.getMemory().pointer(cstrAddr).getByteArray(0,16);//先转成pointer类型,然后调用pointer的方法去获取。
Inspector.inspect(bytes,"jstring2cstr");
hook中修改参数
IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz,支持inline hook,文档看https://github.com/jmpews/HookZz
hookZz.wrap(module.findSymbolByName("_Z12jstring2cstrP7_JNIEnvP8_jstring"), new WrapCallback<HookZzArm64RegisterContext>() { // 注意要选择HookZzArm64RegisterContext
@Override
public void preCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
StringObject obj=vm.getObject(ctx.getIntArg(1));
System.out.println(obj.getValue());
}
@Override
public void postCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
String str=ctx.getPointerArg(0).getString(0);
System.out.println(str);
int hashcode=vm.addLocalObject(new StringObject(vm,"A1natas"));
ctx.setXLong(0,hashcode);
}
});
Number number =module.callFunction(
emulator,
"_Z12jstring2cstrP7_JNIEnvP8_jstring",
vm.getJNIEnv(),
vm.addLocalObject(new StringObject(vm,"Fup1p1") ));//jstring 包装成 String Object 然后丢到vm虚拟机里面
StringObject cstrobj=vm.getObject(number.intValue());
System.out.println(cstrobj.getValue());
hookzz inline Hook
上面介绍的hook也就只能hook起始地址,如果要hook中间代码
hookZz.instrument(module.base +0x1A2C , new InstrumentCallback<Arm64RegisterContext>() { //选择对应的架构
@Override
public void dbiCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) { // 通过base+offset inline wrap内部函数,在IDA看到为sub_xxx那些
System.out.println("W8=0x" + Integer.toHexString(ctx.getXInt(8))+ ", W9=0x" + Integer.toHexString(ctx.getXInt(9)));
}
});
int retval = NativeHelper.callStaticJniMethodInt(emulator, "add(III)I",0x10,0x20,0x30);
System.out.println("retval: 0x"+Integer.toHexString(retval));
以hook上面这行汇编为例子
hookzz replace
IHookZz hookZz = HookZz.getInstance(emulator);
hookZz.replace(module.findSymbolByName("Java_com_xiaojianbang_ndk_NativeHelper_md5"), new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long originFunction) {
System.out.println("have called");
int hashcode=vm.addLocalObject(new StringObject(vm,"Hooked"));
return HookStatus.LR(emulator,hashcode); //如果要返回int也可以,但是注意下面就要改成callStaticJniMethodInt
// return super.onCall(emulator, originFunction);
}
});
StringObject md5Result=NativeHelper.callStaticJniMethodObject(emulator,"md5(Ljava/lang/String)Ljava/lang/String;",new StringObject(vm,"Fup1p1"));
System.out.println("Md5Result:"+md5Result.getValue());
UnicornHook
使用unicorn 进行hook的时候不需要考虑是否要+1
以MD5为例,hook它的context与返回值
emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
RegisterContext context=emulator.getContext();
if(address==module.base+0x1f04){
Pointer plaintext=context.getPointerArg(0);
byte[] plain=plaintext.getByteArray(0,32);
Inspector.inspect(plain, "plaintext");
Pointer md5context=context.getPointerArg(1);
byte[] md5ctx=md5context.getByteArray(0,32);
Inspector.inspect(md5ctx,"md5ctx");
}
else if(address == module.base+0x1f14){
Pointer callback=context.getPointerArg(1);
byte[] md5final=callback.getByteArray(0,16);
Inspector.inspect(md5final,"return");
}
// System.out.println("hooked");
}
@Override
public void onAttach(UnHook unHook) {
}
@Override
public void detach() {
}
}, module.base+0x1F04, module.base+0x1F14,null );
StringObject md5result=NativeHelper.callStaticJniMethodObject(emulator,"md5(Ljava/lang/String;)Ljava/lang/String;",new StringObject(vm,"Fup1p1"));
打印调用栈
emulator.getUnwinder().unwind();
动态调试
操作很简单
Debugger debugger=emulator.attach();
debugger.addBreakPoint(module.base+0x1A28);
debugger.addBreakPoint(module.base+0x1A3C);
命令行调试,类似与gdb
监测内存读写
之前在frida的so层逆向中也谈及监控内存的读写,但是不够精确,因为是靠修改页的读写权限实现的。
String traceFile = "filename";
PrintStream traceStream = new PrintStream(new FileOutputStream(traceFile), true);//由于输出太多,所以让其写入到一个文件中去
emulator.traceRead(module.base, module.base + module.size).setRedirect(traceStream);//追踪内存读,并且重定向到你要写入的文件里去
emulator.traceWrite(module.base, module.base + module.size).setRedirect(traceStream);//追踪内存写,参数就是起始地址和结束地址
Unidbg trace
会记录下执行过的指令,使其记录寄存器的变动(老版本的Unidbg需要自己去修改源码实现)。
String traceFile = "filename";
PrintStream traceStream = new PrintStream(new FileOutputStream(traceFile), true);
emulator.traceCode(module.base, module.base + module.size).setRedirect(traceStream);
评论区