做newstart的pwnpieee题的pie的学习
首先:对于pieee这道题
很简单的栈溢出,除了NX其他的保护都开了,然后呢在左边也发现了后门函数
相对偏移为0x1264(对于这里我们只用关心后三位,因为pie不会随机化地址的低12位,通俗点说就是我们十六进制地址的后三位)而一般而言后三位的地址能够确定我们的函数的地址所以 直接栈溢出加上后三位的地址就可以返回到system(bin/sh)
对于pie的学习还在更新中,主要是第一次遇到pie的题目 还有一种基本解法就是用printf泄露出地址然后找到pie的基地址来做
ret2libc先了解一下基本知识吧
got表:globle offset table 全局偏移量表,位于数据段,是一个每个条目是8字节地址的数组,用来存储外部函数在内存的确切地址。我们的最终目标就是拿到system函数的got表地址,同时知道libc的基地址的话即可找到system函数的真实地址。
plt表:procedure link table 程序链接表,位于代码段,是一个每个条目是16字节内容的数组,使得代码能够方便的访问共享的函数或者变量。可以理解为函数的入口地址,通过劫持返回地址为puts函数的plt表地址,即可执行puts函数。
可执行的二进制文件里面保存的是PLT表的地址,对应 PLT 地址指向的是 GOT 的地址,GOT 表指向的就是 glibc 中的地址那我们可以发现,在这里面想要通过 plt 表获取函数的地址,首先要保证 got 表已经获取了正确的地址(即最靠右的两个箭头已经建立),但是在一开始(尚未发生函数调用时)就进行所有函数的重定位是比较麻烦的,为此,linux 引入了延迟绑定机制。
那么什么是延迟绑定机制:只有动态库libc中的函数在被调用时,才会进行地址解析和重定位工作,也就是说,只有函数发生调用之后,上图中最右侧的两个箭头才建立完成,我们才能够通过got表读取到libc中的函数。至于具体过程相对复杂,这里引用大佬博主的图片简要介绍,当程序第一次执行某个函数A时,发生的过程如下:
在可执行二进制程序调用函数A时,会先找到函数A对应的PLT表,PLT表中第一行指令则是找到函数A对应的GOT表。此时由于是程序第一次调用A,GOT表还未更新(就是图一中最右边俩箭头还没有建立),会先去公共PLT进行一番操作查找函数A的位置,找到A的位置后再更新A的GOT表,并调用函数A。当第二次执行函数A时,发生的流程就很简单了,如下图:
此时A的GOT表已经更新,可以直接在GOT表中找到其在内存中的位置并直接调用。
用通俗一点的话来说就是,第一次调用函数的时候,会首先在调用其plt表象的函数,然后由plt让got去找到这个函数,但是此时这个函数没有被调用过,所以got表里面没有存有这个函数,相当于没有找到这个函数,就会返回plt让自己去找,plt就找到了,存入got表里面,之后的调用,那么got表里面就会有了a函数的地址了,此时就可以直接到got表然后执行a函数了。
例题:newstartweek2-ret2libc 首先打开ida分析一下代码,
发现这里的read函数存在栈溢出
发现只开了NX 不能用shellcode,用shift+f12发现没有system和bin/sh 那么就可以用libc的攻击手法
这是很简单的libc的题目,exp在下
这道题没有给libc的动态连接库,所以有俩种方式可以获得libc的基地址,但是由于我的libsearcher库没有找到,所以只能用网上的libc-database,基本思路就是调用put函数泄露put函数的真实地址,然后去libc-database找到libc的偏移地址之后就能够获取到了libc的基地址了,然后就能够进行攻击了。
这里的libc.dump就是用libcsearch来做的
canary
Canary是一种针对栈溢出攻击的防护手段,其基本原理是从内存中某处(一般是 fs: 0x28 处)复制一个随机数 canary ,该随机数会在创建栈帧时紧跟着 rbp 入栈,如下图所示:
在函数退栈返回前,程序会比对栈上的 canary副本 和原始的 canary ,若二者不同,则说明发生了栈溢出,这时程序会直接崩溃。就相当于我们不能够直接进行栈溢出的操作。
canary的特点::Canary 所生成的随机数有一个非常重要的特点:随机数的第一个字节必然是 0x00 。如此设计的主要目的是实现字符串截断,以避免随机数被泄露。举个例子
如图,长度为 8字节 的字符串 str 就在随机数 canary 的正下方,某函数试图用 printf("%s", str) 输出字符串 str 。这时,若随机数 canary 的首字节不为 0x00 , printf 在输出了字符串 str 后,由于没有遇到 0x00 ,故会继续输出,进而使得 canary 被泄露。
canary绕过最常见的方法有两种:
1.逐字节爆破以获取随机数。(由于现在本人也不太会,所以不做涉及)
2.通过printf函数来泄露出canary的地址
例题:new start week2 canary:
首先checksec一下
然后用ida打开分析
发现有canary保护,不能够简单的栈溢出了 有nx也不能用shellcode relro也在我们不能修改got表,然后发现有栈溢出的漏洞还有格式化字符串漏洞,此时我们就可以用格式化字符串漏洞来泄露出canary的地址来进行我们的攻击。那么我们该怎么泄露出canary的地址呢?
我们先随意输入几个%p 发现在第六个位置会有我们输入进去的aaaa,在下面的栈区也发现了canary距离我们输入进去的地方的偏移为5,所以距离print的偏移就为11,canary的地址就存放在这里
ok找到了canary的地址,也有后门函数,直接进行攻击第一次payload泄露出canary的地址,然后payload2的构成是40的溢出数据然后是canary的地址,记住,64位的需要进行一次空转,栈对齐,然后就是backdoor的地址,就可以获取shell了。解释一下为什么要空转:因为ubuntu18及以上在调用system函数的时候会先进行一个检测,如果此时的栈没有16字节对齐的话,就会强行把程序crash掉,所以需要栈对齐。
栈迁移
我们为什么要使用栈迁移这样的攻击方式?就是因为可溢出的长度不够用,也就是说我们要么是没办法溢出到返回地址只能溢出覆盖ebp,要么是刚好溢出覆盖了返回地址但是受payload长度限制,没办法把参数给写到返回地址后面。总之呢,就是能够溢出的长度不够,没办法写我们的攻击脚本,所以我们才需要换一个地方写我们的攻击脚本。
那么下面我们理解一下栈迁移的核心,就在于两次的leave;ret指令上面,然后我们在理解一下leave和ret的指令
leave:就是mov esp ebp;pop 解释一下第一条指令就是先将ebp赋给esp,此时esp与ebp位于了一个地址,你可以现在把它们指向的那个地址,即当成栈顶又可以当成是栈底。然后pop ebp,将栈顶的内容弹入ebp(此时栈顶的内容也就是ebp的内容,也就是说现在把ebp的内容赋给了ebp)。因为esp要时刻指向栈顶,既然栈顶的内容都弹走了,那么esp自然要往下挪一个内存单元(注意栈的地址,此时向下地址是减小的)。
ret就是pop eip,这个指令就是把栈顶的内容弹进了eip(就是下一条指令执行的地址)
我们来分析一下简单的main上的栈迁移的攻击方式:
首先利用溢出把ebp的内容给修改掉(修改成我们要迁移的那个地址),并且把返回地址填充成leave;ret指令的地址(因为我们需要两次leave;ret)此时main函数准备结束,开始执行第一个leave,此时mov esp ebp让两个指针处于同一位置,现在还是正常运行,接着执行pop ebp就出现了异常,因为此时ebp的内容被修改成了要迁移的地址,因此执行了pop ebp,ebp并没有弹到它本应该去的地方(正常情况下,ebp里装的内容,就是它接下来执行pop ebp要去的地方),而是弹到了我们修改的那个迁移后的地址,接着执行了pop eip,eip里放的又是leave的地址(因为此时是把返回地址弹给eip,这个返回地址,我们先给覆盖成leave;ret的地址。你可能会问,如果这个返回地址不放成leave;ret的地址,行不行?很明显是不行的,因为我们想要实现栈迁移,就必须执行两个leave;ret,main函数正常结束,只有一个level;ret,因此我们在这里必须要它的返回地址写成leave;ret地址,以来进行第二次leave;ret),结果又执行了leave(现在执行第二个leave),此时才是到了栈迁移的核心部分,mov esp ebp,ebp赋给了esp,此时esp挪到了ebp的位置,可你别忘了,现在的ebp已经被修改到了我们迁移后的地址,因此现在esp也到了迁移后的地址,接着pop ebp,把这个栈顶的内容弹给ebp,esp指向了下一个内存单元,这就完成了我们的栈迁移了,然后就可以构造我们的payload了。
例题:newstart week2 stackmigration:
首先也是checksec加ida分析源码
没开什么保护,然后就是分析有俩个read读入的地方,然后第一个地方只能读入8个数据,所以我们不能进行栈溢出,然后就是第二个read函数的时候,溢出数据是0x10,很小,不能够构造出我们的payload,所以这里可以进行栈迁移。但是在shift+f12中并没有发现system和bin/sh的字符,所以这道题还需要我们用libc泄露来做。ok大致思路就是这样,exp如下:
解释一下:在第一个read我们可以泄露出我们的buf的地址,在第二个printf的函数这里会给出我们buf的地址,然后v2又在buf的上面8个字节,所以这里我们需要在我们泄露出来的buf上加上8.还有一个值得注意的就是为什么我们的每个payload最前都需要加上8个字节,因为这里是64位,然后执行了俩次leave ret是 esp会往上-8个字节。
因为我们其余思路就和ret2libc一样了,但是需要注意,第二次时v2的地址和第一次不一样了 所以需要我们再次泄露出来!
感想:真的理解到了很多东西,这个newstart的题目出的挺好的,但是我需要学的还有很多,还应该好好把这个栈的内容巩固之后再去学习堆,压力一下子就来了,新手的第一次博客,写的不好还请指出!