随机数生成器在装配中崩溃

时间:2019-05-23 20:57:33

标签: winapi assembly x86 masm msvcrt

在stackoverflow上找到了此代码,我了解了它的工作原理并尝试实现它。它在INT 1AH指令时崩溃,我不知道为什么。 当我在ollydbg中运行时,它停在同一行。

我还尝试了随机数生成器函数rand(void),但是每当我重新运行代码时,它总是为我提供相同的数字。 (如果我连续调用3次,则有3个数字,但是每次重播时仍然是相同的数字)

.386
.model flat, stdcall

includelib msvcrt.lib
extern exit: proc
extern printf: proc
extern rand: proc

public start


.data
decimal_format DB "%d",0ah

.code
start:
    mov ah, 00h
    int 1ah

    mov ax,dx
    mov dx,0
    mov cx,10
    div cx

    mov ah,2h
    int 21h

    push edx
    push offset decimal_format
    call printf
    add esp,8

    push 0
    call exit
end start

1 个答案:

答案 0 :(得分:3)

如果您正在编写Win32程序,则无法调用BIOS和DOS服务,例如Int 1ahInt 10hInt 21h等。由于Win32程序无法访问,这将导致应用程序崩溃这些服务。

Windows C 库(MSVCRT.LIB)中的基本randsrand基于线性同余生成器(LCG)伪随机数生成器(PRNG) )。此公式依赖于种子值来设置PRNG的初始状态。每次重新启动程序时,程序执行时的初始状态将始终相同。这样,每次对rand的调用都会产生一个伪随机数,但是每次运行程序时,这些数字将具有相同的顺序。

srand可用于更改PRNG的种子值。更改种子值将更改将产生的数字rand,但是在给定相同种子的情况下,它们始终是相同的数字序列。您需要的是一种在每次运行程序时将种子值设置为不同值的机制。您可以将 C time函数与NULL(0)参数一起使用,以获取自1970年1月1日午夜以来的秒数。只要您的程序不不能以在同一秒内执行的方式快速运行。通常这足够好。

然后可以将EAX中的time(0)返回的值传递给srand来设置种子值。程序启动时仅调用一次srand。从那时起,您应该可以致电rand以获得新的随机数。 rand返回0到RAND_MAX之间的值,而RAND_MAX为32767。

此示例代码执行srand(time(0))初始化种子,然后循环10次,打印出通过调用rand检索到的另一个随机数。每次您运行该程序时,输出都应该不同。

.386
.model flat, C

includelib msvcrt.lib
extern exit: proc
extern printf: proc
extern rand: proc
extern srand: proc
extern time: proc

.data
decimal_format DB "%d", 0ah, 0 
                             ; Ensure string is NUL(0) terminated

.code

main PROC
    push ebx                 ; Save callee saved (non-volatile) registers that we use.
                             ; EBX, EBP, ESI, EDI, ESP are non-volatile. For each
                             ; one we clobber we must save it and restore it before
                             ; returning from `main`

    push 0
    call time                ; EAX=time(0)
    add esp, 4
    push eax                 ; Use time as seed
    call srand               ; srand(time(0))
    add esp, 4

    mov ebx, 10              ; Loop 10 times

loopit:
    call rand                ; Get a random number between 0 and 32767 into EAX

    push eax
    push offset decimal_format
    call printf              ; Print the random number
    add esp,8

    dec ebx
    jnz loopit               ; Loop until the counter EBX reaches 0

    pop ebx                  ; Restore callee saved registers
    xor eax, eax             ; Return 0 from our program
    ret
main ENDP

END

其他一些重要变化。我使用了 C (CDECL)调用约定(通过.model flat, C),该约定自动处理用32位代码中的下划线修饰main PROC。我也将start更改为main,并将end start更改为end。我们也不想使用end main,因为该伪指令将使main成为我们代码的入口点,并且将跳过通常必须先进行的 C 运行时初始化。 main被调用。如果未调用 C 运行时初始化,可能会导致 C 库函数意外运行或完全崩溃。

代码完成后,我执行ret返回到 C 启动代码,该代码将为我们退出。该代码还保留了非易失性(保存被调用者)寄存器。有关更多信息,请参见Microsoft 32-bit CDECL calling convention