安卓逆向入门之使用frida框架简单Hook native层的函数

640?wx_fmt=png

公众号:640?wx_fmt=jpeg 菜鸟学Python编程 

 作者:悦来客栈的老板

请读者先完成第一篇中的例子再来学习这一篇,初探安卓逆向神器Frida

‍都是比较简单的东西,像我这种初学者先找找逆向的自信吧。

这篇是我的学习笔记,所以文章比较水,大佬还请轻喷。

这篇需要用到 IDA,所以请先安装IDA。

今天的案例主要会用到这里面教程的apk:

https://www.52pojie.cn/thread-313869-1-1.html

帖子比较早,但是用来学习还是有帮助的。

我们来破解他的第二个例子和第三个例子,分别修改入参和返回值,理解frida框该怎么对native层的函数进行简单的Hook。如果想了解更高深的知识,可以参考尼古拉斯_赵四的这篇文章:

https://blog.csdn.net/jiangwei0910410003/article/details/80372118

好了,言归正传,下载他的例在电脑上,并在手机端进行安装,例子链接:

http://pan.baidu.com/s/1i3wzetf

打开软件,按照他的要求,将100显示成其他的值:

640?wx_fmt=png

既然知道是函数在native层定义的直接,将 so文件拖入 IDA进行分析吧。

切换到 Exports面板,看看有哪些JNI函数:

640?wx_fmt=png

选择 第二个红色方框内的函数,双击跟进去:

640?wx_fmt=png

如果我没有猜错的话,这里的 #0x64就是它返回的数字 100吧,继续按F5,看看它的伪C代码:

640?wx_fmt=png

原来就是一个固定的返回值,那就好办了,按照官方的教程,写下frida脚本:

import frida,sys	

	
def on_message(message, data):	
    if message['type'] == 'send':	
        print("[*] {0}".format(message['payload']))	
    else:	
        print(message)	

	
jscode = """	
    var nativePointer = Module.findExportByName("libgg-jni.so","Java_com_ggndktest1_JniGg_getCoin");	
    send("native pointers:" + nativePointer);	
    Interceptor.attach(nativePointer,{	
        onEnter:function(args){	
            send(args);	
          },	
        onLeave:function(retval){	
            send(retval.toInt32());	
            retval.replace(1000000);	
            send(retval.toInt32());	
          }	
        	
    });	
"""	
process = frida.get_usb_device().attach('com.ggndktest1')	
script = process.create_script(jscode)	
script.on('message', on_message)	
print('[*] Running CTF')	
script.load()	
sys.stdin.read()

这段代码的作用将 100 改为了 1000000,先看看效果,再做说明。

手机连接电脑,开启frida服务,然后运行脚本,先点击手机上的返回键,然后再打开软件,结果如下:

640?wx_fmt=png

可以看到,已经修改成功了,再看看电脑端:

640?wx_fmt=png

下面对代码进行一些说明:

怎么查看包名及修改,上篇已经说过了。以后不再赘述,主要关注jscode代码段就好:

    var nativePointer = Module.findExportByName("libgg-jni.so","Java_com_ggndktest1_JniGg_getCoin");

看看官方教程对 Module.findExportByName的解释:

Module.findExportByName(moduleName|null, exportName),Module.getExportByName(moduleName|null, exportName)

returns the absolute address of the export named exportName in moduleName. If the module isn’t known you may pass null instead of its name, but this can be a costly search and should be avoided. In the event that no such module or export could be found, the find-prefixed function returns null whilst the get-prefixed function throws an exception.

这样就比较好理解,第一个参数是so的文件名,第二个参数导出的函数名,可以在IDA中直接复制:

640?wx_fmt=png

再来看看怎么修改返回值:

Interceptor.attach(target, callbacks): intercept calls to function attarget. This is a NativePointer specifying the address of the function you would like to intercept calls to. Note that on 32-bit ARM this address must have its least significant bit set to 0 for ARM functions, and 1 for Thumb functions. Frida takes care of this detail for you if you get the address from a Frida API (for example Module.getExportByName()).

The callbacks argument is an object containing one or more of:

  • onEnter: function (args): callback function given one argumentargs that can be used to read or write arguments as an array of NativePointer objects.

  • onLeave: function (retval): callback function given one argumentretval that is a NativePointer-derived object containing the raw return value. You may call retval.replace(1337) to replace the return value with the integer 1337, or retval.replace(ptr("0x1234")) to replace with a pointer. Note that this object is recycled across onLeave calls, so do not store and use it outside your callback. Make a deep copy if you need to store the contained value, e.g.: ptr(retval.toString()).

onEnter 这里函数传入的是一个数组,保存这传递进来的参数,可读可写。可以理解为 在Hook的那个函数调用之前执行的代码。

onLeave 这里是一个返回值,并可以将其进行修改,修改的方式又两种:

retval.replace(1337)  integer

retval.replace(ptr("0x1234")) pointer

有了官方的说明,我这里直接将100改为100000:

            retval.replace(1000000);

所以能修改成功。这是修改返回值,那怎么修改传入的参数呢?

不急,我们来看他的第三个案例,下载地址:

