找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 4591|回复: 4

【算法】通过逆向VC6的rand函数研究随机数算法

[复制链接]
发表于 2014-8-13 15:10:33 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
原帖:http://www.0xaa55.com/thread-785-1-1.html
版权所有(C) 2013-2014 技术宅的结界
转载请保留出处。

首先写一个用到rand函数的小程序。
  1. #include<stdio.h>
  2. #include<stdlib.h>

  3. int main(int argc,char**argv)
  4. {
  5.     printf("%d\n",rand());
  6.     printf("%d\n",rand());
  7.     printf("%d\n",rand());
  8.     printf("%d\n",rand());
  9.     printf("%d\n",rand());
  10.     printf("%d\n",rand());
  11.     printf("%d\n",rand());
  12.     printf("%d\n",rand());
  13.     printf("%d\n",rand());
  14.     printf("%d\n",rand());
  15.     printf("%d\n",rand());
  16.     printf("%d\n",rand());
  17.     printf("%d\n",rand());
  18.     printf("%d\n",rand());
  19.     printf("%d\n",rand());
  20.     printf("%d\n",rand());
  21.     return 0;
  22. }
复制代码
20140813140236.png
然后编译,开始单步调试。
找到它调用rand函数的地方,继续单步运行,VC6提示找不到rand.c的时候不管,看反汇编。如下图所示。
20140813140754.png
可以看到这个rand函数很简单,就几条汇编指令。抄出来看,就是这个样子的:
  1. push    ebp
  2. mov     ebp,esp
  3. mov     eax,[holdrand]
  4. imul    eax,eax,343FDh
  5. add     eax,269EC3h
  6. mov     [holdrand],eax
  7. mov     eax,[holdrand]
  8. sar     eax,10h
  9. and     eax,7FFFh
  10. pop     ebp
  11. ret
复制代码
其中,holdrand的初始值是1.
这样一看就简单了。我解释一下它的计算过程就是这样:
holdrand是随机数种子,乘以0x000343FD,加上0x00269EC3,得到的结果作为随机数种子存回holdrand,然后再把刚才得到的结果的高16位去掉最高位作为随机数返回。虽然是这么说的,但是holdrand真的就是随机数种子吗?我们来看看srand函数的反汇编。首先我们写一个简单的程序,然后编译它。
  1. #include<stdio.h>
  2. #include<stdlib.h>

  3. int main(int argc,char**argv)
  4. {
  5.     srand(0xAA55);
  6.     printf("%d\n",rand());
  7.     return 0;
  8. }
复制代码
20140813142109.png
真是一目了然。holdrand就是随机数种子。srand的反汇编:
  1. push    ebp
  2. mov     ebp,esp
  3. mov     eax,dword ptr[seed]
  4. mov     [holdrand],eax
  5. pop     ebp
  6. ret
复制代码
它就是改写了holdrand。那么最终,我们还原出来的rand和srand的源码是这样写的:
  1. int holdrand=1;

  2. void srand(int seed)
  3. {
  4.     holdrand=seed;
  5. }

  6. int rand()
  7. {
  8.     holdrand=holdrand*0x000343FD+0x00269EC3;
  9.     return(holdrand>>0x10)&0x7FFF;
  10.     //反汇编看到它用的是SAR指令进行右移。但是既然结果只保留最低15位那么直接用>>运算符也是可以的。
  11. }
复制代码
真是简单。这就是所谓随机数了吧。我们先验证一下,结果是不是和原始的rand、srand效果一样。
  1. #include<stdio.h>
  2. #include<stdlib.h>

  3. int myholdrand=1;

  4. void mysrand(int seed)
  5. {
  6.     myholdrand=seed;
  7. }

  8. int myrand()
  9. {
  10.     myholdrand=myholdrand*0x000343FD+0x00269EC3;
  11.     return(myholdrand>>0x10)&0x7FFF;
  12. }

  13. int main(int argc,char**argv)
  14. {
  15.     int i;

  16.     fputs("Test1:\n",stdout);

  17.     //测试1:不调用srand,看结果
  18.     for(i=0;i<999999;i++)//试999999次
  19.     {
  20.         int iDifferent=rand()-myrand();
  21.         if(iDifferent)
  22.             printf("Different result:%d\n",iDifferent);
  23.     }

  24.    
  25.     fputs("Test1 finished.\n"
  26.         "Test2:\n",stdout);

  27.     //测试2:调用一次srand然后看结果
  28.     srand(0xAA55);
  29.     mysrand(0xAA55);
  30.    
  31.     for(i=0;i<999999;i++)//试999999次
  32.     {
  33.         int iDifferent=rand()-myrand();
  34.         if(iDifferent)
  35.             printf("Different result:%d\n",iDifferent);
  36.     }

  37.     fputs("Test2 finished.\n",stdout);
  38.     return 0;
  39. }
