姚小宝 发表于 2018-9-25 21:25

从一道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

姚小宝 发表于 2018-9-26 17:41

afei26579 发表于 2018-9-26 14:24
知道随机因子可以预测 随机结果,这个是有规律。
而知道随机结果是很难推算出随机因子的吧?
计算机的随 ...

嗯 知道随机结果推测随机因子这个没试过 有时间试一下 计算机产生的确实都输入伪随机数

kikyoulin 发表于 2018-9-28 09:22

本帖最后由 kikyoulin 于 2018-9-28 09:24 编辑

感谢分享,分析的很清爽。
但没看过瘾,还是想看看sub_400C23里面有什么猫腻(sub_400A33里定义的result虽然作为返回值,但却没用到)(我自己下载看吧,不劳烦大佬了)

顺便膜拜下大佬

wangqiustc 发表于 2018-9-25 22:08

可以可以,做题还是很好玩的

姚小宝 发表于 2018-9-25 22:09

wangqiustc 发表于 2018-9-25 22:08
可以可以,做题还是很好玩的

我。。。抱歉。。。不是很懂编辑所以刚刚打完了好多字。。好像没了

姚小宝 发表于 2018-9-25 22:10

wangqiustc 发表于 2018-9-25 22:08
可以可以,做题还是很好玩的

好了 恢复了

jimo 发表于 2018-9-25 23:02

很好,楼主很用心,

姚小宝 发表于 2018-9-25 23:24

kao2288 发表于 2018-9-25 23:23
有什么用

看看就好

飞翔的路灯 发表于 2018-9-26 07:42

zhenglihua 发表于 2018-9-26 08:26

可以,很强势,步骤很清晰,学习了,赞一个

sstm 发表于 2018-9-26 09:12

xiaowanzi52 发表于 2018-9-26 09:14

看到这堆数字,说实话我头疼 ,所以不看了,我睡了
页: [1] 2 3 4
查看完整版本: 从一道ctf题目理解rand()随机函数