----------------------------------------------------------------------------- [Defcon 2009' Pre-qual] Crypto 400 Solution written by hahah http://beist.org ----------------------------------------------------------------------------- I must say, It was great challenge and we had lots of fun. I'd like to thank, http://ddtek.biz You can download the binary of this challenge. http://beist.org/defcon2009/defcon2009_crypto400_binary And, here is source code of the challenge by chpie's hand-rays :D (it's amazing!) Maybe it could give you some help to understand what crypt400 binary does easily. ## chpie converted crypto400 binary C language. ## void vasprintf_hexcode(Socket sock, char * fmt, char data) { int ret; char * buffer; va_list va; va_start(va, fmt); if (vasprintf(&buffer, fmt, va) != -1 && buffer != 0) { ret = sender_1(sock, buffer, 0); } else ret = -1; free(buffer); return ret; } void printing_out_hex_va_list(Socket sock, char * ptr) { unsigned int loop; sender_1(sock, "Welcome "); iterative_send_syscall(sock, &ptr[36], 17); sender_1(sock, ":\nYour challenge is to decode the following hex encoded string:\n"); for (loop = 0; loop < ptr[32]; loop++) { vasprintf_hexcode(sock, "%02.2x", ptr[loop]); } sender_1(sock, "\nGood luck!\n"); } void printing_out_hex(Socket sock, char * ptr) { unsigned int loop; singlestream_sending(sock, "Welcome "); iterative_send_syscall(sock, &ptr[36], 34); singlestream_sending(sock, ":\nYour challenge is to decode the following hex encoded string:\n"); for (loop = 0; loop < ptr[32]; loop++) { snprintf(stringbuffer, 32, "%02.2x", ptr[loop]); singlestream_sending(sock, stringbuffer); } singlestream_sending(sock, "\nGood luck!\n"); } int recv_until_0xa(Socket sock, char * ptr, unsigned int size) { unsigned char temp; unsigned int loop; memset(ptr, 0x20, size); for (loop = 0; loop < size; loop++) { if (recv(sock, temp, 1, 0) == 0) return -1; if (temp == 0xA) break; ptr[loop] = temp; } return 1; } int recv_working(Socket sock, char * ptr, unsigned int size) { unsigned short recvbuf; unsigned int loop; for (loop = 0; loop < size; loop++) { *((unsigned short *)&ptr[loop * 2]) = 0x20; if (recv(sock, recvbuf, 2, 0) != 2) return 0; ptr[loop * 2] = ntohs(recvbuf); } return 1; } unsigned char global_swapping(void) { unsigned char temp; global_index = global_index + 1; global_char = global_char + global_loop_tb[global_index]; temp = global_loop_tb[global_char]; global_loop_tb[global_char] = global_loop_tb[global_index]; global_loop_tb[global_index] = temp; temp += global_loop_tb[global_char]; return global_loop_tb[temp]; } void singlestream_sending(Socket sock, char * string) { unsigned short temp; while (*string != 0) { temp = htons(*string); send(sock, &temp, 2, 0); string++; } } void global_setup(unsigned char * ptr, unsigned int size) { unsigned int loop; unsigned int saved_loop; global_char = 0; for (loop = 0; loop <= 0xFF; loop++) { global_loop_tb[loop] = loop; } for (loop = 0; loop <= 0xFF; loop++) { global_char += global_loop_tb[loop] + ptr[loop % size]; saved_loop = global_loop_tb[global_char]; global_loop_tb[global_char] = global_loop_tb[loop]; global_loop_tb[loop] = saved_loop; } global_char =0; global_index = 0; } unsigned int reader_1(Socket sock, char * buffer, unsigned int size, unsigned char stopchar) { unsigned int current_readed; unsigned int loop; unsigned int stop = stopchar; loop = 0; do { if (read(sock, ¤t_readed, 1) == 0) return -1; if (current_readed == stopchar) break; if (loop >= size) return -1; buffer[loop++] = current_readed; } while(1); return loop; } unsigned int iterative_send_syscall(Socket sock, char * str, unsigned int length) { unsigned int loop; unsigned int sendedBytes; for (loop = 0; loop < length; loop += sendedBytes) { sendedBytes = send(sock, str[loop], length - loop); if (sendedBytes == 0) return -1; } return loop; } function sender_1(Socket sock, char * str, int unknown) { unsigned int length; unsigned int strlength; strlength = strlen(str); if (unknown) length = strlength + 1; else length = strlength; iterative_send_syscall(sock, str, length); } function ForkedProcessHandler(Socket sock) { int ret; int var; int var_2; unsigned char buffer[14]; char * ptr; int urand_fd; unsigned int readedBytes; FILE * fp; char * urandBuffer; unsigned int loop; sender_1(sock, "Password: ", 0); readedBytes = reader_1(sock, buffer, 0x11, 0xa); buffer[readedBytes] = '\0'; if (strncmp(buffer, "chickenfingers", 15) != 0) { ret = 0; goto exit_fo; } var = var_2&1; urand_fd = open("/dev/urandom", 0); if (var) { urand_buffer = (char *)malloc(76); read(urand_fd, &urand_buffer[68], 5); global_setup(&urand_buffer[68], 5); singlestream_sending(sock, "What is your name? "); } else { ptr = (char *)malloc(60); read(urand_fd, &ptr[52], 5); global_setup(&ptr[52], 5); sender_1(sock, "What is your name? ", 0); } close(urand_fd); fp = fopen("plaintext", "r"); if (!fp) { sender_1(sock, "oops!\n", 0); free(ptr); ret = 0; goto exit_fo; } ptr[32] = fread(ptr, 1, 32, fp); fclose(fp); if (ptr[32] != NULL) { for (loop = 0; loop < ptr[32]; loop++) { ptr[loop] = ptr[loop] ^ global_swapping(); } if (var) { recv_working(sock, &ptr[36], 16); printing_out_hex(sock, ptr); } else { recv_until_0xa(sock, &ptr[36], 16); printing_out_hex_va_list(sock, ptr); } } free(ptr); exit_fo: return ret; } Crypto400 challenge is about Cryptography, but, that has some vulnerabilities. ForkedProcessHandler() function reads 17 bytes, when a user inputs "chikenfingers". So, you can say it has buffer overflow vuln. That means, We can change var_2, and var_2 sets var. If var is 1, this program sends and receives data as short type, and if var is 0, it sends and receives data as byte type. After receiving "chickenfingers", it sets encoding table up using urandom value, and gets 16-byte name. The encoding algorithm is a kind of stream cipher using xor, so if we know the urandom_key, we can get plaintext by encoding once again. After encoding plaintext, it sends Welcome messages. When var is 1, in printing_out_hex function, singlestream_sending(sock, "Welcome "); iterative_send_syscall(sock, &ptr[36], 34); When var is 0 , in printing_out_hex_va_list function, sender_1(sock, "Welcome "); iterative_send_syscall(sock, &ptr[36], 17); In both cases, iterative_send_syscall sends longer than name! This is a important point. When var is 1, because name is saved in short type, ptr[36]~ptr[68] is name, and when vare is 0, ptr[36]~ptr[52] is name. Thus, when var is 1, iterative_send_syscall sends 2 more bytes, and when var is 0, it sends 1 more byte. These excessive bytes are urandom_key values. So, if var is 1, we can get 2 bytes of urandom_key, and if var is 0, we can get 1 byte of it. By sending password like this : "chickenfingers111", I got 2 bytes of urandom_key. Welcome 11111111111111111111111111111111Xi: Your challenge is to decode the following hex encoded string: cb6bfddc02249119f4aadac822171ef1e8af1f92a24eeabb4cd1e7bb94ed59cb Xi is first 2 byte, so I needed 3 more bytes to decode the encoded string. Here is my code that used to get extra 3 bytes using brute forcing. void main() { unsigned char key[]="Xi\x00\x00\x00"; // urandom_key Xi is known, 3 // bytes unknown. unsigned char code[]= "\xcb\x6b\xfd\xdc\x02\x24\x91\x19\xf4\xaa\xda\xc8\x22\x17\x1e\xf1\xe8\xaf\x1f" "\x92\xa2\x4e\xea\xbb\x4c\xd1\xe7\xbb\x94\xed\x59\xcb"; // encoded string unsigned char plain[100]; unsigned int i,j,k,l; unsigned int check; for(i=0; i<=0xff; i++) // 3rd byte { key[2] = i; for(j=0; j<=0xff;j++) // 4th byte { key[3] = j; for(k=0; k<=0xff; k++) // 5th byte { memset(plain,0,sizeof(plain)); memcpy(plain,code,32); key[4] = k; check=1; global_setup(key,5); for(l=0; l<32; l++) { plain[l] ^= global_swapping(); if(plain[l] >'~' || plain[l] <' '){ //plaintext is ascii, I guess :D check=0; break; } } if(check==1) printf("plaintext : %s \nurandom_key : %x %x %x %x %x\n",plain,'X','i',i,j,k); } } } } Finally... I got the plaintext !! plaintext : Anal leakage beats key leakage a urandom_key : 58 69 ab 77 f2 Have a fun.