首頁 > 新聞 > 智能 > 正文

        以 SingPass 應(yīng)用為例分析 iOS RASP 應(yīng)用自保護的實現(xiàn)以及繞過方法(上)-天天日報

        2023-04-08 13:15:16來源:ZAKER科技  

        通過在應(yīng)用程序的安裝目錄中搜索一些關(guān)鍵字,我們實際上得到了兩個結(jié)果,它們含有混淆器名稱的信息:

        NuDetectSDK 二進制文件也使用相同的混淆器,但它似乎沒有參與上圖所示的早期越獄檢測。另一方面,SingPass 是應(yīng)用程序的主要二進制文件,我們可以觀察到與威脅檢測相關(guān)的字符串:


        (相關(guān)資料圖)

        混淆器的名稱已被編輯,但不會影響代碼的內(nèi)容。

        不幸的是,二進制文件沒有泄漏其他字符串,這些字符串可以幫助識別應(yīng)用程序檢測越獄設(shè)備的位置和方式,但幸運的是,應(yīng)用程序沒有崩潰。

        如果我們假設(shè)混淆器在運行時解密字符串,則可以嘗試在顯示錯誤消息時轉(zhuǎn)儲 __data 部分的內(nèi)容。在執(zhí)行時,用于檢測越獄設(shè)備的字符串可能已被解碼并清楚地存在于內(nèi)存中。

        1. 我們運行應(yīng)用程序并等待越獄消息;

        2. 我們使用 Frida 附加到 SingPass,并注入一個庫:

        2.1 在內(nèi)存中解析 SingPass 二進制文件;

        2.2 轉(zhuǎn)儲 __data 部分的內(nèi)容;

        2.3 將轉(zhuǎn)儲寫入 iPhone 的 /tmp 目錄;

        一旦數(shù)據(jù)區(qū)被轉(zhuǎn)儲,__data 部分會發(fā)生以下變化:

        轉(zhuǎn)儲前后的 __data 部分

        此外,我們可以觀察到以下字符串,它們似乎與混淆器的 RASP 功能有關(guān):

        與 RASP 功能相關(guān)的字符串

        所有的 EVT_* 字符串都由一個且只有一個我命名為 on_rasp_detection 的函數(shù)引用。這個函數(shù)是應(yīng)用程序開發(fā)者在觸發(fā) RASP 事件時用來執(zhí)行操作的威脅檢測回調(diào)函數(shù)。

        為了更好地理解這些字符串背后的檢查邏輯,讓我們從用于檢測掛鉤函數(shù)的 EVT_CODE_PROLOGUE 開始。

        EVT_CODE_PROLOGUE:掛鉤檢測

        當通過匯編代碼接近 on_rasp_detection 的交叉引用時,我們可以多次發(fā)現(xiàn)這種模式:

        為了檢測給定函數(shù)是否被鉤住,混淆器加載函數(shù)的第一個字節(jié),并將該字節(jié)與值 0xFF 進行比較。乍一看,0xFF 似乎是任意的,但事實并非如此。實際上,常規(guī)函數(shù)以一個序言開始,該序言在堆棧上分配空間,以保存由調(diào)用約定定義的寄存器和函數(shù)所需的堆棧變量。在 AArch64 中,這個分配可以通過兩種方式執(zhí)行:

        這些指令是不相等的,如果偏移量存在,它們可能會導(dǎo)致相同的結(jié)果。在第二種情況下,指令 sub SP、SP、#CST 用以下字節(jié)編碼:

        正如我們所看到的,該指令的編碼從 0xFF 開始。如果不是這樣,那么該函數(shù)要么以不同的堆棧分配序言開始,要么可能以一個掛鉤的蹦床開始。由于應(yīng)用程序的代碼是通過混淆器的編譯器編譯的,因此編譯器能夠區(qū)分這兩種情況,并為正確的函數(shù)的序言插入正確的檢查。

        如果函數(shù)指令的第一個字節(jié)沒有通過檢查,則跳轉(zhuǎn)到紅色基本塊。這個基本塊的目的是觸發(fā)一個用戶定義的回調(diào),它將根據(jù)應(yīng)用程序的設(shè)計和開發(fā)人員的選擇來處理檢測:

        打印錯誤

        應(yīng)用程序崩潰

        破壞內(nèi)部數(shù)據(jù)

        ……

        從上圖中,我們可以觀察到檢測回調(diào)是從位于 #hook_detect_cbk_ptr 的靜態(tài)變量加載的。調(diào)用此檢測回調(diào)時,混淆器會向回調(diào)提供以下信息:

        1. 檢測碼:EVT_CODE_PROLOGUE 為 0x400;

        2. 可能導(dǎo)致應(yīng)用程序崩潰的受攻擊指針;

        現(xiàn)在讓我們仔細看看檢測回調(diào)的整體設(shè)計。

        檢測回調(diào)

        如上一節(jié)所述,當混淆器檢測到篡改時,它會通過調(diào)用存儲在地址的靜態(tài)變量中的檢測回調(diào)來做出反應(yīng):0x10109D760

        通過靜態(tài)分析 hook_detect_cbk,實現(xiàn)似乎破壞了回調(diào)參數(shù)中提供的指針。另一方面,在運行應(yīng)用程序時,我們觀察到越獄檢測消息,而不是應(yīng)用程序崩潰。

        如果我們查看在該地址讀取或?qū)懭氲慕徊嬉茫覀儠玫揭韵轮噶盍斜恚?/p>所以實際上只有一條指令,init_and_check_rasp+01BC,用另一個函數(shù)覆蓋默認的檢測回調(diào):與默認回調(diào)相比:hook_detect_cbk(被覆蓋的函數(shù))相比,hook_detect_cbk_user_def 不會損壞一個會導(dǎo)致應(yīng)用程序崩潰的指針。相反,它調(diào)用 on_rasp_detection 函數(shù),該函數(shù)引用上圖中列出的所有字符串 EVT_CODE_TRACING、EVT_CODE_SYSTEM_LIB 等。

        通過整體查看 init_and_check_rasp 函數(shù),我們可以注意到 X23 寄存器也用于初始化其他靜態(tài)變量:

        X23 寫入指令

        這些內(nèi)存寫入意味著回調(diào) hook_detect_cbk_user_def 用于初始化其他靜態(tài)變量。特別是,這些其他靜態(tài)變量很可能用于其他 RASP 檢查。通過查看這些靜態(tài)變量 #EVT_CODE_TRACING_cbk_ptr、#EVT_ENV_JAILBREAK_cbk_ptr 等的交叉引用,我們可以找到執(zhí)行其他 RASP 檢查的位置以及觸發(fā)它們的條件。

        EVT_CODE_SYSTEM_LIB

        EVT_ENV_DEBUGGEREVT_ENV_JAILBREAK多虧了 #EVT_* 交叉引用,我們可以靜態(tài)地通過使用這些 #EVT_* 變量的所有基本塊,并突出顯示可能觸發(fā) RASP 回調(diào)的底層檢查。在詳細檢查之前,需要注意以下幾點:

        1. 雖然應(yīng)用程序使用了一個商業(yè)混淆器,除了 RASP 之外,還提供了本地代碼混淆,但代碼是輕度混淆的,這使得靜態(tài)匯編代碼分析非常容易。

        2. 應(yīng)用程序為所有 RASP 事件設(shè)置相同的回調(diào)。因此,它簡化了 RASP 繞過和應(yīng)用程序的動態(tài)分析。

        反調(diào)試

        SingPass 使用的混淆器版本實現(xiàn)了兩種調(diào)試檢查。首先,它檢查父進程 id ( ppid ) 是否與 /sbin/launchd 相同,后者應(yīng)該為 1。

        getppid 通過函數(shù)或系統(tǒng)調(diào)用調(diào)用。

        如果不是這種情況,它會觸發(fā) EVT_ENV_DEBUGGER 事件。第二個檢查基于用于訪問 extern_proc.p_flag 值的 sysctl。如果此標志包含 P_TRACED 值,則 RASP 例程會觸發(fā) EVT_ENV_DEBUGGER 事件。

        在 SingPass 二進制中,我們可以在以下地址范圍內(nèi)找到這兩個檢查的實例:

        越獄檢測

        對于大多數(shù)越獄檢測,混淆器會通過檢查設(shè)備上是否存在(或不存在)某些文件來嘗試檢測設(shè)備是否已越獄。

        借助以下幫助程序,可以使用系統(tǒng)調(diào)用或常規(guī)函數(shù)檢查文件或目錄:

        如上所述,我提到 __data 部分的轉(zhuǎn)儲顯示與越獄檢測相關(guān)的字符串,但轉(zhuǎn)儲并未顯示混淆器使用的所有字符串。

        通過仔細研究字符串編碼機制,可以發(fā)現(xiàn)有些字符串是在臨時變量中即時解碼的。我將在本文的第二部分解釋字符串編碼機制,這樣,我們可以通過在 fopen、utimes 等函數(shù)上設(shè)置鉤子,并在這些調(diào)用之后立即轉(zhuǎn)儲 __data 部分來揭示字符串。然后,我們可以遍歷不同的轉(zhuǎn)儲,查看是否出現(xiàn)了新的字符串。

        最后,該方法無法對所有字符串進行解碼,但可以實現(xiàn)良好的覆蓋。用于檢測越獄的文件列表在附件中給出。

        還有一個檢測 unc0ver 越獄的特殊檢查,包括嘗試卸載 /.installed_unc0ver:

        0x100E4D814: _unmount ( "/.installedunc0ver" )

        環(huán)境

        混淆器還會檢查觸發(fā) EVT_ENV_JAILBREAK 事件的環(huán)境變量。其中一些檢查似乎與代碼提升檢測有關(guān),但仍會觸發(fā) EVT_ENV_JAILBREAK 事件。

        startswith ( )

        從逆向工程的角度來看,startswith ( ) 實際上是作為一個 "or-ed" 的 xor 序列來實現(xiàn)的,以得到一個布爾值。這可能是編譯器優(yōu)化的結(jié)果。你可以在位于地址 0x100015684 的基本塊中觀察這個模式。

        高級檢測

        除了常規(guī)檢查之外,混淆器還執(zhí)行高級檢查,比如驗證 SIP ( 系統(tǒng)完整性保護 ) 的當前狀態(tài),更準確地說,是 KEXTS 代碼簽名狀態(tài)。

        根據(jù)我在 iOS 越獄方面的經(jīng)驗,我認為沒有越獄會禁用 CSR_ALLOW_UNTRUSTED_KEXTS 標志。相反,我猜它是用來檢測應(yīng)用程序是否在允許這種停用的 Apple M1 上運行。

        Assembly range: 0x100004640 – 0x1000046B8

        混淆器還使用 Sandbox API 來驗證是否存在某些路徑:

        通過這個 API 檢查的路徑是 OSX 相關(guān)的目錄,所以我猜它也被用來驗證當前代碼沒有在 Apple Silicon 上被解除。例如,下面是使用 Sandbox API 檢查的目錄列表:

        Assembly range: 0x100ED7684 ( function )

        此外,它使用沙盒屬性 file-read-metadata 作為 stat ( ) 函數(shù)的替代方案。

        Assembly range: 0x1000ECA5C – 0x1000ECE54

        該應(yīng)用程序通過私有系統(tǒng)調(diào)用使用沙盒 API 來確定是否存在一些越獄工件。這是非常明智的做法,但我想這并不符合蘋果的安全政策。

        代碼符號表

        此檢查的目的是驗證已解析導(dǎo)入的地址是否指向正確的庫。換句話說,此檢查驗證導(dǎo)入表沒有被可用于掛鉤導(dǎo)入函數(shù)的指針篡改。

        Initialization: part of sub_100E544E8

        Assembly range: 0x100016FC4 – 0x100017024

        在 RASP 檢查初始化 ( sub_100E544E8 ) 期間,混淆器會手動解析導(dǎo)入的函數(shù)。此手動解析是通過迭代 SingPass 二進制文件中的符號、檢查導(dǎo)入符號的庫、訪問(在內(nèi)存中)此庫的 __LINKEDIT 段、解析導(dǎo)出 trie 等來執(zhí)行的。此手動解析填充一個包含已解析符號的絕對地址的表。

        此外,初始化例程設(shè)置遵循以下布局的元數(shù)據(jù)結(jié)構(gòu):

        symbols_index 是一種轉(zhuǎn)換表,它將混淆器已知的索引轉(zhuǎn)換為 __got 或 __la_symbol_ptr 部分中的索引。索引的來源(即 __got 或 __la_symbol_ptr)由包含類枚舉整數(shù)的 origins 表確定:symbols_index 和 origins 這兩個表的長度都是由靜態(tài)變量 nb_symbols 定義的,它被設(shè)置為 0x399。元數(shù)據(jù)結(jié)構(gòu)后面跟著兩個指針:resolved_la_syms 和 resolved_got_syms,它們指向混淆器手動填充的導(dǎo)入地址表。

        每個部分都有一個專用表:__got 和 __la_symbol_ptr。

        然后,macho_la_syms 指向 __la_symbol_ptr 部分的開頭,而 macho_got_syms 指向 __got 部分。

        最后,stub_helper_start / stub_helper_end 保存了 __stub_helper 部分的內(nèi)存范圍。稍后我將介紹這些值的用途。

        這個元數(shù)據(jù)結(jié)構(gòu)的所有值都是在函數(shù) sub_100E544E8 中進行初始化時設(shè)置的。

        在 SingPass 二進制文件的不同位置,混淆器使用此元數(shù)據(jù)信息來驗證已解析導(dǎo)入的完整性。它首先訪問 symbols_index 和具有固定值的起源:

        由于 symbols_index 表包含 uint32_t 值,#0xCA8 匹配 #0x32A ( 起源表的索引 ) 當除以 sizeof ( uint32_t ) : 0xCA8 = 0x32A * sizeof ( uint32_t ) 。

        換句話說,我們有以下操作:

        然后,給定 sym_idx 值并根據(jù)符號的來源,該函數(shù)訪問已解析的 __got 表或已解析的 __la_symbol_ptr 表。此訪問是通過位于 sub_100ED6CC0 的輔助函數(shù)完成的??梢杂孟旅娴膫未a來概括:

        比較 section_ptr 和 manual_resolved 的索引 sym_idx 處的條目,如果它們不匹配,則觸發(fā)事件 #EVT_CODE_SYMBOL_TABLE。

        實際上,比較涵蓋了不同的情況。首先,混淆器處理 sym_idx 處的符號尚未解析的情況。在這種情況下,section_ptr [ sym_idx ] 指向位于 __stub_helper 部分中的符號解析存根。這就是元數(shù)據(jù)結(jié)構(gòu)包含本節(jié)的內(nèi)存范圍的原因:

        另外,如果兩個指針不匹配,函數(shù)會使用 dladdr 來驗證它們的位置:

        例如,如果導(dǎo)入的函數(shù)與 Frida 掛鉤,則兩個指針可能不匹配。

        在 origin [ sym_idx ] 被設(shè)置為 SYM_ORIGINS::NONE 的情況下,函數(shù)跳過檢查。因此,我們可以通過用 0 填充原始表來禁用這個 RASP 檢查。符號的數(shù)量接近元數(shù)據(jù)結(jié)構(gòu),元數(shù)據(jù)結(jié)構(gòu)的地址是由 ___atomic_load 和 ___atomic_store 函數(shù)泄露的。

        代碼跟蹤檢查

        代碼跟蹤檢查旨在驗證當前沒有被跟蹤。通過查看 #EVT_CODE_TRACING_cbk_ptr 的交叉引用,我們可以識別出兩種驗證。

        GumExecCtx

        EVT_CODE_TRACING 似乎能夠檢測 Frida 的跟蹤檢查是否正在運行。這是我第一次觀察到這種檢查,非常聰明。對于那些想用原始匯編代碼進行分析的人,我將使用 SingPass 二進制文件中的這個地址范圍:0x10019B6FC – 0x10019B82C。

        這是執(zhí)行 Frida Stalker 檢查的函數(shù)圖:

        與 Frida Stalker 檢測相關(guān)的代碼

        是的,此代碼能夠檢測到 Stalker。讓我們從第一個基本塊開始。 _pthread_mach_thread_np ( _pthread_self ( ) ) 旨在獲取調(diào)用此檢查的函數(shù)的線程 ID。

        然后更巧妙的是,MRS ( TPIDRRO_EL0 ) & #-8 用于手動訪問線程本地存儲區(qū)。在 ARM64 上,蘋果使用 TPIDRRO_EL0 的最低有效字節(jié)來存儲 CPU 的數(shù)量,而 MSB 包含 TLS 基地址。

        然后,第二個基本塊(循環(huán)的入口)使用鍵 tlv_idx 訪問線程本地變量,在循環(huán)中取值范圍為 0x100 到 0x200:

        以下調(diào)用 _vm_region_64 ( ... ) 的基本塊用于驗證 tlv_addr 變量是否包含具有正確大?。创笥?0x30)的有效地址。在這些情況下,它會通過這些奇怪的內(nèi)存訪問跳轉(zhuǎn)到以下基本塊:

        觸發(fā) EVT_CODE_TRACING 的條件

        為了弄清楚這些內(nèi)存訪問的含義,我們有必要知道這個函數(shù)與 EVT_CODE_TRACING 事件相關(guān)聯(lián)。哪些知名的公共工具可以與代碼跟蹤相關(guān)聯(lián)?沒有太大的風險,我們可以假設(shè)存在 Frida Stalker。

        如果我們查看 Stalker 的實現(xiàn),我們會注意到在 gumstalker-arm64.c 中的這種初始化:

        所以跟蹤者創(chuàng)建了一個線程局部變量,用于存儲 GumExecCtx 結(jié)構(gòu)的指針,該結(jié)構(gòu)具有以下布局:如果我們添加這個布局的偏移量并且如果我們實際上內(nèi)聯(lián) GumArm64Writer 結(jié)構(gòu),我們可以得到這個表示:由于編譯器強制對齊,destroy_pending_since 位于偏移量 0x08 而不是 0x04 處。

        這樣一來,我們可以觀察到:

        * ( tlv_table + 0x18 ) 有效匹配 GumThreadId thread_id 屬性;

        * ( tlv_table + 0x24 ) 匹配 GumOS target_os;

        * ( tlv_table + 0x28 ) 匹配 GumPtrauthSupport ptrauth_support;

        GumOS 和 GumPtrauthSupport 是在 gumdefs.h 和 gummemory.h 中定義的枚舉,其值如下:

        GumOS 包含 6 個條目,從 GUM_OS_WINDOWS = 0 到 GUM_OS_QNX = 5,這類似于 GUM_PTRAUTH_INVALID = 0,而最后一個條目與 GUM_PTRAUTH_SUPPORTED = 2 相關(guān)聯(lián)。

        因此,前面的奇怪條件被用來對 GumExecCtx 結(jié)構(gòu)進行指紋識別:

        防止這種 Stalker 檢測的一種方法是使用 _GumExecCtx 結(jié)構(gòu)中的交換字段重新編譯 Frida。
        關(guān)鍵詞:

        責任編輯:hnmd003

        相關(guān)閱讀

        相關(guān)閱讀

        精彩推送

        推薦閱讀

        亚洲人成人网站色www| 亚洲最大的成人网| 国产成人久久精品亚洲小说| 7777久久亚洲中文字幕| 亚洲精品无码久久久久久久| 亚洲第一AV网站| 亚洲精品国产美女久久久| 亚洲日本乱码在线观看| 色久悠悠婷婷综合在线亚洲| 不卡一卡二卡三亚洲| 国产成人亚洲综合| 亚洲综合无码精品一区二区三区| 亚洲精品久久久www| 亚洲精品线路一在线观看 | 国产精品亚洲专区无码不卡| 亚洲av乱码一区二区三区按摩 | 亚洲日本人成中文字幕| 亚洲日日做天天做日日谢| 在线亚洲午夜片AV大片| 亚洲人成色77777在线观看| 亚洲中文字幕乱码熟女在线| 亚洲精品无码专区| 色偷偷噜噜噜亚洲男人| 亚洲国产成人久久一区WWW| 亚洲偷自拍拍综合网| 亚洲熟妇av一区二区三区| 亚洲AV午夜福利精品一区二区| 亚洲AV天天做在线观看| 久久亚洲精品国产精品| 亚洲国产精品成人综合色在线婷婷| 亚洲中文字幕人成乱码| 亚洲无码一区二区三区| 色噜噜的亚洲男人的天堂| 亚洲偷自拍拍综合网| 国产∨亚洲V天堂无码久久久| 亚洲天天在线日亚洲洲精| 亚洲的天堂av无码| 亚洲午夜无码久久| mm1313亚洲精品国产| 亚洲欭美日韩颜射在线二| 亚洲AV无码AV男人的天堂|