PWN基础
- 在做题之前先要查看二进制文件,看看是怎么样的类型,使用checksec工具
https://blog.csdn.net/qq_43430261/article/details/105516051
checksec
pwndbg动态调试
- pwndbg的启动直接在终端输入gdb即可
- 可以注释掉pwndbg里面的折叠代码(这里没发现区别,之后在搞),动调的时候会更清晰
1 | # 进入pwndbg里面的编辑文档 |
术语解释
1 | 运行 |
步入、步过、步出、步止
步入:
当程序执行到某个函数调用时,调试器不仅执行该调用,而且会进入到被调用的函数内部,从函数的第一行代码开始逐行执行。
单步步入指令:s
步过:
逐行执行代码,但当遇到函数调用时,不会进入该函数内部,而是直接执行该函数调用,并将控制权返回给调用该函数的代码行之后的下一行。
步出:
在已经步入一个函数之后,快速执行完该函数内剩余的代码,并将控制权返回到调用该函数的地方。
步止:
停止执行代码
单步跳过(指令:n):执行当前行的代码,如果当前行是一个函数调用,则不会进入该函数内部,而是直接执行该函数调用并返回到调用后的下一行代码。
断点:断点是一个标记,指示调试器在程序执行到该位置时暂停执行
安装pwndbg
- 这里不再叙述
常用命令
1 | run # 将程序完全跑一遍 |
1 | x/20g rbp |
基本界面
- registers(寄存器)
- code(代码)
- stack(栈)
- backtrace(回溯)
registers
- 最左边绿色的都是寄存器
code
- 左边0x55555555527d指的是内存地址
- 最左边有个箭头表示程序运行到该位置处
- endbr64下方的是汇编代码
stack
- 最左边的是栈的相对地址
- 中间蓝色的是栈的实际地址
- —>箭头指向的是栈中储存的值
- 如果想看更多栈的情况可以输入
stack 40
- 由于下图调试的程序调用的栈有限,所以只显示了25行的栈
backtrace
- 显示的函数调用情况(从下往上倒着看)
- main函数调用了gets函数
- gets函数调用了上面3、2、1行的内核函数
实际操作
- 会将常用命令都用上一遍
- 编译指令
gcc question_1.c -o question_1_x64
,x64编译 - 调试程序代码
1 |
|
运行程序
run命令:完整的将程序运行一遍
看白色框里面的
start命令:start命令是一个特殊命令,start命令会先在main函数的入口处设置一个断点,当程序运行到main函数的入口处,就会停止,等待调试者的进一步操作
有些时候去掉符号表,gdb找不到main函数入口点,需要调试者自己去找入口点并设置断点
对寄存器的操作
i r指令
核心是rip
1 | rbp rsp # 与栈有关,保护栈 |
- 使用i r指令查看寄存器
- 反汇编rip寄存器指向的函数(默认反汇编是AT&T格式)
- 要用intel汇编格式要输入
set disassembly-flavor intel
断点的设置与使用
断点的设置、查看、删除、失效
- 设置断点
b *0x555555555281
9b + 指令地址
- 查看断点
i b
- 使断点失效
disable b 2
- 使断点失效后断点仍然存在,但是在实际调试的过程中不会起到断点的作用
- 建议之后在调试的过程中,对不需要的断点使用该操作,这样在之后需要使用该断点调试的时候就不用花时间找断点了
- 删除断点
d 2
(这里的2指的是上图中断点的Num)
断点的作用
c
命令执行到断点- 然后反汇编
- 查看此时寄存器的值,此时还没有执行
mov rbp,rsp
指令
- 输入
ni
指令(执行单步)后再查看寄存器,此时完成了mov rpb,rsp
ni与si的区别
- ni指令之后,会直接跳过input函数的内部执行过程,直接执行箭头会直接指向main函数的下跳指令
- 下图的input就会被打印出来
- si指令则会进入到input的实现的过程中里面去,先是进入到input的plt表中
- 此时再对rip寄存器指向的地址处进行反汇编,会发现反汇编的地址不是在main函数中
步出
finish
- 步出之后再进行反汇编,反汇编的指令就会在main函数中
调试漏洞
介绍指令
x
指令查看内存、set
指令修改内存值、p
指令显示值将该位置设置为断点,稍微在前面一两个
- 然后使用
start
指令开始进行调试,c
指令走到断点 - 反汇编查看汇编代码
- 发现这一步
cmp al,0x61
,这正好对应着代码
- 如果满足b[0]=’a’那么程序就会进入shell里面,这时就获取了程序控制权了
- 接下来查看寄存器,发现rax的值为0,那么ax值肯定也是0,如果要跳转那么就要让ax的值为61,就会满足跳转条件
- 再查看cmp语句的上一句:
1 | movzx eax,BYTE PTR [rbp-0x10] |
- 得到再执行cmp指令之前,eax会先被赋值,
这里先介绍两个指令
1 | x/20g rbp |
- x指令与disassemble指令的区别
- x指令是从rip所指的位置开始反汇编,而disassemble指令是直接反汇编rip所在的函数的全部内容
继续调试
先用
p
指令查看rbp和rbp-0x10的值- 再用
x
指令查看rbp-0x10内存(字节形式查看)
所以
movzx eax,BYTE PTR [rbp-0x10]
就是把上图文件中白色框rbp-0x10所指的值第一个字节给eax,也就是把0赋值给了eax利用set改变rbp-0x10里面的值,使0x61赋值给eax
1 | set *0x7fffffffe550=0x61 |
- 修改完值后通过几次
ni
指令即可执行到下图箭头指向处
- 在箭头指向该位置时,si进入func里面去
- 这样就可以取到shell了
在程序输入点操作
- Linux采用小端序,在一个字节中,低位低地址,高位高地址
1 | 0x7fffffffe550: 0x0000006161616161 0x8526886159646000 |
- get是一个不安全的函数
- 当输入a输入进去,只要覆盖到百色框里面的数值,就可以完成栈溢出操作,使得程序被跳转进去
调试x86程序
1 | gcc -m32 question_1.c -fno-omit-frame-pointer -o question_1_x86_esp |
- 在调试的时候可能会出现如下情况
使用该命令即可sudo chmod +x /home/myheart/pwn_learn/character1/test_3/question_1_x86_esp
- 程序源码
1 |
|
- 使用gcc编译32位程序出现错误
- 使用如下指令
1 | sudo apt-get install gcc-multilib g++-multilib module-assistant |
ROP
安装工具
ROPgadget
参考博客:
ROPgadget 安装 错误处理 与使用_ropgadget安装报错-CSDN博客
- 更新一下软件源
1 | sudo add-apt-repository ppa:launchpad-net-ubuntu-capstone-developers/capstone/ubuntu |
- 下载python-capstone
1 | sudo apt-get install python-capstone |
- 拉取ROPgadget
1 | git clone https://github.com/JonathanSalwan/ROPgadget.git |
- 下载好ROPgadget解压,并进入文件夹中,进行安装
1 | cd ROPgadget/ |
one_gadget
参考博客:one_gadget 下载 安装 与使用_obe_gadget-CSDN博客
one_gadget就是用来去查找动态链接库里execve(“/bin/sh”, rsp+0x70, environ)函数的地址的
安装
1 | sudo apt -y install ruby |
- 在
sudo gem install one_gadget
出现问题
- 查看源
1 | gem sources -l |
- 换源,并删除原有的源
1 | gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/ |
- 继续安装,如果仍然失败可能是网络问题
1 | sudo gem install one_gadget |
ROPPER
参考博客:Ubuntu Ropper keystone-engine安装 - 简书 (jianshu.com)
- 安装Keystone-engine
1 | sudo pip3 install keystone-engine |
- 安装ROPPER
1 | $ git clone https://github.com/keystone-engine/keystone.git |
- 在第5步遇到问题,原因:没有安装cmake,退回根目录安装cmake
1 | sudo apt-get update |
- 安装好Cmake后重新回到第5步
- 在第五步又出现问题
- CMake弃用警告
- C编译器未找到
第一个问题:
1 | 修改在keystone\samples目录下的CMakeLists.txt文件 |
第二个问题:
ROP介绍
- ROP(Return-oriented Programming),面向返回编程。
其核心在于利用ret指令,通过自己伪造函数调用栈,执行原有程序中正常代码的特定部分(gadget),从而控制数据域程序执行流程
ROP就是利用栈溢出,将栈高位原来的返回地址覆盖成其他地址,然程序跳转到被修改的地址,从而完成对漏洞的利用
gadget,是一段可以用与构建ROP链的汇编代码。
gadget通常指一对pop、ret指令其功能可以配置一个寄存器的值,并返回至指定地址
例如:
RET指令
- ret指令就是return指令,这是ROP中的核心
- ret指令本质等价于pop ip寄存器,也就是把栈顶的值给ip寄存器。
- 在ROP中,ret指令完成了call指令一样的工作,并且没有对我们的栈空间数据进行修改
- ret指令从寄存器rsp中寻找返回地址,然后将寻找到的地址赋值给rip,rip就可以执行接下来的语句
例如:
1 | mov rax,1 |
- 执行完mov rbx,2,栈指针rsp恰好指向0xFFFFFF这个位置中
- 那么执行ret后,就会把0xFFFFFF赋值给rip,也就让rip指向地址0xFFFFFF,接下来程序就执行rip指向地址的指令
RET指令在ROP中的应用
- 假设要控制rcx寄存器的值,并且我们发现程序中一段指令
1 | pop rax |
- 如果能通过栈溢出,让程序首先跳转到pop rbx上,那么就可以使得rcx寄存器的值被控制,然后再通过ret指令,继续跳转到接下来要执行的位置。
- 同理我们可以通过一个又一个像这样的以ret结尾的片段,从而完成ROP链式攻击
函数调用规则
_cdecl调用规则
- C/C++默认方式,参数从右向左入栈,主调函数负责栈平衡
32位程序调用规则
- 32位以栈空间作为参数存储空间进行传参
- C语言cdecl调用规则要求参数从右至左压入栈
以int func(char argv1,int argv2, int argv3)为例
1 |
|
- 该程序汇编大致如下(纯手搓可能有错)
1 | push rbp |
- 下图为大致栈的动作(此处遗漏了sub指令)
- 实例:
1 |
|
EXP:
64位程序调用规则
- 64位程序调用规则依旧按照C语言cdecl调用规则,要求参数依旧从右至左。
- 只不过64位优先以寄存器为参数存储空间,如参数超过7个,则超过部分使用栈空间进行传参(速度更快)
- 64位函数调用,继续以int func(char argv1,int argv2, int argv3)*为例
1 | mov rdx,0x128 argv3 |
- 优先按照以下顺序使用rdi,rsi,rdx,rcx,r8,r9寄存器用来传参数
例如:
1 | 传递1个参数a那么就只使用rdi寄存器 |
- 实例:
1 |
|
_stdcall
- windows API默认方式,参数从右向左入栈,被调函数负责栈平衡
_fastcall
- 快速调用方式。快速,即这种方式选择将参数优先从寄存器传入(ECX和EDX),剩下的参数再从右向左从栈传入。因为栈是位于内存区域,而寄存器位于CPU内,故存取方式快于内存。
内存对齐
ELF文件保护机制
Canary保护
介绍
- canary意思是金丝雀。来源于过去利用金丝雀查看矿洞的安全程度。
- canary是一种用来防护栈溢出的保护机制,其原理是在一个函数入口处,先从fs/gs寄存器中取出一个四字节(eax)或者8字节(rax)的值存到栈上,当函数结束时会检查这个栈上的值是否和存进去的值一致。
而且canary的值都是以00结尾,为了阶段printf泄露栈上的值或地址
gcc编译指令
1 | gcc canary_test.c -o canary_off -fno-stack-protector |
64位canary
- 将示例代码gcc编译后,使用IDA反汇编可以得到如下汇编代码
- 该汇编代码显示,fs的值会先传入rax中,再将rax里面的值压入栈中
- 在程序执行ret指令之前会将栈中canary的值传入rdx中,将rdx与fs里面的值比较。如果两者值相同,则没有发生栈溢出;反之则发生栈溢出了。
- 在检查到发生栈溢出后,就会call 到 stack_chk_fail函数中去终止程序+
- 在gdb动调里面查看canary保护
- 在rbp之前会出现一个值,这个就是canary值。
32位canary
- gcc编译后使用IDA反汇编得到的结果
- 跳转到的stack_chk_fail_local
- gdb动态调试查看canary保护
绕过canary保护手法
泄露栈中的canary
- 泄露栈中的canary在栈溢出的时候进行,canary的原样输入即可绕过canary
逐位爆破金丝雀
SSP泄露
劫持_stack_chk_fail函数
PIE保护
参考博客:PIE保护详解和常用bypass手段-安全客 - 安全资讯平台 (anquanke.com)
- PIE全称是(position-independent executable),中文解释为地址无关可执行文件
- 该技术是针对代码段(.text)、数据段(.data)、未初始化全局变量段(.bss)等固定的一个防护技术
- 在每次加载程序时都变换加载地址,从而不能通过ROPgadget等工具来帮助解题
PIE没开
- 在PIE没有打开的时候,可执行程序的固定地址为0x400000
- 也就是说改ELF文件的起始地址为0x400000
- 开启gdb后的程序地址
PIE开启
- 在开启PIE保护后,反汇编后的地址就不是0x4000000开头
- 而是从0x1000开始,而每次开启程序时,程序的地址都是随机的
- gdb启动程序后的地址
PIE绕过
partial write
泄露地址
vdso/vsyscall
RELRO保护
NX保护
作业
- 尝试使用Docker部署一题pwn题目,题目可以复制别人的,重要的是部署的过程
+