Linux Kernel Pwn - EBH
Author: 堇姬Naup
前言
之前沒碰過 kernel pwn 沒解出來,現在回來解看看
analyze source code
1 | naup@naup-virtual-machine:~/Desktop/chal/challenge$ ls |
底下是常見 kernel pwn 三個檔案
1 |
|
先來修一下啟動腳本,下面是 source 中有關砍掉 qemu 的段落
另外修改路徑跟跟後面一段是將上傳檔案 mount 進 qemu 內,我們把它砍掉,方便在本地 debug
1 |
|
接下來是 qemu 的 file system
先把他解包一個下來看
1 | mkdir -p sysfile |
接下來進去看 init
1 |
|
這裡面也會設定 mount 進來 user 上傳的檔案kernel.perf_event_paranoid
則是
https://hackmd.io/@Ji0m0/rJvp3iSTX?type=view
之後隔十五秒後執行 user binary,並寫到 result
把這些註解掉重包
1 |
|
重新打包就這樣find . | cpio -o --format=newc > ../initramfs.cpio
之後直接 ./run.sh
啟動 qemu
如何 debug 可以參考我這篇,寫的很詳細
https://naupjjin.github.io/2025/02/05/Linux-Kernel-Pwn-Environment/
用這個提取 vmlinux
https://raw.githubusercontent.com/torvalds/linux/master/scripts/extract-vmlinux
個人更愛用這個提取
https://github.com/marin-m/vmlinux-to-elf
接下來進入漏洞分析
analyze bug
這題漏洞其實很多
最簡單的漏洞發生在
1 |
|
問題在於它限制你 target 只能小於 0xffffffff00000000
但這限制老實說不多,原因是因為 kernel stack 上的 write_to_address return address 位置在 0xffffc900001b7e58
因為沒有 KASLR 的關係,所以他不會動
是不是非常完美的可以控 RIP,並且可以寫入將近 0x60 長度的 payload,接下來就開始串吧
順便說一下要怎麼找 return address
在 qemu 啟動時加入啟動參數
1 | -gdb tcp::1234 \ |
他會卡住,接下來在另一個視窗開 gdb
這邊下 gdb -q -ex "target remote localhost:1234"
連上去
不過會吃 error,原因是權限不夠
下個 echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
就可以了

進去後我選擇下斷點在.text:000000000000010F call _copy_from_user ; PIC mode
用 cat /proc/modules
來去找 KVM 的 base address
之後 c
一下就會卡住了,接下來執行跟 KVM 互動的腳本
我這邊拿我 exploit 中途的來 demo
1 |
|
實際跑起來後就會卡住了

接下來跑到 ret 後就可以看到 return address 了

另外也驗證了可以寫掉 return address 成 pop rdi
ROP
來看如何提權
要提權就是要執行 commit_creds(&init_cred)
這裡其實是可以使用 prepare_kernel_cred 來提權,但是因為能做寫入的長度只有 0x60,代表只能寫 12 條 gadget
https://blog.csdn.net/qq_61670993/article/details/134731172
https://pawnyable.cafe/linux-kernel/LK01/stack_overflow.html
用 cat /proc/kallsyms | grep "..."
來找 gadget
ffffffff810892c0 T commit_creds
ffffffff810895e0 T prepare_kernel_cred
init_cred 這東西是一個特別的 cred,是屬於 init process 的 cred
這邊我卡了一陣子,原因是 init_cred 的 debug symbol 被拔了,所以我們去逆向 prepare_kernel_cred (如果要有修好部分 symbols 的 vmlinux 用 vmlinux-to-elf)
拉進去 IDA ,接下來來看 source code,這邊記得找 v6.2 以前的
https://elixir.bootlin.com/linux/v6.1.129/source/kernel/cred.c#L712
可以看到 prepare_kernel_cred 中
1 | new = kmem_cache_alloc(cred_jar, GFP_KERNEL); |
可以找到這個分支

其中的 0xffffffff 82443f20
是 init_cred address
抓下來,所以一開始先堆
1 | pop rdi |
就可以製造出 commit(&cred)
來提權
接下來就是要跳回 user space 來開個 shell,並且要避免 crash,因為有開 KPTI 跟 SMAP SMEP
我們先了解一下有關這三種保護機制
- SMAP: 無法從 kernel space 讀寫 userspace
- SMEP: 無法從 kernel space 執行 userspace 的 code
- KPTI: userspace kernelspace page 分開
為了要壓短 payload 長度至 0x60 內所以去清 cr3 是不行的
這邊可以用一個酷酷的東西來一次返回 userspace
swapgs_restore_regs_and_return_to_usermode
不過不能夠直接跳到最開頭
我們來分析一下這個東西
1 | 0xffffffff81c00a2f: pop r15 |
首先先跳過前面的 pop 操作,要直接跳到 mov rdi, rsp
1 | 0xffffffff81c00a9e: or rdi,0x1000 |
1 | 0xffffffff81c00ad0: test BYTE PTR [rsp+0x20],0x4 |
首先最前面會 push 東西到 stack 上
這裡要 push 上去兩個垃圾,原因是他會被 pop 掉
而之後 swapgs iretq 會用到這個

需要去設定回 userspace 的各種暫存器
RIP 則設成開 shell 的 function
這樣就可以成功跳回 userspace 並開 shell,並且已經提權完成了
script
1 |
|