TAMUCTF的pwn部分
首先先看一些简单的栈溢出,TAMUCTF的五题pwn全是由gets引起的栈溢出,五题分别代表了栈溢出最基本的题型以及利用方式,值得看一看。
本篇主要针对于最基础的内容,所以尽可能详细的作出解释
pwn1
pwn1是通过溢出覆盖局部变量,从而达到某个正常程序流达不到的分支,通过IDA可以非常直观的看出来哪里可以溢出,以及溢出点在哪里,而且我们要达到的分支在哪里
对于本题来说,gets(&s)即溢出点所在,而所溢出的对象就是跟s变量同样在栈中的变量v5,而当v5通过溢出修改数值==0xF007BA11时,就可以达到我们想要达到的分支。
所以溢出点就是v5在栈中的位置,就是[ebp-Ch]的位置,由于gets内容是从s处开始读入栈中,所以,在相对于s偏移0x23-0x0C=0x17的位置。
即我们可以构造0x17个其他字符,后面跟着0xF007BA11即可获得flag,由于某些十六进制数无法通过字符的形式直接输入,所以建议通过脚本形式来将构造的数据写入
pwn2
跟pwn1相比,pwn2就没有那么直观和简单了,不过依然是栈溢出最基本的题型,通过IDA点进echo函数看看
gets函数,凡是有gets函数的地方一定会产生溢出,区别仅仅是能否利用以及怎么利用,这里与上一题不同,有栈溢出经验的都知道,这里的gets可以造成控制程序流的效果,可以让程序执行到指定的位置,这是栈溢出最常见也是利用效果最好的方式。
下面来看看溢出点,同样是s变量,位于[ebp-EFh]的位置,所以ret地址,也就是我们想要控制的程序流地址,就是相对于s在(0xEF+0x04)的位置,加上的那个0x04时ebp所占的空间,因为ebp始终在ret地址的上方,所以是一个很好的计算偏移的基准。
所以我们构造形如(0xF3*'a'+addr)的数据,addr就是我们的程序流可以到达的地方,其中'a'代指任意数据,不同的题目这里填充的数据可能有所不同,针对这一题则可以填充任意数据。
接下来就要看看我们要让这个程序执行到哪个地方,通过IDA的函数窗口可以看到有哪些函数可以利用
很明显,我们看到了在上一题遇到的相同的函数,所以可以直接跳转到该函数处,从而获得flag,我们可以通过IDA查看该函数的地址
显然0x0804854B就是该函数的地址,也是我们通过控制程序流要达到地方,所以我们构造的最终输入字符串应该是:
0xF3 * 'a' + pack(0x0804854B)
通过脚本输入即可获得flag
pwn3
乍一看,pwn3和pwn2像是一样的题目,同样,我们点进echo看看
这里除了多了两个printf就没有明显的差别了,同样也是gets产生溢出,同样也是通过栈溢出控制程序流,所以我们看看可以控制程序流到达什么地方
很遗憾,这里没有获得flag的函数,所以就不能通过现成的函数来达到我们的目的了,所以我们采取大多数pwn题型最常用的思路,getshell,说明白点就是通过控制程序流获得对方系统的root权限,从而达到读取对方主机上flag文件的目的。
那么如何通过控制程序流获得root权限呢,不同题型、不同漏洞、不同解题思路各有不同方式,这个以后慢慢讲解。
针对这个题目,我们可以首先看看echo函数的指令,第一个printf通过%p打印出来了s的地址,而我们又可以通过gets控制程序流,那么我们就可以控制程序执行到s所在的地址,至于地址中应该是什么指令,我们就可以通过shellcode填充,来getshell。
所以我们的shellcode的功能就应该是在linux x86下,打开/bin/bash,文章最后会附上简单的shellcode生成方式。
所以我们最终要在s处填充的数据应该就是:
shellcode + 'a' * (0xF2 - len(shellcode)) + pack(ret)
#这里的ret就应该是通过printf打印出s的地址
pwn4
pwn4看起来就要比前三题要复杂得多,一般的pwn题思路则应该是大致的了解程序的结构功能和所用的数据结构,然后发现哪里可能会出现问题,寻找问题所在的最简单的思路就是关注危险函数,其中最危险的函数就是gets。
本题显然也是由于gets产生的栈溢出,而且很明显溢出点在s偏移0x20的地方,可以控制程序流,这一般来说是对于这个程序的假设,我们可以通过GDB去调试验证这个假设还有我们所计算的溢出点是否有误。
而对于本题来说,后续的执行流程并未影响栈结构,也并没有使得s在栈中的布局发生变化,所以,在这一题中,我们依然可以通过gets的溢出漏洞控制程序流。
接下来就是我们的重点了,怎么通过控制程序流getshell
通过题目分析就可以发现,我们可以通过直接调用system函数来getshell,即执行system("/bin/sh")
那么该如何调用函数呢,根据之前的经验,只要ret处写上system的地址,即可调用system函数,那么该如何配置参数呢
很简单,通过内存查找找到"/bin/sh"字符所在的内存地址,然后因为在x86模式下,第一个参数往往放在ret之后的栈空间内,所以,将"/bin/sh"地址填充在ret之后,即可成功调用getshell
system函数的地址我们同样可以采用与之前相同的方式查看
"/bin/sh"的查找则可以通过IDA的字符串查找功能来实现
所以最终要填充的数据应该是:
'a' * 0x20 + pack(system) + pack(ret) + pack(bin_sh)
#这里system后的ret是作为调用完system函数后的返回地址
#而这个返回地址之后紧跟着的就是system函数的参数
pwn5
pwn5显然要复杂得多,无论是程序规模还是发现问题的难度,确实要比前几题要有难度,建议从main函数看起,逐步查看了解程序流程,本题是静态编译的程序,简单地说就是没有链接任何库,而是将所有的函数都通过自己的程序来实现,这样的程序对于栈溢出的利用来说,有一个极大的好处,这个后面利用时再说。
通过一步步分析,就可以发现危险函数gets,这也是这个程序栈溢出所在的地方,所以通过程序执行执行到这里,就可以触发栈溢出,同样,通过合理的利用,就可以getshell。
找到gets函数后,本题的溢出点就很明显了,就在dest变量偏移0x20的地方,可以控制程序流
至于如何控制程序流,以及怎么通过控制程序流实现getshell
在这里我们即将要用的其实是ROP(返回导向编程)技术,简单地说,就是通过一个个gadgets(代码片段)的组合,来达到getshell,这些gadgets组合实现的功能,一般来说,都是通过汇编指令执行system("/bin/sh")
这里有汇编基础的可以自行去尝试构造,具体的ROP技术我们以后再详细讲解
这个题目,由于是静态编译,我们就可以利用工具来实现ROP链的构造
这里使用的工具是ROPgadgets,可以通过官网下载,主要的功能是寻找程序中可用的gadgets片段,但是对于某些程序,便可以自行构造出ROP链,比如静态编译的程序。
所以最后的构造其实很简单:
'a' * 0x20 + ropchain
#ropchain是指通过ROPgadgets自动生成的一条利用链
总结
TAMUCTF的五题pwn属于入门级的栈溢出题目,通过这五题的学习对于刚入门的人来说,其实应该还有很多疑问,关于很多的技术、概念、方法以及理论在这里并没有详细的讲解,多数是带过,而且工具的应用这里也尽量简化为只用IDA来分析,本篇仅作为栈溢出最基础的内容的讲解,略微加深的内容将通过其他文章来逐步讲解。
本文所用到的脚本和源程序都可以在github上下到:
https://github.com/LJRosemary/ctf
注:
本篇有关于栈溢出的内容并未涉及任何保护措施的绕过,也并不将保护措施作为本篇的重点,默认所有保护措施都未开启。
附:
shellcode自动生成方式:
- metasploit:msfvenon -p linux/x86/exec CMD=/bin/sh -f python
- peda插件:shellcode generate x86/linux exec