开始做题之前,首先要复现一下做题的思路,首先用IDA软件分析出伪代码之后,可以找到缓冲区距离esp和ebp的偏移值;但IDA上分析的其实不是很准;
所以就学习了一下cyclic指令,使用这个指令会生成一堆有序字符串,使用cyclic -l 0x61616161 (四个字符组成的hex)可以查看这四个字符之前有多少个字符;
利用这个原理,对题目输入这串字符串;返回值肯定会报错无法返回,因为地址被覆盖为了4个字符的hex,用报错的地址来代入上面的指令就可以知道缓冲区的大小,找到正确的返回位置;
另外一个犯得错误就是python3不能在一个字符串上同时写入字符串和byte类型;
注意gets , write , read 这一类的函数,是危险函数,它们的读入不会限制长度,所以输入超出缓冲区的长度的内容会覆盖更低缓冲区的内容,用这个特点来更改返回地址;
ret2text
首先用IDA分析;
用蓝线分出了三个部分,上面的是main函数,中间的是一个叫secure的函数,而下面的是执行system函数的汇编代码以及它的地址;
找到gets函数,那么可以利用输入垃圾字符(比如a)填充满缓冲区,最后用0x0804863a的byte类型填充,让返回地址被覆盖为执行system函数的位置;
上面说过,IDA会突发恶疾,不要信它分析出来的偏移,自己用cyclic指令来找偏移;
这张图的顺序是从上往下,从左往右看;
使用cyclic 200 生成200个有序字符;之后在调试状态下输入这200个字符;继续运行,发现一个错误:Invalid address 0x62616164;使用指令cyclic -l 0x62616164,发现这四个字符之前有112个,所以在地址被覆盖前有112个空位置;
接下来就可以写脚本通过这个程序去执行system函数进而运行shell程序了;
Exp:
1 | from pwn import * |
ret2shellcode
这道题和上面一道的不同在于NX保护关闭,栈内数据可执行,且分析IDA可知,这里没有system函数;
利用第一题的思路可以求出返回地址的偏移也是112;
这道题的原本思路应该是复制输入的数据到buf2,而buf2所在的段应该是可执行的,然后return到buf2执行输入的shellcode;但这道题的buf2所在段是不可执行的;
可以看到buf2所在的bss段属于0x804a000到0x804b000,但如右下角的段显示,rwxp,少个x,也就是不可执行;
这里先放出如果是可执行的思路,那么就很简单了,只需要输入二进制指令开启system函数(称为shellcode)和一堆无用字符填充空白,使最后的返回地址成为buf2,跳转过去后就会立刻执行输入的指令了;
Exp:
1 | from pwn import * |
但有个漏洞理论上是可以利用的,就是NX保护没开,栈上指令可执行,输入的数据也存在于栈上;
那就得想办法使其return到输入的栈地址上;但栈地址是会变换的;试了几个都找不到正确的地址;
gdb上的栈显示是真的看不懂,IDA上的都还好一点,至少还有esp和ebp的指向地址,但很可惜这道题不能远程用IDA调试,别问,问就是有毒;
所以遗憾地没找到返回时,shellcode的栈地址,中大鬼;
ret2syscall
做题之前,首先需要了解一个东西叫做系统调用,linux系统中会有一个地址为int 0x80(默认这个就是一个名称),调用这个地址的时候,会中断程序进入内核状态执行系统命令,通过寄存器的值来执行;
例如:execve的系统调用号为 0xb,存在于eax中;
ebx的值为execve函数的第一个参数,指向/bin/sh的地址;
ecx,edx为0;
当寄存器的值为以上这些的时候,执行int 0x80时,会执行
1 | execve('/bin/sh',NULL,NULL) |
IDA分析:
而这道题没有后门,没有可执行的段,所以需要控制寄存器,返回地址为int 0x80;
控制寄存器需要用到pwn的工具 ROPgadget,使用命令:
1 | ROPgadget --binary ret2syscall --only 'pop|ret' | grep 'eax' |
可以查找出 ret2syscall程序中,关于eax寄存器进行出栈和返回的操作,从而利用这些地址控制eax寄存器并返回当前地址;
同理查找出其他寄存器以修改其值;
在使用ROPgadget指令后可以找到一些可以利用的地址;
这里使用 0x080bb196来控制eax,0x0806eb90来控制edx,ecx,ebx;
并且我们找到了出题人非常体贴的 /bin/sh 字符串;以及int 0x80;
用cyclic算出填充长度也是之前的112;
这里介绍一个pwntools里的函数,flat(),它可以把输入进去的内容变成byte类型,且填充长度为4字节;
则Exp:
1 | from pwn import * |
ret2libc1
IDA分析:
有一个main,和一个secure;但这次的secure并不是执行的 ‘/bin/sh’ ;
在字符串里可以找到 ‘/bin/sh’ ;
所以这次我们在返回处返回调用system函数的地址,将其参数改为 ‘/bin/sh’ 的地址;
由cyclic算出补充长度为112;
Exp:
1 | from pwn import * |
这里说明两点,调用system函数时,第一个参数是返回地址,所以可以随便编一个,如 ‘aaaa’;
调用system函数需要返回.plt段的地址,不能直接返回.got,不然会出错,所以exp里的system地址会不一样;
ret2libc2
这道题和1其实类似,只是少了 ‘/bin/sh’ 的字符串;
所以我们需要构造这样的字符串;可以用gets函数输入一个字符串,接着再调用system;
gets函数的参数好像会关系到ebx,所以可以修改它的值为.bss段上的地址,以方便用于system函数;
这里先找到地址:
Exp:
1 | from pwn import * |
ret2libc3
IDA分析:
这道题相对于1来说没了system的plt地址,也没有 ‘bin/sh’ 的字符串;
如何获得system函数的地址?
wiki上给出了2点:
① system 函数属于 libc,而 libc.so 动态链接库中的函数之间相对偏移是固定的;
② 即使程序有 ASLR 保护,也只是针对于地址中间位进行随机,最低的 12 位并不会发生改变;
说人话就是找到got表中的 libc_start_main,因为每个程序都会有它,所以可以用它来加上已知偏移找到system函数;
这里会使用一个库:LibcSearcher;下载地址:
学习之后写如下内容:
Exp:
1 | from pwn import * |
总结
做ret系列的题可以对程序中的栈结构理解更加深刻,以及对elf的文件结构更加熟悉;
总的下来,更多的体会是偏移的查找困难,之前的逆向工程核心原理中的PE部分,也是磁盘的RAW和内存的RVA换算,以及用头找偏移会比较困难;
之后CTF也会开始了,我想在CTF中我也许也会见到如此类型的题目,或许可以从中找到些偏移计算的灵感;