Dungeon Crawl Stone Soup


Dungeon Crawl Stone Soup, aka DCSS, aka “crawl”, is an open source Rogue-like game, available at https://github.com/crawl/crawl. Public DCSS servers (crawl.kelbi.org, crawl.develz.org, crawl.akrasiac.org, underhound.eu, crawl.beRotato.org, lazy-life.ddo.jp, webzook.net, crawl.xtahua.com, crawl.project357.org) allow users to play DCSS without compiling it or installing it on their own system. On these servers, before starting a game players can upload their own .crawlrc configuration file. These files can include Lua 5.1 scripts, and the global environment for these scripts includes the load and loadstring functions. load and loadstring can be used to load not just other Lua scripts, but also valid or invalid Lua bytecode. Although Lua 5.1 includes a perfunctory bytecode verifier, there are known weaknesses in that verifier which have been well documented in the security community.

I was able to adapt the exploit described at https://github.com/sghctoma/gamehack-defcon23/blob/master/demo6_logitech/redis_exploits/Redis_RCE_v2.pdf, which was itself based on https://gist.github.com/corsix/6575486, to attack DCSS and successfully launch a shell. My exploits are available at https://pastebin.com/36Z5iMa8 and https://pastebin.com/5Gkfvab4. They achieve arbitrary memory reads and writes via the known bytecode weaknesses, and then locate the system(3) function in libc. system is then invoked repeatedly to execute the list of commands; in my exploits, these are /usr/bin/reset and /bin/sh. The exploits can be used against any of the public DCSS servers to achieve remote code execution.

My exploits assume the system is running 64-bit Linux. The above links describe how the attack can be applied to 32-bit systems and Windows systems as well, so any public DCSS system regardless of OS is vulnerable. But if you are on 64-bit Linux, the only additional modification you need to make to my exploits is to this line:
local pht_offset_from_auxwrap = 0x8ba060

To defeat ASLR, the exploits compute the beginning address of the text segment instead of hardcoding it. They first find the memory address of the luaB_auxwrap function and then subtract pht_offset_from_auxwrap. A real attacker would script a brute-force attack to determine the correct value for pht_offset_from_auxwrap on a given server. There is a limited range for the correct value, so a few hundred attempts should be enough. To determine the correct value for a system without a brute-force attack, use /proc/<pid>/maps to determine the start of the text segment and then subtract this value from &luaB_auxwrap.

First, luaB_auxwrap:
(gdb) print &luaB_auxwrap
$1 = (<text variable, no debug info> *) 0x555555e0e060 <luaB_auxwrap>

Next, the start of the DCSS text segment:
$ ps -ef | grep crawl
dmenden+ 243 8 0 15:06 pts/0 00:00:00 gdb ./crawl
dmenden+ 249 243 1 15:08 pts/0 00:00:00 /home/dmendenhall/crawl/crawl-ref/source/crawl
$ cat /proc/249/maps
555555554000-55555605d000 r-xp 00000000 08:10 315161 /home/dmendenhall/crawl/crawl-ref/source/crawl
55555605d000-555556094000 r--p 00b08000 08:10 315161 /home/dmendenhall/crawl/crawl-ref/source/crawl
555556094000-5555560cb000 rw-p 00b3f000 08:10 315161 /home/dmendenhall/crawl/crawl-ref/source/crawl 5
[...]

The very first number in this output (0x555555554000) is the start of the text segment.

Finally:
0x555555e0e060 - 0x555555554000 = 0x8ba060

Patch

To address this vulnerability, I recommended either disabling load/loadstring entirely in the .crawlrc Lua environment, or disabling bytecode loading from load/loadstring. All bytecode chunks will begin with the byte 0x1b (\27 decimal). No Lua script chunks will begin with this byte, so testing this first byte should be enough to block any bytecode.

I also recommended disabling the printing of function addresses
{ print(function() end) },
as this provides another vector for defeating ASLR (not used in my exploits).

Note that an upgrade to Lua 5.2 or Lua 5.3 will not resolve the vulnerability. loadstring was renamed to load, but the ability to load bytecode at runtime is otherwise still available. The bytecode verifier was also completely removed starting in Lua 5.2, making other exploits possible. These other exploits are also well-documented in the security community.

The DCSS developers ultimately fixed this vulnerability in commits fc522ff and 768f60d.

UPDATE 4/12/20: MITRE has issued CVE-2020-11722 for this bug

Comments

Popular posts from this blog

NetHack 3.6.6, or, How to Glitch NetHack

Fuzzing NetHack