CTF PWN工具篇1-pwntools

0x00 引子

今年5月份参加红帽杯比赛,线下攻防赛,因为不懂PWN类题目,被吊打。很不爽,于是从6月初开始重新学习汇编,操作系统到7月底,算告一段落。参考书上教程,从零完成一个简陋的操作系统,只有一个进程。这个过程中最大的收获是对汇编的深入理解,以及程序加载执行的过程的熟悉。这对PWN来说是非常好的基础。现在开始学习PWN类题目。先来梳理下常用的工具。网上有人介绍过,我自己从初学者的角度来看待,尽可能详尽些。
后续会逐个详细介绍在做PWN题中使用到的功能点。

0x01 工具

古书云:人欲善其事,必先利其器。PWN类题目主要的工具有:

  1. pwntools
    这是基于Python开发的工具库。可以很方便的进行本地调试,远程利用。在github上可以找到。
  2. gdb:调试神器
  3. peda: 基于Python开发的gdb的插件,可以方便地显示当前栈,堆等信息。
  4. IDA:反汇编逆向的神器,F5一键查看伪代码。
    目前用到的就这么多,后面有的话,再添加。

    0x02 pwntools

    https://github.com/Gallopsled/pwntools,附上项目的github地址。这种正规的项目,都有完备的使用和开发说明文档,但这些文档太全面,从实用的角度来说,只挑现在用到的功能。

    2.1 安装

    可以方便的使用pip进行安装:
    1
    pip install pwntools
    这是在Ubuntu系统下。如果安装过程中遇到问题了可以参考下官方文档https://docs.pwntools.com/en/stable/

    2.2 pwntools使用的一般流程

  5. 使用process/remote建立交互
  6. 通过调试,泄露堆栈信息找到利用点。
  7. 构造Payload利用
  8. 开启交互式shell,完成利用。
    下面的介绍也是按照这样一个顺序进行。

    2.3 process/remote模块

    PWN题一开始就要建立与可执行程序的交互。这就是通过这两个模块实现的。如果是本地调试的话,就使用process模块,本地调试好了就是远程利用拿FLAG了,用的是remote模块。
    一般使用pwntools的脚本和可执行文件放在同一目录,方便管理。使用process的过程为:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 引入模块
    from pwn import process
    # 生成实例
    p = process('./pwn')

    # 向进程发信息, 模拟用户的输入,在输入后添加回车,传递给进程。
    p.sendline('some thing')

    # 接收进程输出信息
    # 交互时,要确定进程的状态,通过recvuntil,确保程序是运行在期望的状态下,
    # 可以保证Payload是在合适的时机发送的。
    p.recvuntil('some thing')

    # 开启交互shell
    # 这个时候要确认进程已已经成功get shell了,这个函数能开启一个shell。如果
    # Payload 有异常这时脚本会异常中止。
    p.interactive()

    2.4 ELF模块

    大多数CTF比赛中的PWN题是基于Linux下可执行文件的。而Linux下可执行文件的格式为ELF。有时候我们需要从可执行文件中提取出一些有用信息,如函数的相对偏移地址,函数在GOT,PLT表的位置等,包括libc中一个信息。
    这里再说一下自己对程序和进程的差别的理解。
    也就是说进程不是简单地把程序拷贝到内存就可以执行了。这当中有许多历史和技术原因。程序是编译好的可执行文件,存在于磁盘上的。它的内容是按段组织的。可以使用objdump -x pwn2来查看。可以看到文件分成好多段,粘过来的格式乱掉了。
    一般基本的有:
  • .text: 代码段
  • .data: 数据段
    当然用C语言编写得到的可执行程序的段不会只有这2个。如果是汇编的话,就能做到只有这两个段。这里的.text段放的就是程序要执行的代码,是编译好的机器语言。.data段放的是变量,全局变量。因为局部变量是在栈上分配的。除了这两个,还一个header段,是用来说明这个程序的文件格式。
    需要动态链接的程序还会有以下段:
  • .plt: procedure link table, 过程链接表
  • .got: global offset table,全局偏移表。
    当然这两个段跟PWN题强相关的。这两个段用于在加载时,重定位库函数的偏移地址的。可参考这篇文章聊聊Linux动态链接中的PLT和GOT(1)——何谓PLT与GOT

通过ELF加载了可执行文件,就可以方便地读取一些信息了。

1
2
3
4
5
elf = ELF('./pwn')
# symbols里包含了变量,函数的地址。如果没有开启地址随机化的话,读到的地址,就是其实际加载的地址。
elf.symbols['system']
# 也可以读bss段的地址
elf.bss()

2.5 一些通用的模块

这些模块用来处理一些细节
如大小端字节的处理。这两个模块在由泄露信息生成payload时很好用,不需要再手动将32位数值转化为小端4字节二进制数,同时也方便将人可读的信息转化为机器可识别信息。

1
2
3
4
5
# 将32位数值转化为小当前计算机上的表示,x86就是小端得到的是/x78/x56/x34/x12
p32(0x12345678)

# 将32位地址转化为32位int
u32('\x78\x56\x32\x12')