从一道ctf题目理解rand()随机函数
本帖最后由 yechen123 于 2018-12-16 12:43 编辑先上传附件
没有cb的看这里 链接: https://pan.baidu.com/s/19O8WkgqHt7HhsuXTht85Zw 提取码: vsy7
先说一下rand()和srand()函数
一般用法是给srand传给一个种子种子是系统时间
然后用rand产生随机数
看一下rand和srand的代码
void srand(int seed)
{
holdrand=seed;
}
int rand()
{
holdrand=holdrand*0x000343FD+0x00269EC3;
return(holdrand>>0x10)&0x7FFF;
}
可以看出这只是一个伪随机函数 由于一般传给srand的是系统时间 而用户打开软件的时间不确定 所以可以看成随机
但是如果传给srand的并不是系统时间 而是一个确切的数 那么就有可能 能预测rand给出的值
举例
int main()
{
srand(1);
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
}
这个得出的结果是
然后我们自己仿写一个srand函数和rand函数
int main()
{
int hold = 1;
hold = hold*0x000343FD+0x00269EC3;
printf("%d\n", (hold>>0x10)&0x7fff);
hold = hold*0x000343FD+0x00269EC3;
printf("%d\n", (hold>>0x10)&0x7fff);
hold = hold*0x000343FD+0x00269EC3;
printf("%d\n", (hold>>0x10)&0x7fff);
hold = hold*0x000343FD+0x00269EC3;
printf("%d\n", (hold>>0x10)&0x7fff);
hold = hold*0x000343FD+0x00269EC3;
printf("%d\n", (hold>>0x10)&0x7fff);
return 0;
}
结果
可以看出结果是一样的
那么就表明 如果我们知道seed的值
那么就能预测rand产生的值
开始解析题目
这是一道64位系统下linux的题目
用ida打开
可以看到有两个循环
这两个循环中里面有sleep函数
主要是想影响你分析时间
接下来看重点
先输入username和password
在经过五个函数
sub_4008C1(username); // 限制username长度 只能为8或者12
sub_400901(username);
sub_4009B2(username); // 只能为小写或者_
sub_400A33(username, passwrod);
return sub_400C23(username, passwrod);
先分析第一个
__int64 __fastcall sub_4008C1(__int64 username)
{
int i; //
for ( i = 0; i <= 100 && *(i + username); ++i )
;
return sub_400866(i);
}
__int64 __fastcall sub_400866(int i)
{
__int64 result; // rax
if ( 4 * (i >> 2) != i || 4 * (i >> 4) == i >> 2 || !(i >> 3) || (result = (i >> 4), result) )
{
puts("Wrong username or password!!!\n");
exit(0);
}
return result;
}
不断循环一直循环到username结尾 也就是 i就是username长度
在进入sub_400866 这个函数
这段判断代码可以写一个脚本解密一下
int result;
for (int i=0; i<100; ++i)
{
if (4 * (i >> 2) != i || 4 * (i >> 4) == i >> 2 || !(i >> 3) || (result = i >> 4, result))
{
continue;
}
printf("%d\n", i);
}
也就是说 username长度只能为8 或者12
再看第二个函数
signed __int64 __fastcall sub_400901(signed int *username)
{
signed __int64 result; // rax
__int64 v2; //
__int64 v3; //
__int64 v4; //
v2 = *username; // 第一个
v3 = username;
v4 = username;
if ( v2 - v3 + v4 != 0x70667A78
|| v3 + 2 * (v4 + v2) != 0x22F241C1FLL
|| (result = 0x31CD156AC3A69DC4LL, v3 * v4 != 0x31CD156AC3A69DC4LL) )
{
puts("Wrong username or password!!!\n");
exit(0);
}
return result;
}
这里主要是解一个三元一次方程组
解方程组在这里不解释
需要注意的是形参声明是signed int *username
是四个字节的 所以在
v2 = *username; // 第一个
v3 = username;
v4 = username;
中 v2是username前面四个字节
v3是中间四个字节
v4是后面四个字节
可以预测 username长度为8
还有一点需要注意 就是linux下大端小端的问题
比如abcd(61626364)
在内存中存储是64636261
最后解出来
v2 = 0x6d737469换成字母就是 msti倒序过来就是itsm
v3 = 0x6f726265orbeebro
v4 = 0x72656874 rehtther
那么username就是 itsmebrother
再看下一个函数
__int64 __fastcall sub_4009B2(__int64 username)
{
__int64 result; // rax
int i; //
for ( i = 0; ; ++i )
{
result = *(i + username); // 找一遍字母
if ( !result )
break;
if ( (*(i + username) <= 96 || *(i + username) > 122) && *(i + username) != 95 )
{
puts("Wrong username or password!!!\n");
exit(0);
}
}
return result;
}
这个倒是比较简单
主要是判断输入是不是小写和_
再看下一个
__int64 __fastcall sub_400A33(_DWORD *username, _DWORD *password)
{
int v2; // ST1C_4
int v3; // ST20_4
int v4; // ST24_4
int v5; // ST28_4
int v6; // ST2C_4
__int64 result; // rax
int i; //
for ( i = 0; *(password + i); ++i )
{
if ( (*(password + i) <= 96 || *(password + i) > 122)
&& (*(password + i) <= 64 || *(password + i) > 90)
&& (*(password + i) <= 47 || *(password + i) > 57) )// 只能大写 小写 数字
{
puts("Wrong username or password!!!\n");
exit(0);
}
}
srand(username + *username + username);
v2 = *password;
if ( v2 - rand() != 0x16F48AF6 )
{
puts("Wrong username or password!!!\n");
exit(0);
}
v3 = password;
if ( v3 - rand() != 0x200ADA1C )
{
puts("Wrong username or password!!!\n");
exit(0);
}
v4 = password;
if ( v4 - rand() != 0x37774C4 )
{
puts("Wrong username or password!!!\n");
exit(0);
}
v5 = password;
if ( v5 - rand() != 0x5FC38E35 )
{
puts("Wrong username or password!!!\n");
exit(0);
}
v6 = password;
result = (v6 - rand());
if ( result != 0xAECBAF5 )
{
puts("Wrong username or password!!!\n");
exit(0);
}
return result;
}
第一个循环判断password大小写和数字
然后调用srand函数 seed是username + *username + username
前面已经得知username的值
所以可以预测rand的值
好了昨晚的问题解决了
这个是linux下的程序所以在windows下调用rand和linux可能有些不同
直接写代码预测rand产生的值
爆了个警告 不理他
直接运行
最后rand产生的值时
0x000000005664AD74
0x00000000283E9D1A
0x000000006EE9F96C
0x00000000036FC1FF
0x00000000665B8A42
可以得到password的值为
6d59386a
48497736
72616e30
63335034
71484537
写脚本解一下
>>> i = "6d59386a4849773672616e306333503471484537"
>>> for q in range(0, len(i), 2):
... flag += chr(int(i, 16))
...
得到
mY8jHIw6ran0c3P4qHE7
在拼一下 得到的passw3ord为
j8Ym6wIH0nar4P3c7EHq
afei26579 发表于 2018-9-26 14:24
知道随机因子可以预测 随机结果,这个是有规律。
而知道随机结果是很难推算出随机因子的吧?
计算机的随 ...
嗯 知道随机结果推测随机因子这个没试过 有时间试一下 计算机产生的确实都输入伪随机数 本帖最后由 kikyoulin 于 2018-9-28 09:24 编辑
感谢分享,分析的很清爽。
但没看过瘾,还是想看看sub_400C23里面有什么猫腻(sub_400A33里定义的result虽然作为返回值,但却没用到)(我自己下载看吧,不劳烦大佬了)
顺便膜拜下大佬 可以可以,做题还是很好玩的 wangqiustc 发表于 2018-9-25 22:08
可以可以,做题还是很好玩的
我。。。抱歉。。。不是很懂编辑所以刚刚打完了好多字。。好像没了 wangqiustc 发表于 2018-9-25 22:08
可以可以,做题还是很好玩的
好了 恢复了 很好,楼主很用心, kao2288 发表于 2018-9-25 23:23
有什么用
看看就好 可以,很强势,步骤很清晰,学习了,赞一个 看到这堆数字,说实话我头疼 ,所以不看了,我睡了