BUUCTF-axb_2019_fmt64
小结:通过这道题我学习了很多
拿到检查
64位程序开了栈不可执行
ida打开
只有一个main
read读入我们输入的内容,然后sprintf把这一串放到format里面,printf处有格式化字符串漏洞,然后整个过程是在一个循环
思路就是修改函数printf的got表为system函数的地址,然后传入参数binsh(这里有个小坑,后面讲)即可拿到flag
首先找一下偏移,为8
1.泄露printf的got表地址
由于延迟绑定技术,只有在程序中执行过的函数,got中才会绑定其真实地址,所以要泄露的时漏洞之前已经执行过的函数
这里提一嘴32位的泄露地址可以用整个payload :payload = p32(泄露地址) + %偏移$x 来构建,但是64位不行
如果这样写,前面的地址数据经p64()打包后占的是8个字节,我们send 的地址和我们构造的格式化字符串中间还有好多个 ‘00’ ,而在字符串中 ‘00’ 就代表了结束,所以在printf到‘00’时,就被认为字符串已经结束了,自然不会继续往后面printf了,也即是说我们的字符串都被’00’给截断了。没办法,为了字符串不被截断,我们只能将地址给放在字符串的后面了
(具体可参考这位师傅的文章,我之后的内容也有在参考这边文章,同时也做出了我的解释https://www.anquanke.com/post/id/194458?display=mobile)
64位的payload ='%偏移$s'aaaa(补够一个内存单元的内容)+ p64(函数got表地址)#将地址放后面构建payload
拿到printf函数的真实地址,libc查找啥的略过了
2.修改printf函数的got表地址为system函数地址
现在我们找到对应的libc版本,并且拿到system函数的地址,下一步就是用格式化字符串修改got表了
只有后面3字节不同,截取倒数第三字节和后两字节进行修改
这里用>>和&
即算数右移,在汇编语言中,算数右移操作,如果最高位为1,则补1,否则补0, 如将10000000算术右移7位,应该变成11111111,而逻辑右移7位,则不考虑符号位,变为00000001,这点就是算术右移和逻辑右移的区别。
在汇编中,可以用算术右移来进行有符号数据的除法。把一个数右移n位,相当于该数除以2的n次方。
&是位运算符-与,类似的还有|(或),!(非)。
整数在计算机中用二进制的位来表示,程序语言提供一些运算符可以直接操作整数中的位,
称为位运算。
这些运算符的操作数都必须是整型的。
计算机里面,当&作为位运算时,1&1=1 ,1&0=0,0&0=0;即两个相同的内容与之后就 是那个内容,如果是两个不同的内容与之后,就是0。
举个例子(32位)
system_addr=0x12345678
high_sys = (system_addr >> 16) & 0xffff
low_sys = system_addr & 0xffff
这是0x12345678的二进制# 0001 0010 0011 0100 0101 0110 0111 1000
这是0xffff的二进制# 1111 1111 1111 1111
因为计算机在进行处理的时候,最后都是以二进制来处理的。
因此取前两字节的时候,就让整体进行算术右移16比特,因为32位程序的地址是32比特,
此时右移之后,原本高位的16个比特就跑到了处于低位的16个比特,而高位的16个比特都
变成了1,又和0xffff进行了与运算,0xffff就是十六个比特的1,此时对低位16比特的字节进
行与运算之后,低位16比特没有变化,依旧是低位16比特,但是高位16比特从原来的16个1
变成了16个0,也就是高位的16比特没了。至此高位的两个字节就被取出来了。
上述真正的核心其实是,对于0x12345678这个地址而言,每一位都是对应4比特(32位程
序),每个字节就是8比特,也就是说8比特最大能表示的也就是ff,因此在十六进制中每个
字节都是和对应的八个比特一一对应,这样的好处就是每个字节对应的比特和整体所有字节
是没关系的,因此就可以直接去比特右移16,直接把低位的16比特移走,而不干扰高位的
16比特。
至于去后两个字节的时候,就直接进行与运算了,逻辑右移的目的就是去取高字节,把高字
节放到低字节的位置,然后低字节被除去了。与运算(对于0xffff而言)的目的就是保留低字
节的内容,而删去高字节。
(该内容来自ZIKH26师傅,附上他博客的链接https://www.cnblogs.com/ZIKH26)
回到我们现在的题目
可以看到sys_high是大于sys_low,所以先修改小的,再修改大的
单字节修改hhn,双字节修改hn,四字节修改n
之前不理解为什么双字节修改的时候要传入地址,然后在传入整个地址+2
这里给出解释
以上面这个图片为例,0x601030里面存储的是00007f73d9e62e40,要修改最后的三个字节,先修改倒数第三个字节的,就把0x601032(此时跳板指向谁,就会修改谁)当做跳板即可(因为此时0x601032指向的是倒数第三个字节)因为只要修改倒数第三个字节,因此参数使用hhn。再用hn来修改最后两个字节,此时我们写入跳板0x601030(此时跳板指向谁,就会修改谁)到栈中即可。
发现在利用格式化字符串之前,format里面已经有了Repeater; 这就意味着,准备利用格式化字符串写入的时候,就要在最开始减9
'%'+str(sys_high- 9)+'c%12$hhn'+b'%'+ str(sys_low-sys_high)+b'c%13$hn'
(这一串的长度是25,之后我们要把写入的这一串补)
payload2 = payload2.ljust(32,b"A") + p64(puts_got+2) + p64(puts_got)
3.这个时候printf已经被我们改为system函数,所以下面的输出。。也就失效了
传入binsh