淺談Frida無痕Hook
0x0 前言
前段時間有大佬開源了一套名為wxshadow的無痕hook方案,它主要通過頁表的一些機制來實現,詳細實現推薦去看源碼,非常厲害的思路!!( 原文:「[原创]linux/android 利用shadow内存无痕hook方法」 )
wxshadow是一個提供了無痕patch接口的KPM模塊,而非一個可以開箱即用的hook框架,因此我就在想,能否把它集成到Frida中,給Frida裝上無痕Hook功能?本文簡單聊聊它的可行性。
0x1 一些內核知識
一些個人認為要先了解的內核知識,來自「《linux内核深度解析》余华兵」第三章。
1.1 統一的頁表框架
頁表用來把虛擬頁映射到物理頁,並且存放頁的保護位。
在Linux4.11版本之前,Linux內核把頁表分為四級:
- 頁全局目錄 ( Page Global Directory,PGD )
- 頁上層目錄 ( Page Upper Directory,PUD )
- 頁中間目錄 ( Page Middle Directory,PMD )
- 直接頁表 ( Page Table,PT ,表中的元素稱為Page Table Entry,簡稱PTE )
4.11版本把頁表擴展到五級,在PGD和PUD之間增加了頁四級目錄( Page 4th Directory,P4D )。
可以通過CONFIG_PGTABLE_LEVELS宏來配置頁表的級數,以我的P6為例,是3級頁表。
1 | oriole:/ # zcat /proc/config.gz | grep CONFIG_PGTABLE_LEVELS |
每個進程有獨立的頁表,進程的mm_struct實例成員pgd指向全局目錄,前面四級頁表的表項存放下一級頁表的起始地址,直接頁表的表項存放頁幀號( Page Frame Number,PFN )。
頁幀號 + 頁內編移 = 物理地址

1.2 ARM64處理器頁表
ARM64把頁表稱為轉換表( translation table ),最多4級。
- 頁長度是4KB:使用
4級轉換表,0級轉換表對應頁全局目錄,1級轉換表對應頁上層目錄,如此類推( 沒有P4D)。 48位虛擬地址被分解為如下所示:
- 頁長度是64KB:使用
3級頁表,1級轉換表對應頁全局目錄,2級對應頁中間目錄,3級對應直接頁表。

ARM64把表項稱為描述符( descriptor ),長度為64位。描述符的第0位代表當前描述符是否有效,0表示無效,1表示有效。第1位指定描述符的類型,具體如下:
- 在
0 ~ 2級轉換表中,0表示塊( block )描述符,存放一個內存塊( 即巨型頁 )的起始地址,1表示表( table )描述符,存放下一級轉換表的地址。 - 在第
3級轉換表中,0表示保留,1表示頁描述符。
3級轉換表的頁描述符如下圖所示:

在塊描述符和頁描述符中,內存屬性被拆分成一個高屬性塊和一個低屬性塊,如下圖所示:

wxshadow利用了其中幾個關鍵屬性來實現:
- 第
54位:在EL0中表示UXN( Unprivileged execute-Never ),即不允許EL0執行內核代碼;在其他異常級別,表示XN( execute-Never ),不允許執行。 - 第
6 ~ 7位:AP[2:1]( Data Access Permissions,數據訪問權限 )。在階段1轉換中,AP[2]用來選擇只讀或讀寫,1表示只讀,0表示讀寫;AP[1]用來選擇是否允許EL0訪問,1表示允許,0表示不允許。在非異常級別1和0轉換機制的階段2轉換中,AP[2:1]為00表示不允許訪問,01表示只讀,10表示只寫,11表示讀寫。
1.3 巨型頁
當運行內存需求量較大的應用程序時,如果使用長度為4KB的頁,將會產生較多的TLB未命中和缺頁異常,大大影響應用程序的性能。而如果使用長度為2MB,什至更大的巨型頁,可以大幅改善此問題,這正是內核引入巨型頁( Huge Page )的直接原因。
ARM64支持巨型頁的方式有兩種:
- 通過塊描述符來支持

- 通過頁/塊描述符的連續位來支持

