Writeup-Pwnable: syscall

// 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 );

这题就是提供了一个可以write-anything-anywhere的系统调用(也不算anything,有点限制),系统调用的地址存在0x8000e348+223 = 0x8000e6c4, flag在/root/flag.

按理说应该不难,但是我做了很久。后来想了一下,主要是内联汇编不熟(没有写好 clobbers 导致各种崩)。再就是没有深入理解 Linux 的权限控制机制,一开始想当然的觉得 kernel space 就肯定有 root 权限,后来发现就算用了系统调用,跑到奇怪的地址上执行,uid 还是这个进程的 uid1. 所以尝试的 open-read-write, chown()各种都已失败告终,权限不够。直接在 kernel space execve()的我也是想多了。

其实思路还是挺简单的,首先修改 223 号系统调用的内容,然后调用这个修改过的 223 号系统调用,在 kernel space 把 uid 改掉,之后在 user space execve()就好了。

在现在版本的 Linux 内核修改 uid,需要通过prepare_creds()commit_creds()两步2。这两个函数的地址存在/proc/kallsyms:

$ cat /proc/kallsyms | grep 'prepare_creds\|commit_creds'
8003f44c T prepare_creds
8003f56c T commit_creds
...

我参考 @acama 的版本3写了一个( @acama 的版本prepare_creds()之后直接就commit_creds(), 这估计只在老版本可以).prepare_creds()返回的结构体定义可以看参考4.

这个生成的指令是不能用原先的 223 号系统调用直接写进内存的,所以我准备了一个真正的write-anything-anywhere的跳板:

先把 waa 写进内存,然后把 cred 写进内存。至于写到哪里,我随手写了两个地址: 0x83f5cafe, 0x83f6beee.

exp.c

References

Creative Commons License