PWN-二进制漏洞审计

举报
御麟 发表于 2023/03/13 14:16:09 2023/03/13
【摘要】 ​ PWN的另一个名字是二进制漏洞审计​编辑Pwn和逆向工程一样,是操作底层二进制的,web则是在php层面进行渗透测试我是从re开始接触CTF的,有一点二进制基础,本文可能会忽略一些基础知识的补充”Pwn”是一个黑客语法的俚语词 ,是指攻破设备或者系统。发音类似“砰”,对黑客而言,这就是成功实施黑客攻击的声音——砰的一声,被“黑”的电脑或手机就被你操纵了。Pwn在CTF竞赛是体现技术实力的...

 PWN的另一个名字是二进制漏洞审计

编辑

Pwn和逆向工程一样,是操作底层二进制的,web则是在php层面进行渗透测试

我是从re开始接触CTF的,有一点二进制基础,本文可能会忽略一些基础知识的补充

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

Pwn在CTF竞赛是体现技术实力的关键部分,也是最难的部分。

下面讲解一下Pwn有关的知识

                        ——Your computer,my access!

目录

Pwn的目的

在PWN正式学习之前的基础知识

需要有一点Linux的基础

安装kali Linux

学习以下内容

配置python3环境

安装Linux工具

程序的编译和链接

编译过程

file命令

gcc使用的vim查看底层二进制

逐步使用gcc编译

可执行文件

Windows:PE

Linux:ELF

堆栈

堆栈数据结构

运行时堆栈

ESP寄存器

入栈操作

出栈操作

PUSH指令

POP指令

PUSHFD指令

POPFD指令

PUSHAD指令

POPAD指令

堆栈帧

堆栈参数

Fastcall调用方式

值传递

引用传递

传递数组

访问堆栈的参数

显式的堆栈参数

清除堆栈

C调用方式-cdecl

STDCALL调用规范

局部变量

保存和恢复寄存器

引用参数

StackOverflow

C语言函数调用栈

栈帧结构

StackOverflow原理

缓存区溢出

栈溢出

堆溢出

BSS溢出

什么样的代码会发生缓存区溢出

PWN必备的工具

IDA pro

pwngdb

安装过程

checksec

安装过程

ROPgadget

one_gadget

使用pwntools

进入python3解释器,导入pwntools包

常用的函数



Pwn的目的

最终目的:获得一个shell

Shell是系统的用户界面,提供了用户与内核进行交互操作的一种接口。

它接收用户输入的命令并把它送入内核去执行。

实际上Shell是一个命令解释器,它解释由用户输入的命令并且把它们送到内核。

编辑

获得一个shell意味着获取了一个服务器的控制台,我们就可以随心所欲地操作服务器中的数据。


在PWN正式学习之前的基础知识

需要有一点Linux的基础

安装kali Linux

需要安装wmware虚拟机

使用kali的iso文件将kali安装到虚拟机当中

如果想要完全按照blog中使用的安装工具的方法来安装工具,不出现任何别的问题,可以使用与我同一版本的Kali

kali-linux-2021.3a-installer-amd64.iso

提取码 2I55

学习以下内容

  • linux基础命令
  • vim文件
  • gcc使用
  • gdb的使用
  • 安装Linux程序

课程推荐MIT的CS计算机操作环境导论

也可以跟着acwing的yxc学习Linux基础课

下面是一些Kali必备的环境,如果后文工具要按照我的方法配置

配置python3环境

kali一般自带一个python2,我们还需要安装一个python3

在终端中执行以下命令安装python3,权限不够的话在命令前加上sudo来执行

cd /usr/local/
mkdir /usr/local/python3
cd /python3
wget https://www.python.org/ftp/python/3.8.0/Python-3.8.0.tgz
就在该目录下解压
tar -zxvf Python-3.8.0.tgz
cd Python-3.8.0
 ./configure --enable-optimizations --prefix=/usr/local/Python3/ && make && make install
