vmpwn总结


分析各种存储器格式

初始化结构

_DWORD *sub_8049570()
{
  _DWORD *v0; // eax
  _DWORD *v1; // ST1C_4

  v0 = malloc(0x2Cu);
  v1 = v0;
  *v0 = 0;
  v0[1] = 0;
  v0[2] = 0;
  v0[3] = 0;
  v0[4] = 0;
  v0[5] = 0;
  v0[9] = 0;
  v0[10] = calloc(4u, 0x40u); //0x100
  v1[6] = v1[10] + 0xFC;
  v1[7] = v1[10] + 252;
  v1[8] = 0;
  return v1;
}

通过初始化结构定义结构体,每个字段的大小,类型

补充结构体字段命名

然后通过指令分析确定各个字段的含义

像这种进行数学运算的肯定是寄存器

sub_106C(&REG_dword_203060[op_info->op_data1], &REG_dword_203060[op_info->op_data2]);// *a1 += *a2;

包含加加减减操作,都是取指指令肯定是栈

save_to_qword_203058(&REG_dword_203060[op_info->op_data2]);//  *(_DWORD *)(qword_203058 + 4LL * ++esp_dword_203010) = *a1;

对某个内存的访问就是定义的内存

load(&REG_dword_203060[op_info->op_data1], &REG_dword_203060[op_info->op_data2]);// dword_203060[*a1] = dword_203080[*a2]; 寄存器取出两个索引,从内存取值放入寄存器

命名规则

指针和值分开命名

在命名的时候如果是指针需要在名字里体现出来

get_opi_addr(ptr, 1u)
ptr->buffer_ptr

关注点

关注点主要是两个

  • 是指针还是实际的数,在命名上要有体现
  • 取的数据是一个字节还是四个字节
*(&ptr->reg_0 + *v20) *= *(_DWORD *)(ptr->buffer_ptr + 2);

*(&ptr->reg_0 + v19) *= *(&ptr->reg_0 + *(unsigned __int8 *)get_opi_addr(ptr, 2u));

下面的语句表示一个具体值,表示ptr的某一位的值的某一位的值。

ptr->bufer_ptr->field_3
和ptr[2][3]  一样的效果

下面语句表示ptr的某一位的值+3*(1)(这个值是个指针,1是因为char占一个字节)

(char *)ptr->bufer_ptr + 3

分析指令格式

是否被加密

  • 在run的时候是输入的原始数据还是输入的数据进行了加密,一般是进行解密然后输入的数据需要先加密。

  • 有些会进行位交换,取其中几位分别为指令和操作数

    这里写了移位的脚本

#从fro到to进行移位操作,索引从0开始

def left(item,i):
    return (item<<(i*8))&0xffffffffffffffff
def yiwei(content,fro,to,bit=64):
    if bit==64:
        fro_bit=(content>>(fro*8))&0xff #取出来第fro位
        fro_bit_8=fro_bit|left(fro_bit,1)|left(fro_bit,2)|left(fro_bit,3)|left(fro_bit,4)|left(fro_bit,5)|left(fro_bit,6)|left(fro_bit,7)#把fro位复制到其他位
        result= (0xff << (to * 8))&fro_bit_8 #删除多余的
        # print("result:",hex(to))
        return result
    if bit==32:
        fro_bit = (content>>(fro*8))&0xff  # 取出来第fro位
        fro_bit_8 = fro_bit|left(fro_bit,1)|left(fro_bit,2)|left(fro_bit,3)   # 把fro位复制到其他位
        return (0xff << (to * 8)) & fro_bit_8  # 删除多余的

decoding=yiwei(item,3,5)|yiwei(item,2,4)|yiwei(item,1,3)|yiwei(item,0,2)|yiwei(item,4,1)|yiwei(item,5,0)

注意这里实现的是解密,是什么样的输入经过二进制文件实现的加密之后变成想要的值。

从脚本角度就是想要的输入进行解密,然后发送

指令格式

  • 指令的长度,可以是变成也可以是定长

  • 操作数和操作码所在的位置

    分析关键指令

    实际上实现的虚拟指令可能会很多,只需要下面指令类型中的一些即可

寄存器拷贝

不同寄存器之间的拷贝,相当于move指令,有了它会很方便

 *(&ptr->reg_0 + v2) = *(&ptr->reg_0 + *(unsigned __int8 *)get_opi_addr(ptr, 2u));// reg_op1=reg_op2

当然,实在不行可以用寄存器相加代替

寄存器和栈,内存交互

一般情况下栈或者寄存器是整个结构下的一个指针,修改这个指针再修改指针指向的内容可以实现任意地址写

*(_DWORD *)ptr->esp_addr = *(&ptr->reg_0 + *(unsigned __int8 *)get_opi_addr(ptr, 1u));// *(esp-4)=reg_op1 指令长度为2   push reg_op1

这里需要精确地汇编指令,可能ida反编译结果不准确,汇编指令可能是段寻址

.text:00000000000011B5                 mov     rax, cs:EBP_qword_203058

需要动态调试

段寻址对应gdb调试

确定是放到一个地址处还是放到一个地址指向的内容

操作数读入寄存器

实现交互

*(&ptr->reg_0 + *(unsigned __int8 *)get_opi_addr(ptr, 1u)) = *(_DWORD *)(ptr->buffer_ptr + 2);// reg_op1=(四字节)op2

内容输出

用来计算地址,但是不常见,如果有多次输入可能会有这个指令

putchar(*(char *)ptr->reg3);          // put_reg3

算数指令

加载数据之后可以计算出相对的偏移得到想要的地址,不需要输出到显示器

获取shell

劫持执行流主要有两种方式

  • 1、基地址加偏移量,修改偏移量就可以实现任意地址写,一般libc加载地址是变的,这个偏移量也会变,计算起来比较麻烦。
  • 2、向一个地址处写入地址,再向这个地址写入值。这种方式简单但是需要程序里面提供这种两层地址的寻址方式。可以重点观察bss段写入的指针。

泄露

  • puts指令输出地址指向的内容
  • load指令加载地址指向的内容

两个需要一个,但是需要load指令得到地址指向内容的值

用寄存器数学运算计算出实际system地址的偏移,libc的基地址等

修改

修改寄存器的方式在内存中写入一个地址(puts_got),向地址处写数据实现任意地址写(system)


评论
  目录