滴滴CTF之唯一的pwn题详解
前言:这道题比赛期间没怎么研究,第一次搞了一天没搞出,不知道什么错,然后放弃了,然后比完后,搜了一波exp后,自己具体调试了一波,懂了整个过程,有些师傅们的wp有些小瑕疵,还有就是师傅们从来不详细写简单题的writeup,导致新手很难懂exp。好了,开始正文:
题目简介
1 | int __cdecl main(int a1) |
sub_80485DB(stdin, stdout)内部代码
1 | int __cdecl sub_80485DB(FILE *stream, FILE *a2) |
sub_804862D()内部代码
1 | int sub_804862D() |
从sub_804862D()这里明显可以看出整数溢出的洞,输入-1即可,-1在32位机器上为0xffffffff,具体可以自行调试查看,转化为signed int为-1,小于63,接下来在read(v1, &buf, nbytes);就可以输入很长的一串字符,看上去好像很简单对吧,整数溢出,然后接着栈溢出,ROP一波梭。。。我觉得是这样的,可是运行自己的exp起来后,发觉有很大的问题,每次都是出错
具体调试,你可以从
1 | #!/usr/bin/env python |
定位
- 在具体地方附加,用gdb.attach(io)附加,然后finish 3次出到最外层,最外层的地址就是0x80开头的这种,然后单步运行,你会发觉最后出错的地方在read这里,然后read函数出错,这里我当时不知道怎么继续调试下去了,一直卡死在这,还有个小问题,我发觉第一次printf会输出一些乱码,按照往常来说,这里肯定泄露了信息,所以具体这里泄露了什么信息,就得你自己去发现了。
后面从大佬的wp里发觉,他说ecx不能改,然后我就去看汇编代码了,
- 发觉最后一行有点道道,你看我上一张图的错误,0x6161615d,为什么不是0x61616161呢,ecx-4,联系起来没有,刚好0x61616161-0x6161615d = 4 ,也就是说,我把ecx覆盖成错误的值了,所以就没法正确读入了,那我如何找到ecx的正确位置呢,这时候就运用gdb了
我直接将Passwd(‘a’*0x60)这句注释掉,然后python运行这个脚本,然后用
gdb-peda$ pattern_create 200
创建一串字符,然后复制,然后
gdb-peda$ c
在打开的另一个终端里输入这串字符
然后获得这张图
Program received signal SIGSEGV (fault address 0x4141333d)
有这句话
然后复制这个address
0x4141333d,在将他+4,为什么要加4呢,因为我们刚刚那里ecx-了个4
所以变成0x41413341
gdb-peda$ pattern_offset 0x41413341
获得偏移,1094792001 found at offset: 68
定点泄露
68个偏移,好了,知道错误在哪了,如何泄露ecx?关键点来了,还记得刚开始的printf输出的乱码吗,这里肯定有泄露信息,从ida里我看不出什么东西,所以,gdb也调试一波了
你可以直接gdb filename 或者 gdb 启动后在file加载都行,
然后输入start开始,这时候你们的界面可能跟我不太一样,因为我装了插件,peda和pwndbg
单步输入n 走到push ecx这里,因为我需要的是ecx,和ebp,为什么需要ebp呢,因为知道ebp我在知道偏移,整个栈的位置都知道了,所以我需要把这里的数据记录下来,可以看到,寄存器里存的值
ecx 0xffffd160
ebp 0xffffd148
然后我需要继续运行软件,到我应该栈溢出的地方,这时候先下个断,
gdb$ b read
在read函数处下断,然后再
gdb$ c
这时候会在read函数处断下,不担心,输入finish
gdb$ finish
然后让你输入名字,直接回车,这时候继续单步,单步就是输入n(next)
在fprintf这里停下,看地址,取出0xffffd090
然后
gdb$ x/20wx 0xffffd090
好了,我看到关键点了,0xffffd148,在第三行第三列里,也就是ebp的值,也就是说,我们需要输入10个4字节就可以到这个地方,还有,你看ebp后面的地址,0xf7e2c935,这里是不是很像libc函数的地址,这里我为什么说像呢,从上面我看寄存器那张图里可以看到__libc_start_main+241的地址,他就是这个类型的。
所以,我输出一下这里的地址看看内容
1 | gdb-peda$ x/wx 0xf7e2c935 |
果真如此,setbuf的地址我们也拿到了,一次性泄露了ebp和setbuf+21的地址,所以这时候怎么获得ecx的值呢,这里你就想每次栈的相对位置会变吗,不会的,所以,开局ebp和ecx的地址相差也不会变,所以我就用ebp和ecx的偏差
ecx 0xffffd160
ebp 0xffffd148
ecx - ebp = 0x18
所以ebp+0x18就是ecx了,找到ecx了,也找到setbuf地址了,现在便剩下
ROP
payload = ‘a’*0x44 + p32(ecx)+p32(system_addr)+p32(0) + p32(bin_sh)
按理说payload这样就行了,先填充位置,在填充ecx,在返回地址就完美了,运行下试试
这里我不告诉你们具体怎么做了,因为都是运行我刚刚讲的几条命令,finish,c,n,
看这里esp指向了我的system,也就是说我还没到返回地址,还有你看最后一步,他还会通过ecx-4改掉esp,所以还没到重点,所以很明显,我还要将两个pop填充掉,也就是得弄两个空的给他
payload = ‘a’0x44 + p32(ecx)+ p32(0) * 2 + p32(system_addr)+p32(0) + p32(bin_sh)
这样下去可以填充掉那部分,还有个他改了esp的地址,看他修改了多少偏移
从图中可以看出esp跟ecp差了4个偏移,也就是44=16大小
所以我们可以
payload = ‘a’*0x44 + p32(ecx-16)+ p32(0) * 2 + p32(system_addr)+p32(0) + p32(bin_sh)
yes,成功
小结
现在从整体上来分析下过程,先利用printf泄露ebp和setbuf+21地址,然后利用ebp和ecx的固定偏移计算出ecx,然后定点覆盖到ecx具体地址,定位ret的位置,然后进行payload构造,最后成功ROP
附上最后的exp
1 | #!/usr/bin/env python |
总结:
- 这道题其实不难,难的是大部分要调试,调试部分的话,我写的也7,8成了,具体就靠你们自己慢慢去调试了,关键还是要看清栈的结构,知道他的位置,其余就是基本的ROP了,
- 虽然题目不难,但我光调试花了好长时间,最主要还是没了解还可以这么玩,学到了师傅们的姿势,谢谢ddctf,谢谢大佬们的writeup
- 如果还有什么不懂了,可以下方留言
本文作者:NoOne
本文地址: https://noonegroup.xyz/posts/afd9f177/
版权声明:转载请注明出处!