检测程序
拿到文件,为64位linux文件,然后checksec为
发现开了canary和nx(栈上不可执行)
canary:(函数返回时会检测这个值,如果改变,就会崩溃,可以防止shellcode覆盖返回地址)
nx:防止栈上执行代码
RELRO(表示重定位表只读)
分析流程
这里大家多看一下程序,运行一下,就可以知道逻辑了,这个是main函数主体:
简单的意思输入不同的数字,会进入不同的流程
当输入9011时,会进入sub_4008A3这个函数
这里存在栈溢出,因为buf的长度为16个字节,拷贝的长度为0x1000,所以会存在栈溢出,所以当时想到的解题思路就是进入这个分支,栈溢出,覆盖返回地址,直接进入调用system(“/bin/sh”)拿到shell,读取flag(后来由于程序本身的原因是走不通的)。
解题思路
继续看,如果进入这个if分支,需要输入的数值和读到的数值一样才行。
fd = open("/dev/urandom", 0); 这个是读取随机数,每次读取的值不一样,然后和我们输入的数值相比较,因为每次都随机,无法预测到随机数,这样永远都猜不到读取到的随机数值,也无法进入if大括号内,无法触发执行qmemcpy触发栈溢出。方法是:这里需要打开1024次,然后再次打开这个设备文件会出错,返回错误,读进去的unk_602068数组也全为0,然后我们输入8个0,判断相等,就可以进入if分支内了。
再看unk_602080这个全局变量,最后需要布置好数据,而且需要绕过canary,才能覆盖返回地址,那么如何知道canary值呢?
这里提醒一下,每次程序运行的时候canary的值是不一样的,而且,一个程序运行时候所有的canary值都是一样的值。
我们回到主程序:
当输入数值2的时候,会进入这个函数:
会从键盘输入的字符拷贝到这个全局变量中。
当输入1的时候:
函数的意思是会将输入的字符串拷贝到栈中的buf中,然后输出,这里只能执行一次函数read,puts,不能执行第二次,从这里我们通过利用这个函数可以打印出canary值,直接上gdb调试,进入这个函数。
在0x40a22下断点:输入1,会进入这个函数:
看到canary值,0x4dc9d7e 0x8b445b00,存放在rbp-8的位置,而buf的位置为rpb-0x110,只要puts出0x110-8位置的8个值,就是canary值,前面的buf直接填充可见字符,但是8位的canary值,最低地址为0,会被截断,所以也需要覆盖最低位为0x00替换为可见字符,直接打印后面的7个字符,就可以算出canary值了
下图为没有读取字符时的buf
通过生成0x110-8 -1个可见字符,这样会打印后面的7位的canary,这里啰嗦一下。
下一步会调用puts进行打印所有可见字符
打印最后的7位值即位canary值。通过python转化为16进制即可。
这时候拿到canary值了。但是接下来在退出函数的时候会崩溃,因为修改了canary值,然后跟原始的值比较,不一样程序会异常退出,这样就无法利用了。
所以这里想想,应该如何拿到canary值,而且不会出现异常退出呢,我们必须让canary值落在0x7fff~dc80 – 0x7fff~dc80+0x108=0x7fff~dd88区间内,因为这个区间才是buf的内容,才能打印出来,而且程序不会崩溃,一旦出界了,就打印不出来,而且会破坏栈。
豁然开朗
所以采取的办法是,调用递归函数,一直压栈数据,将canary值压到很低的栈上,然后栈不会清空数据,将会保存一些canary值,这时候调用sub_400a22函数,但要记住,这个函数只会调用一次,会从这个区间看到想要的canary然后打印出来,这里再次记住一下刚才的buf首地址末尾为dc80,一直观察这个区间的数据变化情况。
重新运行程序,回到main函数:输入2,会进入这个函数,读取键盘内容到全局数组,unk_602080中
这里如果用户输入n,就会一直递归调用,随时观察栈的变化
这次运行的canary值为:$1 = 0x52be8e08d875400
第一次运行0x400999这个递归函数的栈地址rsp=0x7ff~0xdd80,与刚才的区间dc80~dd88还差一点,对应的canary值也在对应的栈中
第二次进入这个函数,再观察栈:
这次为dd60,
第三次进入递归:
第4次为dd20,第5次为dd00,第6:次:dce0:,发现每次减去0x20
第7次为dcc0,第8次为dca0 ,相应的canary值依次散落在这个区间。
只要进入了这个区间,canary值会分布在这里,这时退出这个函数,然后进入sub_400a22函数打印出来通过puts将buf的内容打印出来,也会打印出相应的canary值了。
进入这个函数看对应的堆栈
dc80+0xa8=dd28,就能看到canary,可见在第4次递归,canary值就落在那个区间中了,通过sub_400a22,填充a9个可见字符,0xdd28-0xdc80=0xa8 0xa8+1=0xa9,因为canary的值低地址一个字节为0,防止被puts截断,需要a8+1,然后就可以打印出canary值了,程序也没有崩溃。
这时输入9011进入函数,进行栈布置,覆盖返回地址,
因为程序不包含system的地址,没有libc,并且nx,所以覆盖返回地址,并且构造栈中数据,进行rop,直接调用 open(“./flag”), read, puts就可以打印出flags了
总结:
1. 进行递归,打印出canary
2. 通过多次打开设备文件出错,然后栈溢出,进行rop,直接输出flag
note:rop这步挺浪费时间的, 不知道有什么好用的工具吗?
写的比较简单,也是这几年来第一次学习和调试pwn,一直想学,就是没精力....希望能与大家学习交流。
最后谢谢id:another,流韵山庄的思路和指导
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法
最后于 2018-8-31 20:48
被LowRebSwrd编辑
,原因: 拼写错误