Android安全Frida Native hook汇总

Posted Jouzzy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android安全Frida Native hook汇总相关的知识,希望对你有一定的参考价值。

1. 基础用法

(1)按方法名看参数、bt、返回值、output参数

// 按方法名看参数、bt、返回值
var arg8;
var arg9;
var MtdName = "TssAesGcmDecrypt";
Java.perform(function () 
    Interceptor.attach(Module.findExportByName(SoName, MtdName), 
        onEnter: function (args) 
            send(MtdName + " hooked:");
            send("called from\\\\n" + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\\\\n"));
            console.log("\\\\n");
            send(MtdName + " args:");

            for (var it = 0; it < 9; it++) 
                send(MtdName + " args[" + it + "]=" + args[it]);
                if (args[it] > 0x15000000) 
                
                    var buffer = Memory.readByteArray(args[it],128);
                
                else
                
                    var buffer = args[it];
                
                console.log(buffer);
                console.log("\\\\n");
            

            arg8 = parseInt(args[7]);
            arg9 = parseInt(args[8]);
        ,

        onLeave: function (retval) 
            send(MtdName + " RetVal : " + retval);
            var buffer2 = Memory.readByteArray(retval, 128);
            console.log(buffer2);
            console.log("\\\\n");
            
            send(MtdName + " arg[7] : 0x" + arg8.toString(16));
            var buffer3 = Memory.readByteArray(ptr(arg8), 128);
            console.log(buffer3);
            console.log("\\\\n");

            send(MtdName + " arg[8] : 0x" + arg9.toString(16));
            var buffer3 = Memory.readByteArray(ptr(arg9), 128);
            console.log(buffer3);
            console.log("\\\\n");
            
            send(MtdName + " finished \\\\n");
        
    );
);

(2)打印任意地址的内容

例如,打印返回值(地址)附近的内容

        onLeave: function (retval) 
            send("RetVal : " + retval);
            var buffer2 = Memory.readByteArray(retval, 128);
            console.log(buffer2);
            console.log("\\\\n");

            var tmp = parseInt(retval)+1032;
            send("RetVal + 1032: 0x" + tmp.toString(16));
            var buffer2 = Memory.readByteArray(ptr(tmp), 128);
            console.log(buffer2);
            console.log("\\\\n");

            var tmp = parseInt(retval)+1232;
            send("RetVal + 1232: 0x" + tmp.toString(16));
            var buffer2 = Memory.readByteArray(ptr(tmp), 128);
            console.log(buffer2);
            console.log("\\\\n");

            send(MtdName + " finished \\\\n");
        

输出:

[*] RetVal : 0x730eb32c00
           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  c2 34 2a bc bd ad 38 41 14 1e 72 16 87 9c f1 12  .4*...8A..r.....
00000010  1e 43 9b 50 de 6a 1a dc 22 c4 b8 82 d1 cc 3d 0b  .C.P.j..".....=.
00000020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000040  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000050  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000060  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000070  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................


[*] RetVal + 1032: 730eb33008
           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  96 ce b4 c4 9b b3 f4 f8 06 e4 91 13 00 00 00 00  ................
00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000040  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000050  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000060  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000070  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................


[*] RetVal + 1232: 730eb330d0
           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  a6 70 89 84 62 e4 52 bf 8b 77 f5 b3 30 f9 c6 24  .p..b.R..w..0..$
00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000040  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000050  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000060  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000070  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................


[*] ParseGcmKey finished

(3)定位虚表函数偏移

//定位虚表函数偏移
Java.perform(function()
    var Offset=0x1516C;//BLR             X8
    Interceptor.attach(Module.findBaseAddress(SoName).add(ptr(Offset)),
        onEnter: function(args) 
            console.log("\\\\n" + Offset + " - x8: " +  this.context.x8);
            console.log("\\\\n" + "base : " + Module.findBaseAddress(SoName));
            var funcOff = parseInt(this.context.x8,16)-parseInt(Module.findBaseAddress(SoName),16);
            console.log("\\\\n" + "funcOffset : " + funcOff.toString(16) );
        ,
        onLeave: function(retval)
            console.log(Offset +" finished \\\\n");
        
    );
);

输出:

[*] Running hooking

86380 - x8: 0x7290b5c418

base : 0x7290b43000

funcOffset : 19418

ida中按g,跳转到相应函数的位置,定位到函数:

(4)判断地址是否能hook到

Java.perform(function()
    var Offset=0xF86C;//LDR             X24, [X8,#8]
    Interceptor.attach(Module.findBaseAddress(SoName).add(ptr(Offset)),
        onEnter: function(args) 
            console.log("\\\\n" + Offset.toString(16) + " hooked ");
        ,
        onLeave: function(retval)
            console.log(Offset +" finished \\\\n");
        
    );
);

(5)从地址读取ByteArray、String

var buffer = Memory.readByteArray(args[it],128);
var buffer = Memory.readCString(args[it]);
for (var it = 0; it < 2; it++) 

    send(MtdName4 + " args[" + it + "]=" + args[it]);
    if (args[it] > 0x15000000) 
    
        if (it == 1)
        
            var buffer = Memory.readByteArray(args[it],128);
        
        else
        
            var buffer = Memory.readCString(args[it]);
        
    
    else
    
        var buffer = args[it];
    
    console.log(buffer);
    console.log("\\\\n");

2. 进阶用法

(1)枚举so中的符号

function hook_libart() 
    var proc = Process.findModuleByName("libart.so");
    var symbols = proc.enumerateSymbols();     //枚举模块的符号

    var addr_GetStringUTFChars = null;
    var addr_FindClass = null;
    var addr_GetStaticFieldID = null;
    var addr_SetStaticIntField = null;

    for (var i = 0; i < symbols.length; i++) 
        var name = symbols[i].name;
        if (name.indexOf("art") >= 0) 
            if ((name.indexOf("CheckJNI") == -1) && (name.indexOf("JNI") >= 0)) 
                if (name.indexOf("GetStringUTFChars") >= 0) 
                    console.log(name);
                    addr_GetStringUTFChars = symbols[i].address;
                 else if (name.indexOf("FindClass") >= 0) 
                    console.log(name);
                    addr_FindClass = symbols[i].address;
                 else if (name.indexOf("GetStaticFieldID") >= 0) 
                    console.log(name);
                    addr_GetStaticFieldID = symbols[i].address;
                 else if (name.indexOf("SetStaticIntField") >= 0) 
                    console.log(name);
                    addr_SetStaticIntField = symbols[i].address;
                
            
        
    

(2)把C函数定义为NativeFunction来写文件

function write_reg_dat2() 

    //把C函数定义为NativeFunction来写文件
    var addr_fopen = Module.findExportByName("libc.so", "fopen");
    var addr_fputs = Module.findExportByName("libc.so", "fputs");
    var addr_fclose = Module.findExportByName("libc.so", "fclose"作者:H01mes撰写的这篇关于frida框架hook native函数的文章很不错,值得推荐和学习,也感谢原作者。

0x01 前言

关于android的hook以前一直用的xposed来hook java层的函数,对于so层则利用adbi,但是不知道为什么adbi给我的体验并不是很好,刚好前段时间了解到frida框架支持android、ios、linux、windows、macos,而且在android设备上可以同时hook java、native十分方便,最重要的一点是不需要重启手机,于是就研究了一下

0x02 搭建环境

操作系统:windwos7
移动设备:Nexus 4 (4.4.4)

首先你需要一个android手机,建议使用google系,这样会省去很多麻烦,而且root的话也十分的方便,只需要去SuperSU下载卡刷包或者apk刷个root进去便可。说回来,frida是python的一个模块,所以使用frida的话还需要一些python的基础,一举两得..顺便入了Python的门,发现python是真他娘的好用 : D

安装frida模块

一定要确保你的windows有安装python(2、3的版本我都安装了),在cmd运行下面命令安装frida模块:

pip install frida

如果你同时有两个python版本,你可以使用pip2或者pip3来代替pip

下载frida-server导入手机并运行

点击下载frida-server-10.6.28-android-arm.xz,下载解压后用如下命令把frida-server-10.6.28-android-arm推送到手机上

adb push frida-server-10.6.28-android-arm /data/local/tmp/frida-server

更改权限并运行

adb shell
su
cd /data/local/tmp
chmod 777 frida-server
./frida-server

测试frida能否成功交互

新开一个cmd,并转发端口(好像不转发也是可以的,我后来用的话都没有转发端口)

adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

好了,现在我们测试一下frida是否能成功交互,在cmd中输入如下命令:

frida-ps -U

测试交互

此时可以看到frida可以成功交互了。

0x03 研究frida

利用命令行工具hook libc.so的open()函数

首先我们知道frida是python的一个模块,那么这样我们当然可以通过写python脚本import frida来实现对frida的利用,此外frida同时会提供几个命令行工具(工具存放在python/Scripts目录下面,所以你可以添加到系统环境变量方便使用),例如frida,可以通过

frida -help

查看使用帮助:

frida -help

此外还有frida-ps,还有frida-trace、与frida-dicover,官网上的资料也少,我大概看了看好像也没啥好用的...

继续说回frida这个命令行工具,在上图的帮助信息中我们看到:

"-U" 参数代表我们连接的是远程USB server,同理你也可以使用其他参数来连接,

"-f "参数则表示在手机端启动一个你指定的android程序,那个FILE则表示应用的包名,通常"-f"这个参数配合"--no-pause"参数来使用,因为可能不让进程恢复的话可能会有奇怪的问题,

"-p""-n"命令分别表示attach到进程的名字或者pid,

"-l"参数则是代表需要注入的javascript脚本,而这个javascript的脚本就是我们所写的hook代码,完成函数的hook,内存的dump等一系列功能,所以顺便又可以学一手node.js岂不美滋滋....

当我们使用frida这个命令行工具成功attach到目标进程的时候,frida会给我们返回一个Frida CLI,说明白点就是一个交互窗口,下面我们就能看到。

好了,下面我们就利用frida命令行工具来hook一下chrome浏览器中libc.so的open函数

frida -U -f com.android.chrome --no-pause

此次我用的是"-f"参数,也就表示,我需要重新启动这个chrome浏览器,并且attach上去,当然如果你仅仅只想attach到正在运行的应用程序的某一个进程,你可以用"-p"参数,那么你肯定好奇了,你不是说要hook open函数吗?? 你的代码呢??逗大家玩呢?? 哈哈,很好你发现了问题,如果我们只是这样启动程序肯定是不会hook open函数的,还记得我刚才说的javascript代码吗? 我们hook的代码可都在那个里面,你可以在刚才的命令中加"-l"参数指定你的js hook代码,load到目标进程。

Frida CLI

很好,目前我们就有这样一个交互窗口了,我们刚才忘了使用"-l"参数,不过不要紧,我们可以在交互窗口中用"%load"命令来指定需要加载的js代码(如果你需要在程序启动的时候就hook程序的某个函数,那么我不希望你忘掉用"-l"参数 :D ),js代码:

Interceptor.attach(Module.findExportByName("libc.so" , "open"), 
    onEnter: function(args) 
        log("open() called!")
    ,
    onLeave:function(retval)
    
    
);

有一点要补充下,我看到国外有位大佬说要在js代码外面包裹个setImmediate();好像会避免一些超时的问题,不过我目前还没发现有什么问题,你喜欢的话可以加上:D

setImmediate(function() 
Interceptor.attach(Module.findExportByName("libc.so" , "open"), 
    onEnter: function(args) 
        log("open() called!")
    ,
    onLeave:function(retval)
    
    
);
);

将代码load进去!

frida CLI load js脚本 并 hook open函数

顺手点几下屏幕,发现这个open函数已经hook到了,当然这个只是CLI的很小一部分功能,它支持命令补全,具体详细的用法或命令可以去看官网的JavaScript API

利用python frida模块hook libc.so的open()函数

前面说的是frida提供的命令行工具,那么下面我们就要自己写python代码来利用frida模块了,我们还是hook chrome的open函数(open函数:我招你惹你了? : / ),python代码如下:

# hook chrome进程的libc.so中的函数open
import frida
import sys

device = frida.get_usb_device()
pid = device.spawn(["com.android.chrome"])
session = device.attach(pid)
device.resume(pid)

scr = """
Interceptor.attach(Module.findExportByName("libc.so" , "open"), 
    onEnter: function(args) 
        send("open called!");
    ,
    onLeave:function(retval)
    
    
);
"""
def on_message(message ,data):
    print(message['payload'])


script = session.create_script(scr)
script.on("message" , on_message)
script.load()
sys.stdin.read()

效果如下:

python hook open函数

ok,是不是很干脆? 我们来看一下代码:

device = frida.get_usb_device() 表示连接usb设备,我们在ipython看一下这个device是个啥:

device

好像是一个类对象吧,我最近才接触python,很多不是很懂... 继续tab一下看看device都有什么属性:

device属性

在tab过后我们发现有如上这么多属性,而上面我们代码中用到的spawn()方法,它的参数是python列表[“com.android.chrome”],然后返回值传递给pid 这样我们便可以启动chrome这个应用:


可以看到返回值是int类型的,目前我们发现程序还并没有启动,main thread还处于阻塞状态,而我们通过
device.resume(pid)便可以让应用恢复运行,不过在此之前,我们需要通过device.attach(pid)方法获取session对象来附加到目标进程中,我们看一下session都有什么属性:

这些属性大家可以自己尝试下,我们通过 script = session.create_script(scr)来创建js脚本,scr就是我们需要load到目标进程中的js代码,创建脚本后返回给script
script 可以看到script是frida.core.Script的一个对象,大家有能力的话可以去阅读一下源码,script包含的属性如下: script属性

在执行script.on("message" , on_message)script.load()后我们就成功把js代码注入到com.android.chrome进程中了,如果进程调用open函数,就会通过js代码中的send函数发回message,我们这回直接打印message:

message

可以看到send传回的message在python中是字典类型,其中'payload'字段就是我们send所写的内容“open called!”

看回js代码

Interceptor.attach(Module.findExportByName("libc.so" , "open"), 
    onEnter: function(args) 
        send("open called!");
    ,
    onLeave:function(retval)
    
    
);

在进程调用open函数后send只是去打印"open called!",那我不光想打印open函数有没有调用,我还想看看它的值是什么,能做到吗?
当然可以 :D,这个args其实就包括我们想要的东西,onEnter 表示在函数调用之前执行的代码,onLeave 表示在函数执行后需要执行的代码,所以这个retval也就包括了返回值,关于我们用的Interceptor API,frida官方的JavaScript API 文档是这么写的:

Interceptor

Interceptor.attach()第一个参数是一个NativePointer指针,在之前我们用的Module.findExportByName("libc.so" , "open")的返回值,关于Moudle同样在文档中可以找到,第二个参数则就是我们的js代码块,我们所要的打印参数与返回值也就是在这里完成。

Module.findExportByName

我们完善一下代码,关于libc.so的open函数参数是这样定义的,int open( const char * pathname, int flags);
那我们就尝试打印出来pathname,并把结果写入到文件中,毕竟总是输出到cmd会看的很乱也不好找,修改后的代码如下:

# hook chrome进程的libc.so中的函数open
import frida
import sys
import io

device = frida.get_usb_device()

session = device.attach(int(sys.argv[1]))

scr = """
setImmediate(function() 
Interceptor.attach(Module.findExportByName("libc.so" , "open"), 
    onEnter: function(args) 

        send("open called! args[0]:",Memory.readByteArray(args[0],256));
    ,
    onLeave:function(retval)
    
    
);

);
"""

def on_message(message ,data):
    file_object=open("d:\\\\log.txt",'ab+')
    file_object.write(message['payload'].encode())
    file_object.write(data.split(b'\\x00')[0])
    file_object.write('\\n'.encode())
    file_object.close()


script = session.create_script(scr)
script.on("message" , on_message)
script.load()
sys.stdin.read()

log如下:

open called! args[0]:/data/data/com.android.chrome/shared_prefs/com.android.chrome_preferences.xml
open called! args[0]:/data/data/com.android.chrome/cache/Cache/3e0cddc5ac3f04af_0
open called! args[0]:/data/data/com.android.chrome/cache/.com.google.Chrome.XETk74
open called! args[0]:/data/data/com.android.chrome/cache/.com.google.Chrome.XETk74
open called! args[0]:/data/data/com.android.chrome/shared_prefs/com.android.chrome_preferences.xml
open called! args[0]:/dev/ashmem
open called! args[0]:/data/data/com.android.chrome/app_tabs/0/tab_state0
open called! args[0]:/data/data/com.android.chrome/cache/Cache/c94e1f14f3976339_0
open called! args[0]:/data/data/com.android.chrome/cache/Cache/3fc24daa4a4425e7_0
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/data/data/com.android.chrome/cache/.com.google.Chrome.E0rQGy
open called! args[0]:/data/data/com.android.chrome/cache/.com.google.Chrome.E0rQGy
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/dev/ashmem
open called! args[0]:/data/data/com.android.chrome/app_textures/32
open called! args[0]:/data/data/com.android.chrome/cache/Cache/e1c32483ea7ba39f_0
open called! args[0]:/dev/ashmem

细心的同学可能会发现,我这次没有用spawn()函数来启动chrome应用,因为如果你想hook的动作如果不是在程序开始就进行的话,而是可控的动作,那么你直接attach到其相应的进程上就好了,这样可以避免我从程序一开始就大量hook open函数,此次我们启动的方式为python open.py 27032 其中第一个参数就是pid 指定我们需要hook的进程,我选择的是chrome的主进程的进程号,我们继续往下看。

send("open called! args[0]:",Memory.readByteArray(args[0],256));

可以看到args[0]表示open的第一个参数,由于我并不知道第一个参数的长度有多长,所以我用Memory.readByteArray(args[0],256)来读取256个字节(文件名一般不会长过256个字节吧? :D),然后传递给send(message[, data])的第二个参数data,关于send(message[, data])的介绍也在官网的JS API中,如下截图:


可见第二个参数是需要ArrayBuffer类型的,而Memory.readByteArray(args[0],256)的返回值刚好是ArrayBuffer类型的,所以直接传值就可以,那么有的同学会问,如果不是ArrayBuffer类型的怎么办,比如说文档中的hexdump(target[, options])函数,它的返回值不是ArrayBuffer类型的,那么我们就需要利用下面的函数来转换:

function str2ab(str) 
            var buf = new ArrayBuffer(str.length); // 1 bytes for each char
            var bufView = new Uint8Array(buf);
            for (var i=0, strLen=str.length; i < strLen; i++) 
                bufView[i] = str.charCodeAt(i);
                
            return buf;
        

将上面这段代码放入hook代码中,然后调用它便可以解决问题。好了,数据通过send(message[, data])传递给python的on_message(message,data)函数,其中第一个参数我们前面已经介绍过了,他是一个python字典类型,其中的message['payload']存放的就是send(message[, data])的第一个参数内容,on_message(message,data)第二个参数data则是send(message[, data])的第二个参数,不过是以bytes的类型传递给python data参数。所以我们写入文件的话,如果文件打开方式为"ab+"那么直接可以写进去,否则如果是"a+"来打开文件的话,那么需要data.decode()转换为str类型来写入,那细心的同学可能问了,你为啥写的data.split(b'\\x00')[0],首先我打开方式是"ab+",所以不需要转换类型,其次由于我之前不知道args[0]有多长,所以我读取了256个字节,那这256个字节肯定不是我全想要的,我们知道文件名中肯定不能存在0x00字节,而且C语言中字符串是要以0x00'\\0'作为截断的,所以我用split(b'\\x00')[0]分割字符串并且取列表中的第一项输出到文件。

到此我们的open函数就算是hook完了(open函数 :D ),下一期我们再hook一下别的东西,敬请期待哦。


0x04 参考文章与相关资源:

frida-docs
Android逆向之hook框架frida篇
HACKING ANDROID APPS WITH FRIDA I
HACKING ANDROID APPS WITH FRIDA II - CRACKME
HACKING ANDROID APPS WITH FRIDA III - OWASP UNCRACKABLE 2
Android OWASP crackmes: Write-up UnCrackable Level 3
frida - learn by example

作者:H01mes
链接: https://www.jianshu.com/p/b833fba1bffe


说明: 作者H01mes写的这篇基于Frida框架的hook native函数的文章非常不错,整个文章读下来非常有收获,虽然有些地方frida hook框架使用已经更新了,但是通过对这篇文章的阅读,对于frida hook的使用一定更深了一个层次。


以上是关于Android安全Frida Native hook汇总的主要内容,如果未能解决你的问题,请参考以下文章

android逆向工具

Android App安全监测隐私权限工具及自测 图文详解

frida native层 hook的思路技巧

frida native层 hook的思路技巧

andorid jar/库源码解析之frida体验

Android 逆向Frida 框架 ( Frida 2 种运行模式 | Frida 12.7.5 版本相关工具下载地址 | 在 Android 模拟器上运行 Frida 远程服务程序 )