diff --git a/doc/1.5.1_c_basic.md b/doc/1.5.1_c_basic.md index 67245df..8bff7df 100644 --- a/doc/1.5.1_c_basic.md +++ b/doc/1.5.1_c_basic.md @@ -154,6 +154,11 @@ long long | -9 223 372 036 854 775 808 | 9 223 372 036 854 775 807 | 64 bits - `uint[# of bits]_t` - uint8_t, uint16_t, uint32_t +- 有符号整数 + - ![](../pic/1.5.1_signed_integer.png) +- 无符号整数 + - ![](../pic/1.5.1_unsigned_integer.png) + 更多信息在 `stdint.h` 和 `limits.h` 中: ```text $ man stdint.h diff --git a/doc/2.2_gdb&peda.md b/doc/2.2_gdb&peda.md index a1cf954..28a0d89 100644 --- a/doc/2.2_gdb&peda.md +++ b/doc/2.2_gdb&peda.md @@ -44,6 +44,8 @@ long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data); ## gdb 基本操作 +注意:使用 gdb 调试时,会自动关闭 ASLR,所以可能每次看到的栈地址都不变。 + ## gdb-peda @@ -57,4 +59,3 @@ echo "source ~/peda/peda.py" >> ~/.gdbinit echo "DONE! debug your program with gdb and enjoy" ``` ### peda命令 - diff --git a/doc/3.3.2_integer_overflow.md b/doc/3.3.2_integer_overflow.md index c064962..bf9d254 100644 --- a/doc/3.3.2_integer_overflow.md +++ b/doc/3.3.2_integer_overflow.md @@ -1 +1,233 @@ # 整数溢出 + +- [什么是整数溢出](#什么是整数溢出) +- [整数溢出](#整数溢出) +- [整数溢出示例](#整数溢出示例) +- [CTF 中的整数溢出](#CTF-中的整数溢出) + + +## 什么是整数溢出 +#### 简介 +在 C 语言基础的章节中,我们介绍了 C 语言整数的基础知识,下面我们详细介绍整数的安全问题。 + +由于整数在内存里面保存在一个固定长度的空间内,它能存储的最大值和最小值是固定的,如果我们尝试去存储一个数,而这个数又大于这个固定的最大值时,就会导致整数溢出。(x86-32 的数据模型是 ILP32,即整数(Int)、长整数(Long)和指针(Pointer)都是 32 位。) + +#### 整数溢出的危害 +如果一个整数用来计算一些敏感数值,如缓冲区大小或数值索引,就会产生潜在的危险。通常情况下,整数溢出并没有改写额外的内存,不会直接导致任意代码执行,但是它会导致栈溢出和堆溢出,而后两者都会导致任意代码执行。由于整数溢出出现之后,很难被立即察觉,比较难用一个有效的方法去判断是否出现或者可能出现整数溢出。 + + +## 整数溢出 +#### 有符号整数上溢出 +```c +int i; +i = INT_MAX; // 2 147 483 647 +i++; +printf("i = %d\n", i); // i = -2 147 483 648 +``` + +#### 有符号整数下溢出 +```c +i = INT_MIN; // -2 147 483 648 +i--; +printf("i = %d\n", i); // i = 2 147 483 647 +``` + +#### 无符号数截断 +涉及无符号数的计算永远不会溢出,如果计算结果无法用无符号数表示,就会发生截断。 + +加法截断: +```text +0xffffffff + 0x00000001 += 0x0000000100000000 (long long) += 0x00000000 (long) +``` +乘法截断: +```text +0x00123456 * 0x00654321 += 0x000007336BF94116 (long long) += 0x6BF94116 (long) +``` + +#### 整型提升和宽度溢出 +整型提升是指当计算表达式中包含了不同宽度的操作数时,较小宽度的操作数会被提升到和较大操作数一样的宽度,然后再进行计算。 + +示例:[源码](../src/Other/3.3.2_width_overflow.c) +```c +#include +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); +} +``` +```text +$ ./a.out +宽度溢出 +l = 0xabcddcba (32 bits) +s = 0xffffdcba (16 bits) +c = 0xffffffba (8 bits) +整型提升 +s + c = 0xffffdc74 (32 bits) +``` +使用 gdb 查看反汇编代码: +```text +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 + 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 + 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 + 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 + 0x000005f6 <+137>: add esp,0x10 + 0x000005f9 <+140>: sub esp,0xc + 0x000005fc <+143>: lea eax,[ebx-0x18f7] + 0x00000602 <+149>: push eax + 0x00000603 <+150>: call 0x400 + 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 + 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` 类型的参数,常常被误用而产生整数溢出,接着就可能导致缓冲区溢出漏洞。 + +```c +#include + +void *memcpy(void *dest, const void *src, size_t n); +``` +`memcpy()` 函数将 `src` 所指向的字符串中以 `src` 地址开始的前 `n` 个字节复制到 `dest` 所指的数组中,并返回 `dest`。 + +```c +#include + +char *strncpy(char *dest, const char *src, size_t n); +``` +`strncpy()` 函数从源 `src` 所指的内存地址的起始位置开始复制 `n` 个字节到目标 `dest` 所指的内存地址的起始位置中。 + +两个函数中都有一个类型为 `size_t` 的参数,它是无符号整型的 `sizeof` 运算符的结果。 +```c +typedef unsigned int size_t; +``` + + +## 整数溢出示例 +现在我们已经知道了整数溢出的原理和主要形式,下面我们先看几个简单示例,然后实际操作利用一个整数溢出漏洞。 + +#### 示例 +示例一,整数转换: +``` +char buf[80]; +void vulnerable() { + int len = read_int_from_network(); + char *p = read_string_from_network(); + if (len > 80) { + error("length too large: bad dog, no cookie for you!"); + return; + } + memcpy(buf, p, len); +} +``` +这个例子的问题在于,如果攻击者给 `len` 赋于了一个负数,则可以绕过 `if` 语句的检测,而执行到 `memcpy()` 的时候,由于第三个参数是 `size_t` 类型,负数 `len` 会被转换为一个无符号整型,它可能是一个非常大的正数,从而复制了大量的内容到 `buf` 中,引发了缓冲区溢出。 + +示例二,溢出: +``` +void vulnerable() { + size_t len; + char *buf; + + len = read_int_from_network(); + buf = malloc(len + 5); + read(fd, buf, len); + ... +} +``` +这个例子看似避开了缓冲区溢出的问题,但是如果 `len` 过大,`len+5` 有可能发生溢出。比如说,在 x86-32 上,如果 `len = 0xFFFFFFFF`,则 `len+5 = 0x00000004`,这时 `malloc()` 只分配了 4 字节的内存区域,然后在里面写入大量的数据,缓冲区溢出也就发生了。 + +示例三,截断: +``` +void main(int argc, char *argv[]) { + unsigned short int total; + total = strlen(argv[1]) + strlen(argv[2]) + 1; + char *buf = (char *)malloc(total); + strcpy(buf, argv[1]); + strcat(buf, argv[2]); + ... +} +``` +这个例子接受两个字符串类型的参数并计算它们的总长度,程序分配足够的内存来存储拼接后的字符串。首先将第一个字符串参数复制到缓冲区中,然后将第二个参数连接到尾部。如果攻击者提供的两个字符串总长度无法用 `total` 表示,则会发生截断,从而导致后面的缓冲区溢出。 + +#### 实战 +看了上面的示例,我们来真正利用一个整数溢出漏洞。 + + +## CTF 中的整数溢出 diff --git a/pic/1.5.1_signed_integer.png b/pic/1.5.1_signed_integer.png new file mode 100644 index 0000000..6a161a7 Binary files /dev/null and b/pic/1.5.1_signed_integer.png differ diff --git a/pic/1.5.1_unsigned_integer.png b/pic/1.5.1_unsigned_integer.png new file mode 100644 index 0000000..e016b70 Binary files /dev/null and b/pic/1.5.1_unsigned_integer.png differ diff --git a/src/Others/3.3.2_width_overflow.c b/src/Others/3.3.2_width_overflow.c new file mode 100644 index 0000000..5636dc8 --- /dev/null +++ b/src/Others/3.3.2_width_overflow.c @@ -0,0 +1,18 @@ +#include +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); +}