2019年12月,在Linux內核中推送了新的Binder提交。 此補丁修復了用於處理Binder事務中特定類型的對象的索引的計算。
本文研究了已更正問題的含義,爲什麼是安全漏洞以及如何利用它。
在閱讀本文之前,強烈建議先閱讀有關粘合劑內部的文章。
相關補丁和版本
CVE-2020-0041發佈於2020年3月的Android安全公告中。
附加到此CVE的修補程序是提交16981742 ,其說明如下:
活頁夾:修復num_valid的錯誤計算 對於BINDER_TYPE_PTR和BINDER_TYPE_FDA交易, num_valid local計算錯誤,導致 在binder_validate_ptr()中進行範圍檢查以錯過越界 抵消。 修復:bde4a19(“活頁夾:使用用戶空間指針作爲緩衝區空間的基礎”)
描述中提到了binder_valid_ptr()
函數out-of-bounds
。 這似乎是安全修復程序!
該錯誤是通過代碼重構(commit bde4a19 )在2019年2月引入的。 實際上,幾乎沒有設備受到影響,因爲大多數供應商都使用舊的內核,並且此漏洞僅影響最新的內核。 據我所知,只有Android 10上的Pixel 4和Pixel 3 / 3a XL受到影響:
- 像素4-內核msm-coral-4.14-android10
- Pixel 3 / 3a XL-內核msm-bonito-4.9-android10
補丁概述
差異--git a / drivers / android / binder.cb / drivers / android / binder.c 索引e9bc9fc..b2dad43 100644 --- a /驅動程序/android/binder.c +++ b / drivers / android / binder.c @@ -3310,7 +3310,7 @@ 活頁夾大小 struct binder_fd_array_object * fda = to_binder_fd_array_object(hdr); -size_t num_valid =(buffer_offset-off_start_offset)* + size_t num_valid =(buffer_offset-off_start_offset)/ sizeof(binder_size_t); struct binder_buffer_object *父= binding_validate_ptr(target_proc,t-> buffer, @@ -3384,7 +3384,7 @@ t->緩衝區-> user_data + sg_buf_offset; sg_buf_offset + = ALIGN(bp-> length,sizeof(u64)); -num_valid =(buffer_offset-off_start_offset)* + num_valid =(buffer_offset-off_start_offset)/ sizeof(binder_size_t); ret = binding_fixup_parent(t,線程,bp, off_start_offset,
該修補程序修復了num_valid
索引的計算,該索引在binder_fixup_parent()
的調用中用作參數。 乘號*
被除法/
代替。
當活頁夾事務包含活頁夾對象時,偏移量列表將給出不同活頁夾對象在事務緩衝區中的位置。
活頁夾交易的沖銷緩衝區
讓我們舉個例子,如果對象的偏移量爲0x10
(上圖中的對象BINDER_TYPE_PTR C),則索引的正確值應爲0x2 :
//正確的版本 size_t num_valid = (buffer_offset - off_start_offset) / sizeof (binder_size_t); / * 如果(buffer_offset-off_start_offset)= 0x10 num_valid = 0x10 / 0x8 num_valid = 0x2 * /
在易受攻擊的版本中,計算得出的值爲0x80 。
//版本不正確 size_t num_valid = (buffer_offset - off_start_offset) * sizeof (binder_size_t); / * 如果(buffer_offset-off_start_offset)= 0x10 num_valid = 0x10 * 0x8 num_valid = 0x80 * /
那是完全不同的! 越野車版本允許通過偏移緩衝區(藍色)發送帶有父索引的活頁夾對象。
偏移緩衝區越界
函數binder_validate_ptr()
使用num_valid
並檢查兩件事:
- 如果給定索引(此處爲
off_start_offset
)小於num_valid
,則該函數僅信任已處理的對象。 - 在索引(
off_start_offset
)中找到的偏移量處是否存在有效的binder_buffer_object
(使用幻數進行檢查)。
//drivers/android/binder.c 靜態 整數 bind_fixup_parent ( struct bind_transaction * t, struct binder_thread * 線程 , struct binder_buffer_object * bp, binding_size_t off_start_offset, binding_size_t num_valid, binding_size_t last_fixup_obj_off, 活 頁 夾 大小( t last_fixup_min_off) { // [...] 如果 ( ! ( bp- > 標誌 和 BINDER_BUFFER_FLAG_HAS_PARENT)) 返回 0 ; 父 = 粘結劑有效期_ptr(target_proc, b 和 對象, bp- > 父, off_start_offset 和 parent_offset, num_valid);
//drivers/android/binder.c 靜態 結構 binder_buffer_object * binder_validate_ptr ( struct binder_proc * proc, 結構 binder_buffer * b, struct binder_object * 對象, 活頁夾大小 索引 binding_size_t start_offset, 活頁夾大小 *對象 偏移量, binding_size_t num_valid) { // [...] 如果 (索引 > = num_valid) 返回 NULL; buffer_offset = 起始偏移量 + sizeof (binder_size_t) * 索引; binding_alloc_copy_from_buffer( & proc- > alloc, & object_offset, b, buffer_offset, sizeof (object_offset)); object_size = binding_get_object(proc, b, object_offset, object); 如果 ( ! object_size || object- > hdr.type != BINDER_TYPE_PTR) 返回 NULL; // [...]
即使父索引存在出界(讀訪問),也只能訪問內存的有限部分,因爲內核會驗證內存在接收方事務緩衝區中。 此外,在對象的開始處需要一個幻數,因此偏移量必須指向該幻數。 此外,如果魔術不正確,內核不會泄漏該值。
目前,該錯誤的影響很難看到。 要了解可能的利用,我們需要對Binder中的對象父系統有更好的瞭解。
有活頁夾的父母
活頁夾對象BINDER_TYPE_PTR
和BINDER_TYPE_FDA
具有一個字段parent
和parent_offset
,該字段允許在父緩衝區內修補指針。 此功能由HIDL語言(硬件服務)使用,並且在上一則有關資料夾內部的文章中進行了解釋。
HIDL_STING示例
hidl_string
結構是使用BINDER_TYPE_PTR
父母的一個很好的例子。
//從system / libhidl / base / include / hidl / HidlSupport.h中提取 struct hidl_memory { // ... 私人的: hidl_handle mHandle __attribute__ ((aligned( 8 ))); uint64_t mSize __attribute__ ((aligned( 8 ))); hidl_string mName __attribute__ ((aligned( 8 ))); };
當進程A hidl_string
進程B發送hidl_string
,該結構包含一個屬於進程A的指針。爲了使該結構在接收方進程內存中有效,Binder驅動程序必須通過屬於該內存空間的指針對其進行更改。這是由BINDER_TYPE_PTR
parent
和parent_offset
字段完成的。
// BINDER_TYPE_PTR的結構 structinder_object_header { __u32 類型; }; structinder_buffer_object { 結構 binder_object_header hdr; __u32 標誌; binding_uintptr_t 緩衝區; binding_size_t 長度; binding_size_t 父對象; //父對象的索引(在偏移緩衝區中) 活頁夾大小 //偏移以修補父緩衝區 };
爲了發送hidl_string
,第一個緩衝區(A)用於發送hidl_memory
C結構,第二個緩衝區(B)用於存儲實際字符串(在這種情況下爲“我的演示字符串”)。 緩衝區A是B的父級,而A的偏移量0是使用目標過程存儲器中字符串的地址修補的。
帶有hidl_string的Binder父示例
家長修正規則
一些規則限制了在綁定對象中使用父對象。 當然,在調用此檢查之前,綁定程序內核已經檢查了指向緩衝區及其大小的指針是否有效(指向調用者內存的指針)。
通過調用binder_fixup_parent()
檢查有關父代活頁夾對象層次結構的這些規則。
內核應用的規則如下:
通過活頁夾檢查規則()
- [1]父索引必須小於或等於
num_valid
。 內核已驗證num_valid
之前的所有對象。
通過活頁夾檢查規則()
- [2]僅允許對已驗證的最後一個緩衝區對象或其父對象進行修復
- [3]我們僅允許緩衝區內的修正在偏移增加時發生
對於本文的下一部分,這些以前的規則已由[1]到[3]的數字標識。
規則檢查示例(有效)
爲了驗證事務的所有綁定程序對象,內核會按照它們在偏移緩衝區中註冊的順序檢查它們。 請記住,此緩衝區包含一個偏移量列表,其中活頁夾對象存儲在事務數據緩衝區中。
有效的活頁夾父母
此示例有效,並遵守所有規則。
規則檢查示例(無效1)
無效的父級偏移
該示例無效,因爲它違反了規則[3]:
我們只允許緩衝區內的修正以增加的偏移量發生
規則檢查示例(無效2)
在下圖中,內核檢查與對象D對應的偏移量時,驗證失敗。
無效的父母
該示例無效,因爲它違反了規則[2]:
僅允許對最後一個經過驗證的緩衝區對象或其父對象進行修復
在我們的例子中,最後一個驗證的對象是C ,而對象D的父對象是B。 但是B不是C或A (C的父母)。 層次結構無效。
如何利用漏洞?
利用此錯誤的一種有趣方法是使父緩衝區的字段buffer
和length
具有任意值。
該錯誤允許輕鬆繞過規則[1],但是很難繞過規則[3]。
開發理念
訣竅是在驗證過程中更改父級層次結構。 這可以使用extra
緩衝區來完成! 確實,內核使用緩衝區的這一部分來存儲與BINDER_TYPE_PTR
對象有關的數據。 如果活頁夾對象的父索引指向該多餘部分,則當內核在此位置複製數據時,其父對象將被更改。
內核驗證-初始配置
要執行此漏洞利用,需要具有以下層次結構的三個緩衝區對象(添加一個未在偏移列表中註冊的緩衝區):
對象D包含任意數據。
我們在計算num_valid
使用此漏洞爲對象B和C設置相同的父對象,並設置其父索引引用額外的數據部分(紫色區域)。 在驗證之前,多餘的部分未初始化,並且包含以前的活頁夾交易記錄的數據。 這些數據可以通過發送事務並使用所需的offset A
值向緩衝區噴灑來控制。
內核驗證-選中C時暫停
內核從offset A
開始檢查偏移量列表中包含的對象。 直到C的所有對象都是有效的。 每次處理對象時,都會如下圖所示在多餘部分中複製其相關緩衝區。
該圖顯示了算法的狀態。 內核已經檢查了緩衝區A和B ,但未檢查對象C。 此時, parent_index
的偏移量值尚未修改(設置爲與offset A
對應的值),並且最後一個驗證的對象爲B。
內核驗證-對象C
活頁夾驅動程序處理對象C時,它首先在多餘的部分複製其緩衝區。 但是,此副本將覆蓋parent_index
的先前值。 準備緩衝器C的數據以用與對象D相對應的新值來改變該值。
此時,父母的等級會發生變化!
遵守所有規則! 實際上, C的父對象(此處爲D )必須是最後一個經過驗證的對象或其父對象之一。
使用此配置,我們已繞過了binder_validate_ptr()
和binder_validate_fixup()
內核驗證-父修補
一旦對象C通過所有檢查,內核便通過調用函數binder_alloc_copy_to_buffer()
修補父緩衝區。
//提取binder.c 靜態 詮釋 粘結劑_固定up_父母 (...){ //在這裏檢查規則[...] buffer_offset = bp- > parent_offset + ( uintptr_t ) parent- > 緩衝區 - ( uintptr_t )b- > user_data; binding_alloc_copy_to_buffer( & target_proc- > alloc, b, buffer_offset, & bp- > 緩衝區, sizeof (bp- > 緩衝區));
請記住,執行此代碼時,不會映射目標進程。
所有/ dev / binder設備都可以在內核內存中訪問。 用戶界面進程映射了活頁夾文件描述符時。 內核分配頁面(在此處使用kzalloc),並將這些頁面映射到進程內存中。
在綁定程序事務期間,內核可以通過在接收方進程的內存地址上應用偏移量來檢索此分配的內核地址。 在正常調用中, parent->buffer
的值屬於目標進程的/dev/binder
內存,因爲該值先前是在處理父對象時由內核修補的。 驅動程序可以通過以下計算獲得相應的內核地址:
kernel_proc_buffer = 父 -> 緩衝區 -b- > user_data
使用我們的漏洞,我們可以部分控制kernel_proc_buffer
因爲b->user_data
是未知的。
寫入父緩衝區的值(加上偏移量)是當前對象的地址(在我們的示例中,是目標進程內存中對象C的地址:另外)
不幸的是,對於我們的利用而言,函數binder_alloc_copy_to_buffer()
對要修補的地址執行了附加檢查。
//在drivers / android / binder_alloc.c中 int binding_alloc_copy_to_buffer ( struct binder_alloc * alloc, struct binder_buffer * 緩衝區, binding_size_t buffer_offset, 無效 * src, size_t 字節) { return binding_alloc_do_buffer_copy(alloc, true, buffer, buffer_offset, src, 字節); } 靜態 整數 bind_alloc_do_buffer_copy ( struct binder_alloc * alloc, bool to_buffer, struct binder_buffer * 緩衝區, binding_size_t buffer_offset, 無效 * ptr, size_t 字節) { / *所有副本必須對齊32位且大小爲32位* / BUG_ON( ! check_buffer(分配, 緩衝區, buffer_offset, 字節)); // [...]
函數binder_alloc_do_buffer_copy()
檢查要修補的緩衝區是否在當前綁定程序事務的當前接收緩衝區內。
此漏洞利用不允許針對內核內存 。
可以注意到,如果地址無效,內核將調用BUG_ON
,這將停止內核執行。
通過繞過所有檢查,我們可以將任意值設置爲parent->buffer
但是我們只能嘗試一次,否則內核將停止! 我們需要內存泄漏才能知道目標進程中/dev/binder
的地址。
爲了驗證該理論,讓我們來看一下所描述的利用工作。 由於尚未發生泄漏,因此我們在Android仿真器中運行了經過修改的內核。
靜態 void binder_alloc_do_buffer_copy ( struct binder_alloc * alloc, bool to_buffer, struct binder_buffer * 緩衝區, binding_size_t buffer_offset, 無效 * ptr, size_t 字節) { 如果 ( ! check_buffer(alloc, buffer, buffer_offset, bytes)){ size_t buffer_size = binder_alloc_buffer_size(alloc, buffer); pr_info( “ [JB] check_buffer buffer_size:0x%lx字節= 0x%lx偏移量= 0x%lx \ n ” , buffer_size, 字節, buffer_offset); } / *所有副本必須對齊32位且大小爲32位* / BUG_ON( ! check_buffer(分配, 緩衝區, buffer_offset, 字節));
添加了調試打印(調用pr_info()
,以檢查buffer_offset
值是否無效)
POC
自定義內核(基於msm-bonito-4.9-android10)與Pixel 3a XL固件一起啓動。 PoC使用先前圖中描述的父層次結構將綁定程序事務發送到servicemanager
器。
。 / 模擬器 -AVD Pixel_3a_XL_API_29_64b- 內核 custom_bzImage- 顯示 - 內核 - 否 - 窗口 - 詳細 - 隨機 - 否 - 快照
[148.291702]活頁夾:3410:3410 ioctl c0306201 7fff98cb5f20返回-22 [148.295022] binding_alloc:[JB] check_buffer buffer_size:0x10e0字節= 0x8偏移量= 0x71829fdc8b8 [148.299460] ------------ [在此處剪切] ------------ [148.301159]內核BUG位於drivers / android / binder_alloc.c:1133! [148.303042]無效的操作碼:0000 [#1] PREEMPT SMP NOPTI [148.304537]模塊鏈接在: [148.305422] CPU:0 PID:3410 Comm:poc未受污染4.14.150HELLO +#28 [148.307397]硬件名稱:QEMU Standard PC(i440FX + PIIX,1996),BIOS rel-1.11.1-0-g0551a4be2c-prebuilt.qemu-project.org 04/01/2014 [148.311690]任務:0000000086b3eedc task.stack:0000000000a1c204 [148.313730] RIP:0010:binder_alloc_do_buffer_copy + 0x8d / 0x15e [148.315692] RSP:0018:ffffa11501effa48 EFLAGS:00010246 [148.317540] RAX:0000000000000000 RBX:ffff9e98a62079c0 RCX:0000000000000008 [148.320403] RDX:ffff9e98aa0e5dd8 RSI:0000000000000000 RDI:ffff9e98aa0e5da0 [148.323268] RBP:ffffa11501effaa0 R08:0000000000000ff4 R09:0000000000000000 [148.325435] R10:0000000000000000 R11:0000000000000000 R12:0000000000000008 [148.328290] R13:0000071829fdc8b8 R14:ffff9e98aa0e5da0 R15:ffff9e98a62079c0 [148.330194] FS:000000000048d648(0000)GS:ffff9e98bfc00000(0000)knlGS:0000000000000000 [148.331780] CS:0010 DS:0000 ES:0000 CR0:0000000080050033 [148.332740] CR2:00007435311239a0 CR3:0000000010ee2000 CR4:00000000000006b0 [148.333848]通話跟蹤: [148.334207] binding_alloc_copy_to_buffer + 0x1a / 0x1c [148.334895] binding_fixup_parent + 0x186 / 0x1ac
調試字符串證明PoC起作用,因爲偏移量值無效(0x71829fdc8b8的偏移量很大!)
活頁夾分配器: [JB] check_buffer buffer_size : 0x10e0 字節 = 0x8 偏移量 = 0x71829fdc8b8
/ dev / binder的內存映射泄漏
在不知道目標進程的內存映射的情況下,此PoC幾乎是沒有用的:(。但是,沒有丟失任何信息!
Android Java應用程序具有特定性,它們都是Zygote
或Zygote64
(取決於32/64位)。
Zygote
是帶有預初始化的Java虛擬機的進程。 當系統需要啓動新的Java應用程序時, Zygote
被派生並開始執行該應用程序。 這種設計可以減少初始化步驟。 Java應用程序可以儘快啓動。 但是,在執行對fork()
的調用時,虛擬內存將被克隆,因此其內存映射也將被克隆。 因此,Zygote的所有孩子都共享相同的映射。
讓我們檢查一下模擬器:
根1612 1 4758476 190144 poll_schedule_timeout 0 S zygote64 ... u0_a103 3891 1612 4927284 124964 SyS_epoll_wait 0 S com.foo.mypoc
cat / proc / $(pidof com.foo.mypoc)/ maps | grep“ / dev / binder” 7a6242192000-7a6242290000 r--p 00000000 00:12 7315 / dev
假設我們可以將任意代碼作爲com.foo.mypoc
包執行,通過檢查進程內存映射,可以找到/dev/binder
的映射位置。 在我們的情況下,它映射爲0x7a6242192000 。
進程com.foo.mypoc
是從zygote64
分叉的。 其他與同一個母親一起的過程如下:
generic_x86_64:/#ps -e | grep $(pidof zygote64) 根1612 1 ... zygote64 系統1845 1612 ... system_server u0_a89 1996 1612 ... com.android.systemui network_stack 2118 1612 ... com.android.networkstack radio 2199 1612 ... com.android.phone 系統2210 1612 ... com.android.settings u0_a55 2261 1612 ... android.ext.services u0_a84 2296 1612 ... com.android.launcher3 u0_a102 2321 1612 ... com.android.inputmethod.latin u0_a87 2436 1612 ... com.android.dialer u0_a37 2465 1612 ... android.process.acore secure_element 2553 1612 ... com.android.se 無線電2586 1612 ... com.android.ims.rcsservice 系統2626 1612 ... com.android.emulator.multidisplay u0_a77 2686 1612 ... com.android.smspush u0_a67 2705 1612 ... com.android.printspooler u0_a40 2787 1612 ... android.process.media u0_a97 2884 1612 ... com.android.email u0_a78 2947 1612 ... com.android.messaging u0_a81 2971 1612 ... com.android.onetimeinitializer u0_a52 3005 1612 ... com.android.packageinstaller u0_a54 3027 1612 ... com.android.permissioncontroller u0_a39 3050 1612 ... com.android.providers.calendar u0_a62 3075 1612 ... com.android.traceur u0_a41 3097 1612 ... com.android.externalstorage 系統3134 1612 ... com.android.localtransport 系統3230 1612 ... com.android.keychain u0_a103 3891 1612 ... com.foo.mypoc
軟件包com.android.settings
看起來很有趣,因爲它作爲system
運行。
generic_x86_64:/#cat / proc / $(pidof com.android.settings)/ maps | grep“ / dev / binder” 7a6242192000-7a6242290000 r--p 00000000 00:12 7315
實際上,綁定器設備與我們的應用程序com.foo.mypoc
映射在同一位置!
開發思路
使用先前的PoC和目標進程的內存映射,可以覆蓋與已驗證的活頁夾對象有關的數據。
Userland綁定程序庫( libbinder.so
和libhwbinder.so
)信任綁定程序驅動程序處理,並認爲所有綁定程序對象均已正確打補丁。 如果修補程序未正確完成,則在宗地反序列化步驟中,應用程序可能很容易受到攻擊。
文件描述符
覆蓋BINDER_TYPE_FDA
修補的對象,使其指向BINDER_TYPE_FDA
且未經檢查的文件描述符列表。 我們可以想象在目標進程中關閉任意文件描述符,以將其替換爲受控文件描述符。
活頁夾緩衝區
覆蓋BINDER_TYPE_PTR
對象的大小。 如果使用該漏洞更改了嵌入式緩衝區結構(如hidl_string
)的大小字段,則新值將是一個指針,並且對於正確的緩衝區無效。
詳細信息 :: hidl_pointer < const char > mBuffer; uint32_t mSize; //嘗試覆蓋大小 bool mOwnsBuffer;
活頁夾/句柄對象
覆蓋BINDER_TYPE_HANDLE
/ BINDER_TYPE_WEAK_HANDLE
對象的指針。 當綁定程序內核模塊處理這些對象時,它將在遠程進程中用其原始指針值替換處理程序。 內核在其內存中保留處理程序/指針之間的映射,並使用此表修復BINDER_TYPE_HANDLE
或BINDER_TYPE_BINDER
。 有時,遠程服務需要實例化對象以執行命令。 要使用此對象,客戶端將發送一個對象句柄( BINDER_TYPE_HANDLE
)。 內核將其替換爲BINDER_TYPE_BINDER
,該對象在目標接收緩衝區中包含實際指針。
如果攻擊者使用該漏洞替換了BINDER_TYPE_BINDER
對象指針,則他可以控制該對象類型的所有字段以獲取代碼執行BINDER_TYPE_BINDER
。
正常交易
對象指向受控數據
結論
對這個錯誤及其利用方法的分析非常有趣且新穎。 它允許與活頁夾父母一起玩。
即使此漏洞不允許定向內核內存,也可能導致利用特權更高的應用程序。
本文完成的分析只是利用此漏洞所需的第一步。 將這些原語轉換爲實際的權限提升漏洞需要大量的工作。
我認爲,在將代碼添加到Linux內核源代碼之前,請仔細檢查一下該錯誤。 我的陳述與我先前對secctx patch進行的補丁分析相同,最近在內核源代碼中插入了幾個“簡單”的錯誤。
幸運的是,此最新漏洞僅影響了少數設備(Android 10上的Pixel 4和3a)。