PWN - 栈溢出攻击入门

前言

经过一场CTF比赛,感觉以前学的知识都是没有得到升华。上网找了很多关于栈溢出攻击的实现方法,大家都说得很专业,我基本连入门都没看明白。经过一番尝试,这里做个小结,作为一个简单的入门。

工具就不多说了,大家都可以网上很容易找到各种安装教程,毕竟都只是一些常用的工具:

  • checksec
  • IDA Pro
  • readelf
  • gdb
  • ROPgadget
  • python with pwntools

第一步,用checksec进行文件安全信息分析。

checksec pwn

可以看到文件的一些基本信息:

参数参数含义当前值说明
Arch程序位数amd64-64-little这是一个64-bit的程序,子函数调用要用64-bit的方法。
RELRO

”Partial RELRO”,说明我们可以对GOT表具有写权限。

”Full RELRO”,简单理解为GOT表不可写。

“Canary RELRO”,不但保护了,而且还随机化了。

Full RELRO不能通过改写GOT表破解。
Stack是否有栈溢出保护Canary found有栈溢出保护(下面我们会先破解这个保护
NX

non-executable, 堆栈是否禁止执行

enable

堆栈禁止执行权限,

不考虑写shellcode到堆栈然后执行。

PIEELF的地址是否进行随机化加载No PIE (0x400000)ELF没有随机化加载(松了一口气)

第二步,用IDA Pro打开文件进行反编译分析

打开pwn文件,找到main函数,按键盘F5进行反编译成C++语言:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  ...
  char buf[40]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v6; // [rsp+38h] [rbp-8h]
  ...
  while...{
  ...
    v4 = read(0, buf, 0x200uLL);
  ...
    write(1, buf, (unsigned int)(v4 + 8));
  ...
  }
  return 0;
}

可以看出read函数读buf的时候是0x200个字节,但是buf定义只有40(0x28)个字节。这里很明显可以通过read读入实现栈溢出攻击,然后write把需要的内容打印出来。

从上面图中可以看出,栈溢出保护是检查var_8的值和之前的值有没有变化而判断有没有栈溢出。从下面的图中可以看到var_8其实就是我们buf后面的unsigned __int64 v6。

所以我们可以先尝试让buf的40个字节填满,然后当write去输出buf的时候就会把var_8/v6,也就是我们常说的canary打印出来,从而我们获取当前的canary,然后在第二次输入(read)的时候,我们就可以通过输入<buf[40]> + <canary>去到leave;retn;这个分支了。

第三步,用ROPgadget尝试找到ROP链

运行下面的命令:

ROPgadget --binary ./pwn --only "pop|ret"

我们可以找到两个挺有用的片段的地址:

  •         片段一:pop rdi; ret;
  •         片段二:pop rsi; pop r15; ret

这就涉及了调用子函数的方法:

位数调用子函数方法
32位

函数参数在函数返回地址的上方

32位程序函数调用时,依次将子函数的参数从右到左入栈,然后再压栈eip和ebp。

64位

64位程序如果子函数的参数数量<=6个,则会将参数从左到右依次存入rdi,rsi,rdx,rcx,r8,r9这6个寄存器中,如果还有参数,则像32位一样压栈。

ssize_t write(int fd, const void *buf, size_t n)
{
  ...
}

__int64 __fastcall system(__int64 a1, __int64 a2, __int64 a3, __int64 a4, u32 *a5, u32 a6)
{
  ...
}

可以看到,write和system的函数如上所示。

如果我们要执行system("/bin/sh"),就要先将"/bin/sh"的地址放到rdi(第一个参数)然后再call system。这就可以用到片段一。构建让程序read的ROP链如下:

<buf[40]> + <canary> + <rbp> + <片段一地址> + <binsh字符串的真实地址,会被pop去rdi> + <system函数的真实地址>

如果我们要write(xx, buf, xx),就要先将要输出的buf的地址放到rsi(第二个参数)然后再call write。这就要用到片段二。片段二其中,pop r15我们不需要用到,所以在构建ROP链的时候,要多加一个无用数让它pop出来。构建让程序read的ROP链如下:

<buf[40]> + <canary> + <rbp> + <片段二地址> + <buf地址,会被pop去rsi> + <没用的数据,会被pop去r15> + <write函数的真实地址>

<rbp> 我这里一般取0,因为leave的时候会pop rbp;所以如果是leave;retn;的程序段,要放多一个栈数据。

  • 汇编命令leave等价于mov rsp rbp;pop rbp;
  • 汇编命令retn等价于 pop rip;

第四步,尝试找到函数的真实地址

