PWN入门到入狱-Integer

关于整数的异常情况主要有三种:

  • 溢出
    • 只有有符号数才会发生溢出。有符号数最高位表示符号,在两正或两负相加时,有可能改变符号位的值,产生溢出
    • 溢出标志 OF 可检测有符号数的溢出
  • 回绕
    • 无符号数 0-1 时会变成最大的数,如 1 字节的无符号数会变为 255,而 255+1 会变成最小数 0
    • 进位标志 CF 可检测无符号数的回绕
  • 截断
    • 将一个较大宽度的数存入一个宽度小的操作数中,高位发生截断

有符号整数溢出

  • 上溢出
1
2
3
4
int i;
i = INT_MAX; // 2 147 483 647
i++;
printf("i = %d\n", i); // i = -2 147 483 648
  • 下溢出
1
2
3
i = INT_MIN;  // -2 147 483 648
i--;
printf("i = %d\n", i); // i = 2 147 483 647

无符号数回绕

涉及无符号数的计算永远不会溢出,因为不能用结果为无符号整数表示的结果值被该类型可以表示的最大值加 1 之和取模减(reduced modulo)。因为回绕,一个无符号整数表达式永远无法求出小于零的值。

使用下图直观地理解回绕,在轮上按顺时针方向将值递增产生的值紧挨着它:

a

1
2
3
4
5
6
7
unsigned int ui;
ui = UINT_MAX; // 在 x86-32 上为 4 294 967 295
ui++;
printf("ui = %u\n", ui); // ui = 0
ui = 0;
ui--;
printf("ui = %u\n", ui); // 在 x86-32 上,ui = 4 294 967 295

截断

  • 加法截断:
1
2
3
0xffffffff + 0x00000001
= 0x0000000100000000 (long long)
= 0x00000000 (long)
  • 乘法截断:
1
2
3
0x00123456 * 0x00654321
= 0x000007336BF94116 (long long)
= 0x6BF94116 (long)

整型提升和宽度溢出

