总述
本篇文章讲解两个小知识点:整数溢出,伪随机数
学习了,整数溢出原理和利用整数溢出调整判断数据。
了解了,伪随机数的生成和绕过方法:覆盖种子,利用相同libc去撞库得同一种子
伪随机数和整数溢出是两个小知识点,可以在一些题目中设置为前置关卡,为了不被拿捏还是要看看的。
整数溢出
原理:
整数在计算机中是使用固定的位数来存储的,例如常见的有 8 位、16 位、32 位或 64 位整数。 如果结果超出了该整数类型所能表示的范围,就会发生整数溢出。
在 C 语言中,整数的基本数据类型分为短整型 (short),整型 (int),长整型 (long),这三个数据类型还分为有符号和无符号,每种数据类型都有各自的大小范围 (64位,32位字节减半):
类型 | 字节 | 范围 |
---|---|---|
short int | 2 | 0 |
unsigned short int | 2 | 0~65535 |
int | 4 | 0 |
unsigned int | 4 | 0~4294967295 |
long int | 8 | 正 : 0 负: 0x8000000000000000 |
unsigned long int | 8 | 0~0xffffffffffffffff |
1 |
|
整数溢出会有两种形式:上界溢出和下届溢出:
上界溢出:
可以看见 两种不同的数据类型在编译的时候会产生不一样的汇编指令。因为计算机底层指令是不区分有符号和无符号的,数据都是以二进制形式存在 (编译器的层面才对有符号和无符号进行区分,产生不同的汇编指令)。
这个时候会有两种上界溢出情况:
第一种: 0x7fff + 1 add 0x7fff, 1 == 0x8000
,这种上界溢出对无符号整型就没有影响,但是在有符号短整型中,0x7fff
表示的是 32767
,但是 0x8000
表示的是 -32768
,用数学表达式来表示就是在有符号短整型中 32767+1 == -32768
。
第二种: 0xffff + 1 add 0xffff, 1
,这种情况需要考虑的是第一个操作数 , 有符号型加法的汇编代码是 add eax, 1
,因为 eax=0xffff
,所以 add eax, 1 == 0x10000
, 虽然 eax
的结果为 0x10000,但是只把 ax=0x0000
的值储存到了内存中,从结果看和无符号是一样的。
无符号的汇编代码是对内存进行加法运算 add [rbp+b], 1 == 0x0000
。 在有符号短整型中,0xffff==-1,-1 + 1 == 0
,从有符号看这种计算没问题。但是在无符号短整型中,0xffff == 65535, 65535 + 1 == 0
。
下届溢出:
下届溢出的道理和上界溢出一样,在汇编代码中,只是把 add
替换成了 sub
。
一样也是有两种情况:
第一种是 sub 0x0000, 1 == 0xffff
对于有符号来说 0 - 1 == -1
没问题,但是对于无符号来说就成了 0 - 1 == 65535
。
第二种是 sub 0x8000, 1 == 0x7fff
,对于无符号来说是 32768 - 1 == 32767
是正确的,但是对于有符号来说就变成了 -32768 - 1 = 32767
。
利用:
在一些程序中,如果使用整数来控制缓冲区的大小,整数溢出可能导致缓冲区大小计算错误。
这里举例一道攻防世界的int_overflow(整数溢出):
这题是一道32位的题目,ida开启后看到核心区为:
首 先 v3 设置了一个 unsigned _int8 v3 无符号 8位参数
v3 = 0 ~ 255
然后 v3 = strlen(s);
v3长度最大为8位 255
len是个unsigned int 8,而strlen()返回值是一个size_t类型的变量,它是无符号32bit的。
也就是说在最后的return上如果我们输入的值大于255, 编译器会截断后者的末八位赋值给前者。存在整数型溢出漏洞,溢出部分会将后八位赋值给前面,在255的基础上加上原本限制的4 - 8在溢出后将255多余的部分赋值给v3 然后就能绕过if判断 。
然后就可以利用这个进行栈溢出,而且题目给了后门可以直接getflag。
1 | payload = b'a'*(0x14+4) + p32(door) + b'a'*232 |
伪随机数
伪随机数(Pseudo-Random Number,PRN)是使用确定的算法计算出来的数值序列,虽然这些序列在一定程度上呈现出随机的特性,但它们并不是真正意义上的随机数,因为其生成过程是完全确定和可重复的。
随机数的生成
rand()函数,生成伪随机数的范围在0到MAX之间,这个最大值依赖于所指定的库,一般不少于32767,该随机数的生成依赖于种子,也就是说,如果种子一样,生成的随机数序列就一样,所以是伪随机数,当然很多题目会把时间作为种子,以此增加随机性。
srand()函数,初始化随机数发生器,用来设置rand函数的种子seed。系统在调用rand函数时,会先调用srand函数,如果没有就会默认种子为1。
绕过方法
一:覆盖种子,去获得指定种子下的随机数序列。
二: 通过调用该模块以及利用与题目相同的libc文件,来达到一个与题目中随机数生成方式相同的效果,实现撞库
这里用 [SWPUCTF 2022 新生赛]Darling 这题来帮助理解:
看到已经有一个固定的种子了,且没有其他输入点,说明要我们去找对应的种子
1 |
|
还有一种是把时间作为种子,以此增加随机性,需要用如下方法写:
1 | from pwn import * |
一句话:伪随机数的核心就是种子