mv /usr/bin/python /usr/bin/python.bak
ln -s /usr/local/Python3/bin/python3.8  /usr/bin/python

输入Python3,Python2进入对应版本的Python解释器

编辑

如上图就是装好了python2和python3

按下Ctrl+Z退出Python解释器 

Python解释器可以一句一句得解释执行Python语句

安装Linux工具

我们会安装一些pwn的工具

我们可以搜索博客,按照博客给出的命令按部就班进行安装

经常会因为与博客作者版本不同导致实现各种各样问题

我们需要耐心所有这些问题的解决办法

当我们实操经验增多,对Linux理解加深的时候,这类问题会更容易解决

Linux配置国内源

默认的配置文件中的是国外源,不好用,我们需要使用国内源

进入配置文件

sudo vim /etc/apt/sources.list

用#将原来的源注释掉,加入国内源

编辑

#清华大学

deb http://mirrors.tuna.tsinghua.edu.cn/kali kali-rolling main contrib non-free
deb-src https://mirrors.tuna.tsinghua.edu.cn/kali kali-rolling main contrib non-free

#中科大

deb http://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib
deb-src http://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib
#阿里云

deb http://mirrors.aliyun.com/kali kali-rolling main non-free contrib
deb-src http://mirrors.aliyun.com/kali kali-rolling main non-free contrib

如果配置好国内源还不能正常使用,出现换源之后签名无效的问题,在终端执行下面两条命令

wget archive.kali.org/archive-key.asc
apt-key add archive-key.asc


程序的编译和链接

这里涉及到Linux的一些命令和工具

编译过程

编译->汇编->链接

file命令

可以查看文件类型

gcc使用的vim查看底层二进制

在vim中查看底二进制,在命令行中输入:%!xxd

还原为字符文件 %!xxd -r

gcc直接编译

gcc 要编译的文件

逐步使用gcc编译

gcc -S 源代码文件 得到汇编文件

gcc 汇编语言文件 得到可执行文件

./可执行文件 可以执行可执行文件


可执行文件

Windows:PE

可执行程序

exe

动态链接库

dll

静态链接库

lib

Linux:ELF

可执行程序

out

动态链接库

so

静态链接库

a

需要对PE文件和ELF文件有一定的了解,这类资源可以自行搜索解决


堆栈

这里讲解一下汇编语言的堆栈机制,读完不能很好理解的读者可以自行学习汇编语言,但是读完本博客的内容做一个抽象性的理解其实也足够了

下面是我的两篇关于汇编语言基础的blog:

汇编语言学习 上

汇编语言学习 下

堆栈数据结构

堆栈数据结构就是我们数据结构课中学到的栈

编辑

 栈顶添加新元素,删除元素在栈顶删除

后进去先出FILO

本文要讲的是运行时堆栈

运行时堆栈

运行时堆栈是内存数组,程序的数据存放在内存上,运行时用抽象数据结构中栈的形式来处理数据

ESP寄存器

ESP寄存器(extended stack pointer,扩展堆栈指针)

可以理解为抽象数据结构栈中指向栈顶的指针

存放某个位置的32位偏移量

基本上不会被程序员修改

用CALL,RET,PUSH,POP指令间接修改

入栈操作

32位入栈操作是把栈顶指针减4,再将数值复制到栈顶指针指向的位置

编辑

运行时堆栈在内存中是向下生长的,从高地址向低地址扩展

出栈操作

从堆栈删除元素,站定指针减小

下面介绍一些汇编指令

PUSH指令

首先减少ESP的值,将操作数复制到堆栈

POP指令

将ESP指向的堆栈元素复制到一个操作数当中,增加ESP的值

PUSHFD指令

将32位EFLAGS寄存器的内容压入堆栈

POPFD指令

将栈顶元素弹出到32位EFLAGS寄存器

PUSHAD指令

按照EAX,ECX,EDX,EBX,ESP,EBP,ESI,EBI的顺序