整型提升是指当计算表达式中包含了不同宽度的操作数时,较小宽度的操作数会被提升到和较大操作数一样的宽度,然后再进行计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdio.h>
void main() {
int l;
short s;
char c;

l = 0xabcddcba;
s = l;
c = l;

printf("宽度溢出\n");
printf("l = 0x%x (%d bits)\n", l, sizeof(l) * 8);
printf("s = 0x%x (%d bits)\n", s, sizeof(s) * 8);
printf("c = 0x%x (%d bits)\n", c, sizeof(c) * 8);

printf("整型提升\n");
printf("s + c = 0x%x (%d bits)\n", s+c, sizeof(s+c) * 8);
}
1
2
3
4
5
6
7
$ ./a.out
宽度溢出
l = 0xabcddcba (32 bits)
s = 0xffffdcba (16 bits)
c = 0xffffffba (8 bits)
整型提升
s + c = 0xffffdc74 (32 bits)
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
gdb-peda$ disassemble main
Dump of assembler code for function main:
0x0000056d <+0>: lea ecx,[esp+0x4]
0x00000571 <+4>: and esp,0xfffffff0
0x00000574 <+7>: push DWORD PTR [ecx-0x4]
0x00000577 <+10>: push ebp
0x00000578 <+11>: mov ebp,esp
0x0000057a <+13>: push ebx
0x0000057b <+14>: push ecx
0x0000057c <+15>: sub esp,0x10
0x0000057f <+18>: call 0x470 <__x86.get_pc_thunk.bx>
0x00000584 <+23>: add ebx,0x1a7c
0x0000058a <+29>: mov DWORD PTR [ebp-0xc],0xabcddcba
0x00000591 <+36>: mov eax,DWORD PTR [ebp-0xc]
0x00000594 <+39>: mov WORD PTR [ebp-0xe],ax
0x00000598 <+43>: mov eax,DWORD PTR [ebp-0xc]
0x0000059b <+46>: mov BYTE PTR [ebp-0xf],al
0x0000059e <+49>: sub esp,0xc
0x000005a1 <+52>: lea eax,[ebx-0x1940]
0x000005a7 <+58>: push eax
0x000005a8 <+59>: call 0x400 <puts@plt>
0x000005ad <+64>: add esp,0x10
0x000005b0 <+67>: sub esp,0x4
0x000005b3 <+70>: push 0x20
0x000005b5 <+72>: push DWORD PTR [ebp-0xc]
0x000005b8 <+75>: lea eax,[ebx-0x1933]
0x000005be <+81>: push eax
0x000005bf <+82>: call 0x3f0 <printf@plt>
0x000005c4 <+87>: add esp,0x10
0x000005c7 <+90>: movsx eax,WORD PTR [ebp-0xe]
0x000005cb <+94>: sub esp,0x4
0x000005ce <+97>: push 0x10
0x000005d0 <+99>: push eax
0x000005d1 <+100>: lea eax,[ebx-0x191f]
0x000005d7 <+106>: push eax
0x000005d8 <+107>: call 0x3f0 <printf@plt>
0x000005dd <+112>: add esp,0x10
0x000005e0 <+115>: movsx eax,BYTE PTR [ebp-0xf]
0x000005e4 <+119>: sub esp,0x4
0x000005e7 <+122>: push 0x8
0x000005e9 <+124>: push eax
0x000005ea <+125>: lea eax,[ebx-0x190b]
0x000005f0 <+131>: push eax
0x000005f1 <+132>: call 0x3f0 <printf@plt>
0x000005f6 <+137>: add esp,0x10
0x000005f9 <+140>: sub esp,0xc
0x000005fc <+143>: lea eax,[ebx-0x18f7]
0x00000602 <+149>: push eax
0x00000603 <+150>: call 0x400 <puts@plt>
0x00000608 <+155>: add esp,0x10
0x0000060b <+158>: movsx edx,WORD PTR [ebp-0xe]
0x0000060f <+162>: movsx eax,BYTE PTR [ebp-0xf]
0x00000613 <+166>: add eax,edx
0x00000615 <+168>: sub esp,0x4
0x00000618 <+171>: push 0x20
0x0000061a <+173>: push eax
0x0000061b <+174>: lea eax,[ebx-0x18ea]
0x00000621 <+180>: push eax
0x00000622 <+181>: call 0x3f0 <printf@plt>
0x00000627 <+186>: add esp,0x10
0x0000062a <+189>: nop
0x0000062b <+190>: lea esp,[ebp-0x8]
0x0000062e <+193>: pop ecx
0x0000062f <+194>: pop ebx
0x00000630 <+195>: pop ebp
0x00000631 <+196>: lea esp,[ecx-0x4]
0x00000634 <+199>: ret
End of assembler dump.

在整数转换的过程中,有可能导致下面的错误:

  • 损失值:转换为值的大小不能表示的一种类型
  • 损失符号:从有符号类型转换为无符号类型,导致损失符号

漏洞多发函数

我们说过整数溢出要配合上其他类型的缺陷才能有用,下面的两个函数都有一个 size_t 类型的参数,常常被误用而产生整数溢出,接着就可能导致缓冲区溢出漏洞。

1
2
3
#include <string.h>

void *memcpy(void *dest, const void *src, size_t n);

memcpy() 函数将 src 所指向的字符串中以 src 地址开始的前 n 个字节复制到 dest 所指的数组中,并返回 dest

1
2
3
#include <string.h>

char *strncpy(char *dest, const char *src, size_t n);

strncpy() 函数从源 src 所指的内存地址的起始位置开始复制 n 个字节到目标 dest 所指的内存地址的起始位置中。

两个函数中都有一个类型为 size_t 的参数,它是无符号整型的 sizeof 运算符的结果。

1
typedef unsigned int size_t;