Someone pointed out a shell script that was posted to GitHub named ‘killbutmakeitlooklikeanaccident.sh’.

It contains a single command:

#!/bin/bash

gdb -p "$1" -batch -ex 'set {short}$rip = 0x050f' -ex 'set $rax=231' -ex 'set $rdi=0' -ex 'cont'

If you run it, passing a PID as the first argument, the target process terminates with exit code 0. Neat!

This is distinctively different from using the kill command, which sends a SIGTERM to the target processes, causing signal handlers to be called and generally makes the process terminate with exit code 143.

This script only works on x86-64.

How does it work?

The shell script executes gdb, the GNU Debugger, in batch mode (-batch) and tells it to attach to the target process (-p). Then, through the -ex arguments, executes the following script:

set {short}$rip = 0x050f
set $rax=231
set $rdi=0
cont

Let’s look at it line-by-line:

set {short}$rip = 0x050f

This line writes 0x050f to the location RIP is pointing to. GDB syntax can be confusing! This construct ({type}) is covered in the manual under Expressions. Basically, it writes a value to an object of a type (in this case, short) at the provided address. So; it writes 0f 05 (corrected for endianness) at the location RIP is pointing to, which is the next instruction the CPU will execute when the program resumes. Using rasm2 from radare2, we can quickly determine the instruction being executed:

  $ rasm2 -a x86 -d 0f05
  syscall

The target process will execute a syscall instruction next.

set $rax=231

syscall(2) states that, for x86-64, the system call number should be loaded into the RAX register.

We can check which system call has number 231:

$ grep 231 /usr/include/x86_64-linux-gnu/asm/unistd_64.h
#define __NR_exit_group 231

The exit_group system call which terminates all threads in the process.

set $rdi=0

According to syscall(2) the first argument for the system call is stored in the RDI register. According to exit_group(2) the system call has one argument: the process exit code.

cont

The GDB cont command is shorthand for continue (you can also use the even shorter c). This resumes the process, from which point the process will execute the exit_group system call and terminate with exit code 0.

ARM64 version

For fun, I ported the shell script to ARM64/AArch64:

#!/bin/bash

gdb -p "$1" -batch -ex 'set {int}$pc = 0xd4000001' -ex 'set $w8=94' -ex 'set $x0=0' -ex 'cont'

This makes the process execute the svc #0 instruction with registers similarly set up for the exit_group system call.