最近在玩系统自带的freecell,XP下它带了1m的开局,但发现为什么总是随机开局都超不过几万,初步怀疑是不是种子有什么问题。
用od加载,定位rand函数,呵呵,原来是程序与dll之间的衔接有点小问题:

代码:
01003153  /$  56            push    esi
01003154  |.  6A 00         push    0                                ; /timer = NULL
01003156  |.  FF15 E4110001 call    dword ptr [<&msvcrt.time>]       ; \time
0100315C  |.  50            push    eax                              ; /seed
0100315D  |.  FF15 E8110001 call    dword ptr [<&msvcrt.srand>]      ; \srand,这句是初始化随机种子
01003163  |.  8B35 EC110001 mov     esi, dword ptr [<&msvcrt.rand>]  ;  msvcrt.rand
01003169  |.  59            pop     ecx
0100316A  |.  59            pop     ecx
0100316B  |.  FFD6          call    esi                              ; [rand,连续三次计算随机数,看起来没什么问题
0100316D  |.  FFD6          call    esi
0100316F  |>  FFD6          call    esi
01003171  |.  83F8 01       cmp     eax, 1                      ;是否为0或者是负数
01003174  |.^ 72 F9         jb      short 0100316F         ;不符合要求则继续rand
01003176  |.  3D 40420F00   cmp     eax, 0F4240            ;比较是否大于1M
0100317B  |.^ 77 F2         ja      short 0100316F
0100317D  |.  5E            pop     esi
0100317E  \.  C3            retn
程序代码看起来很正常,随机数范围应该是1-1m,但dll里面有问题了:

代码:
77C071D3 >  E8 4D2D0000     call    77C09F25
77C071D8    8B48 14         mov     ecx, dword ptr [eax+14]
77C071DB    69C9 FD430300   imul    ecx, ecx, 343FD
77C071E1    81C1 C39E2600   add     ecx, 269EC3                      ; ASCII 02,"漓"
77C071E7    8948 14         mov     dword ptr [eax+14], ecx
77C071EA    8BC1            mov     eax, ecx
77C071EC    C1E8 10         shr     eax, 10
77C071EF    25 FF7F0000     and     eax, 7FFF                              ;主要就是这两句,将dword直接截断为word,意味着很大于7fff(32767)的开局不会被随机取到
77C071F4    C3              retn
解决的办法就是修改dll,把碍事的两行nop掉,但这个对其它应用软件影响不确定。改程序也可以,比较安全,就是费点劲,跳走到空地,然后抄一点rand函数代码,保证随机数的范围不出错,然后跳回来。大概代码是:

代码:
77C09F27    56              push    esi
77C09F28    57              push    edi
77C09F29    FF15 0410BE77   call    dword ptr [<&KERNEL32.GetLastErr>; ntdll.RtlGetLastWin32Error
77C09F2F    FF35 CCFAC277   push    dword ptr [77C2FACC]
77C09F35    8BF8            mov     edi, eax
77C09F37    FF15 4410BE77   call    dword ptr [<&KERNEL32.TlsGetValu>; kernel32.TlsGetValue
77C09F3D    8BF0            mov     esi, eax                      ;上一行和本行比较关键,取得随机数存放地

77C09F87    57              push    edi
77C09F88    FF15 0412BE77   call    dword ptr [<&KERNEL32.SetLastErr>; ntdll.RtlSetLastWin32Error
77C09F8E    5F              pop     edi
77C09F8F    8BC6            mov     eax, esi
77C09F91    5E              pop     esi

77C071D8    8B48 14         mov     ecx, dword ptr [eax+14]
77C071DB    69C9 FD430300   imul    ecx, ecx, 343FD
77C071E1    81C1 C39E2600   add     ecx, 269EC3                      ; ASCII 02,"漓"
77C071E7    8948 14         mov     dword ptr [eax+14], ecx
77C071EA    8BC1            mov     eax, ecx

然后返回



by Fpc

  • 标 题:答复
  • 作 者:Fpc
  • 时 间:2008-06-06 13:31

感谢海风,对原程序做了下修改,随机性能可能还有改进余地,不过已经影响不大了。
我用了rdtsc 

代码:
01003163  |.  8B35 EC110001 mov     esi, dword ptr [<&msvcrt.rand>]  ;  msvcrt.rand
01003169  |.  59            pop     ecx
0100316A  |.  59            pop     ecx
0100316B  |.  FFD6          call    esi                              ; [rand
0100316D  |.  33C8          xor     ecx, eax                    ; 借用上一步rand的结果
0100316F  |.  0F31          rdtsc                                    ; 伪随机eax, edx没用到,空间不够了
01003171  |.  33C1          xor     eax, ecx
01003173  |.  B9 40420F00   mov     ecx, 0F4240
01003178  |.  F7F9          idiv    ecx                             ; 用1m除一下
0100317A  |.  8BC2          mov     eax, edx                  ; 取余数
0100317C  |.  90            nop
0100317D  |.  5E            pop     esi
0100317E  \.  C3            retn
附上修改后的freecell:
上传的附件 freecell.zip