CTF中做Linux下漏洞利用的一些心得
其实不是很爱搞Linux,但是因为CTF必须要接触一些,漏洞利用方面也是因为CTF基本都是linux的pwn题目。
基本的题目分类,我认为就下面这三种,这也是常见的类型。
下面就分类来说说
0x0.栈溢出
栈溢出一般都是CTF中,PWN类别的第一题。基本思想就是覆盖栈中返回地址啦,这个谁都知道。这种题一定会有NX保护,怎么跳是问题。常见的是leak出一个函数的got表,leak方法题中一定会给出的,然后看看有没有给出libc。如果有libc就能算system()和/libc/sh的地址了,直接溢出就OK了。如果没有libc的话,如果能构造一个无限次循环的漏洞函数的话,可以使用pwn库的DynELF函数找出system地址,再想方设法的往一个地方写个/libc/sh就可以了(因为DynELF没法泄漏字符串的地址)。如果这道题比较简单的话,出题人会在程序里调用system()函数,这样你直接跳过去用就可以了。
基本的思路就是这些,如果不能(不乐意)使用DynELF函数就可以使用ret2dl_resolve方法了,这两者是差不多的。之前写过的www.Ox9A82.com/Ox9A82/p/5487275.html
还有一种就是x64下的栈溢出,因为x64并不是通常的使用栈传递参数,而是把参数放在寄存器中传递所以不能直接的去布置参数了。但是,可以有通用的gadgets来用。http://www.Ox9A82.com/Ox9A82/p/5487725.html这个是用来布置参数加执行,而且可以构成rop链。
另外的是,如果有canary保护的话,可能就不是栈溢出(或者不是要去覆盖返回地址了),因为canary对于直接的覆盖返回地址来说是没办法绕过的。
总体思路如下:
0x1.格式化字符串
格式化字符串的题,一般都比栈溢出分值高一档。格式化字符串漏洞找起来应该还是相当的容易的,主要格式化串是否可控即可。对于传统的格式化字符串漏洞来说,只要在格式化串中带有要进行写入的目标地址即可,使用%1000x来形成1000字符,使用%4$n来指定栈中的参数即可达到目的。而现在很多人不这么玩了,很多出题人把格式化串放到堆或是bss段中,这样你就不能像传统的那样去读取格式化串中的目标地址了,不在栈中你是不可能读到的。对于这种题目的做法就是要进行两次漏洞利用,第一次在栈中构造跳板。第二次利用跳板去进行任意地址写。具体的说就是:第一次:在栈中找一个指向栈里面的指针(这种指针肯定会有,因为堆栈框架就是这样的),往这个栈的地方写入第二次要写入的地址。第二次:就跟正常利用一样了,使用前面输入的那个进行任意地址写。
然后要明确的一点是,格式化字符串漏洞的本质是任意地址写。所以参数构造也是一个问题,因为不像栈溢出,栈溢出了参数就构造出来了。任意地址写参数可能没地方搞。另外注意一点是:要注意开没开nx、要注意开没开nx、要注意开没开nx。重要的事情说三遍。因为没开nx的话,几乎就是明晃晃的说,往我的堆/bss写shellcode吧,我可以执行。
总体思路:
0x2.堆
对于堆进行考察的题一般都处于压轴的位置。我个人认为堆的应该分为两种,即堆的漏洞和模拟堆的漏洞。先说模拟堆的漏洞,这种题并不是真的在考堆的漏洞利用。而且出题人自己利用链表去模拟出一个堆来,更恰当的说我认为应该叫链表漏洞。这类题的一大特征就是给出了一个内存块的详细构造,比如有一个前向指针、一个后向指针(双链表)、有一个放用户名的域、有一个放编号的域等等。这个是在模拟一种堆的管理机制,所以最大的区别在于这个东西它没有check机制,不像堆一样有溢出保护机制。这种出法或者有溢出有unlink导致任意地址写等玩法。
再来说真正的考察堆漏洞的,因为堆是有溢出保护的,unlink的宏在进行unlink之前会检查当前的指针合不合法,这个在题中的体现就是有的题会在bss段中存放每个块的指针,利用这个指针就可以bypass保护机制。然后就是利用方法,这个在之前也写过,就是去伪造两个堆块,利用堆的空块合并机制。当然了,首先得有堆溢出,没有溢出你拿什么去伪造堆块。伪造的目的是构造如下情况:
在灰色的原堆块中搞了个新的伪空堆块出来(注意,是通过堆溢出,溢出下一堆块头部实现的构造伪空堆块)。然后,我们再想办法去释放橙色堆块,就引发了空块合并机制。
最后谈谈经验,不一定对,就是做了几个题总结出来的。首先堆的题很多情况下不只是一个漏洞,往往有个什么整数溢出啊栈溢出啊什么的做辅助利用。然后就是堆的题一般都是标准的选单程序,就是给你个类似于记事本的功能选单,有什么新建笔记、编辑笔记、删除笔记、查看笔记之类的。一般都是先看新建的功能,因为通过新建功能可以看出分配的结构是什么样的,和怎么分配的。而删除功能往往都是用来进行free的。编辑笔记是经常设置漏洞的出题点。查看笔记可能会用于leak东西。
0x3.End
最后说下调试,因为用python库写exp(如pwn库和zio库)时都是打本地的,而且都是自动的,所以调试起来就很难办。可以在py中加入raw_input("wait input");来中断一下,然后用gdb附加。附加方法是gdb attch pid,pid可以用看pid的命令得到,也可以gdb attch `pidof elf文件名`来实现附加到进程上。而且还有一个好处就是脱离调试器后进程不会终止,这是和windows不一样的地方。但是注意调exp的时候如果python脚本退出了是会杀掉进程的,所以要保证python脚本不能提前退出,比如可以在最后加上.interact()函数来防止退出。
个人实在不是太喜欢Linux,所以也都是因为做比赛才来接触。感觉PWN这个东西积累很重要,套路也很重要。