将所有32位通用寄存器压入堆栈

POPAD指令

按照与PUSHAD相反的顺序将其弹出堆栈


堆栈帧

堆栈参数

堆栈帧是一块堆栈保留区域

存放

  • 被传递的实际参数
  • 子程序的返回值
  • 局部变量
  • 被保存的寄存器

创建步骤

  1. 将被传递的实际参数压入堆栈
  2. 当子程序被调用时,该子程序的返回值压入堆栈
  3. 子程序开始执行的时候,EBP被压入堆栈
  4. 设置EBP等于ESP,EBP成为子程序所有参数的引用基址
  5. 如果有局部变量,修改ESP在堆栈中为其预留空间
  6. 需要保留的寄存器,将它们压入堆栈

Fastcall调用方式

顾名思义,是一种希望快速的调用方式

我们来分析一下这个调用方式: 

调用自过程的时候,需要首先将参数传入EAX,EBX,ECX,EDX,少数情况还会传入ESI,EDI

我们知道寄存器是CPU内部的原件,堆栈在内存上,寄存器调用明显更快

但是我们知道通用寄存器很少,很多都有特定的功能,乘法需要用到EAX,还有许多寄存器用来循环数值和参与计算的操作数

因此寄存器不可能一直存放传递给过程的参数

在过程调用之前, 存放参数的寄存器需要首先入栈,然后向其分配过程参数

但是这些额外的入栈操作会让代码变得混乱,还有可能消除性能优势

值传递

一个参数通过数值传递时,该值的副本会被压入堆栈

.data
val1 DWORD 3
val2 DWORD 6
.code
push val2
push val1
call AddTwo

引用传递

通过引用来传递的参数包含的是对象的地址

push OFFSET val2
push OFFSET val1

传递数组

将数组的地址压入堆栈 

不愿意采用将每个数组元素压入堆栈的原因是这样很慢而且浪费堆栈空间

访问堆栈的参数

1.将传递的参数压入堆栈,调用子过程

2.EBP寄存器存放的是原来栈帧的基址,我们需要现将EBP压入栈保存

3.然后将当前的ESP作为新的栈帧的基址

示例

int AddTwo(int x,int y)
{
    return x+y;
}

将EBP入栈,设置ebp位esp的值

AddTwo PROC
    push ebp
    mov ebp,esp


ADD(5,6)

6

[EBP+12]
5 [EBP+8]
返回地址 [EBP+4]
EBP mov ebp,esp

这样通过当前EBP和偏移量就能访问传入的参数和原来的ebp(返回地址)

显式的堆栈参数

堆栈参数的引用表达式形如[esp+8],称它们为显式的堆栈参数

清除堆栈

子程序返回时,必须将参数从堆栈中删除

否则会导致内存泄露,堆栈会被破坏

C调用方式-cdecl

用于C和C++语言

子程序的参数按逆序入栈

解决了运行时堆栈的问题

在调用子过程后,紧跟一条语句让堆栈指针ESP加上一个数,该数的值即为子程序参数所占的堆栈空间

main PROC
    push 6
    push 5
    call AddTwo
    add esp,8
    ret
main ENDP

能将参数从堆栈中删除

STDCALL调用规范

给RET指令添加了一个参数,使程序在返回调用过程的时候,ESP会加上这个参数

这个添加的整数和过程参数占用的堆栈空间字节数相等

AddTwo PROC
    push ebp
    mov ebp,esp
    mov eax,[ebp+12]
    add eax,[ebp+8]
    pop ebp
    ret 8
AddTwo ENDP

局部变量

在子过程中创建的变量

局部变量在ebp下

void Mysub()
{
    int X=10;
    int Y=20;
}

每个变量的存储大小都要向上取整保存为4的倍数

两个局部变量一共保留8个字节

MySub PROC
    push ebp
    mov ebp,esp
    sub esp,8
    mov DWORD PTR [ebp-4],10
    mov DWORD PTR [ebp-8],20
    mov esp,ebp
    pop ebp
    ret
