274 lines
7.9 KiB
Text
274 lines
7.9 KiB
Text
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 <count> passwords of length <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
|