| 关键词: 地址 函数 system write shellcode libc 程序 字符串 27 内存 |
一步一步学PWN ![]() 一、环境:ubuntu虚拟机 pwntools LibcSearcher ROPgadget 参考:https://www.fjt1.com 二、寄存器相关知识:EIP:主要用于存放当前代码段即将被执行的下一条指令的偏移,但其本质上并不能直接被指令直接访问。 最典型的栈溢出利用是覆盖程序的返回地址为攻击者所控制的地址,也就是覆盖EIP ESP:栈顶指针,始终指向栈顶 EBP:栈底指针,通常叫栈基址 三、软件保护机制:CANNARY(栈保护) 栈溢出保护是一种缓冲区溢出攻击缓解手段,当启用栈保护后,函数开始执行的时候会先往栈里插入cookie信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行。攻击者在覆盖返回地址的时候往往也会将cookie信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux中我们将cookie信息称为canary。 NX(DEP)(数据执行保护 Data Execution Prevention) NX即No-eXecute(不可执行)的意思,NX(DEP)的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。 PIE(ASLR) 内存地址随机化机制(address space layout randomization),有以下三种情况: 0 - 表示关闭进程地址空间随机化 1 - 表示将mmap的基址,stack和vdso页面随机化 2 - 表示在1的基础上增加堆(heap)的随机化 四、简单原理:当我们在输入数据时,如果程序对输入没有限制,会导致输入的超出预定的范围,覆盖掉原来的数据 ![]() 左边为正常情况,右边是输入超出限制后 如果我们输入的不是普通的数据,而是构造的数据会怎样? ![]() 构造后栈分布情况 我们完全可以通过更改原来的返回地址来控制程序的走向,上图中就利用返回地址来实现执行 shellcode 当然上面那种情况是啥保护都不开的情况,我们先从最简单的开始 这里用的是蒸米rop x86的例子 源码如下: #include 使用以下命令进行编译: gcc -m32 -fno-stack-protector -z execstack -o level1 level1.c -m32意思是编译为32位的程序 -fno-stack-protector和-z execstack这两个参数会分别关掉DEP和Stack Protector 在 root 权限下执行: echo 0 > /proc/sys/kernel/randomize_va_space 这样就关掉了整个系统的ASLR 使用 checksec 命令来检查一下保护机制: ![]() 使用一个python脚本来产生点数据 python pattern.py create 150 gdb ./level1 调试程序 ![]() r 运行,输入之前用脚本创造的字符串 回车后发现报错如下: ![]() 再使用 python 脚本来计算一下偏移: python pattern.py offset 0x37654136 这里解释一下,在 gdb 里报错的原因是本来的返回地址被我们输入的字符串覆盖掉了,覆盖为了0x37654136,当程序去返回的时候出错了,使用 pattern.py offset计算出来的就是整个栈的大小 也就是说此时的栈情况是如图所示: ![]() 根据之前讲的原理,如果我们能够找到填充的字符串开始的地方,把 0x37654136 改成找到的地址就可以执行我们的语句了 就像这样: ![]() 那就可以顺利执行到我们希望能够执行的 shellcode 了 还有个问题: gdb 的调试环境会影响 buf 在内存中的位置,虽然我们关闭了 ASLR,但这只能保证 buf 的地址在 gdb 的调试环境中不变,但当我们直接执行 ./level1 的时候,buf 的位置会固定在别的地址上 这里采用的方法是开启:core dump ulimit -c unlimited 开启之后,当出现内存错误的时候,系统会生成一个 core dump 文件在当前目录下。然后我们再用 gdb 查看这个 core 文件就可以获取到 buf 真正的地址了。 使用gdb调试转储的 gdb level1 core ![]() 使用 x/10s $esp-144 查看shellcode地址 ![]() 为什么是 esp-144 ? 因为我们报错的时候其实已经执行的到返回地址了,所以要在 140 的栈空间的基础上加上 4 字节的返回地址空间 用 python 结合 pwntools 写利用脚本 from pwn import * 利用成功: 接下来来进阶: 依旧是蒸米的,使用这条命令编译打开栈不可执行 gcc -m32 -fno-stack-protector -o level2 level2.c 这样之前的脚本就没法用了,因为我们的 shellcode 是写在栈上的。 这时候就要用到另一种方法了 -- ret2libc ret2libc 即控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置,一般情况下,我们会选择执行 system("/bin/sh")。 那么我们如何得到 system 函数的地址呢?这里就主要利用了两个知识点
简单点说就是我们现在没法在栈上执行需要的命令了,但是我们可以去别的地方执行,在 libc.so 中就正好有我们需要找的:system("/bin/sh")。 上题: ![]() 前面说到关掉 ASLR 后 system() 函数在内存中的地址是不会变化的,并且 libc.so 中也包含 "/bin/sh" 这个字符串,并且这个字符串的地址也是固定的 我们使用 gdb 来找到这些地址 用 gdb ./level2 调试 我们首先在main函数上下一个断点: b main ![]() 然后执行程序: r ![]() 这样的话程序会加载 libc.so 到内存中 然后我们就可以通过: print system 这个命令来获取 system 函数在内存中的位置 ![]() 接下来我们可以通过find命令来查找: find "/bin/sh" "/bin/sh" 这个字符串 ![]() 这样就找到了我们需要的数据: system地址:0xf7e41940 /bin/sh地址:0xf7f6002b 写出 exp: from pwn import * 关于 ret 地址: system() 后面跟的是执行完 system() 后要返回的地址,接下来才是要给 system() 传递的参数:"/bin/sh" 字符串的地址。而我们目的是执行 "/bin/sh",ret 无所谓。 现在栈的分布大致是: ![]() 利用成功: ![]() ![]() 现在我们打开 ASLR,依旧是在root下 echo 2 > /proc/sys/kernel/randomize_va_space 这样因为地址随机化,我们以前的exp已经无效了 我们可以用 ldd level2 命令看一下 libc 的地址,每一次都在变化 ![]() 那我们该怎么解决呐? 我们可以先泄漏出 libc.so 某些函数在内存中的地址,然后再利用泄漏出的函数地址根据偏移量计算出 system() 函数和 /bin/sh 字符串在内存中的地址,然后再执行我们的 ret2libc 的 shellcode 就可以了 使用: objdump -d -j .plt level2 查看可以利用的函数 ![]() 使用: objdump -R level2 查看对应 got 表 ![]() 因为 system() 函数和 write() 在 libc.so 中的 offset (相对地址)是不变的,所以如果我们得到了 write() 的地址并且拥有 libc.so 就可以计算出 system() 在内存中的地址了 from pwn import * 来解析一下 exp: payload1的意思是:填充 140 个 A 后返回地址覆盖为 write 函数,通过 got_write 将 write 函数的真实地址泄露出来,执行完成 write 函数之后,返回地址为 vul 函数,再次利用 vul 函数的溢出漏洞,完成漏洞利用工作 利用成功: ![]() 文件及脚本打包: 链接:https://pan.baidu.com/s/1r0yNqwS_AzpXaqrfVQZ3nA |
| 本文出处: https://www.toutiao.com/a6714583862363554316/ |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|