Binary Overflow.

格式化字符串漏洞

字数统计: 1.7k阅读时长: 7 min
2018/10/26 Share

0x00 前言


这个漏洞刚开始在网上看到的 第一次没有看懂 后面找到一个博客上的例子 但还是没有调试成功 又问了团队的丁佬(Greenhand)
丁佬带我操作了一番 让我有了个了解 下面给大家细细讲解一下!

0x01 漏洞原理


所谓格式化字符串是当你用prinft这种 需要格式化的输出语句时候 平常人可能会是printf(format,string)
但也会有人为了省力气 printf(string) 但这样是不行的! 你不能将这个format的权限给别人 否则会导致格式化字符串漏洞 进而导致
任务地址可写!!!
先讲一下printf中format都有什么 都可以干什么
这是printf压入栈中的顺序
Image test
format参数
1
2
3
4
5
6
7
%c:输出字符,配上%n可用于向指定地址写数据。
%d:输出十进制整数,配上%n可用于向指定地址写数据。
%x:输出16进制数据,如%i$x表示要泄漏偏移i处4字节长的16进制数据,%i$lx表示要泄漏偏移i处8字节长的16进制数据,32bit和64bit环境下一样。
%p:输出16进制数据,与%x基本一样,只是附加了前缀0x,在32bit下输出4字节,在64bit下输出8字节,可通过输出字节的长度来判断目标环境是32bit还是64bit。
%s:输出的内容是字符串,即将偏移处指针指向的字符串输出,如%i$s表示输出偏移i处地址所指向的字符串,在32bit和64bit环境下一样,可用于读取GOT表等信息。
%n:将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置,如%100×10$n表示将0x64写入偏移10处保存的指针所指向的地址(4字节),而%$hn表示写入的地址空间为2字节,%$hhn表示写入的地址空间为1字节,%$lln表示写入的地址空间为8字节,在32bit和64bit环境下一样。有时,直接写4字节会导致程序崩溃或等候时间过长,可以通过%$hn或%$hhn来适时调整。
%n是通过格式化字符串漏洞改变程序流程的关键方式,而其他格式化字符串参数可用于读取信息或配合%n写数据。

先给大家看一个例子

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
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int target;

void printbuffer(char *string)
{
printf(string);
}

void vuln()
{
char buffer[512];
fgets(buffer, sizeof(buffer), stdin);
printbuffer(buffer);
if(target == 0x01025544) {
printf("you have modified the target :)\n");
} else {
printf("target is %08x :(\n", target);
}
}

int main(int argc, char **argv)
{
vuln();
}

在printbuffer中的printf没有加入格式 那我们可以利用这个格式化字符串漏洞想办法把target地址写为0x01025544 这样就可以打印you have modified the target :)
接下来我们把编译好的文件拖入od中调式 不懂od的可以先去学学od(ollydbg)
我们在printbuffer(buffer)这个地方下一个断点
Image text
让后F9开始运行
我们先输入 %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x 去看看都有什么
Image text
%x打印出来就是向低处偏移的地址中存的值 接下来试试

1
1234%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x

1234是为了让我们验证该栈下那个位置可以写入 我们就找后面%x打印出来值是1234的ascii值就可以(16进制)
Image text
可以看到在第12个%x处打印出来了1234的16进制ascii 证明这个地方是可以写入的!
前面1234刚好4个字节 可以换为地址的4个字节 最后一位%x换位%n 因为在该处写入 相当于在这个位置处 把前面那个地址处的值修改 而修改的值
就是前面打印出来的个数 我们可以通过%x控制 只要前面打印出来总和是0x01025544就可以 但是这个数有点大 我怕我运行时候由于打印卡住 我是直接修改的eax ecx

我们先去找target的地址
Image text
target地址就是0x00405050 这就有一个问题了 前面00通过什么字符去打印进去 因为00在字符里面默认是空 我们这里就不用管他 那个字节位置随便用一个字符占着 因为后面我们可以直接去修改
eax
Image text
我把前面的后三字节改为405050头位不用管 也可以随便输入一个

我们运行然后输入PP@d%x %x %x %x %x %x %x %x %x %x %x %n
Image text
然后我们F7单步进入 这个断点 去找什么将我们头部分那个地址存入EAX寄存器
Image text
成功找到这个地方 我们手动修改EAX和ECX的值 EAX为我们那个地址的值 ECX为赋值给该地址的值
Image text
修改为寄存器的值后 就相当于0x00405050地址中的值被赋为0x01025544 到此漏洞就利用成功
Image text

后面再送大家一个例子

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
typedef void (*ErrFunc)(unsigned long);


void GhastlyError(unsigned long err)
{
printf("Unrecoverable error! – err = %d\n", err);
//This is, in general, a bad practice.
//Exits buried deep in the X Window libraries once cost
//me over a week of debugging effort.
//All application exits should occur in main, ideally in one place.
exit(-1);
}


void RecoverableError(unsigned long err)
{
printf("Something went wrong, but you can fix it – err = %d\n", err);
}


void PrintMessage(char* file, unsigned long err)

{
ErrFunc fErrFunc;
char buf[512];
if(err == 5)
{
//access denied
fErrFunc = GhastlyError;
}
else
{
fErrFunc = RecoverableError;
}
_snprintf(buf, sizeof(buf)-1, "Cannot find %s", file);
//just to show you what is in the buffer
printf("%s", buf);
//just in case your compiler changes things on you
printf("\nAddress of fErrFunc is %p\n", &fErrFunc);
printf("\nAddress of GhastlyError is %p\n", &GhastlyError);
printf("\nAddress of RecoverableError is %p\n\n\n", &RecoverableError);
//Here's where the damage is done!
//Don't do this in your code.
fprintf(stdout, buf);
printf("\nCalling ErrFunc %p\n", fErrFunc);
fErrFunc(err);
}


void foo(void)
{
printf("Augh! We've been hacked!\n");
}


int main(int argc, char* argv[])
{

FILE* pFile;
//a little cheating to make the example easy
printf("Address of foo is %p\n", foo);
//this will only open existing files
pFile = fopen(argv[1], "r");
if(pFile == NULL)
{
PrintMessage(argv[1], errno);
}
else
{
printf("Opened %s\n", argv[1]);
fclose(pFile);
}
return 0;
}

也是同样的例子 需要你利用漏洞去执行foo函数
漏洞在fprintf(stdout, buf)
最后利用去上例子同样方法就可以了 修改ErrFunc这个函数指针到foo函数地址
Image text
修改后ErrFunc的地址 并且通过后面的fErrFunc(err)函数成功调用
Image text

资料来源:
https://www.anquanke.com/post/id/85785
https://bbs.pediy.com/thread-217035.html

CATALOG
  1. 1. 0x00 前言
  2. 2. 0x01 漏洞原理