分析各种存储器格式
初始化结构
_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(®_dword_203060[op_info->op_data1], ®_dword_203060[op_info->op_data2]);// *a1 += *a2;
包含加加减减操作,都是取指指令肯定是栈
save_to_qword_203058(®_dword_203060[op_info->op_data2]);// *(_DWORD *)(qword_203058 + 4LL * ++esp_dword_203010) = *a1;
对某个内存的访问就是定义的内存
load(®_dword_203060[op_info->op_data1], ®_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
需要动态调试
确定是放到一个地址处还是放到一个地址指向的内容
操作数读入寄存器
实现交互
*(&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)