MySub ENDP

从堆栈中删除局部变量,只需要执行:

mov esp,ebp

esp向上移动=内存释放

可以给局部变量的偏移量定义一个符号,在代码中使用这些符号

X_local EQU DWORD PTR [ebp-4]
Y_local EQU DWORD PTR [ebp-8]

MySub PROC
    push ebp
    mov ebp,esp
    sub esp,8
    mov X_local,10
    mov Y_local ,20
    mov esp,ebp
    pop ebp
    ret
MySub ENDP

保存和恢复寄存器

子程序在修改寄存器之前将它们的当前值保存到堆栈

通常在ebp入栈,设置ebp等于esp之后,相关寄存器入栈

栈帧
解释说明
传递的参数 [EBP+8]

返回地址

[EBP+4](原来栈帧的EBP)
EBP

当前栈帧的EBP

ECX
EDX 当前ESP指向的位置

EBP被初始化之后,整个过程中它的值将保持不变

ECX,EDX入栈并不影响EBP按照原来的偏移量访问传递的参数

引用参数

引用参数通常是基址-偏移量寻址方式进行访问

每个引用参数都是一个指针

.data
count=100
array WORD count DUP(?)

.code
push OFFSET array
push count
call ArrayFill
ArrayFill PROC
    push ebp
    mov ebp,esp
数组偏移量
数组长度

返回地址

EBP


下面我们来进入与Pwn的正文

编辑



StackOverflow

StackOverflow是一种常见的Pwn的手段

同时与有与之同名的网站StackOverflow是全球最大的编程问答社区

编辑

C语言函数调用栈

复习和补充一下函数调用栈的内容

  • 函数调用栈是值程序运行时内存一段连续的区域

  • 用来保存函数运行时的状态信息

  • 称之为“栈”是因为发生函数调用的时候,调用函数的状态被保存在栈内

  • 在函数调用结束之后,栈顶的函数状态被弹出,栈顶恢复到调用函数的状态

  • 函数调用栈在内存中从高地址向低地址扩展

栈帧结构

previous stack frame pointer
arguments
return address
stack frame pointer
callee saved registers
local variable

arguments会倒序压入栈


StackOverflow原理

我们要控制程序执行流

就要控制EIP,RIP这种PC寄存器

只要EIP寄存器能写入我们想要的值,整个程序执行流就会被我们劫持

能传给EIP值的位置只有return address这里


缓存区溢出

本质是向定长的缓存区中写入了超长的数据,造成超出的数据覆写了合法内存区域

栈溢出

  • 最常见,漏洞比例最高,危害最大的二进制漏洞

  • 在CTF PWN中往往是漏洞利用的基础

堆溢出

  • 现实中的漏洞占比不高

  • 堆管理器复杂,利用花样繁多

  • CTF PWN中的常见题型

BSS溢出

  • 现实中与CTF比赛中占比都不高

  • 攻击效果依赖于BSS上存放了何种控制数据

什么样的代码会发生缓存区溢出

#include<stdio.h>



int main(){

    char str[8];

    read(0,str,24);

    return 0;

}

上述代码企图在8个元素的数组中输入24个数据

编辑

编译执行结果如上图,我们在运行的时候输入了超过8个字符

在一些Linux中这种缓存区溢出会导致程序崩溃

如果要是输入一些精心构造的数据,就能实现劫持

我们输入的数据位置和存储原函数EBP的位置是相邻的

我们可以利用StackOverflow修改存储原函数的EBP的值

这样函数返回的时候,原函数的EBP被我们修改了,我们就修改了PC,劫持了这个程序执行流程

一个合格的程序员是要有一定的安全意识,注意程序存在的安全问题

我们在劫持了程序执行流之后,可以将返回地址改为程序员自己留的后门函数