0x2 Frida集成wxshadow的可行性
wxshadow提供了PR_WXSHADOW_PATCH和PR_WXSHADOW_RELEASE,前者可以用於替代frida的patch行為,後者用於清理現場。
1 | // wxshadow_bp.c |
實際嘗試時,遇到了兩個問題。
問題一:會發生livelock的情況。
原因是Frida hook會把目標地址patch成如下形式,用到了arm64的ldr literal技術( 代碼 / 數據在相鄰位置 )。
1 | ldr x16, [pc, #8] |
若PR_WXSHADOW_PATCH時仍然是按這種形式來patch,會導致livelock ( APP卡死 )
因為wxshadow本質上維護了original(r--)( 讀 )和wxshadow(--x)( 執行 )兩種視圖,當wxshadow直接套用到上述patch中,就會發生以下情況:
1 | ; 當前視圖: wxshadow(--x) |
而wxshadow的作用範圍是一整頁,因此對同頁下的ldr literal也要做處理。
問題二:wxshadow無法對libart.so大部份函數進行hook。
這與libart.so在某次更新時改用巨型頁( Huge PMD )有關,而wxshadow是以PTE為單位進行管理,雖然其中有對巨型頁做處理,但測試下來發現是不夠的。
1 | // https://android.googlesource.com/platform/art/+/07ff2833c7 |
解決思路是手動把2MB的PMD分割成512個4KB的PTE,實測可行。
基於wxshadow提供能力,能很好地隱藏Frida本身的一些特徵:
- 默認hook行為:以Frida16.5.9為例,默認會hook
libc.so、libselinux.so和libandroid_runtime.so,用到Java相關API時,會hooklibart.so。 注:之後的某個版本開始還會默認hooklinker - maps特徵:啟動一次frida-server後,maps特徵( rwxp段、N段的
libc.so等 )會永久留下,即使關掉frida-server也無用,必須要重開機才行。 原因是frida-server的那些默認hook行為是對zygote進程做的,而zygote進程是所有APP進程的父進程。
除此之外,Frida還有memfd、注入線程等特徵,可以在內核層做繞過。
總結:Frida集成wxshadow是完全可行的,遇到的各種問題基本都有解法。
0x3 測試
兼容性和穩定性如何?用一些樣本來測測看。
3.1 CrackProof
以前分析過這個保護,那時候還不知道它的名字叫CrackProof,個人認為是個很強的保護 ( 分析文章:https://bbs.kanxue.com/thread-286746.htm )
遊戲邏輯在libpad.so裡,之前分析的時候這個so似乎還沒有加密,現在終於加密了,但也是直接dump就行的整體加密。
嘗試實現秒殺功能,對於這種非常規遊戲引擎開發的遊戲,讓AI來找簡直再合適不過:
1 | 使用ida-pro-mcp, 分析"玩家攻擊" 相關的邏輯在哪? |
然後用魔改的Frida對AI給出的地址進行hook,成功實現秒殺。

運行得也挺穩定,未曾出現過閃退的問題。
3.2 nProtect
之前也分析過,這次用另一個樣本來測 ( https://bbs.kanxue.com/thread-288477.htm )
記得NP裡是有自定義linker保護的,沒想到直接dump libil2cpp.so也行。把gm文件也dump出來後,直接用Il2cppDumper就能把SDK給dump出來。
同樣嘗試實現秒殺功能,同樣交給AI去分析,結合ida和dump.cs,也是很簡單就找到對應的邏輯。

魔改Frida spawn這個APP時,有機會spawn失敗( Frida閃退 / APP卡住,並非被檢測到 ),但機率比較低,屬於可接受範圍之內。
小結:除此之外還測試了幾個Appdome保護的樣本,雖然都有機會發生上述spawn失敗的情況,但基本不影響使用,說明wxshadow已經是個非常成熟的方案。
( 如果有其他檢測強的樣本,歡迎分享給我看看^^ )
0x4 結語
一開始選用了17.9.3這個版本來進行魔改,改到後面發現會莫名其妙地崩潰,一度讓我以為是改崩了,但後來用原版的17.9.3去測試也一樣崩,說明是這個版本有問題???最後還是用回了16.5.9這個版本,以前一直用的這個版本,個人認為是比較穩定的一個版本。
對Frida魔改集成wxshadow的所有工作,可以完全交給AI來完成。在AI的幫助下,試錯成本低了很多,只要有思路,AI總能給你一個滿意的答覆,或許時代真的變了吧。
( 最近在看工作機會,有大佬公司缺人的可以私聊 / vx:ngiokweng )
/image61.png)
/image1.png)
/image.png)
/image.png)



/image3.png)
