• 无符号整数
  • 有符号整数
  • 截断
  • 配合不安全函数进行栈溢出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>    
int main()
{
int InputTest;
unsigned short OutputTest;

while(1)
{
printf("InputTest:");
scanf("%d", &InputTest);
OutputTest = InputTest;

printf("OutputTest:%d\n", OutputTest);

getchar();
}
return 0;
}

上表中,会把输入的InputTest传给无符号短整型OutputTest。无符号的16位整数(2个字节)可以表示0~2^16 -1,也就是0xFFFF

运行结果:

https://github.com/CytQ/BlogImgs/blob/master/Integer_Overflow/application.png?raw=true

从上面可以发现,如果输入的值为0~65535,显示是没有问题的。如果超过了65535(比如65536),那么返回的结果是0,65537结果是1。

老规矩,拖入ida pro动态分析

给scanf下断点,可以看到,值最终存放在ecx寄存器中。

https://github.com/CytQ/BlogImgs/blob/master/Integer_Overflow/65535.png?raw=true

如果我们输入65535,也就是0xFFFF.

如果输入65536,结果是0x10000

https://github.com/CytQ/BlogImgs/blob/master/Integer_Overflow/65536.png?raw=true

接下来的步骤是:OutputTest = InputTest,对应的汇编为:

1
2
3
4
mov dx, [ebp-4]
mov word ptr [ebp-8], dx
mov eax, [ebp+var_8]
and eax, 0FFFFh

也就是将0018FF44这个地址的数值赋给eax,并做与操作.这样就完全剔除了高16位,只保留低16位的数值。

这也是为什么输入65536后数值会从0重新开始增加。

无符号整数的上溢和下溢

负数 = 负数的补码 = 负数的反码 + 1

E.G: -1 可以表示为 100000001,取反 111111110,再加1,也就是111111111

对于无符号的整数,-1 = 0xFFFFFFFF

常见的问题:

void memcpy(void dest, const void *src, size_t n):从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中

void memset(void s, int ch, size_t n):将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。

下溢:

1
2
3
4
5
6
7
8
9
10
11
12
BOOL fun(size_t cbSize)    
{
if( cbSize > 1024 )
{
return FALSE;
}
char *pBuf = new char[cbSize-1];
// 存在溢出隐患
memset(pBuf, 0x90, cbSize-1);
...
return TRUE;
}

cbsize = 0,那么memset的第三个参数就为-1,而第三个参数为无符号整数,因此数据为0xFFFFFFFF,空间超过可执行的范围,因此崩溃。

上溢:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BOOL fun(char *s1, size_t len1, char *s2, size_t len2)    
{
if(len1 + len2 + 1 > 1024)
{
return FALSE;
}
char pBuf = new char[len1 + len2 + 1];
if(pBuf == NULL)
{
return FALSE;
}
memcpy(pBuf, s1, len1);
// 存在溢出隐患
memcpy(pBuf + len1, s2, len2);
...
return TRUE;
}

len1和len2都是无符号整数,如果len1=8,len2=0xFFFFFFFF,那么对于len1+len2+1这个运算来说,len2+1会变成0,然后再加上8,那么它的结果就是8。也就是说,new可能只分配了8个字节的空间,但是却要将长为0xFFFFFFFF的字符串复制到这个空间,那么结果就会造成程序的崩溃。

有符号整数

  • 有符号整数之间的比较。
  • 有符号整数的运算。
  • 无符号整数和有符号整数的对比。
1
2
3
4
5
6
7
8
9
10
int copy_something(char *buf, int len)    
{
char szBuf[800];
if(len > sizeof(szBuf))
{
return -1;
}
// 存在溢出隐患
return memcpy(szBuf, buf, len);
}

上述代码的问题同样出在memcpy函数的第三个参数。由于memcpy使用的是无符号整数作为第三个参数的,但是最初的len是有符号整数。假设我们赋予len一个负值,那么就一定能够通过if语句的检测,但是当这个负值被运用在memcpy函数中时,len就可能变成一个非常大的正数,导致缓冲区及其后面的内容被改写,使得程序崩溃。

截断问题

截断问题主要发生在高位数的整数(如32位)复制到低位数的整数(如16位)的时候,发生的溢出现象

1
2
3
4
5
6
7
8
9
10
11
12
13
BOOL fun(byte *name, DWORD cbBuf)    
{
unsigned short cbCalculatedBufSize = cbBuf;
byte *buf = new byte(cbCalculatedBufSize);
if(buf == NULL)
{
return FALSE;
}
// 存在溢出隐患
memcpy(buf, name, cbBuf);
...
return TRUE;
}

对于fun函数来说,如果它的第二个参数cbBuf的值为0x00010020,由于cbCalculatedBufSize只能接收16位的内容,那么在赋值后,该变量的值为0x0020,因此new仅仅分配了0x20这么多的字节,但是name的大小实际为0x00010020,这就造成了缓冲区溢出情况的出现。

使用整数溢出实现栈溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include "string.h"    
#include "stdlib.h"

char overflow[] =
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
"\x61\x62\x63\x64\x65\x66\x67\x68";

int fun(int i)
{
// i是一个32位整数,s为16位。
// 如果i = 65536,那么s = 0
unsigned short s;
char szBuf[8];
s = i;

// 判断s是否大于8,我们原本输入的是65536,不合法,但截取低16位后为0,成功绕过
if(s > 8)
{
return 0;
}

if(i > sizeof(overflow))
{
memcpy(szBuf, overflow, sizeof(overflow));
}
else
{
// memcpy是个不安全函数
// 只有8字节的空间塞进去了65536个字节,栈溢出
// overflow设置为我们编写好的shellcode即可
memcpy(szBuf, overflow, i);
}
return 1;
}

int main(int argc, char *argv[])
{
int i, ret;
i = 65536;
ret = fun(i);
return 0;
}

参考链接

https://blog.csdn.net/ioio_jy/article/details/50576353