不知不觉就打开了第三题。。。。
calc [150 pts]
提示是*Have you ever use Microsoft calculator?*这种提示,第一时间上网找信息,发现在google上真的流传着microsoft calculator的bug,好像触发方式为:
- 打开一个计算器
- 输入4
- 使用sqrt,会得到结果2
- 然后用当前数字减去2
- 发现结果为-8.1648465955514287168521180122928e-39,并不是0
嗯。。。大概是暗示运算可能存在问题?
首先打开,发现程序大致如下:
这里由于提示,我们就尤其关注一下calc的内容
会发现开启了栈保护,并且每次都会清空s,看起来是不要打算栈溢出攻击了。。。
首先是get_expr,这个函数会将大部分的不合法字符串过滤掉,只读入±*/%和所有的数字。
(get_expr细节)
init_pool也是一个初始化函数,将一个数组中的数据初始化为0。然后这里尤其关注一下parse_expr,这个函数是一个解析表达式的过程。windows的表达式出错就和这个密切相关,这里进行深入的了解:
开始分析就遇到了问题:为什么
1 | if ( (unsigned int)(*(_BYTE *)(i + expr) - 48) > 9 ) |
要将大于9(也就是为数字的时候)的过滤掉?后来在gdb调试的时候发现这里的为unsigned,也就是无符号的,所以只要小于48(也就是符号范围内)的都会被考虑(之前其他符号已经被过滤了)
明白之后继续看,会发现将单独出现的0都过滤了(防止出现除数为0)
这个地方出现了两个有点问题的地方,第一,atoi是有极限的,我记得好像是最高位为1的时候翻译就会一直为0还是什么的。。。然后是第二个,就是打了??的位置,这段要是直接理解起来的话意思为
- 首先从传入的a2(开始为全0)中读取当前的数据v4,之后自己内容自增1
- 然后以v4为下标,并且将换算得到的数据赋值过去。。。。。
这做起来的目的很奇怪啊。。。。后来想到应该是这样的
HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
放入了数字个数|第一个数字|第二个数字|…
HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
这样的话就说的通了,只不过是带有头部的数组而已。
那么现在我们回头看原先的calc函数的内容
也就能够明白了:array_from_1其实还是array数组,只不过是&array[1]而已,printf的时候,是通过判断array[0]中的数据,来决定自己要输出的内容的位置,从而从array_from_1中选取数据输出。
eval的内容如下
根据算法,发现了如下的漏洞: - 由于算法eval的运算法则为【使用当前的第一位array[0]作为下标进行取值】,所以当我们使用形如"*400","-300"的写法的时候,会将array[0]作为运算左值,array[1]作为运算右值,从而可以做到泄漏信息的目的已知calc中的数组的起始地址为ebp-1440,而ebp-0xc处存放了一个canary,于是计算得知(1440 - 0xc)/4=357(除以4是因为一个int的大小为4),于是当array[0]的值为358的时候,此时便会取到canary的具体值"0xdce1ae00"
由于始终想不到在哪里进行注入。。。思考了很久,直到成功的从网上找到了writeup。。。这里参考一下别人的思路。 - 考虑到有输入过滤,所以首先排除stack中存入shellcode的想法,然后考虑ret2lib的方法,所以要检查此时是否存在动态连接:
从图上可以发现,没有动态连接的部分,说明此程序是静态编译的,不能使用ret2lib的方法。但是相对的,就可以找到ROPgadget(也就是所谓的【跳板门】)从而构造execve("\bin\sh") - 关键的注入其实还是利用之前提到的泄漏的问题,如果我们通过+300能够修改array[0]的值为符号后面指定的数字那么+300+1的话就能够通过修改了array[0],从而在eval里面可以对特定的地址内容进行修改,这样就完成了注入。
为了测试是否能够注入成功,我们首先尝试导出返回值的内容:
可以看到,此时的字符串的起始地址为:ebp-1036,而看到后面取值的时候:
此时都为int四字节对其,则此时距离返回值的距离为1440/4=360,而为了能够输出此时的地址,看到printf的输出为
也就是此时距离返回值地址为361(包括一个ebp),我们直接输入*362或者+361便可导出数据。测试成功后,接下来尝试修改一下返回值地址,将其修改成目的地址0x0804947B,看看看能否成功:
成功了!这种修改甚至绕开了canary,这就更加的方便。接下来我们只要构造ROPgadget即可:
最终目的是 构建execve(’\bin\sh’),所以我们的目的自然是【产生int 0x80中断并且此时ebx必须指向执行的程序位置,也就是/bin/sh】,那么我们的手段应该是 - 将eax的值最终提升为11
- 往栈中写入数据’\bin\sh’
- 将ebx的值指向一个栈的位置
- 将ecx/edx的值进行置零(?)
- 执行int 80
为了实现第一条,首先要能够将eax的值变为0,并且能够立刻返回。本来是想使用pwntools快速构建rop的,但是我的电脑上总是报错,最后只好下载了一个ROPgadget来用。。。虽然用着很不方便,但是也找到了一些内容:
1 | 0808F2D3 xor eax, eax |
然后为了实现能够将数据字符串放入栈中,我们可以直接同过往其中写入数据的方式,将/bin/sh写入数据栈中,但是此时为了能够让ebx正好指向这个栈的位置,我们需要将这个过程放在最后执行,然后通过寻找pop ebx 的方法将数据的地址放到:
1 | 080701D0 pop edx |
由于此时是打开了ASTL的,所以我们得通过printf来将我们需要的目的数据泄漏才行。由于我们已知栈中的情况为
也就是说,此时通过加入数据可以通过返回值指向为0x080701d0,然后将edx,ecx,ebx值修改为我们想要的值(其中ebx的值要在最后计算出来)
然后
1 | 08049a21 int 0x80;触发中断 |
最后触发这个位置的中断,我们就可以完成我们的攻击。
那么综上所述,我们的栈应该变成如下的样子:
然后最后,每次都先获得相关值,通过计算出栈中值和当前值的offset,然后通过下一次输入数据的时候输入【当前地址+offset】完成地址的修改。
发现太久没写代码,这段代码写的不是一般的烂啊。。。。然后得到如下的答案:
测试过程中发现,似乎edx的值不去理会也没有太大问题?
第一次边界溢出成功!
附上代码
1 | # -*- coding:utf-8 |