复制代码
20140813143144.png
好!看来算法是正确的了。
现在的问题是:它真的是“随机数”吗?或者它真的能保证它产生0~32767之间数字的概率是相等的吗?
我们用这样的一份代码来做测试。
  1. #include<stdio.h>
  2. #include<stdlib.h>

  3. unsigned short NumGenerated[32768]={0};//数字出现的次数

  4. int main(int argc,char**argv)
  5. {
  6.     int i;
  7.     for(i=0;i<32768;i++)//测试32768次
  8.         NumGenerated[rand()]++;//统计每个数字出现的次数
  9.    
  10.     for(i=0;i<32768;i++)
  11.     {
  12.         if(NumGenerated[i ]!=1)//如果不是1
  13.             printf("Number %d appears %u times.\n",i,NumGenerated[i ]);
  14.     }
  15.     return 0;
  16. }
复制代码
这样如果直接运行的话,有可能会产生超过一屏的结果,因此我们用rand>result.txt命令来运行它。
20140813144235.png
20140813144848.png
诶?怎么会……
20140813144900.png
诶??
好像全部范围都出现过数字,有重复的,也有没出现过的。。
我测试过,如果测试次数是16777216,那么0到32767之间所有数字都出现过至少一次,没有0出现。有图为证。
20140813145224.png
重新写了代码:
  1. #include<stdio.h>
  2. #include<stdlib.h>

  3. unsigned short NumGenerated[32768]={0};

  4. int main(int argc,char**argv)
  5. {
  6.     int i;
  7.     unsigned short Min=-1,Max=0,MinV,MaxV;

  8.     for(i=0;i<16777216;i++)//测试16777216次
  9.         NumGenerated[rand()]++;//统计每个数字出现的次数
  10.    

  11.     for(i=0;i<32768;i++)
  12.     {
  13.         printf("Number %d appears %u times.\n",i,NumGenerated[i ]);
  14.         if(NumGenerated[i ]>Max)
  15.         {
  16.             Max=NumGenerated[i ];
  17.             MinV=i;
  18.         }
  19.         if(NumGenerated[i ]<Min)
  20.         {
  21.             Min=NumGenerated[i ];
  22.             MaxV=i;
  23.         }
  24.     }
  25.     printf( "Min: Number %d appears %u times.\n"
  26.             "Max: Number %d appears %u times.\n",MinV,Min,MaxV,Max);
  27.     return 0;
  28. }
复制代码
然后再运行rand>result.txt命令,看result.txt的结果,基本上每个数字都出现了大约450次到550次之间。
20140813150125.png
20140813152332.png
出现次数最少的数字是31509,出现了418次
出现次数最多的数字是16291,出现了618次
从以上结果看出来,这个随机数还真是不够随机呀。。
回复

使用道具 举报

发表于 2014-8-20 09:18:51 | 显示全部楼层
{:soso_e113:}233看起来此站不算太火哦=w=
这么久没人回...
回复 赞! 靠!

使用道具 举报

发表于 2014-9-8 19:53:24 | 显示全部楼层
跪拜

(他说要10个字节才能发送呢,那用的是UTF-8吧)
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2014-9-8 20:11:17 | 显示全部楼层
已扣除一个宅币 发表于 2014-9-8 11:53
跪拜

(他说要10个字节才能发送呢,那用的是UTF-8吧)

没错,就是UTF-8
回复 赞! 靠!

使用道具 举报

发表于 2014-9-8 20:22:55 | 显示全部楼层
0xAA55 发表于 2014-9-8 20:11
没错,就是UTF-8

UTF8 ->(1110 xxxx  10xx xxxx 10xxx xxxxx)
(为了防止装逼失败,瞅了一样源代码)
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-11-23 16:23 , Processed in 0.038909 second(s), 27 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表