Search code examples
assemblyx86masmirvine32

x86 Assembly, How to get a user input wihout it being displayed in console?


I'm really new to assembly language and trying to write a simple program. It gets a user input (password) which should not be shown in console.

For normal input, I use the Irvine32 library like so. But this method displays the input user enters, for inputs like password, this should be hidden which I'm not sure how to do.


INCLUDE Irvine32.inc
.data
idPromptStr byte "Please enter ID : ", 13, 10, 0
id DWORD ? 
.code
main proc
    mov edx,OFFSET idPromptStr
    call    WriteString
    call ReadInt ;Reads the integer value from console and moves it to eax.
    mov id,eax ;Input value is taken in eax. 
main endp
end main


Solution

  • TL;DR You need to clear the ENABLE_ECHO_INPUT bit from the console mode, using GetConsoleMode and SetConsoleMode.

    ReadString uses ReadConsoleA internally, using the console that Windows automatically creates for executable with the console subsystem (i.e. it uses GetStdHandle to retrieve the console input and output handles).

    Internal code of ReadString

    Whenever ReadConsoleA will or will not echo the read characters depends on the console mode.
    Specifically, the flag ENABLE_ECHO_INPUT (value 0x4), when cleared, will prevent echoing the characters.

    To get the current console mode use GetConsoleMode, to clear the ENABLE_ECHO_INPUT bit AND the mode with the negation of ENABLE_ECHO_INPUT (i.e. and rm32, ~ENABLE_ECHO_INPUT in NASM).
    Then set the console mode with this new value.
    Here's the function to disable and reenable the echo:

      ;No args, return the console mode to pass to EnableEcho
    DisableEcho:
      push esi 
      push edi 
      
      ;-- Get console input handle ---
      push STD_INPUT_HANDLE
      call _GetStdHandle@4
      
      mov esi, eax   
      
      
      ;-- Clear the ENABLE_ECHO_INPUT bit --
      sub esp, 04
      push esp 
      push eax
      call _GetConsoleMode@8
      pop eax
      mov edi, eax 
      
      and eax, ~ENABLE_ECHO_INPUT
     
      push eax 
      push esi
      call _SetConsoleMode@8
      
      mov eax, edi
    
      pop edi 
      pop esi  
      ret
    
      ;edx = value returned from DisableEcho
    EnableEcho:
      ;-- Get console input handle ---
      push STD_INPUT_HANDLE
      call _GetStdHandle@4
      
      ;-- Set mode --
      push edx 
      push eax 
      call _SetConsoleMode@8
        
      ret 
    

    Note this code is written for NASM and to be linked with Microsoft's link. Adapt it to your tools.

    DisableEcho returns the original console mode that you must pass to EnableEcho (in edx as per Irvine's call convention).
    Like:

    call DisableEcho
    
    ;Here echo is disabled when calling ReadXXX
    
    mov edx, eax              ;Assuming eax has been preserved
    call EnableEcho
    

    Adapt the code to your needs, I didn't use global variables because, contrary to Irvine, I prefer pure functions whenever possible.
    Some people find the lack of global variables harder to follow.

    Here's a complete program that will read a username, a password, and an OTP code (just to show that echo is enabled before and after the password prompt) and then will print all of them.

    BITS 32
    
    GLOBAL _start
    
    %define STD_INPUT_HANDLE -10
    %define ENABLE_ECHO_INPUT 4
    
    %define STRLEN 82
    
    EXTERN _ReadString@0
    EXTERN _ExitProcess@4
    EXTERN _SetConsoleMode@8
    EXTERN _GetConsoleMode@8
    EXTERN _GetStdHandle@4
    EXTERN _WriteString@0
    
    SECTION .bss
    
      myUsername    resb STRLEN
      myPassword    resb STRLEN
      myOTP         resb STRLEN 
     
    SECTION .data
    
      strUsername   db "Username: ", 0
      strPassword   db "Password:", 0
      strOTP        db 13, 10, "OTP code: ", 0   
      strCRLF       db 13, 10, 0
      
    SECTION .text
    
    _start:
    
      ;-- Read the username --
    
      mov edx, strUsername
      call _WriteString@0
    
      mov edx, myUsername
      mov ecx, STRLEN 
      call _ReadString@0
    
      ;-- Disable echo --
      call DisableEcho
      mov esi, eax 
      
      ;-- Read the password --
    
      mov edx, strPassword
      call _WriteString@0
    
      mov edx, myPassword 
      mov ecx, STRLEN 
      call _ReadString@0
      
      ;-- Restore the echo ---
      
      mov edx, esi 
      call EnableEcho
    
      ;-- Read the otp --
    
      mov edx, strOTP
      call _WriteString@0
    
      mov edx, myOTP
      mov ecx, STRLEN 
      call _ReadString@0
      
      ;-- Show --
      
      
      mov edx, myUsername
      call _WriteString@0
    
      mov edx, strCRLF
      call _WriteString@0
      
      mov edx, myPassword
      call _WriteString@0
      
      mov edx, strCRLF
      call _WriteString@0
      
      mov edx, myOTP
      call _WriteString@0
      
      ;-- Exit --
      
      push 0
      call _ExitProcess@4
      
      
      ;No args, return the console mode to pass to EnableEcho
    DisableEcho:
      push esi 
      push edi 
      
      ;-- Get console input handle ---
      push STD_INPUT_HANDLE
      call _GetStdHandle@4
      
      mov esi, eax   
      
      
      ;-- Clear the ENABLE_ECHO_INPUT bit --
      sub esp, 04
      push esp 
      push eax
      call _GetConsoleMode@8
      pop eax
      mov edi, eax 
      
      and eax, ~ENABLE_ECHO_INPUT
     
      push eax 
      push esi
      call _SetConsoleMode@8
      
      mov eax, edi
    
      pop edi 
      pop esi  
      ret
    
      ;edx = value returned from DisableEcho
    EnableEcho:
      ;-- Get console input handle ---
      push STD_INPUT_HANDLE
      call _GetStdHandle@4
      
      ;-- Set mode --
      push edx 
      push eax 
      call _SetConsoleMode@8
        
      ret