summaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
authorJakob L. Kreuze <jakob@memeware.net>2018-01-04 15:51:01 -0500
committerJakob L. Kreuze <jakob@memeware.net>2018-01-04 15:51:01 -0500
commitb4150040a46d4e7bbbe919cdf9be60e0109191bc (patch)
tree6fea6925bf05bdc311be98d75ceb3182ebaf78c9 /README.md
parent4211a84821e84cfe7848e32476510e2c768e3577 (diff)
Final edits in preparation for disclosure.
Diffstat (limited to 'README.md')
-rw-r--r--README.md67
1 files changed, 63 insertions, 4 deletions
diff --git a/README.md b/README.md
index e4191d4..480c9b4 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,69 @@
![Bad BEHAVIOR][img_1]
-## Proof of concept exploit and research by [Jakob.][1]
-Exploit of GZDoom's ACS interpreter, performing an out-of-bounds write with
-maliciously-crafted ACS bytecode.
+A vulnerability was recently discovered that affects several implementations of
+ACS, a domain-specific scripting language for Doom maps. As a stack machine, ACS
+has several opcodes for incrementing and decrementing a stack pointer. If these
+increments and decrements are performed without bounds checking, it is possible
+for a maliciously-crafted BEHAVIOR lump to produce an out-of-bounds write. Here
+is the implementation of the PUSHBYTE opcode, taken from GZDoom's ACS
+interpreter:
+```c
+case PCD_PUSHBYTE:
+ PushToStack (*(uint8_t *)pc);
+ pc = (int *)((uint8_t *)pc + 1);
+ break;
+```
-[1]: http://jakob.space/
+Where PushToStack is a macro defined as:
+
+```c
+#define PushToStack(a) (Stack[sp++] = (a))
+```
+
+The stack pointer is incremented, but it does not check to see if the new value
+is greater than the stack's maximum size. Because the buffer used by the
+interpreter is placed on the process's stack, it is possible to overwrite the
+interpreter routine's return pointer, resulting in arbitrary code execution.
+
+**Maintainers of Doom source ports,**
+
+It is critical that you inspect your port's implementation of the ACS
+interpreter, and patch it if vulnerable to the exploit presented here. Every
+opcode that modifies the stack pointer should perform a bounds check, ensuring
+that it does not fall below 0, and does not rise greater than the maximum index
+of the stack buffer. A rather crude example of a solution for PUSHBYTE is shown
+below, however, brevity could be retained by making use of operator overloading,
+or using a class with methods for pushing and popping that do perform bounds
+checking.
+
+```c
+case PCD_PUSHBYTE:
+ if (++sp >= STACK_SIZE) {
+ I_Error("Corrupted stack pointer in ACS VM");
+ }
+ Stack[sp] = (*(uint8_t *)pc);
+ pc = (int *)((uint8_t *)pc + 1);
+ break;
+```
+
+Provided for vulnerability assessment is the file `example.wad`, which contains
+a BEHAVIOR lump that will overwrite the interpreter routine's return pointer in
+the latest GZDoom development builds. The stack offset should be different
+across different source ports, but implementations where the vulnerability has
+been dealt with should throw an error upon loading MAP01 regardless.
+
+Additionally provided is a Python script which will craft a malicious BEHAVIOR
+lump to write 0xdeadbeefcafebabe at a given offset from the beginning of the
+interpreter's stack buffer. This is intentionally very obtuse to work with,
+however if you are a source port maintainer and would like to be able to further
+experiment with the vulnerability, it is provided in its full form. A majority
+of the code is present to form a valid BEHAVIOR lump; the real exploit is only
+in the creation of the `payload` list.
+
+A more detailed and prose-like writeup, with details on the research process is
+[available here][1].
+
+[1]: http://jakob.space/blog/post/Bad+BEHAVIOR
[img_1]: https://raw.githubusercontent.com/TsarFox/bad-behavior/master/logo.png