如需轉(zhuǎn)載請(qǐng)注明出處 by SimonKoh
0x01 前言
關(guān)于android的hook以前一直用的xposed來(lái)hook java層的函數(shù)影涉,對(duì)于so層則利用adbi井氢,但是不知道為什么adbi給我的體驗(yàn)并不是很好蔬顾,剛好前段時(shí)間了解到frida框架支持android娃圆、ios褪储、linux疗韵、windows、macos李命,而且在android設(shè)備上可以同時(shí)hook java登淘、native十分方便,最重要的一點(diǎn)是不需要重啟手機(jī)封字,于是就研究了一下
0x02 搭建環(huán)境
操作系統(tǒng):windwos7
移動(dòng)設(shè)備:Nexus 4 (4.4.4)
首先你需要一個(gè)android手機(jī)形帮,建議使用google系,這樣會(huì)省去很多麻煩周叮,而且root的話也十分的方便,只需要去SuperSU下載卡刷包或者apk刷個(gè)root進(jìn)去便可界斜。說(shuō)回來(lái)仿耽,frida是python的一個(gè)模塊,所以使用frida的話還需要一些python的基礎(chǔ)各薇,一舉兩得..順便入了Python的門项贺,發(fā)現(xiàn)python是真他娘的好用 : D
安裝frida模塊
一定要確保你的windows有安裝python(2、3的版本我都安裝了)峭判,在cmd運(yùn)行下面命令安裝frida模塊:
pip install frida
如果你同時(shí)有兩個(gè)python版本开缎,你可以使用pip2或者pip3來(lái)代替pip
下載frida-server導(dǎo)入手機(jī)并運(yùn)行
點(diǎn)擊下載frida-server-10.6.28-android-arm.xz,下載解壓后用如下命令把frida-server-10.6.28-android-arm推送到手機(jī)上
adb push frida-server-10.6.28-android-arm /data/local/tmp/frida-server
更改權(quán)限并運(yùn)行
adb shell
su
cd /data/local/tmp
chmod 777 frida-server
./frida-server
測(cè)試frida能否成功交互
新開(kāi)一個(gè)cmd林螃,并轉(zhuǎn)發(fā)端口(好像不轉(zhuǎn)發(fā)也是可以的奕删,我后來(lái)用的話都沒(méi)有轉(zhuǎn)發(fā)端口)
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
好了,現(xiàn)在我們測(cè)試一下frida是否能成功交互疗认,在cmd中輸入如下命令:
frida-ps -U
此時(shí)可以看到frida可以成功交互了完残。
0x03 研究frida
利用命令行工具h(yuǎn)ook libc.so的open()函數(shù)
首先我們知道frida是python的一個(gè)模塊伏钠,那么這樣我們當(dāng)然可以通過(guò)寫(xiě)python腳本import frida來(lái)實(shí)現(xiàn)對(duì)frida的利用,此外frida同時(shí)會(huì)提供幾個(gè)命令行工具(工具存放在python/Scripts目錄下面谨设,所以你可以添加到系統(tǒng)環(huán)境變量方便使用)熟掂,例如frida,可以通過(guò)
frida -help
查看使用幫助:
此外還有frida-ps扎拣,還有frida-trace赴肚、與frida-dicover,官網(wǎng)上的資料也少二蓝,我大概看了看好像也沒(méi)啥好用的...
繼續(xù)說(shuō)回frida這個(gè)命令行工具誉券,在上圖的幫助信息中我們看到 "-U" 參數(shù)代表我們連接的是遠(yuǎn)程USB server,同理你也可以使用其他參數(shù)來(lái)連接侣夷,"-f "參數(shù)則表示在手機(jī)端啟動(dòng)一個(gè)你指定的android程序横朋,那個(gè)FILE則表示應(yīng)用的包名,通常"-f"這個(gè)參數(shù)配合"--no-pause"參數(shù)來(lái)使用百拓,因?yàn)榭赡懿蛔屵M(jìn)程恢復(fù)的話可能會(huì)有奇怪的問(wèn)題琴锭,"-p" 與"-n"命令分別表示attach到進(jìn)程的名字或者pid,"-l"參數(shù)則是代表需要注入的javascript腳本衙传,而這個(gè)javascript的腳本就是我們所寫(xiě)的hook代碼决帖,完成函數(shù)的hook,內(nèi)存的dump等一系列功能蓖捶,所以順便又可以學(xué)一手node.js豈不美滋滋....
當(dāng)我們使用frida這個(gè)命令行工具成功attach到目標(biāo)進(jìn)程的時(shí)候地回,frida會(huì)給我們返回一個(gè)Frida CLI,說(shuō)明白點(diǎn)就是一個(gè)交互窗口俊鱼,下面我們就能看到刻像。
好了,下面我們就利用frida命令行工具來(lái)hook一下chrome瀏覽器中l(wèi)ibc.so的open函數(shù)
frida -U -f com.android.chrome --no-pause
此次我用的是"-f"參數(shù)并闲,也就表示细睡,我需要重新啟動(dòng)這個(gè)chrome瀏覽器,并且attach上去帝火,當(dāng)然如果你僅僅只想attach到正在運(yùn)行的應(yīng)用程序的某一個(gè)進(jìn)程溜徙,你可以用"-p"參數(shù),那么你肯定好奇了犀填,你不是說(shuō)要hook open函數(shù)嗎蠢壹?? 你的代碼呢九巡?图贸?逗大家玩呢?? 哈哈求妹,很好你發(fā)現(xiàn)了問(wèn)題乏盐,如果我們只是這樣啟動(dòng)程序肯定是不會(huì)hook open函數(shù)的,還記得我剛才說(shuō)的javascript代碼嗎制恍? 我們hook的代碼可都在那個(gè)里面父能,你可以在剛才的命令中加"-l"參數(shù)指定你的js hook代碼,load到目標(biāo)進(jìn)程净神。
很好何吝,目前我們就有這樣一個(gè)交互窗口了,我們剛才忘了使用"-l"參數(shù)鹃唯,不過(guò)不要緊爱榕,我們可以在交互窗口中用"%load"命令來(lái)指定需要加載的js代碼(如果你需要在程序啟動(dòng)的時(shí)候就hook程序的某個(gè)函數(shù),那么我不希望你忘掉用"-l"參數(shù) :D )坡慌,js代碼:
Interceptor.attach(Module.findExportByName("libc.so" , "open"), {
onEnter: function(args) {
log("open() called!")
},
onLeave:function(retval){
}
});
有一點(diǎn)要補(bǔ)充下黔酥,我看到國(guó)外有位大佬說(shuō)要在js代碼外面包裹個(gè)setImmediate();好像會(huì)避免一些超時(shí)的問(wèn)題,不過(guò)我目前還沒(méi)發(fā)現(xiàn)有什么問(wèn)題洪橘,你喜歡的話可以加上:D
setImmediate(function() {
Interceptor.attach(Module.findExportByName("libc.so" , "open"), {
onEnter: function(args) {
log("open() called!")
},
onLeave:function(retval){
}
});
});
將代碼load進(jìn)去跪者!
順手點(diǎn)幾下屏幕,發(fā)現(xiàn)這個(gè)open函數(shù)已經(jīng)hook到了熄求,當(dāng)然這個(gè)只是CLI的很小一部分功能渣玲,它支持命令補(bǔ)全,具體詳細(xì)的用法或命令可以去看官網(wǎng)的JavaScript API
利用python frida模塊hook libc.so的open()函數(shù)
前面說(shuō)的是frida提供的命令行工具弟晚,那么下面我們就要自己寫(xiě)python代碼來(lái)利用frida模塊了忘衍,我們還是hook chrome的open函數(shù)(open函數(shù):我招你惹你了? : / )卿城,python代碼如下:
# hook chrome進(jìn)程的libc.so中的函數(shù)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()
效果如下:
ok枚钓,是不是很干脆? 我們來(lái)看一下代碼:
device = frida.get_usb_device()
表示連接usb設(shè)備瑟押,我們?cè)趇python看一下這個(gè)device是個(gè)啥:
好像是一個(gè)類對(duì)象吧搀捷,我最近才接觸python,很多不是很懂... 繼續(xù)tab一下看看device都有什么屬性:
在tab過(guò)后我們發(fā)現(xiàn)有如上這么多屬性勉耀,而上面我們代碼中用到的spawn()方法,它的參數(shù)是python列表[“com.android.chrome”]蹋偏,然后返回值傳遞給pid 這樣我們便可以啟動(dòng)chrome這個(gè)應(yīng)用:
可以看到返回值是int類型的便斥,目前我們發(fā)現(xiàn)程序還并沒(méi)有啟動(dòng),main thread還處于阻塞狀態(tài)威始,而我們通過(guò)
device.resume(pid)便可以讓?xiě)?yīng)用恢復(fù)運(yùn)行枢纠,不過(guò)在此之前,我們需要通過(guò)device.attach(pid)方法獲取session對(duì)象來(lái)附加到目標(biāo)進(jìn)程中黎棠,我們看一下session都有什么屬性:
這些屬性大家可以自己嘗試下镰绎,我們通過(guò)script = session.create_script(scr)來(lái)創(chuàng)建js腳本畴栖,scr就是我們需要load到目標(biāo)進(jìn)程中的js代碼吗讶,創(chuàng)建腳本后返回給script
可以看到script是frida.core.Script的一個(gè)對(duì)象,大家有能力的話可以去閱讀一下源碼膜毁,script包含的屬性如下:
在執(zhí)行script.on("message" , on_message)與script.load()后我們就成功把js代碼注入到com.android.chrome進(jìn)程中了瘟滨,如果進(jìn)程調(diào)用open函數(shù)室奏,就會(huì)通過(guò)js代碼中的send函數(shù)發(fā)回message胧沫,我們這回直接打印message:
可以看到send傳回的message在python中是字典類型绒怨,其中'payload'字段就是我們send所寫(xiě)的內(nèi)容“open called!”
看回js代碼
Interceptor.attach(Module.findExportByName("libc.so" , "open"), {
onEnter: function(args) {
send("open called!");
},
onLeave:function(retval){
}
});
在進(jìn)程調(diào)用open函數(shù)后send只是去打印"open called!"南蹂,那我不光想打印open函數(shù)有沒(méi)有調(diào)用念恍,我還想看看它的值是什么峰伙,能做到嗎?
當(dāng)然可以 :D策彤,這個(gè)args其實(shí)就包括我們想要的東西店诗,onEnter 表示在函數(shù)調(diào)用之前執(zhí)行的代碼捧弃,onLeave 表示在函數(shù)執(zhí)行后需要執(zhí)行的代碼塔橡,所以這個(gè)retval也就包括了返回值葛家,關(guān)于我們用的Interceptor API癞谒,frida官方的JavaScript API 文檔是這么寫(xiě)的:
Interceptor.attach()第一個(gè)參數(shù)是一個(gè)NativePointer指針,在之前我們用的Module.findExportByName("libc.so" , "open")的返回值枢希,關(guān)于Moudle同樣在文檔中可以找到桌吃,第二個(gè)參數(shù)則就是我們的js代碼塊,我們所要的打印參數(shù)與返回值也就是在這里完成茅诱。
我們完善一下代碼,關(guān)于libc.so的open函數(shù)參數(shù)是這樣定義的瑟俭,int open( const char * pathname, int flags);
那我們就嘗試打印出來(lái)pathname,并把結(jié)果寫(xiě)入到文件中契邀,畢竟總是輸出到cmd會(huì)看的很亂也不好找,修改后的代碼如下:
# hook chrome進(jìn)程的libc.so中的函數(shù)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
細(xì)心的同學(xué)可能會(huì)發(fā)現(xiàn)坯门,我這次沒(méi)有用spawn()函數(shù)來(lái)啟動(dòng)chrome應(yīng)用微饥,因?yàn)槿绻阆雋ook的動(dòng)作如果不是在程序開(kāi)始就進(jìn)行的話古戴,而是可控的動(dòng)作,那么你直接attach到其相應(yīng)的進(jìn)程上就好了简软,這樣可以避免我從程序一開(kāi)始就大量hook open函數(shù),此次我們啟動(dòng)的方式為python open.py 27032 其中第一個(gè)參數(shù)就是pid 指定我們需要hook的進(jìn)程疼蛾,我選擇的是chrome的主進(jìn)程的進(jìn)程號(hào),我們繼續(xù)往下看赠法。
send("open called! args[0]:",Memory.readByteArray(args[0],256));
可以看到args[0]表示open的第一個(gè)參數(shù)砖织,由于我并不知道第一個(gè)參數(shù)的長(zhǎng)度有多長(zhǎng)新锈,所以我用Memory.readByteArray(args[0],256)來(lái)讀取256個(gè)字節(jié)(文件名一般不會(huì)長(zhǎng)過(guò)256個(gè)字節(jié)吧妹笆? :D)晾浴,然后傳遞給send(message[, data])的第二個(gè)參數(shù)data脊凰,關(guān)于send(message[, data])的介紹也在官網(wǎng)的JS API中狸涌,如下截圖:
可見(jiàn)第二個(gè)參數(shù)是需要ArrayBuffer類型的帕胆,而Memory.readByteArray(args[0],256)的返回值剛好是ArrayBuffer類型的般渡,所以直接傳值就可以芙盘,那么有的同學(xué)會(huì)問(wèn)儒老,如果不是ArrayBuffer類型的怎么辦记餐,比如說(shuō)文檔中的hexdump(target[, options])函數(shù)片酝,它的返回值不是ArrayBuffer類型的练湿,那么我們就需要利用下面的函數(shù)來(lái)轉(zhuǎn)換:
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代碼中鞠鲜,然后調(diào)用它便可以解決問(wèn)題。好了霞捡,數(shù)據(jù)通過(guò)send(message[, data])傳遞給python的on_message(message,data)函數(shù),其中第一個(gè)參數(shù)我們前面已經(jīng)介紹過(guò)了碧信,他是一個(gè)python字典類型砰碴,其中的message['payload']存放的就是send(message[, data])的第一個(gè)參數(shù)內(nèi)容,on_message(message,data)第二個(gè)參數(shù)data則是send(message[, data])的第二個(gè)參數(shù)板丽,不過(guò)是以bytes的類型傳遞給python data參數(shù)。所以我們寫(xiě)入文件的話埃碱,如果文件打開(kāi)方式為"ab+"那么直接可以寫(xiě)進(jìn)去,否則如果是"a+"來(lái)打開(kāi)文件的話砚殿,那么需要data.decode()轉(zhuǎn)換為str類型來(lái)寫(xiě)入,那細(xì)心的同學(xué)可能問(wèn)了似炎,你為啥寫(xiě)的data.split(b'\x00')[0]辛萍,首先我打開(kāi)方式是"ab+",所以不需要轉(zhuǎn)換類型贩毕,其次由于我之前不知道args[0]有多長(zhǎng)耳幢,所以我讀取了256個(gè)字節(jié)睛藻,那這256個(gè)字節(jié)肯定不是我全想要的店印,我們知道文件名中肯定不能存在0x00字節(jié)倒慧,而且C語(yǔ)言中字符串是要以0x00'\0'作為截?cái)嗟娜伊拢晕矣?strong>split(b'\x00')[0]分割字符串并且取列表中的第一項(xiàng)輸出到文件炫贤。
到此我們的open函數(shù)就算是hook完了(open函數(shù) :D ),下一期我們?cè)賖ook一下別的東西付秕,敬請(qǐng)期待哦
0x04 參考文章與相關(guān)資源:
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