http://pan.baidu.com/s/1jG22HMY

他的要求:将当前用户类型修改为Gold Vip 用户。

640?wx_fmt=png

即将 Normal User 改为 Gold Vip。

那我们依然对so文件拖入到 IDA中,按照教程,我们需要了解这个函数的逻辑:

640?wx_fmt=png

双击跟进去看看:

640?wx_fmt=png

看汇编代码也能看懂,比较简单,不过是几个比较的指令,分别对 2,3,1进行比较,然后跳转不同的地方。我们还是看看它的伪C代码吧:

640?wx_fmt=png

这什么鬼????请教了 Lilac 大佬后,告诉我需要导入 jni.h头文件,按CTRL + F9进行头文件导入:

640?wx_fmt=png

然后在函数的第一个参数位置单击右键,选择 Convert to struct*:

640?wx_fmt=png

然后再选择_JNIEnv,

640?wx_fmt=png

代码变成了这样:

640?wx_fmt=png

然后我们在NewStringUTF右键,选择Force call type:

640?wx_fmt=png

然后点击确定,就看到了伪C代码:

640?wx_fmt=png

这里,它传递了3个参数,我们看逻辑,得知修改第三个参数就可以改变其逻辑,写下如下frida脚本,并运行:

import frida,sys	

	
def on_message(message, data):	
    if message['type'] == 'send':	
        print("[*] {0}".format(message['payload']))	
    else:	
        print(message)	

	
jscode = """	
    var nativePointer = Module.findExportByName("libgg-jni.so","Java_com_ggndktest1_JniGg_VipLevel");	
    send("native pointers:" + nativePointer);	
    Interceptor.attach(nativePointer,{	
        onEnter:function(args){	
            send(args[2]);	
            args[2]=ptr(1); 	
          },	
          	
        onLeave:function(retval){	
            send(retval)	
          }	
        	
    });	
"""	
process = frida.get_usb_device().attach('com.ggndktest1')	
script = process.create_script(jscode)	
script.on('message', on_message)	
print('[*] Running CTF')	
script.load()	
sys.stdin.read()

代码和案例2的代码差不多,这里就不过多讲解了。看看效果:

640?wx_fmt=png

可以看到,已经变成了 Gold Vip,说明修改已经成功了。

电脑端显示:

640?wx_fmt=png

这里的 0x5就是它原来传递的参数,被我改为了 1,也就将逻辑改变了。

比较简单,这只是一篇学习笔记,大佬们请轻喷。

再次感谢 Lilac 大佬提供的IDA相关的知识,省去了很多时间,果然有个大佬带要少走很多弯路呀。

推荐阅读

Python 爬虫面试题 170 道:2019 版

爬虫必备-JS之Dom操作大全

爬虫工程师教你如何入门Android逆向

把 Android App 逆向分为几步?三步

添加微信[italocxa].回复:加群,加入Python交流群

你好!Frida是一个强大的动态代码注入和调试工具,可以用于hook和修改应用程序的行为。如果你想要hook Native代码,可以使用Frida的JavaScript API来实现。 首先,你需要在设备上安装Frida,并确保设备已经越狱(iOS)或者已经root(Android)。 接下来,你需要编写一个JavaScript脚本来进行hook。在脚本中,你可以使用Frida提供的一些函数来定位和修改Native函数。 例如,下面的代码可以用来hook一个Native函数并修改它的行为: ```javascript // 导入Frida模块 const frida = require('frida'); // 目标进程的名称 const targetProcessName = 'your_target_process_name'; // 要hook函数名称 const targetFunctionName = 'your_target_function_name'; // Frida attach到目标进程 frida.attach(targetProcessName) .then(session => { // 创建一个脚本对象 const script = session.createScript(` // 找到目标函数 const targetFunction = Module.findExportByName(null, "${targetFunctionName}"); // 替换目标函数的实现 Interceptor.replace(targetFunction, new NativeCallback(() => { // 修改函数的行为,这里可以写你想要的逻辑 console.log("Function ${targetFunctionName} hooked!"); // 调用原始函数 const originalFunction = new NativeFunction(targetFunction, 'void', []); originalFunction.call(); // 可以在这里添加你的自定义代码 }, 'void', [])); }); // 加载并运行脚本 script.load() .then(() => { console.log("Script loaded successfully!"); }) .catch(error => { console.log(`Script error: ${error}`); }); }) .catch(error => { console.log(`Attach error: ${error}`); }); ``` 在上面的代码中,我们首先导入了Frida模块,然后指定了目标进程的名称和要hook函数名称。然后我们使用`frida.attach()`函数来连接到目标进程,并创建一个脚本对象。在脚本中,我们使用`Module.findExportByName()`函数来找到目标函数,然后使用`Interceptor.replace()`函数替换目标函数的实现。在替换的实现中,我们可以添加一些自定义的逻辑以修改函数的行为。 最后,我们使用`script.load()`函数加载并运行脚本。如果一切顺利,你应该能看到"Script loaded successfully!"的输出。 这只是一个简单的例子,你可以根据你的需求进行更复杂的hook和修改。希望对你有所帮助!如果有任何问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值