cpu 8086 bits 16 ;;; MS-DOS syscalls gettim: equ 2Ch ; Get system time write: equ 40h ; Write to file exit: equ 4Ch ; Exit with return code ;;; MS-DOS process data arg: equ 80h ; Command line argument (length + string) ;;; BIOS calls conout: equ 0Eh ; Write character to console vstate: equ 0Fh ; Get current video state section .text org 100h mov ah,gettim ; Seed the RNG using the system time int 21h ; (in case of no argument) mov [rnddat],cx mov [rnddat+2],dx mov si,arg ; See if we have any arguments lodsb test al,al jnz hasarg jmp usage ; If not, print usage string ;;; Parse the command line arguments hasarg: xor bx,bx ; We do, zero-terminate the string xchg al,bl mov [si+bx],al mov di,count ; Place to start reading arguments doarg: lodsb ; Get argument byte .chr: test al,al ; Zero? jnz .arg ; If not, there are arguments left jmp check ; If so, we're done .arg: cmp al,' ' ; Space? je doarg ; Then get next character cmp al,'/' ; Option? je opt dec byte [nargs] ; Otherwise, it's an argument jnz rdnum ; Read a number if we still need one jmp usage ; Otherwise, incorrect arguments rdnum: xor bp,bp ; Place to keep number .chr: mov bl,al ; Keep the character in case not a digit sub al,'0' ; Make into digit cmp al,9 ; Valid digit? ja .done ; If not, done with number add bp,bp ; Multiply accumulator by 10 mov dx,bp add bp,bp add bp,bp add bp,dx xor ah,ah ; Then add the digit add bp,ax lodsb ; Read next digit jmp .chr .done: mov ax,bp ; Write the number into memory stosw mov al,bl ; Restore the character (next argument) jmp doarg.chr opt: lodsb ; Get option character or al,32 ; Make lowercase cmp al,'e' ; E? je .e cmp al,'s' ; S? je .s jmp usage ; If not, invalid argument .e: inc byte [excl] ; /E: turn on exclusion jmp doarg .s: lodsb ; /S: should be followed by '=' cmp al,'=' je .s_ok jmp usage ; If not, invalid argument .s_ok: xor bp,bp ; DX:BP = RNG state xor dx,dx .hex: lodsb ; Get (potential) hex digit mov ah,al ; Keep it around call hexdgt ; Parse hexadecimal digit jnc .hdone ; If invalid, we're done shl bp,1 ; Make room for it in the state rcl dx,1 ; Because it's a 32-bit value, shl bp,1 ; which is stored in two 16-bit registers, rcl dx,1 ; we have to shift and rotate the bits shl bp,1 ; one by one. rcl dx,1 shl bp,1 rcl dx,1 or dl,al ; Finally, add in the hexadecimal digit jmp .hex .hdone: mov [rnddat],bp ; Store random seed mov [rnddat+2],dx mov al,ah ; Restore the next character jmp doarg.chr ;;; Check the arguments check: dec byte [nargs] ; Were all arguments used? jz .argok jmp usage .argok: mov cx,[count] ; CX = count mov bp,[length] ; BP = length test cx,cx ; Sanity check (count must not be zero) mov si,errmsg.cnt jnz .cntok jmp error .cntok: cmp bp,4 ; Length must be at least four mov si,errmsg.len jae .len2 jmp error .len2: cmp bp,255 ; Length must be no more than 255 mov si,errmsg.lmax jbe mkmask jmp error ;;; Make a bitmask in which BP fits (for generating random ;;; numbers in the right range) mkmask: mov cx,bp mov bx,cx .loop: shr cx,1 or bx,cx test cx,cx jnz .loop stc rcl bx,1 mov [lmask],bl ; Password <= 255 chars ;;; Generate a password genpwd: lea cx,[bp-4] ; Get length minus four mov di,buffer ; Write password into buffer mov dl,[excl] ; Exclusion flag mov bx,lc ; A lowercase letter, mov ah,lc.len call rndchr mov bx,uc ; An uppercase letter call rndchr ; (same length of course) mov bx,dgt ; A digit, mov ah,dgt.len call rndchr mov bx,sym ; And a symbol mov ah,sym.len call rndchr test cx,cx ; If CX=0, we need no extra characters jz .done mov bx,cr ; Otherwise, we need CX extra characters mov ah,cr.len .loop: call rndchr loop .loop .done: mov ax,0A0Dh ; End with a newline stosw ;;; Swap the first four characters with random characters ;;; from the password, so the four necessary characters won't ;;; necessarily be at the beginning. swap: mov dx,bp ; Get length mov dh,[lmask] ; Load the mask mov si,buffer ; Password buffer xor bh,bh xor ah,ah ; Starting at zero .loc: call rand ; Get random byte and al,dh ; Mask off unused bits cmp al,dl ; Result within password? jae .loc ; If not, get another random number mov bl,ah ; Load first character in CL mov cl,[si+bx] mov bl,al ; Load random character in CH mov ch,[si+bx] mov [si+bx],cl ; Write them back the other way around mov bl,ah mov [si+bx],ch inc ah ; Next character cmp ah,4 ; Are we there yet? jb .loc ; If not, do another character. ;;; Write password to standard output xor bx,bx ; File handle 0 = stdout lea cx,[bp+2] ; Length = password length + 2 (for newline) mov dx,si ; Location of password mov ah,write ; Write the password int 21h jnc .ok ; Carry clear = success mov si,errmsg.wrt ; Otherwise, print error message jmp error .ok: dec word [count] ; Need any more passwords? jz stop ; If not, stop jmp genpwd ; Otherwise, make another password ;;; Quit with return code 0 (success) stop: mov ax,exit<<8 int 21h ;;; Generate a random character. BX=table, AH=length, ;;; DL=exclusion on/off rndchr: call rand ; Random number and al,7Fh ; from 0 to 127 cmp al,ah ; Within valid length? jae rndchr ; If not, get new random number xlatb ; Otherwise, get character from table test dl,dl ; Is exclusion on? jz .out ; If not, just store it push cx ; Otherwise, keep CX around, push di ; and DI, mov cx,ex.len ; See if character is in exclusion table mov di,ex repne scasb pop di ; restore DI pop cx ; Restore CX je rndchr ; And if it was found, get another character .out: stosb ; Store in password buffer ret ;;; Random number generator using XABC algorithm ;;; Returns random byte in AL rand: push cx push dx mov cx,[rnddat] ; CH=X CL=A mov dx,[rnddat+2] ; DH=B DL=C inc ch ; X++ xor cl,ch ; A ^= X xor cl,dl ; A ^= C add dh,cl ; B += A mov al,dh ; C' = B shr al,1 ; C' >>= 1 xor al,cl ; C' ^= A add al,dl ; C' += C mov dl,al ; C = C' mov [rnddat],cx mov [rnddat+2],dx pop dx pop cx ret ;;; If AL is a hexadecimal digit in ASCII, set AL to be its ;;; value. Carry flag set if digit was valid. hexdgt: or al,32 ; Make letter lowercase (numbers unchanged) sub al,'0' ; Subtract 0 cmp al,10 ; If it is a valid digit now, we're done jc .out sub al,39 ; Otherwise, correct for cmp al,17 .out: ret ;;; Write the help message, and stop with return code 2 usage: mov si,help ;;; Write an error message, directly to the console, bypassing ;;; DOS to ensure it does not end up in the redirected output. ;;; Then, stop with return code 2 (failure) error: mov ah,vstate ; Retrieve current video state int 10h ; (this sets BH = current page) .loop lodsb ; Get string byte test al,al ; Zero? jz .out ; Then we have reached the end mov ah,conout ; Otherwise, write the character int 10h jmp .loop ; And get another one .out: mov ax,exit<<8|2 ; Stop with error code 2 int 21h section .data nargs: db 3 ; We need two non-option arguments excl: db 0 ; Exclusion is off by default cr: ;;; Password characters lc: db 'abcdefghijklmnopqrstuvwxyz' .len: equ $-lc uc: db 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' .len: equ $-uc dgt: db '0123456789' .len: equ $-dgt sym: db '!"#$%&()*+,-./:;<=>?@[]^_{|}~',39 ; 39=single quote .len: equ $-sym cr.len: equ $-cr ex: db 'Il1O05S2Z' ; Excluded characters .len: equ $-ex ;;; Help message help: db 'PASSGEN [/S=seed] [/E] count length',13,10 db 9,'generate passwords of length ',13,10 db 13,10 db 9,'/S=seed: set RNG seed (hexadecimal number)',13,10 db 9,'/E: exclude visually similar characters',13,10 db 0 errmsg: ;;; Error messages .len: db 'Minimum password length is 4.',0 .lmax: db 'Maximum password length is 255.',0 .cnt: db 'At least one password must be generated.',0 .wrt: db 'Write error.',0 section .bss count: resw 1 ; Amount of passwords to generate length: resw 1 ; Length of passwords lmask: resb 1 ; Mask for random number generation rnddat: resb 4 ; RNG state buffer: resb 257 ; Space to store password