https://github.com/DERE-ad2001/Frida-Labs
免责声明
本公众号"好文推送"旨在分享网络安全领域的相关知识和工具,仅限于学习和研究之用。由于传播、利用本公众号"好文推送"所提供的信息而造成的任何直接或者间接的后果及损失,由使用者承担全部法律及连带责任,公众号"好文推送"及作者不为此承担任何责任。
有侵权烦请告知,我们会立即删除并致歉,谢谢。
准备工作
已经root的安卓设备一台,电脑手机安装完frida环境
adb 连接adb connect 172.20.10.2:40529(手机开启无线调试)
启动frida
安装软件
adb install ""
Frida 0x1 简单hook
Frida setup, Hooking a method
jadx打开app。
显然hook:check函数使得i*2+4==i2就行,其中i是随机数。
所以hook后
1、i2=i*2+4
setTimeout(//该方法接受一个匿名方法并注册到js运行库中
Java.perform(
//是 Frida 中的一个函数,用于为脚本创建一个特殊上下文,以便与 Android 应用程序中的 Java 代码进行交互
function() {//一个匿名函数
let MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");//要hook的类
MainActivity["check"].implementation = function (i, i2) {//覆盖调用栈中原函数的执行地址实现hook
i2 = i*2+4
console.log(`MainActivity.check is called: i=${i}, i2=${i2}`);
this["check"](i, i2);//调用原来的check函数
};
}
))
直接是实现了任意密码
2、i = (i2-4)/2 这里要考虑到除2就不是int了,故只能输入2的整数倍的值切要大于4
setTimeout(//该方法接受一个匿名方法并注册到js运行库中
Java.perform(
//是 Frida 中的一个函数,用于为脚本创建一个特殊上下文,以便与 Android 应用程序中的 Java 代码进行交互
function() {//一个匿名函数
let MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");//要hook的类
MainActivity["check"].implementation = function (i, i2) {//覆盖调用栈中原函数的执行地址实现hook
i = (i2-4)/2
console.log(`MainActivity.check is called: i=${i}, i2=${i2}`);
this["check"](i, i2);//调用原来的check函数
};
}
))
3、直接输入一个密码0、4
setTimeout(
Java.perform(
function() {
let MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");
MainActivity["check"].implementation = function (i, i2) {
console.log(`MainActivity.check is called: i=${i}, i2=${i2}`);
this["check"](0, 4);
};
}
))
hook get_random()函数
1、直接设置随机数为0,那么密码应该是4
setTimeout(//该方法接受一个匿名方法并注册到js运行库中
Java.perform(
//是 Frida 中的一个函数,用于为脚本创建一个特殊上下文,以便与 Android 应用程序中的 Java 代码进行交互
function() {//一个匿名函数
let MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");
MainActivity["get_random"].implementation = function () {
console.log(`MainActivity.get_random is called`);
let result = this["get_random"]();//获取原来的随机数
console.log(`MainActivity.get_random result=${result}`);
result = 0;
console.log(`MainActivity.get_random result=${result}`);
return result;
};
MainActivity["check"].implementation = function (i, i2) {
//i2 = i*2+4
console.log(`MainActivity.check is called: i=${i}, i2=${i2}`);
this["check"](i, i2);
};
}
))
2、直接打印密码
setTimeout(//该方法接受一个匿名方法并注册到js运行库中
Java.perform(
//是 Frida 中的一个函数,用于为脚本创建一个特殊上下文,以便与 Android 应用程序中的 Java 代码进行交互
function() {//一个匿名函数
let MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");
MainActivity["get_random"].implementation = function () {
console.log(`MainActivity.get_random is called`);
let result = this["get_random"]();//获取原来的随机数
console.log(`MainActivity.get_random result=${result}`);
console.log("The value to bypass the check "+(result* 2 + 4));
return result;
};
}
))
Frida 0x2 主动调用静态函数
显然这里get_flag是我们要的东西,先hook看看
setTimeout(
Java.perform(
function() {
let MainActivity = Java.use("com.ad2001.frida0x2.MainActivity");
MainActivity["get_flag"].implementation = function (a) {
console.log(`MainActivity.get_flag is called: a=${a}`);
this["get_flag"](a);
};
}
))
显然没有调用,那我们主动调用,if条件要满足传入参数4919
setTimeout(
Java.perform(
function() {
let MainActivity = Java.use("com.ad2001.frida0x2.MainActivity");
MainActivity["get_flag"] (4919);
}
))
这里直接脚本启动修改不了,显然是hook早了。需要启动后再hook,那只能在命令行hook了。
Frida 0x3 主动设置类静态值
显然先要让chcker.code==512,看下check
功能就是点击256下就可以,那么点击的时候会increase吗?hook一下看看
setTimeout(
Java.perform(
function() {
let Checker = Java.use("com.ad2001.frida0x3.Checker");
let AnonymousClass1 = Java.use("com.ad2001.frida0x3.MainActivity$1");
AnonymousClass1["onClick"].implementation = function (v) {
var code = Checker.code.value;
console.log(`AnonymousClass1.onClick is called: codoe=${code}`);
this["onClick"](v);
};
}
))
显然不会,那我们现在有两个思路,一调用increase 256次,二直接设置code==512
1、调用increase 256次
setTimeout(
Java.perform(
function() {
let Checker = Java.use("com.ad2001.frida0x3.Checker");
let AnonymousClass1 = Java.use("com.ad2001.frida0x3.MainActivity$1");
AnonymousClass1["onClick"].implementation = function (v) {
while(Checker.code.value != 512){
Checker.increase();
}
var code = Checker.code.value;
console.log(`AnonymousClass1.onClick is called: codoe=${code}`);
this["onClick"](v);
};
}
))
也是可以的,直接在onclick的时候调用256次
也是直接拿下了
2、直接设置code==512
setTimeout(
Java.perform(
function() {
let Checker = Java.use("com.ad2001.frida0x3.Checker");
let AnonymousClass1 = Java.use("com.ad2001.frida0x3.MainActivity$1");
AnonymousClass1["onClick"].implementation = function (v) {
Checker.code.value = 512
var code = Checker.code.value;
console.log(`AnonymousClass1.onClick is called: codoe=${code}`);
this["onClick"](v);
};
}
))
Ok也是可以直接拿下的。
Frida 0x4 主动调用动态函数1
打开发现mainActivity什么都没有,启动看看是在干什么。好的也只是打印了下hello hackers:)
可以看到有个check类,那么我们只要调用check类里的get_flag就行了,easy
Java.perform(
function() {
let Check = Java.use("com.ad2001.frida0x4.Check");
Check.get_flag(1337)
}
)
啊报错了,怎么回事,那是因为get_flag不是static修饰的了,所以我们要这个对象。
1、生成一个对象
Java.perform(
function(){
let Check = Java.use("com.ad2001.frida0x4.Check");
var Check1 = Check.$new()
var str = Check1.get_flag(1337)
console.log('flag'+str)
}
)
2、内存里寻找一个对象
setTimeout(
Java.perform(
function() {
Java.choose("com.ad2001.frida0x4.Check",{
onMatch:function(instance){
console.log("onMatch")
var str = instance.get_flag(1337)
console.log(str)
},
onComplete:function(){
console.log("onComplete")
}
})
}
)
)
当然这是我在内存里生成了许多对象,直接内存里找不到。
Frida 0x5 主动调用动态函数2
显然这里也不是一个static方法。我们依然用4里的两种方法。
1生成一个对象
Java.perform(
function(){
let MainActivity= Java.use("com.ad2001.frida0x5.MainActivity");
var MainActivity2 = MainActivity.$new()
var str = MainActivity2.flag(1337)
console.log('flag'+str)
}
)
显然失败了,由于 Android 的生命周期和线程规则,直接使用 Frida 创建 MainActivity 或任何 Android 组件的实例可能会很棘手。Android 组件(如 Activity 子类)依赖应用程序上下文才能正常运行。在 Frida 中,您可能缺少必要的上下文。Android UI 组件通常需要具有关联 Looper 的特定线程。如果您正在处理 UI 任务,请确保您在具有活动 Looper 的主线程上。活动是更大的 Android 应用程序生命周期的一部分。创建 MainActivity 的实例可能需要应用程序处于特定状态,并且通过 Frida 管理整个生命周期可能并不简单。总之,为 MainActivity 创建实例并不是一个好主意。
2、内存里寻找一个对象
setTimeout(
Java.perform(
function() {
Java.choose("com.ad2001.frida0x5.MainActivity",{
onMatch:function(instance){
console.log("onMatch")
var str = instance.flag(1337)
console.log(str)
},
onComplete:function(){
console.log("onComplete")
}
})
}
)
)
看app里也是成功拿下。
由此可见生成一个对象还是要内存里寻找一个对象取决于实际需求。
Frida 0x6 主动调用动态函数3
可以看到此次传参使用的是一个对象,所以我们要使用类的处理方法,从前面所学到的,我们这里要构造一个checker对象并设置num1和num2,然后主动调用get_flag
Java.perform(
function(){
let Checker = Java.use("com.ad2001.frida0x6.Checker");
var A = Checker.$new()
A.num1.value = 1234
A.num2.value = 4321
Java.choose("com.ad2001.frida0x6.MainActivity",{
onMatch:function(instance){
console.log("onMatch")
var str = instance.get_flag(A)
},
onComplete:function(){
console.log("onComplete")
}
})
}
)
Ok 也是拿下了。
Frida 0x7 hook构造函数
这里我们也是看到程序里调用了flag函数,那么有两种hook方案,一是主动调用,二是hook让程序调用的条件满足。
1、主动调用
Java.perform(
function(){
let Checker = Java.use("com.ad2001.frida0x7.Checker");
var A = Checker.$new(513,513)
Java.choose("com.ad2001.frida0x7.MainActivity",{
onMatch:function(instance){
console.log("onMatch")
var str = instance.flag(A)
},
onComplete:function(){
console.log("onComplete")
}
})
}
)
Ok拿下
2、hook让程序调用的条件满足。这里显然我们可以hook flag函数或者hook checker的构造函数
2.1 hook flag函数
let MainActivity = Java.use("com.ad2001.frida0x7.MainActivity");
MainActivity["flag"].implementation = function (A) {
console.log(`MainActivity.flag is called: A=${A}`);
this["flag"](A);
};
直接拿下
2.2 hook checker的构造函数
setTimeout(
Java.perform(
function() {
let Checker = Java.use("com.ad2001.frida0x7.Checker");
Checker["$init"].implementation = function (a, b) {
console.log(`Checker.$init is called: a=${a}, b=${b}`);
this["$init"](513, 513);
};
}
)
)
也是直接拿下
Frida 0x8 hook native层获取参数值
这里就是提交了个ip然后比较,然后函数在frida0x8.so文件里,过if语句很简单,直接hook cmpstr函数返回值为1即可。但是这样其实不知道flag是多少。
setTimeout(
Java.perform(
function() {
let MainActivity = Java.use("com.ad2001.frida0x8.MainActivity");
MainActivity["cmpstr"].implementation = function (str) {
console.log(`MainActivity.cmpstr is called: str=${str}`);
let result = this["cmpstr"](str);
console.log(`MainActivity.cmpstr result=${result}`);
return 1;
};
}
)
)
那么现在我们要掏出神器ida了
这里找到了反汇编一下
Hook native层,那么我们要用这个方法
Interceptor.attach(targetAddress, {
onEnter: function (args) {
console.log('Entering ' + functionName);
// Modify or log arguments if needed
},
onLeave: function (retval) {
console.log('Leaving ' + functionName);
// Modify or log return value if needed
}
});
这里我们需要知道目标函数的内存地址 targetAddress,获取so的动态地址可以使用以下方法
-
Using the frida API :
Module.enumerateExports()
枚举 -
Using the frida API :
Module.getExportByName()
通过name获取 -
Using the frida API :
Module.findExportByName()
通过name搜索 -
Calculate the offset and
add()
it to theModule.getBaseAddress()
address 基地址+偏移量 -
Using the frida API :
Module.enumerateImports()
枚举
enumerateExports
此 API 枚举指定模块的所有导出(符号)。导出的函数由我们的应用程序在 Java 空间中使用。它需要一个参数,即您要枚举其导出的模块(共享库或可执行文件)的名称。
Module.enumerateExports("libfrida0x8.so")
Module.getExportByName("libfrida0x8.so","Java_com_ad2001_frida0x8_MainActivity_cmpstr")
Module.getBaseAddress("libfrida0x8.so")
偏移量为0864 对应arm架构的0864偏移量。
Module.enumerateImports("libfrida0x8.so")//导入
我们要hook的是strcmp,所以打印下
Module.enumerateImports("libfrida0x8.so")[4]["address"]
现在知道地址我们就可以hook了,不同的架构具有不同的调用约定和不同的寄存器
不建议使用js脚本,因为要在so加载之后才能有address。
var strcmp_adr = Module.enumerateImports("libfrida0x8.so")[4]["address"];
Interceptor.attach(strcmp_adr, {
onEnter: function (args) {
//console.log('Entering ');
var arg0 = Memory.readUtf8String(args[0]);//从内存中读取字符串,参数需要是指针
var arg1 = Memory.readUtf8String(args[1]);
if (arg0.includes("Hello")) {//加if因为用到strcmp的函数太多了
console.log("Hookin the strcmp function");
console.log(arg1);
}
},
onLeave: function (retval) {
}
})
Frida 0x9 hook native层修改返回值
看看链接库有什么
Module.enumerateExports("liba0x9.so")
直接hook修改返回值试试
var check_flag = Module.enumerateExports("liba0x9.so")[0]["address"];
Interceptor.attach(check_flag, {
onEnter: function () {
console.log('Entering ');
},
onLeave: function (retval) {
console.log(retval);
retval.replace(1337)//retval = 0x539 需要用replace去替换
console.log(retval);
}
})
反汇编看看so文件里面怎么写的,可以看到也是直接返回一个值。
Frida 0xA 主动调用native方法
app内显示的为Hello hackers,根据上述代码可以看出来,在so文件中还有一个get_flag函数。
那我们的想法肯定是要调用get_flag函数。但是这个函数并未被加载到java空间内,因此我们要用frida主动调用。
可以看到这个函数传入两个int参数,当a2+a1=3时可以解密flag
使用基地址+偏移量的方式0x1DD60
var baseAddress = Module.getBaseAddress("libfrida0xa.so").add(0x1DD60);
var native_adr = new NativePointer(baseAddress);//
const native_function = new NativeFunction(native_adr, 'void', ['int', 'int']);
native_function(1,2);
Frida 0xB 动态patch
直接看看getFlag
接收一个参数直接返回,有问题,看下汇编代码
这里将地址[ebp+var_10]的值赋值成deadbeef,并和539比较,显然不成立,走了另外的分支。
所以这里需要对这里patch。
直接nop掉 arm中一个nop字节,刚好nop掉b.ne,由于代码就紧接着后面,直接nop两次也是可以的。
使用arm64Write
var address = Module.getBaseAddress("libfrida0xb.so").add(0x15248);
var writer = new Arm64Writer(address);//直接修改内存中的代码
try {
writer.putNop();
writer.putNop();
writer.flush();
}
finally {
writer.dispose();
}
失败,有读写保护
Memory.protect(address, size, protection);
var address = Module.getBaseAddress("libfrida0xb.so").add(0x15248);
Memory.protect(address, 0x100, "rwx");
var writer = new Arm64Writer(address);//直接修改内存中的代码
try {
writer.putNop();
writer.putNop();//nop一次或者两次都可以,只要执行之后的代码就行。
writer.flush();
}
finally {
writer.dispose();
}
也是成功拿下。
2、使用b代替b.ne
var address = Module.getBaseAddress("libfrida0xb.so").add(0x15248);
Memory.protect(address, 0x100, "rwx");
var writer = new Arm64Writer(address);//直接修改内存中的代码
var target = Module.getBaseAddress("libfrida0xb.so").add(0x1524C);
try {
writer.putBImm(target);//放入b指令,接收一个地址作为跳转地址
writer.flush();
}
finally {
writer.dispose();
}
原文始发于微信公众号(安全小圈):通过Frida-Labs 筑牢frida基础
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论