后门一般是指那些绕过安全性控制而获取对程序或系统访问权的程序。在软件的开发阶段,程序员常常会创建后门以便修改程序。但是,如果这些后门被其他人知道,或是在发布软件之前没有删除后门,那么他就成了安全风险,容易被hacker当成漏洞进行攻击。

后门函数是开发者自己留的,执行后门函数就可以控制shell

编辑



PWN必备的工具

本节的安装方式都是在kali2021上亲测可用

  • IDA pro

  • pwntools

  • pwndbg

  • checksec

  • ROPgadget

  • one_gadget

IDA pro

这是一款逆向工程的强大工具,由于hexray公司生产,官网版本是需要付费的,hexray公司需要用这款工具来开工资

IDA pro可以反汇编一个可执行文件,也可以进一步反编译成C语言,可以逆向分析一个可执行文件的运行机制

具体使用请参考我的另一篇专门介绍IDA的博客,这里不做赘述,点击下方链接进入

IDA基本使用


pwntools

安装方法

一种方法:

先安装pip3,依次执行以下命令

sudo apt update
sudo apt install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
pip3 -V

然后执行

pip3 install pwntools

这种可能遇到环境问题,选择另一种

另一种方法:

在终端输入

sudo apt install python3-pwntools

编辑


pwngdb

是gdb的一个插件

gdb是用来调试C语言文件来设计的

但是我们在pwn的时候会调试二进制文件,或者说是汇编语言代码

这时就需要pwngdb

安装过程,在终端输入以下命令

git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh

编辑


安装过程

先安装gdb

在终端执行以下命令

wget http://ftp.gnu.org/gnu/gdb/gdb-11.2.tar.gz
tar -zxvf gdb-11.2.tar.gz
cd gdb-11.2
./configure --with-python='/usr/bin/python3.9'    //根据python的具体版本进行修改 
make
sudo make install


checksec

用来查看文件的保护措施

一般是做pwn题的第一步

检查一下安全保护措施

了解之后再进行Attack

使用方法

旧版本 checksec 文件名

新版本 checksec --file=文件名

编辑

以新版本为例,上图列出来文件的一些保护机制 

安装过程

如果装好pwntools的话,就已经有了checksec

checksec -v

查看一下是否安装了checksec

只安装checksec的方法:

依次在终端输入三个命令

git clone https://github.com/slimm609/checksec.sh.git
cd checksec.sh
sudo ln -s checksec /usr/local/bin/checksec


ROPgadget

查找程序中用来ROP的代码片段


one_gadget

很强大的一个工具,找到一些获取shell的代码片段,然后整合在一起


使用pwntools

进入python3解释器,导入pwntools包

在命令行终端输入python3

然后在python解释器执行pwntools的命令

首先第一句

form pwntools import *

接下来使用pwntools库中的函数

常用的函数

io=remote("IP",Port)

创建一个io实例,第一个参数是主机名,第二个是端口号

io.send("") 向进程发送数据

io.sendline("") 向进程发送数据,末尾加一个换行符

io.recv() 从进程接收数据

io.recvline()

io.interactive()


保护措施

checksec可以检查文件的保护机制,各种保护机制如下

NX

  • 程序与操作系统的防护措施,编译时决定是否生效,由操作系统实现。

  • 通过在内存页的标识中增加“执行”位,可以表示该内存页是否可以执行,若程序代码的EIP执行至不可运行的内存页,则CPU将直接拒绝执行“指令”造成内存崩溃

ASLR Address Space Layout Randomization

  • 系统的防护措施,程序装载时生效

  • /pro/sys/kernel/randomize_va_space=0:没有随机化。即关闭ASLR

  • /pro/sys/kernel/randomize_va_space=1:保留的随机化。共享改库,栈,mmap()以及VDSO将被随机化

  • /pro/sys/kernel/randomize_va_space=2:完全的随机化。在randomize_va_space=1的基础上,通过brk()分配的内存空间也将被随机化。

PIE

随机化Data,Text,Bss


未完待续

The Mass

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。