借助DynELF实现无libc的漏洞利用小结

转自:https://www.anquanke.com/post/id/85129

前言

在没有目标系统libc文件的情况下,我们可以使用pwntools的DynELF模块来泄漏地址信息,从而获取到shell。本文针对linux下的puts和write,分别给出了实现DynELF关键函数leak的方法,并通过3道CTF题目介绍了这些方法的具体应用情况。

DynELF

DynELF是pwntools中专门用来应对无libc情况的漏洞利用模块,其基本代码框架如下。

借助DynELF实现无libc的漏洞利用小结_第1张图片

需要使用者进行的工作主要集中在leak函数的具体实现上,上面的代码只是个模板。其中,address就是leak函数要泄漏信息的所在地址,而payload就是触发目标程序泄漏address处信息的攻击代码。

使用条件

不管有没有libc文件,要想获得目标系统的system函数地址,首先都要求目标二进制程序中存在一个能够泄漏目标系统内存中libc空间内信息的漏洞。同时,由于我们是在对方内存中不断搜索地址信息,故我们需要这样的信息泄露漏洞能够被反复调用。以下是大致归纳的主要使用条件:

1)目标程序存在可以泄露libc空间信息的漏洞,如read@got就指向libc地址空间内;

2)目标程序中存在的信息泄露漏洞能够反复触发,从而可以不断泄露libc地址空间内的信息。

当然,以上仅仅是实现利用的基本条件,不同的目标程序和运行环境都会有一些坑需要绕过。接下来,我们主要针对write和puts这两个普遍用来泄漏信息的函数在实际配合DynELF工作时可能遇到的问题,给出相应的解决方法。

write函数

write函数原型是write(fd, addr, len),即将addr作为起始地址,读取len字节的数据到文件流fd(0表示标准输入流stdin、1表示标准输出流stdout)。write函数的优点是可以读取任意长度的内存信息,即它的打印长度只受len参数控制,缺点是需要传递3个参数,特别是在x64环境下,可能会带来一些困扰。

在x64环境下,函数的参数是通过寄存器传递的,rdi对应第一个参数,rsi对应第二个参数,rdx对应第三个参数,往往凑不出类似“pop rdi; ret”、“pop rsi; ret”、“pop rdx; ret”等3个传参的gadget。此时,可以考虑使用__libc_csu_init函数的通用gadget,具体原理请参见 文章 。简单的说,就是通过__libc_csu_init函数的两段代码来实现3个参数的传递,这两段代码普遍存在于x64二进制程序中,只不过是间接地传递参数,而不像原来,是通过pop指令直接传递参数。

第一段代码如下:

借助DynELF实现无libc的漏洞利用小结_第2张图片

第二段代码如下:

这两段代码运行后,会将栈顶指针移动56字节,我们在栈中布置56个字节即可。

这样,我们便解决了write函数在leak信息中存在的问题,具体的应用会放到后面的3道题目中讲。

puts函数

puts的原型是puts(addr),即将addr作为起始地址输出字符串,直到遇到“\x00”字符为止。也就是说,puts函数输出的数据长度是不受控的,只要我们输出的信息中包含\x00截断符,输出就会终止,且会自动将“\n”追加到输出字符串的末尾,这是puts函数的缺点,而优点就是需要的参数少,只有1个,无论在x32还是x64环境下,都容易调用。

为了克服输入不受控这一缺点,我们考虑利用puts函数输出的字符串最后一位为“\n“这一特点,分两种情况来解决。

(1)puts输出完后就没有其他输出 , 在这种情况下的leak函数可以这么写。

借助DynELF实现无libc的漏洞利用小结_第3张图片

(2)puts输出完后还有其他输出 , 在这种情况下的leak函数可以这么写。

借助DynELF实现无libc的漏洞利用小结_第4张图片

其他需要注意的地址

在信息泄露过程中,由于循环制造溢出,故可能会导致栈结构发生不可预料的变化,可以尝试调用目标二进制程序的_start函数来重新开始程序以恢复栈。

XDCTF2015-pwn200

本题是32位linux下的二进制程序,无cookie,存在很明显的栈溢出漏洞,且可以循环泄露,符合我们使用DynELF的条件。具体的栈溢出位置等调试过程就不细说了,只简要说一下 借助DynELF实现利用的要点:

1)调用write函数来泄露地址信息,比较方便;

2)32位linux下可以通过布置栈空间来构造函数参数,不用找gadget,比较方便;

3)在泄露完函数地址后,需要重新调用一下_start函数,用以恢复栈;

4)在实际调用system前,需要通过三次pop操作来将栈指针指向systemAddress,可以使用ropper或ROPgadget来完成。

接下来就直接给出利用代码。

借助DynELF实现无libc的漏洞利用小结_第5张图片

LCTF2016-pwn100

本题是64位linux下的二进制程序,无cookie,也存在很明显的栈溢出漏洞,且可以循环泄露,符合我们使用DynELF的条件,但和上一题相比,存在两处差异:

1)64位linux下的函数需要通过rop链将参数传入寄存器,而不是依靠栈布局;

2)puts函数与write函数不同,不能指定输出字符串的长度。

根据上文给出的解决方法,构造利用脚本如下。

借助DynELF实现无libc的漏洞利用小结_第6张图片
借助DynELF实现无libc的漏洞利用小结_第7张图片

RCTF2015-welpwn

本题也是64位linux下的二进制程序,无cookie,也存在明显的栈溢出漏洞,且可以循环泄露,符合我们使用DynELF的条件,与其他两题的区别主要在于利用过程比较绕。

整个程序逻辑是这样的,main函数中,用户可以输入1024个字节,并通过echo函数将输入复制到自身栈空间,但该栈空间很小,使得栈溢出成为可能。由于复制过程中,以“\x00”作为字符串终止符,故如果我们的payload中存在这个字符,则不会复制成功;但实际情况是,因为要用到上面提到的通用gadget来为write函数传参,故肯定会在payload中包含“\x00”字符。

这个题目设置了这个障碍,也为这个障碍的绕过提供了其他条件。即由于echo函数的栈空间很小,与main函数栈中的输入字符串之间只间隔32字节,故我们可以利用这一点,只复制过去24字节数据加上一个包含连续4个pop指令的gadget地址,并借助这个gadget跳过原字符串的前32字节数据,即可进入我们正常的通用gadget调用过程,具体脚本如下。

借助DynELF实现无libc的漏洞利用小结_第8张图片

由于该题目程序中也包含puts函数,故我们也可以用puts函数来实现leak,代码如下。

借助DynELF实现无libc的漏洞利用小结_第9张图片

参考文章

Pwntools中的DynELF模块的使用

Finding Function's Load Address

 __libc_csu_init函数的通用gadget


附件

题目打包下载 : http://pan.baidu.com/s/1qXA9JXi

你可能感兴趣的:(借助DynELF实现无libc的漏洞利用小结)