首页
社区
课程
招聘
[原创] 淺談Frida無痕Hook
发表于: 7小时前 295

[原创] 淺談Frida無痕Hook

7小时前
295

0x0 前言

前段時間有大佬開源了一套名為wxshadow的無痕hook方案,它主要通過頁表的一些機制來實現,詳細實現推薦去看源碼,非常厲害的思路!!( 原文:「[原创]linux/android 利用shadow内存无痕hook方法」 )

wxshadow是一個提供了無痕patch接口的KPM模塊,而非一個可以開箱即用的hook框架,因此我就在想,能否把它集成到Frida中,給Frida裝上無痕Hook功能?本文簡單聊聊它的可行性。

0x1 一些內核知識

一些個人認為要先了解的內核知識,來自「《linux内核深度解析》余华兵」第三章。

1.1 統一的頁表框架

頁表用來把虛擬頁映射到物理頁,並且存放頁的保護位。

在Linux4.11版本之前,Linux內核把頁表分為四級:

  1. 頁全局目錄 ( Page Global Directory,PGD )
  2. 頁上層目錄 ( Page Upper Directory,PUD )
  3. 頁中間目錄 ( Page Middle Directory,PMD )
  4. 直接頁表 ( Page Table,PT ,表中的元素稱為Page Table Entry,簡稱PTE )

4.11版本把頁表擴展到五級,在PGD和PUD之間增加了頁四級目錄( Page 4th Directory,P4D )。

可以通過CONFIG_PGTABLE_LEVELS宏來配置頁表的級數,以我的P6為例,是3級頁表。

oriole:/ # zcat /proc/config.gz | grep CONFIG_PGTABLE_LEVELS
CONFIG_PGTABLE_LEVELS=3

每個進程有獨立的頁表,進程的mm_struct實例成員pgd指向全局目錄,前面四級頁表的表項存放下一級頁表的起始地址,直接頁表的表項存放頁幀號( Page Frame Number,PFN )。

頁幀號 + 頁內編移 = 物理地址

1.2 ARM64處理器頁表

ARM64把頁表稱為轉換表( translation table ),最多4級。

  1. 頁長度是4KB:使用4級轉換表,0級轉換表對應頁全局目錄,1級轉換表對應頁上層目錄,如此類推( 沒有P4D )。

    48位虛擬地址被分解為如下所示:

  2. 頁長度是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表示不允許。在非異常級別10轉換機制的階段2轉換中,AP[2:1]00表示不允許訪問,01表示只讀,10表示只寫,11表示讀寫。

1.3 巨型頁

當運行內存需求量較大的應用程序時,如果使用長度為4KB的頁,將會產生較多的TLB未命中和缺頁異常,大大影響應用程序的性能。而如果使用長度為2MB,什至更大的巨型頁,可以大幅改善此問題,這正是內核引入巨型頁( Huge Page )的直接原因。

ARM64支持巨型頁的方式有兩種:

  1. 通過塊描述符來支持

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

0x2 Frida集成wxshadow的可行性

wxshadow提供了PR_WXSHADOW_PATCHPR_WXSHADOW_RELEASE,前者可以用於替代frida的patch行為,後者用於清理現場。

// wxshadow_bp.c
case PR_WXSHADOW_PATCH:
    pid = (pid_t)arg2;
    mm = resolve_pid_to_mm(pid);
    if (!mm) { args->ret = -3; args->skip_origin = 1; break; }
    ret = wxshadow_do_patch(mm, arg3, (void __user *)arg4, arg5);
    kfunc_mmput(mm);
    args->ret = ret;
    args->skip_origin = 1;
    break;

case PR_WXSHADOW_RELEASE:
    pid = (pid_t)arg2;
    mm = resolve_pid_to_mm(pid);
    if (!mm) { args->ret = -3; args->skip_origin = 1; break; }
    if (arg3 == 0) {
        ret = wxshadow_release_pages_for_mm(mm, "release_all");
    } else {
        ret = wxshadow_do_release(mm, arg3);
    }
    kfunc_mmput(mm);
    args->ret = ret;
    args->skip_origin = 1;
    break;

實際嘗試時,遇到了兩個問題。

問題一:會發生livelock的情況。

原因是Frida hook會把目標地址patch成如下形式,用到了arm64的ldr literal技術( 代碼 / 數據在相鄰位置 )。

ldr x16, [pc, #8]
br  x16
.dword on_enter_trampoline

PR_WXSHADOW_PATCH時仍然是按這種形式來patch,會導致livelock ( APP卡死 )

因為wxshadow本質上維護了original(r--)( 讀 )和wxshadow(--x)( 執行 )兩種視圖,當wxshadow直接套用到上述patch中,就會發生以下情況:

; 當前視圖: wxshadow(--x)
exec: ldr x16, [pc, #8]
; 觸發read fault, 切換到original(r--)
read: on_enter_trampoline
; 讀完後, 回到ldr指令, 觸發exec fault, 切換到wxshadow(--x)
exec: ldr x16, [pc, #8]

; 死循環...

而wxshadow的作用範圍是一整頁,因此對同頁下的ldr literal也要做處理。

問題二:wxshadow無法對libart.so大部份函數進行hook。

這與libart.so在某次更新時改用巨型頁( Huge PMD )有關,而wxshadow是以PTE為單位進行管理,雖然其中有對巨型頁做處理,但測試下來發現是不夠的。

// 066K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6S2L8X3c8J5L8$3W2V1i4K6u0W2k6$3!0G2k6$3I4W2M7$3!0#2M7X3y4W2i4K6u0W2j5$3!0E0i4K6u0r3M7r3I4S2N6r3k6G2M7X3#2Q4x3V1k6S2M7Y4c8Q4x3V1k6Q4x3V1u0Q4x3V1j5H3y4$3k6X3x3U0R3K6x3$3x3%4
Compile libart.so and libart-compiler.so with 2MB section alignment.

Adds the appropriate linker flags for libart and libart-compiler to have
2MB section alignment. This allows the executable segment of these
libraries to be backed by transparent hugepages on supporting systems.

解決思路是手動把2MB的PMD分割成512個4KB的PTE,實測可行。

基於wxshadow提供能力,能很好地隱藏Frida本身的一些特徵:

  1. 默認hook行為:以Frida16.5.9為例,默認會hook libc.solibselinux.solibandroid_runtime.so,用到Java相關API時,會hook libart.so

    注:之後的某個版本開始還會默認hook linker

  2. 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來找簡直再合適不過:

使用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 )


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 3
支持
分享
最新回复 (3)
雪    币: 104
活跃值: (8537)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
kkb
4小时前
0
雪    币: 5
活跃值: (4035)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
意义不大,但是还是支持一下
3小时前
0
雪    币: 7327
活跃值: (6710)
能力值: ( LV12,RANK:280 )
在线值:
发帖
回帖
粉丝
4
wx_范迪塞尔 意义不大,但是还是支持一下
「淺談」
2小时前
0
游客
登录 | 注册 方可回帖
返回