首页
社区
课程
招聘
[原创]CSAW'18 CTF (reverse)- kvm
发表于: 2023-5-14 17:04 8051

[原创]CSAW'18 CTF (reverse)- kvm

2023-5-14 17:04
8051

一道关于kvm(kernel-based virtual machine)逆向的ctf题目
在介绍题目前,先给出一些关于kvm的前置知识:
1.KVM全称是kernel-based virtual machine(基于内核的虚拟机)。它包含一个为处理器提供底层虚拟化,可加载的核心模块kvm.ko,CPU 状态存储在 KVM 虚拟机的 VCPU 中,内存状态存储在虚拟机的 RAM 中,设备状态存储在虚拟机的 I/O 端口中。。
2.QEMU: KVM 虚机使用的 QEMU 代码,运行在用户空间,提供硬件 I/O 虚拟化,通过 IOCTL /dev/kvm 设备和 KVM 交互
分析二进制文件逻辑
第一步 我们看到ida里有一个main函数,先查看内容
图片描述
发现分别调用了sub_AA0 sub_CAE sub_12BD
sub_AA0
打开设备“/dev/kvm”,获取kvm句柄

传递文件描述符作为 ioctl 系统调用的参数,获取虚拟机版本号

ioctl(kvm_state->fd, KVM_GET_API_VERSION, 0)


创建虚拟机
图片描述

其中fd是与在 vm_init 中创建的虚拟机关联的文件描述符

sub_CAE
创建vcpu,,为vCPU分配内存空间,并将内存空间映射到用户态 (vcpu结构是一个虚拟cpu,成员kvm_run是用于在内核和用户空间之间传递CPU 信息的结构)

1
2
cpu->kvm_run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED,
                       cpu->kvm_fd, 0);

图片描述
图片描述
sub_12BD
易看出此处为来获取虚拟 CPU 的所有寄存器值

1
ioctl(cpu->kvm_fd, KVM_SET_SREGS, &sregs)

图片描述
我们去逆向失败的地方看一下
图片描述
发现程序以0x4090AE82 作为请求调用ioctl来为虚拟 CPU 设置新的寄存器值
图片描述
查看sub_202174函数
图片描述

 

关键点:比较用户输入
图片描述
反汇编为地址2174处的函数调用
图片描述
图片描述
进行I/O,端口为0xE9直接向主机控制台发送文本。
线程进入循环,并捕获虚拟机退出原因,做相应的处理。虚拟机遇到IO操作,访问硬件设备,会退出执行,并将CPU执行上下文返回到QEMU。
图片描述
下面给出kvm_run结构介绍

1
2
3
4
5
6
7
struct kvm_run {
    __u8 request_interrupt_window; //向VCPU注入一个中断,让VCPU做好相关准备工作
    
    __u8 ready_for_interrupt_injection; //响应request_interrupt_window的中断请求,当设置时,说明VCPU可以接收中断。
    __u8 if_flag; //中断使能标识,如果使用了APIC,则无效
    struct {
            __u64 hardware_exit_reason; //当发生VM-Exit时,该字段保存了由于硬件原因导致VM-Exit的相关信息。

转到下一个函数
这个函数判断上一个函数返回的值,为0,返回wrong退出
图片描述
去地址1300h查看,发现是一个二叉树


返回查看sub_202354
图片描述
结合前面对cpu的寄存器的操作,我们转到地址为0x3493310D
反汇编这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def decode(c, root):
if root.value == 0xFF:
ret = decode(c, root.left)
if ret == 1:
write_bit(0)
return 1
else:
ret = decode(c, root.right)
if ret == 1:
write_bit(1)
return 1
else:
return 0
else:
if c == root.value:
return 1
else:
return 0

我们发现这段代码为霍夫曼编码(霍夫曼编码使用变长编码表对源符号进行编码,编码表通过评估来源符号出现机率的方法得到的,出现机率高的字母用较短的编码,出现机率低的则用较长的编码,使编码之后的字符串的平均长度、期望值降低)
wirte_bit(用户输入的值)从地址0x1300开始写入,并用 memcmp函数进行比较,此处就是解题的关键点。

 

解题代码
用霍夫曼解码算法来解压缩密码,下面的类表示一个节点,它有两个子节点(左、右)和一个值。成员 id 是负载文件中节点结构的地址。

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
import struct
 
class HuffmanNode:
    def __init__(self, value=None):
        self.value = value
        self.left = None
        self.right = None
 
def read_qword(file, addr):
    file.seek(addr)
    dword = struct.unpack("<Q", file.read(8))[0]
    return dword
 
def build_huffman_tree(file):
    root_addr = 0x1300
    root_value = read_qword(file, root_addr)
    root = HuffmanNode(root_value)
 
    def build_tree(node):
        if node.value == 0xFF:
            left_addr = read_qword(file, node.id + 8)
            right_addr = read_qword(file, node.id + 16)
            if left_addr != 0 and left_addr < 0x1310:
                left_node = HuffmanNode()
                node.left = left_node
                left_node.id = left_addr
                left_value = read_qword(file, left_addr)
                left_node.value = left_value
                build_tree(left_node)
            if right_addr != 0 and right_addr < 0x1310:
                right_node = HuffmanNode()
                node.right = right_node
                right_node.id = right_addr
                right_value = read_qword(file, right_addr)
                right_node.value = right_value
                build_tree(right_node)
 
    build_tree(root)
    return root
 
def read_compressed_data(file):
    file.seek(0x580)
    data = file.read(0x54A)
    binarystring = ""
    for c in data:
        binarystring += format(ord(c), "08b")
    return binarystring
 
def decode_huffman_data(binarystring, root):
    current_node = root
    decoded_data = ""
    for bit in binarystring:
        if bit == "0":
            current_node = current_node.left
        elif bit == "1":
            current_node = current_node.right
        if current_node.value is not None:
            decoded_data += chr(current_node.value)
            current_node = root
    return decoded_data
 
def main():
    with open("payload.bin", "rb") as f:
        huffman_tree = build_huffman_tree(f)
        binarystring = read_compressed_data(f)
        decoded_data = decode_huffman_data(binarystring, huffman_tree)
        print(decoded_data)
 
if __name__ == "__main__":
    main()

得到的:flag{who would win? 100 ctf teams or 1 obfuscat3d boi?}


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 1
支持
分享
最新回复 (1)
雪    币: 2787
活跃值: (30801)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2023-5-14 18:19
2
游客
登录 | 注册 方可回帖
返回
//