TiYunZong: An Exploit Chain to Remotely Root Modern Android Devices Guang Gong, 360 Alpha Lab #BHUSA @BLACKHATEVENTS
About 360 Alpha Lab · Found 300+ Android vulnerabilitiesGoogle Qualcomm etc · Won the highest reward in the history of the ASR program in 2017. · Won the highest reward in the history of all Google VRP program in 2019 · 6 Pwn contest winners · Pwn2Own Mobile 2015( Nexus 6) · Pwn0Rama 2016 (Nexus 6p) · Pwn2Own 2016(Chrome) · PwnFest 2016(Pixel) · Pwn2Own Mobile 2017(Galaxy S8 · TianFuCup 2018(Chrome)
Agenda · Why Google pixel phone is a tough target · Remote attack surface of Android devices · Experience of pwning Android devices · Overview the exploit chain, TiYunZong · Detail the three vulnerabilities in the chain · Demonstrate remotely rooting pixel phone
Why Google Pixel Phone Is A Tough Target
Why Google Pixel Phone Is A Tough Target
Devices
Pwned Times Year Mobile Pwn2Own 2017 Mobile Pwn2Own 2018 Mobile Pwn2Own 2019 Total
Apple iPhone 5(1 partial win) 2 0 7
Google Pixel 0 0 0 0
Samsung Galaxy 3 2 3(1 partial win) 8
Huawei Mate/P 2 0 0 2
Mobile Pwn2Own results of the latest three years
Xiaomi Mi N/A 5 3(1 partial win) 8
Remote Attack Surface of Smart Phones
Attacks through internet VS Attacks through adjacent network
Internet Network Adjacent Network
Remote Attack Surface of Smart Phones
One-click Internet Deployable Attack VS Zero-click Adjacent Network Attack
Internet Network Adjacent Network
Experience of Pwning Android Devices Remote working exploit chains targeting Android Found by me in recent years
The Exploit Chain(TiYunZong)
The Exploit Chain(TiYunZong)
The RCE Vulnerability CVE-2019-5877
Torque in Chrome v8
JSFunction Memory Layout · JSFunction in Chrome v8 is the internal representation of function in JavaScript. · The size of JSFunction object in v8 is not fixed. it may contain the field PrototypeOrInitialMap or not. · The field PrototypeOrInitialMap is the last field of JSFunction if it has.
JSFunction Memory Layout d8> %DebugPrint(Array); DebugPrint: 0x2b7bcbd91b79: [Function] in OldSpace - map: 0x2f2f62402ff1 <Map(HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x2b7bcbd82091 <JSFunction (sfi = 0xc83e79480e9)> - elements: 0x3b058dc40bf9 <FixedArray[0]> [HOLEY_ELEMENTS] - function prototype: 0x2b7bcbd91dc9 <JSArray[0]> - initial_map: 0x2f2f62403041 <Map(PACKED_SMI_ELEMENTS)> - shared_info: 0x0c83e79547c9 0x2f2f62402ff1: [Map] - type: JS_FUNCTION_TYPE - instance size: 64 - callable - constructor - has_prototype_slot - constructor: 0x2b7bcbd822e1 <JSFunction Function (sfi = 0xc83e7954449)>
JSFunction Memory Layout d8> %DebugPrint(parseInt) DebugPrint: 0x2b7bcbd8b999: [Function] in OldSpace - map: 0x2f2f624003e1 <Map(HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x2b7bcbd82091 <JSFunction (sfi = 0xc83e79480e9)> - elements: 0x3b058dc40bf9 <FixedArray[0]> [HOLEY_ELEMENTS] - function prototype: - shared_info: 0x0c83e79557a1 0x2f2f624003e1: [Map] - type: JS_FUNCTION_TYPE - instance size: 56 - callable - constructor: 0x3b058dc401b1
JSFunction Memory Layout d8> %DebugPrint(Proxy) DebugPrint: 0x2b7bcbd8d6d1: [Function] in OldSpace - map: 0x2f2f62401d31 <Map(HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x2b7bcbd82091 <JSFunction (sfi = 0xc83e79480e9)> - elements: 0x3b058dc40bf9 <FixedArray[0]> [HOLEY_ELEMENTS] - function prototype: - shared_info: 0x0c83e795e749 0x2f2f62401d31: [Map] - type: JS_FUNCTION_TYPE - instance size: 56 - callable - constructor - constructor: 0x3b058dc401b1
The Bug(CVE-2019-5877) macro GetDerivedMap(implicit context: Context)( target: JSFunction, newTarget: JSReceiver): Map { try { const constructor = Cast(newTarget) otherwise SlowPath; const map = Cast(constructor.prototype_or_initial_map) otherwise SlowPath; *** oob access if (LoadConstructorOrBackPointer(map) != target) { goto SlowPath; } return map; } label SlowPath { return runtime::GetDerivedMap(context, target, newTarget); } }
Trigger the Bug JavaScript var malformedTypedArray = Reflect.construct(Uint8Array, [4], Proxy) Torque transitioning builtin CreateTypedArray( context: Context, target: JSFunction, newTarget: JSReceiver, arg1: JSAny, arg2: JSAny, arg3: JSAny): JSTypedArray { assert(IsConstructor(target)); const map = GetDerivedMap(target, newTarget); ... //create a TypedArray with the returned map }
How to Exploit macro GetDerivedMap(implicit context: Context)( target: JSFunction, newTarget: JSReceiver): Map { try { const constructor = Cast(newTarget) otherwise SlowPath; const map = Cast(constructor.prototype_or_initial_map) otherwise SlowPath; *** oob access if (LoadConstructorOrBackPointer(map) != target) { goto SlowPath; } return map;*** SlowPath will make OOB access useless, The execution flow must go to here. } label SlowPath { return runtime::GetDerivedMap(context, target, newTarget); } }
Exploit Strategy
- Free the object below the Proxy function.
- Re-occupy the free space with an object whose Map's (named map x) constructor is Uint8Array, and then drop all reference to Map x so that GC will mark the Map x as white and will sweep it in scheduled sweep tasks.
- Trigger the OOB access bug before Map x get swept by GC, so the vulnerable GetDerivedMap macro won't bail out to the slow path. The pointer of Map x will still be used in CreateTypedArray.
- After sweep task finished, re-occupy the freed space of Map x with a map whose constructor is Uint32Array, so we can get a malformed typed array, its map is Uint32Array, but its layout, especially its element kind is Uin8Array type, it's easy to implement arbitrary read and write with this malformed object.
- With the ability of arbitrary read and write, we can enable MojoJS bindings and exploit the following Mojo vulnerability to escalate from Chrome sandbox.
The EoP Vulnerability CVE-2019-5870
Chrome's Multi-Process Architecture
The Mojo Interface Definition of Content Decryption ModuleCDM)
1 interface ContentDecryptionModule {
2 SetClient(pending_associated_remote client);
3 Initialize(string key_system,
4
url.mojom.Origin security_origin,
5
CdmConfig cdm_config)
6 => (CdmPromiseResult result, int32 cdm_id,
7
pending_remote? decryptor);
8 SetServerCertificate(array certificate_data)
9 => (CdmPromiseResult result);
10 ......
11 };
The Implementation of the Initialized Function of CDM
1 void MojoCdmService::Initialize(const std::string& key_system,
2
const url::Origin& security_origin,
3
const CdmConfig& cdm_config,
4
InitializeCallback callback) {
5 DVLOG(1) << func << ": " << key_system;
6 DCHECK(!cdm_); ------------------>In debug version, this DCHECK will be trigger
7
8 auto weak_this = weak_factory_.GetWeakPtr();
9 cdm_factory_->Create(
10 key_system, security_origin, cdm_config,
11 base::Bind(&MojoCdmService::OnSessionMessage, weak_this),
12 base::Bind(&MojoCdmService::OnSessionClosed, weak_this),
13 base::Bind(&MojoCdmService::OnSessionKeysChange, weak_this),
14 base::Bind(&MojoCdmService::OnSessionExpirationUpdate, weak_this),
15 base::Bind(&MojoCdmService::OnCdmCreated, weak_this,
16
base::Passed(&callback)));
17 }
The Callback Function OnCdmCreated 1 void MojoCdmService::OnCdmCreated( 2 InitializeCallback callback, 3 const scoped_refptr<::media::ContentDecryptionModule>& cdm, 4 const std::string& error_message) { 5 mojom::CdmPromiseResultPtr cdm_promise_result(mojom::CdmPromiseResult::New()); 6 7 if (!cdm) { 8 ...... 9 } 10 cdm_ = cdm; 11 if (context_) { 12 cdm_id_ = context_->RegisterCdm(this); ---------------------------->register twice here 13 DVLOG(1) << func << ": CDM successfully registered with ID " << cdm_id_; 14 } 15 ... 16 }
The Fucntion RegisterCdm 1 int MojoCdmServiceContext::RegisterCdm(MojoCdmService* cdm_service) { 2 DCHECK(cdm_service); 3 int cdm_id = GetNextCdmId(); 4 cdm_services_[cdm_id] = cdm_service; --->two cdm ids map to one cdm_service 5 DVLOG(1) << func << ": CdmService registered with CDM ID " << cdm_id; 6 return cdm_id; 7 }
Trigger UAF 1 std::unique_ptr MojoCdmServiceContext::GetCdmContextRef( 2 int cdm_id) { 3 ...... 4 auto cdm_service = cdm_services_.find(cdm_id); 5 if (cdm_service != cdm_services_.end()) { 6 if (!cdm_service->second->GetCdm()->GetCdmContext()) {----------> UAF in GetCdm 7 NOTREACHED() << "All CDMs should support CdmContext."; 8 return nullptr; 9 } 10 return std::make_unique(cdm_service->second->GetCdm()); 11 } 12 ...... 13 return nullptr; 14 }
How To Exploit
1 (gdb) p sizeof(media::MojoCdmService)
2 $21 = 48
3 (gdb) p sizeof(media::MediaDrmBridge)
4 $3 = 168 //the size is 160 in release version
5 (gdb) x/10xw 0xb6993300 //the content of MediaDrmBridge
6 0xb6993300:
0xca3f3a0c 0x00000000
0x00000100
0xca3f3a4c
7 0xb6993310:
0xca3f3a6c 0xb6a90750
0xb6a90750
0xb6a90760
8 0xb6993320:
0x00000000
0x00000000
9 (gdb) x/10xw 0xca3f3a0c //the content of virtual table of MediaDrmBridge
10 0xca3f3a0c <_ZTVN5media14MediaDrmBridgeE+8>: 0xca237e09 0xca207a79 0xca237fad 0xca2382f9
11 0xca3f3a1c <_ZTVN5media14MediaDrmBridgeE+24>:
0xca2384a9 0xca238601 0xca238741 0xca238881
12 (gdb) info symbol 0xca238881
13 media::MediaDrmBridge::GetCdmContext() + 1 in section .text of libmedia.cr.so
Exploit the EoP Bug
MojoCdmService cdm_
MediaDrmBridge Virtual Table Pointer
;Shell Command
Virtual Table
GetCdmContext libllvm-glnext.so
base +28
system function pointer base +28
The Root Vulnerability CVE-2019-10567
The KGSL Driver Architecture of Adreno GPU
User Space Kernel Space
APPs IOCTL
LIBGSL(Graphic Support Layer library )
Adreno Context Command Queue
KGSL Driver /dev/kgsl-3d
Dispatcher
RPTR RB0
WPTR
RPTR RB1
WPTR
RPTR RB2
WPTR
RPTR RB3
WPTR
Globally Mapped Pages
1 crosshatch:/ # cat /sys/kernel/debug/kgsl/globals
2 0x00000000fc000000-0x00000000fc000fff
4096 setstate
3 0x00000000fc001000-0x00000000fc040fff
262144 gpu-qdss
4 0x00000000fc041000-0x00000000fc048fff
32768 memstore
5 0x00000000fc049000-0x00000000fc049fff
4096 scratch
6 0x00000000fc04a000-0x00000000fc04afff
4096 pagetable_desc
7 0x00000000fc04b000-0x00000000fc052fff
32768 ringbuffer
8 0x00000000fc053000-0x00000000fc053fff
4096 pagetable_desc
9 0x00000000fc054000-0x00000000fc05bfff
32768 ringbuffer
10 0x00000000fc05c000-0x00000000fc05cfff
4096 pagetable_desc
11 0x00000000fc05d000-0x00000000fc064fff
32768 ringbuffer
12 0x00000000fc065000-0x00000000fc065fff
4096 pagetable_desc
13 0x00000000fc066000-0x00000000fc06dfff
32768 ringbuffer
14 0x00000000fc06e000-0x00000000fc09dfff
196608 profile
15 0x00000000fc09e000-0x00000000fc0a5fff
32768 ucode
16 0x00000000fc0a6000-0x00000000fc0a8fff
12288 capturescript
17 0x00000000fc0a9000-0x00000000fc113fff
438272 capturescript_regs
18 0x00000000fc114000-0x00000000fc114fff
4096 powerup_register_list
19 0x00000000fc115000-0x00000000fc115fff
4096 alwayson
20 0x00000000fc116000-0x00000000fc116fff
4096 preemption_counters
21 0x00000000fc117000-0x00000000fc326fff
2162688 preemption_desc
22 0x00000000fc327000-0x00000000fc327fff
4096 perfcounter_save_restore_desc
23 0x00000000fc328000-0x00000000fc537fff
2162688 preemption_desc
24 0x00000000fc538000-0x00000000fc538fff
4096 perfcounter_save_restore_desc
25 0x00000000fc539000-0x00000000fc748fff
2162688 preemption_desc
26 0x00000000fc749000-0x00000000fc749fff
4096 perfcounter_save_restore_desc
27 0x00000000fc74a000-0x00000000fc959fff
2162688 preemption_desc
28 0x00000000fc95a000-0x00000000fc95afff
4096 perfcounter_save_restore_desc
29 0x00000000fc95b000-0x00000000fc95bfff
4096 smmu_info
The Format of the Scratch Memory
The scratch memory is one-page data that is mapped into the GPU. This allows for some `shared' data between the GPU and CPU. For example, it will be used by the GPU to write updated RPTR for each ring buffer. The format of the scratch is:
Offset 0 0x10
Length(Bytes) 44 84
Content RB0 RPTR,RB1 RPTR, RB2 RPTR, RB3 RPTR RB0 Context Restore Address, RB1 Context Restore Address RB2 Context Restore Address, RB3 Context Restore Address
Where is the Bug
1 int adreno_ringbuffer_probe(struct adreno_device *adreno_dev, bool nopreempt)
2 {
3 struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
4 struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev);
5 int i;
6 int status = -ENOMEM;
7
8 if (!adreno_is_a3xx(adreno_dev)) {
9
status = kgsl_allocate_global(device, &device->scratch, //scratch is allocated as writable by normal Command Processor
instructions
10
PAGE_SIZE, 0, KGSL_MEMDESC_CONTIG, "scratch");
11
if (status != 0)
12
return status;
13 }
14 ...
15 }
Where is the Bug
1 unsigned int *adreno_ringbuffer_allocspace(struct adreno_ringbuffer *rb,
2
unsigned int dwords)
3 {
4 struct adreno_device *adreno_dev = ADRENO_RB_DEVICE(rb);
5 unsigned int rptr = adreno_get_rptr(rb); ---------------------------> read rptr from scratch memory
6 unsigned int ret;
7
8 if (rptr <= rb->_wptr) {
9
unsigned int *cmds;
10
11
if (rb->_wptr + dwords <= (KGSL_RB_DWORDS - 2)) {
12
ret = rb->_wptr;
13
rb->_wptr = (rb->_wptr + dwords) % KGSL_RB_DWORDS;
14
return RB_HOSTPTR(rb, ret);
15
} ......
16 }
Read And Write Pointer of a Ring Buffer
Ring Buffer Ring Buffer
Free Space
rptr
Pending Command Processor Instructions
_wptr
Pending Command Processor Instructions Free Space
Free Space
_wptr
rptr
Pending Command Processor Instructions
rptr points to the next instruction which will be executed by the GPU _wptr points to the start of free space of a ring buffer
Allocate Space From Ring Buffer
Ring Buffer Ring Buffer
Free Space
Pending Command Processor Instructions
CP_NOP
Free Space
rptr
_wptr
_wptr
rptr FCrPe_eNOSPpace _wptr
Pending Command Processor Instructions
Free Space _wptr
Ring Buffer
Pending Command Processor Instructions
CP_NOP
_wptr
_wptr
Free Space
rptr
Pending Command Processor Instructions
Overwrite Exist Instructions
Ring Buffer
RPTR
Ring Buffer
Free
RPTR
Command Processor Instructions Wrongly Marked as Free Command Processor Instructions
_wptr Free RPTR _wptr
CP Instruction Sequence of Executing IOCTL_KGSL_GPU_COMMAND
Command Processor Instructions In Indirect Buffer Command Processor Instructions In Indirect Buffer Command Processor Instructions In Indirect Buffer Command Processor Instructions In Indirect Buffer
rptr
Ring Buffer
CP_SET_PROTECTED_MODE user_profiling CP_INDIRECT_BUFFER_PFE CP_INDIRECT_BUFFER_PFE CP_INDIRECT_BUFFER_PFE CP_INDIRECT_BUFFER_PFE user_profiling CP_SET_PROTECTED_MODE
_wptr
Enable Protected mode Start User Profiling Indirect Buffer End User Profiling Disable Protected mode
The Process of Exploiting CVE-2019-10567
CP Instructions In Indirect Buffer CP_WAIT_REG_MEM
CP_SET_PROTECTED_MODE user_profiling
RPTR Prefetched Instructions
CP_INDIRECT_BUFFER_PFE CP_INDIRECT_BUFFER_PFE CP_INDIRECT_BUFFER_PFE CP_INDIRECT_BUFFER_PFE CP_INDIRECT_BUFFER_PFE
Deceive to
user_profiling CP_SET_PROTECTED_MODE
RPTR _wptr
1 Before Overwrite
CP_SET_PROTECTED_MODE user_profiling CP_INDIRECT_BUFFER_PFE CP_INDIRECT_BUFFER_PFE CP_INDIRECT_BUFFER_PFE CP_INDIRECT_BUFFER_PFE user_profiling CP_SET_PROTECTED_MODE CP_SET_PROTECTED_MODE user_profiling CP_INDIRECT_BUFFER_PFE user_profiling CP_SET_PROTECTED_MODE
CP_SET_PROTECTED_MODE user_profiling CP_INDIRECT_BUFFER_PFE CP_INDIRECT_BUFFER_PFE CP_INDIRECT_BUFFER_PFE CP_INDIRECT_BUFFER_PFE user_profiling CP_SET_PROTECTED_MODE CP_NOP CP_SET_PROTECTED_MODE CP_SuEsTe_rP_RpOrToEfCiTlEiDn_gMODE CP_INDIRECT_BUFFER_PFE user_profiling CP_SET_PROTECTED_MODE
2 After Overwrite 3 Execute from Middle
Disable Protected mode CP Instructions In Indirect Buffer To Modify TTBR
Demo
Thanks