Frida入門學習筆記-hook native中的函數(1)

如需轉載請注明出處 by SimonKoh

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這個應用:


spawn

可以看到返回值是int類型的,目前我們發現程序還并沒有啟動,main thread還處于阻塞狀態,而我們通過
device.resume(pid)便可以讓應用恢復運行,不過在此之前,我們需要通過device.attach(pid)方法獲取session對象來附加到目標進程中,我們看一下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中,如下截圖:

send 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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。