在第二步可以看得出,system函数是在动态链接库里面的函数,而且"/bin/sh"字符串,也只有在动态库里面有。为了构建我们的ROP链去运行system("/bin/sh"),我们需要找到这个函数的真实地址。因为每次加载动态链接库后的地址都会不一样,我们需要在运行的过程中找到动态链接库的基址。

PLT地址 -> GOT地址 -> 真实内存地址

选择一个函数,在pwn里面有plt地址链接到动态链接库。我们这里可以选择puts函数,然后把puts函数的got地址所指向的数据(真实内存地址)打印出来,就可以得到puts函数的真实地址。

<buf[40]> + <canary> + <rbp> + <片段二地址> + <puts函数的got地址> + <pop去r15的没用的数据> + <write函数的plt地址>

最后把puts函数的真实地址减去puts函数在动态链接库里面的相对地址,就可以计算出动态链接库的基址。

libc_base = puts_real_address - puts_address_in_libc

有了动态链接库的基址,那么我们就可以求出动态链接库里面system函数和"/bin/sh"字符串的真实地址:

system_real_address = libc_base + system_address_in_libc

第五步,用python写出对应的程序

如果上面都理解了,这里就不多做阐述了。

附件:一些名字的解释

缩写含义
RELRORelocation Read-Only, 重定向只读
PLTProcedure Link Table,程序链接表
GOTGlobal Offset Table,全局偏移表

### 回答1: 打开一个 Pwn 题需要以下几个步骤: 1. 下载并安装 Pwn 工具集。Pwn 工具集包括 GDB、IDA Pro、pwntools 等工具,可以帮助你进行 Pwn 题目的分析和攻击。你可以在官网上下载并安装 Pwn 工具集。 2. 阅读题目描述和提示。Pwn 题目通常会提供一些提示和描述,让你了解该题目的基本信息和攻击思路。 3. 分析漏洞点。在进行 Pwn 题目攻击之前,你需要先找到漏洞点。这可能需要你分析题目代码、反汇编二进制文件、使用调试器等方式。 4. 构造 payload。一旦你找到了漏洞点,你需要构造 payload 来利用该漏洞。payload 可以是一段 shellcode 或者其他恶意代码,它可以让你在目标程序中执行任意代码。 5. 执行攻击。最后,你需要执行攻击并验证是否成功。你可以使用 Pwn 工具集中的工具来验证攻击是否成功,例如使用 GDB 调试实时查看程序的状态。 需要注意的是,Pwn 题目是比较复杂的,需要一定的计算机系统知识和技能。因此,在进行 Pwn 题目攻击之前,建议你先学习一些基础知识,例如计算机系统原理、汇编语言、C 语言等。 ### 回答2: 要打开一个pwn题,首先需要了解pwn题的基本知识和入门技巧。Pwn题通常是指有漏洞可以进行攻击的二进制程序。下面是打开一个pwn题的一般步骤: 1. 了解目标:阅读题目描述和相关信息,了解题目的背景故事、要求和限制条件。同时,检查题目文件的属性和权限。 2. 查看源代码:如果有提供的源代码,应该仔细阅读以了解其中的漏洞和可能的攻击点。关注包含用户输入的部分,如输入函数、变量声明和参数传递等。 3. 漏洞分析:根据题目类型和源代码,分析可能存在的漏洞类型。常见的漏洞类型包括缓冲区溢出、格式化字符串漏洞、堆溢出等。 4. 构造攻击载荷:根据对漏洞的分析,构造相应的攻击载荷。这可以是一系列的输入,以便利用漏洞触发特定的目标操作。 5. 调试程序:使用调试器(例如gdb)加载目标程序并跟踪代码的执行过程。这有助于理解程序的行为以及找到攻击载荷的正确位置。 6. 利用漏洞进行攻击:在调试过程中,找到合适的位置并向目标程序输入构造的攻击载荷。这可能会导致目标程序发生崩溃或执行任意代码。 7. 获取权限或执行目标操作:根据题目要求,尝试获取目标操作的权限或执行特定的操作。这可能需要进一步的攻击,例如提权、绕过安全机制等。 8. 编写脚本或自动化攻击:完成手动攻击后,可以根据需要编写脚本或使用自动化工具来自动化攻击过程,以便更高效地解决类似的问题。 总之,打开一个pwn题需要对二进制程序的漏洞和攻击技术有一定的了解,并且需要进行分析、调试和尝试不同的方法来攻击目标。不同的pwn题可能需要不同的技术和思路,因此需要不断学习和实践来提高自己的能力。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值