p = Runtime.getRuntime().exec("su");
所以,1159 PID的sh会启动su,这里需要注意的是,su进程的Real UID是10073,因为继承自parent,但是,其EUID却提升为了ROOT,这就是由于SUID被设置的原因。 由于su进程的EUID是ROOT,所以导致了su可以做很多高权限的事情。
su会通过:
sprintf(sysCmd, "am start -a android.intent.action.MAIN -n com.koushikdutta.superuser/com.koushikdutta.superuser.SuperuserRequestActivity --ei uid %d --ei pid %d > /dev/null", g_puid, ppid);
if (system(sysCmd)) return executionFailure("am.");启动SuperUser Request Activity来询问用户是否授权当前应用,如果否的话,则su将return 结束, 否则su会继续运行。
得到用户许可后(通过sqlite和SuperUser Request Activity交流用户许可),su会将自己的Real UID也设置为ROOT:
if(setgid(0) || setuid(0))
return permissionDenied();由于su的EUID是ROOT,所以su有权限执行以上代码,执行完后su进程的Real UID,effective UID都变成了ROOT。 这里为什么要同时提升Real UID的权限,后面会说明。
然后,su会启动一个额外的sh来运行用户的指令:
char *exec_args[argc + 1];
exec_args[argc] = NULL; exec_args[0] = "sh"; int i; for (i = 1; i < argc; i++) { exec_args[i] = argv[i];}
execv("/system/bin/sh", exec_args); return executionFailure("sh");这里非常关键的代码是execv! 这里没有使用fork,而是直接使用execv,这将导致不会创建新的进程运行sh image,而是直接在当前su的进程空间load并执行sh image。 所以,return executionFailure("sh"); 正常情况下是永远不会被执行的,执行完execv后,就开始直接sh的代码了。
这里可以看到,由于复用su当前进程来运行sh,使得sh运行的进程的EUID,Real UID都是ROOT,那么,任何操作都可以执行了。 如果不提升Real UID为Root的话,那么只要sh运行中通过启动外部程序的方式来完成操作的话,就会出现权限问题,因为新启动的进程只会继承父进程的Real UID,而不是EUID,那么新起的进程的Real UID=EUID= parent RealUID != ROOT,那么某些操作可能有问题。 所以,su把自己的Real UID也提升为ROOT后,则不管启动后的任何代后的子进程执行操作,都是以ROOT的权限运行。
RootExplorer通过得到sh流后,就可以通过该流执行任何shell指令了:
try { // Preform su to get root privledges p = Runtime.getRuntime().exec( "su"); // Attempt to write a file to a root-only DataOutputStream os = new DataOutputStream(p.getOutputStream()); os.writeBytes( "echo \"Do I have root?\" >/system/sd/temporary.txt\n"); // Close the terminal os.writeBytes( "exit\n"); os.flush();
通过ps也可以明显看到su变成了sh,如下,
app_73 1143 103 301620 39944 ffffffff 400194c4 S com.speedsoftware.rootexp
lorer app_73 1159 1143 764 376 c003e454 4001cf94 S /system/bin/sh root 1161 1159 772 388 c012e760 4007c578 S sh这里的PID=1161的进程其实是su(可以通过打log验证),当执行execv("/system/bin/sh", exec_args);后,进程名就变成了sh。当然进程EUID不变,仍然是ROOT。
通过cat 进程的proc信息,我们也可以看到其实RootExplorer自身的UID或者权限根本没有被提升(这是和linux上执行su不一样的地方),
:/proc/1143 # cat status
cat status Name: re.rootexplorer State: S (sleeping) Tgid: 1143 Pid: 1143 PPid: 103 TracerPid: 0 Uid: 10073(Real) 10073(Effecttive) 10073(saved) 10073 Gid: 10073 10073 10073 10073而su演变成的sh,却是具有所有的ROOT UID:
:/proc/1161 # cat status
cat status Name: sh State: S (sleeping) Tgid: 1161 Pid: 1161 PPid: 1159 TracerPid: 0 Uid: 0 0 0 0 Gid: 0 0 0 0
总结: Android中App授权获取Root权限,其实不是App自身的权限提升了,而是通过具有ROOT权限的sh流来执行shell命令