Linux Kernel - EoP by modprobe & 2021 3kCTF echo nerf
Author: 堇姬Naup
前言
一個酷酷的 kernel pwn 萬解 www
調用鏈
當你去執行一個檔案時會有以下調用鏈
do_execve
第一步去 call do_execve
https://elixir.bootlin.com/linux/v6.13.7/source/fs/exec.c#L2040
1 | static int do_execve(struct filename *filename, |
第一個參數會傳入 filename pointer
之後去 call execveat
do_execveat_common
https://elixir.bootlin.com/linux/v6.13.7/source/fs/exec.c#L1896
1 | static int do_execveat_common(int fd, struct filename *filename, |
首先他初始化一個叫做 linux_binprm 的東西
他用來儲存可執行文件的相關訊息
之後去檢查 filename 是不是個 error pointer
1 | /* |
檢查是否曾經標記為行程數過多 (PF_NPROC_EXCEEDED)。
且再次確認目前是否仍然超過 RLIMIT_NPROC 限制。
如果真的超過了,就回傳錯誤碼 -EAGAIN,跳到錯誤處理段落 out_ret
flags 相關可以看這邊
https://elixir.bootlin.com/linux/v6.13.7/source/include/linux/sched.h#L1671
1 | retval = count(argv, MAX_ARG_STRINGS); |
計算 argv 和 envp 的數量,檢查是否超過限制或為空,確保整體不超出堆疊大小限制
將filename、環境變數、參數按順序複製到行程的堆疊空間中
最後去 call
1 | retval = bprm_execve(bprm); |
bprm_execve
https://elixir.bootlin.com/linux/v6.13.7/source/fs/exec.c#L1841
1 | int retval; |
先去初始化了 brpm 的 cred
1 | check_unsafe_exec(bprm); |
之後檢查 bprm 是否有不安全的 execve() 條件
然後去call exec_binprm
1 | retval = exec_binprm(bprm); |
exec_binprm
https://elixir.bootlin.com/linux/v6.13.7/source/fs/exec.c#L1796
1 | pid_t old_pid, old_vpid; |
這會去儲存當前所在的 pid 到 bprm 中
然後去 call search_binary_handler
search_binary_handler
https://elixir.bootlin.com/linux/v6.13.7/source/fs/exec.c#L1749
1 | list_for_each_entry(fmt, &formats, lh) { |
search_binary_handle() 遍歷 formats,去嘗試 load_binary()
如果沒有找到對應的 binary handle
就去這個分支
1 | if (need_retry) { |
之後去 call request_module
request_module
https://elixir.bootlin.com/linux/v6.13.7/source/kernel/module/kmod.c#L131
request_module() 會試圖通過 modprobe 來載入所需的模組,然後繼續執行該檔案
call_modprobe
https://elixir.bootlin.com/linux/v6.13.7/source/kernel/module/kmod.c#L71
這段最重要的在這裡,他會用 root 權限去執行 modprobe_path
所以說如果我們能覆蓋掉 modprobe_path 成我們的 script
就可以做到 EoP
PS: call_usermodehelper_setup 會在 userspace 創一個 subprocess
1 | argv[0] = modprobe_path; |
通過 cat /proc/sys/kernel/modprobe
可以查看當前 modprode_path 指向的東西
也就是說當有 kernel 任意寫去任意寫調 modprobe_path 時這裡會改變
modprobe path EoP
準備兩個 file
一個 file 是無法被 kernel 識別的 file (/tmp/exec)
一個是 script,用來將 flag 寫到其他可讀的地方 (/tmp/p)
1 | void setup() |
我們先去修改 mod_probe 成 /tmp/p
再去執行 /tmp/exec 就可以執行我們讀 flag 的操作了
3kctf 2021 - echo
https://static.2021.ctf.the3000.org/challenges/pwn/echo-447e7d55a7bc1f96518a18b7d645320b.tgz
由於這次 exploit 目的在於復現出 modprobe 這個方法來 RCE
因此先把這題 kaslr 關掉
一樣先做一些前置的準備
前置準備
start.sh
1 |
|
然後解壓整包 file
1 | mkdir -p sysfile |
analyze source code
非常短,他添加了一個 syscall,當你去 call 他時他會往你指定的位置寫入東西
就是一個任意寫
1 |
|
這邊先來嘗試寫 modprobe_path
第一步要來找他的 address
先把 init 改成 root ,來去印 symbolsetsid /bin/cttyhack setuidgid 0 /bin/sh
通過這種方式可以直接找到 address (0xffffffff81837cc0)
1 | / # cat /proc/kallsyms | grep modprobe_path |
並且可以看到 modprobe 現在指在 /sbin/modprobe
1 | / # cat /proc/sys/kernel/modprobe |
執行完這份 exploit 後就會改指向 /tmp/a 了
1 | / # cat /proc/sys/kernel/modprobe |
1 |
|
之後我們只要去執行 /tmp/unknown
就會觸發 modprobe_path 了,我們的 flag 就被寫入到一般使用者可以讀的檔案了
exploit
1 |
|