NetHack 3.6.4

The NetHack DevTeam released NetHack 3.6.4 on December 18, 2019, primarily to address CVE-2019-19905 which I reported to them on December 13. I was impressed with the five day turnaround.

When I started my vulnerability analysis, I considered the following for exploitation:
  • Game play itself -- is there a series of game commands that can cause exploitable memory corruption? This seemed the least promising. The game is heavily played, billions of commands have been entered over the years, and the game engine is highly randomized making exploitability of any one bug, if found, very difficult. I had also read that the DevTeam employs a game fuzzer as part of their regular QA, so that made finding a novel bug even less likely.
  • Command line arguments -- is there a command line option that could cause exploitable memory corruption? Because researchers had explored this in the past, I (wrongly) assumed that there were no more bugs to find in command line parsing. But also, there is no way that I know of to control the command line arguments used to launch NetHack on a public server. That meant any bug I found would only be locally exploitable for setuid/setgid privilege escalation. I really wanted to find something that could lead to remote code execution. I did return to command line arguments as part of my NetHack 3.6.5 research.
  • .nethackrc -- can the Options file be used to cause exploitable memory corruption? Crucially, this is a flat file with a hand-written parser, and the public servers allowed you to upload whatever you wanted to configure your own game options. This route looked the most promising.
I found CVE-2019-19905 after only a few hours of analyzing the options file parser. I noticed quickly that lines could be extended indefinitely using a backslash character. Here is an excerpt of 3.6.3 parse_conf_file in files.c. NetHack source code is available on github here.

                /* merge now read line with previous ones, if necessary */
                if (!ignoreline) {
                    len = strlen(inbuf) + 1;
                    if (buf)
                        len += strlen(buf);
                    tmpbuf = (char *) alloc(len);
                    if (buf) {
                        Sprintf(tmpbuf, "%s %s", buf, inbuf);
                        free(buf);
                    } else
                        Strcpy(tmpbuf, inbuf);
                    buf = tmpbuf;
                }

My first thought was that perhaps there was some limit to the number of lines that could be concatenated together with backslashes. I spent about 30 minutes on nethack.alt.org copying and pasting backslash characters trying to make a .nethackrc that would either crash or DoS the server. This ended up being fruitless. Parsing did take longer and longer, but the parser was able to handle everything I threw at it.

So then I moved on to the code downstream from the concatenator. I found this code in parse_config_line in files.c:

boolean
parse_config_line(origbuf)
char *origbuf;
{
#if defined(MICRO) && !defined(NOCWD_ASSUMPTIONS)
    static boolean ramdisk_specified = FALSE;
#endif
#ifdef SYSCF
    int n;
#endif
    char *bufp, buf[4 * BUFSZ];
    uchar translate[MAXPCHARS];
    int len;
    boolean retval = TRUE;
    int src = iflags.parse_config_file_src;

    /* convert any tab to space, condense consecutive spaces into one,
       remove leading and trailing spaces (exception: if there is nothing
       but spaces, one of them will be kept even though it leads/trails) */
    mungspaces(strcpy(buf, origbuf));

That looked like an classic buffer overflow. If origbuf could be bigger than buf, strcpy would overflow to the stack. Writing shellcode followed by a stack address on top of the function return address would cause the normal function return to instead "return" to the stack and execute the shellcode just written. I quickly verified that while origbuf is normally smaller than 4 * BUFSZ, the backslash concatenation code could make a line longer than sizeof(buf). I had my RCE vulnerability.

Before I submitted my findings to the DevTeam, I wanted to write a proof-of-concept exploit. I made the following changes to my environment to get the POC to work:
  • Added -fno-stack-protector -D_FORTIFY_SOURCE=0 to CFLAGS in the NetHack src Makefile
  • Added -z execstack to LFLAGS in the NetHack src Makefile
  • Turned off ASLR from the command line using setarch `uname -m` -R /bin/bash
Curiously, I also had to switch from Windows Subsystem for Linux v1 to v2. v1 wasn't allowing stack execution for some reason. This required me to join the Windows Insider program.

The POC can be found here.

Comments

Popular posts from this blog

Dungeon Crawl Stone Soup

NetHack 3.6.6, or, How to Glitch NetHack

Fuzzing NetHack