ciscn_2019_pwn1
ciscn_2019_pwn1
山林川泽ida分析
首先来看题目
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
可以看到题目里有明显的格式化字符串漏洞
再来看一下函数表
1 | _init_proc |
发现存在system,但是并不是直接给的后门
1 | int sys() |
攻击手段
言归正传,到这里我们的思路已经很清晰了,通过格式化字符串漏洞找出偏移然后劫持fini_array重新触发漏洞
但是要怎么样去构造我们的payload呢?
我们先来看一个知识点
p64()+b’%nc’+‘%A$n’
在漏洞利用中,%n、%hn和%hh都可以用于将已经存储在堆栈上的数值写入内存中的任意位置。这些格式字符串的容量取决于它们所针对的底层数据类型 %n格式字符串用于将已经打印出来的字符数(而不是已经写入输出缓冲区的字符数)写入指定地址。因此,它的容量取决于可控制的输出大小,通常在4字节范围内。 %h格式字符串将16位无符号整数写入指定地址。由于其只能写入两个字节,因此其容量范围为0到65535。 %hhn格式字符串将8位无符号整数写入指定地址。由于其只能写入一个字节,因此其容量范围为0到255
先来看exp
1 | from pwn import * |
payload解析
1 | payload=p32(fini_array+2)+p32(fini_array)+p32(printf_got+2)+p32(printf_got) |
payload=p32(fini_array+2)+p32(fini_array)+p32(printf_got+2)+p32(printf_got)
此部分将四个 32 位地址依次拼接成 payload
。具体来说,fini_array + 2
和 fini_array
是对 fini_array
数组中不同位置的地址引用,printf_got + 2
和 printf_got
是对 printf
函数在 GOT 表中不同位置的地址引用。把这些地址放在 payload
开头,目的是在后续格式化字符串处理时,让 %hn
能将数据写入这些地址
payload+=(f’%{0x804-0x10}c%4$hn’+f’%{0x8534-0x804}c%5$hn’).encode()
%{0x804 - 0x10}c
:这里的c
是格式化字符,表示输出字符。{0x804 - 0x10}
是一个计算结果,此表达式的作用是输出0x804 - 0x10
个字符,目的是凑够输出字符数。%4$hn
:4$
表示引用第 4 个参数,也就是前面payload
中拼接的第 4 个地址(即printf_got
)。%hn
会把当前输出的字符数的低 16 位写入该地址。f'%{0x8534 - 0x804}c%5$hn'
:同理,%{0x8534 - 0x804}c
输出0x8534 - 0x804
个字符,%5$hn
把当前输出字符数的低 16 位写入第 5 个参数对应的地址(即printf_got + 2
)
payload += (f’%{0x10000 - 0x8534 + 0x804}c%6$hn’ + f’%{0x83d0-0x804}c%7$hn’).encode()
- 这部分和第二部分类似,
%{0x10000 - 0x8534 + 0x804}c%6$hn
会把当前输出字符数的低 16 位写入第 6 个参数对应的地址(即fini_array
),%{0x83d0 - 0x804}c%7$hn
会把当前输出字符数的低 16 位写入第 7 个参数对应的地址(即fini_array + 2
)。
疑点
1.0x804-0x10是为什么
输出 0x804 - 0x10 个字符。0x10 是前面四个地址拼接部分的长度(每个地址 4 字节,共 16 字节,即 0x10)。通过输出这些字符,使得已输出字符数达到 0x804。0x804是main函数的高16位地址
同时%6$hn:6$
表示引用第 6 个参数(即 fini_array
),% hn
会将当前已输出字符数的低 16 位写入到 fini_array
地址处。
2.为什么要分两次传入
- 在 32 位系统中,地址是 32 位(4 字节)的数据,但格式化字符串漏洞利用中的
%hn
转换说明符每次只能写入 16 位(2 字节)的数据。%hn
的这种特性决定了要写入一个完整的 32 位地址,就必须分两次进行,分别写入地址的低 16 位和高 16 位。
3.程序怎样调用的system
在ELF文件中,程序结束后会调用__libc_csu_fini中的指针来清理程序,这个时候我们将指针数组array[1]的值覆盖为main,结束后就会再次调用main函数,然后我们第二次篡改array[1]的值是system函数的地址,最后传入/bin/sh即可get shell
4.为什么要通过计算输出字符串的数量间接传入地址而不是直接传入
在这道题中需要我们灵活控制传入地址的低16位和高16位,所以采用这种间接的方法