flat assembler
Message board for the users of flat assembler.

Index > Tutorials and Examples > A Win32 console keypress example (Updated)

Goto page 1, 2  Next
Author
Thread Post new topic Reply to topic
MatQuasar



Joined: 25 Oct 2023
Posts: 105
MatQuasar 30 Mar 2024, 08:35
ReadConsoleA is for reading input while displaying the character typed.
ReadConsoleInputA is for suitable for wait for keypress.

I don't know, there is also PeekConsoleInputA (without clearing the input buffer).
I just want a simple function like PAUSE command. This is the example I key in character by character, no macros.

This is an updated version as of April 2, 2024

Code:
;Revision 2 - With bug fixes suggested by AsmGuru62 (Thanks!)


;BOOL WINAPI ReadConsoleInput(
;  _In_  HANDLE        hConsoleInput,
;  _Out_ PINPUT_RECORD lpBuffer,
;  _In_  DWORD         nLength,
;  _Out_ LPDWORD       lpNumberOfEventsRead
; );


;typedef struct _INPUT_RECORD {
;  WORD  EventType;
;  union {
;    KEY_EVENT_RECORD          KeyEvent;
;    MOUSE_EVENT_RECORD        MouseEvent;
;    WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
;    MENU_EVENT_RECORD         MenuEvent;
;    FOCUS_EVENT_RECORD        FocusEvent;
;  } Event;
; } INPUT_RECORD;


format PE console

section ".code" code readable executable

entry $

    push     -11
    call     [GetStdHandle]
    mov      dword [_output], eax

    push     0
    push     _dummy
    push     _len
    push     _message
    push     eax
    call     [WriteConsoleA]

    push     -10
    call     [GetStdHandle]
    mov      dword [_input], eax

    push     _mode
    push     dword [_input]
    call     [GetConsoleMode]
    test     eax, eax
    jz       .done

    or       dword [_mode], 0x0008      ;ENABLE_WINDOW_INPUT
    push     dword [_mode]
    push     dword [_input]
    call     [SetConsoleMode]
    test     eax, eax
    jz       .done

.waiting:
    push     _dummy
    push     1
    push     _buffer
    push     dword [_input]
    call     [ReadConsoleInputA]

    cmp      [_buffer], 1
    jnz      .waiting

    cmp      [_keydown], 1
    jnz      .waiting

.done:
    push     0
    call     [ExitProcess]


section ".data" data readable writeable

    _message  db   "Press any key to continue...", 13, 10
    _len      = $ - _message
    _dummy    dd   ?
    _input    dd   ?
    _output   dd   ?
    _mode     dd   ?
    _buffer   dw   ?
              dw   ?
    _keydown  dd   ?
    _event    rb   50 - 2 - 2 - 4

section ".idata" import readable

    dd 0, 0, 0, RVA kernel_name, RVA kernel_table
    dd 0, 0, 0, 0, 0

    kernel_name db "KERNEL32.DLL", 0

    kernel_table: ReadConsoleInputA dd RVA _ReadConsoleInputA
                  GetStdHandle dd RVA _GetStdHandle
                  WriteConsoleA dd RVA _WriteConsoleA
                  ExitProcess dd RVA _ExitProcess
                  GetConsoleMode dd RVA _GetConsoleMode
                  SetConsoleMode dd RVA _SetConsoleMode
                  dd 0

    _ReadConsoleInputA: dw 0
                        db "ReadConsoleInputA", 0

    _GetStdHandle: dw 0
                   db "GetStdHandle", 0

    _WriteConsoleA: dw 0
                    db "WriteConsoleA", 0

    _ExitProcess: dw 0
                  db "ExitProcess", 0

    _GetConsoleMode: dw 0
                     db "GetConsoleMode", 0

    _SetConsoleMode: dw 0
                     db "SetConsoleMode", 0

    


Last edited by MatQuasar on 06 Apr 2024, 12:45; edited 4 times in total
Post 30 Mar 2024, 08:35
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20309
Location: In your JS exploiting you and your system
revolution 30 Mar 2024, 08:40
MatQuasar wrote:
I don't know, there is also PeekConsoleInputA (without clearing the input buffer).
Something like this might do that.
Code:
proc WIN32_is_key_waiting
        ;return 0 if no keys waiting
        locals
                read_count      dd ?
        endl
        invoke  PeekNamedPipe,[handle_std_input],NULL,0,NULL,addr read_count,NULL
        mov     eax,[read_count]
        test    eax,eax
        jnz     .done
        invoke  GetFileType,[handle_std_input]
        cmp     eax,FILE_TYPE_CHAR
    .done:
        setnz   al
        movzx   eax,al
        ret
endp    
Post 30 Mar 2024, 08:40
View user's profile Send private message Visit poster's website Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1619
Location: Toronto, Canada
AsmGuru62 30 Mar 2024, 12:02
Windows Console by default is set up in a way, where user can type characters and they are echoed to screen right away and the input ends with [ENTER] key. You need to use the SetConsoleMode function to cancel that behavior, so the ReadConsoleInput can actually wait for a key to be pressed and then decide to echo it or not -- all this will depend on your code.
Post 30 Mar 2024, 12:02
View user's profile Send private message Send e-mail Reply with quote
MatQuasar



Joined: 25 Oct 2023
Posts: 105
MatQuasar 30 Mar 2024, 12:33
Thanks revolution for your supplementary code and AsmGuru62 for your tip & trick.

I just tested my code at Post #1 in Windows Terminal and PowerShell, it didn't work properly by waiting for keypress. It only worked in Command Prompt window.
Any idea why?
Post 30 Mar 2024, 12:33
View user's profile Send private message Reply with quote
bitRAKE



Joined: 21 Jul 2003
Posts: 4024
Location: vpcmpistri
bitRAKE 30 Mar 2024, 17:45
Display the console mode for each interface. I'd wager they are different. Best to use SetConsoleMode as AsmGuru62 suggests.

_________________
¯\(°_o)/¯ “languages are not safe - uses can be” Bjarne Stroustrup
Post 30 Mar 2024, 17:45
View user's profile Send private message Visit poster's website Reply with quote
MatQuasar



Joined: 25 Oct 2023
Posts: 105
MatQuasar 30 Mar 2024, 20:28
bitRAKE wrote:
Display the console mode for each interface. I'd wager they are different. Best to use SetConsoleMode as AsmGuru62 suggests.


Thanks, I think it is because ENABLE_WINDOW_INPUT is not enabled on Windows Terminal and PowerShell.

SetConsoleMode wrote:
If the hConsoleHandle parameter is an input handle, the mode can be one or more of the following values. When a console is created, all input modes except ENABLE_WINDOW_INPUT and ENABLE_VIRTUAL_TERMINAL_INPUT are enabled by default.


But I am having extra work to do to debug the GetConsoleMode and SetConsoleMode. Strange, the mode values returned is 0x40202E no matter what values I set or clear, and is the same across all interfaces. Maybe something wrong with my hex conversion routine. I will try again tomorrow.
Post 30 Mar 2024, 20:28
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20309
Location: In your JS exploiting you and your system
revolution 30 Mar 2024, 21:23
MatQuasar wrote:
... the mode values returned is 0x40202E ...
That value is an address in your data section.
Post 30 Mar 2024, 21:23
View user's profile Send private message Visit poster's website Reply with quote
MatQuasar



Joined: 25 Oct 2023
Posts: 105
MatQuasar 30 Mar 2024, 21:33
revolution wrote:
MatQuasar wrote:
... the mode values returned is 0x40202E ...
That value is an address in your data section.


OMG, I passed wrong value to wsprintfA, should have passed in bracket, i.e. [_mode] instead of _mode.

Thank you very much, revolution!
Post 30 Mar 2024, 21:33
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20309
Location: In your JS exploiting you and your system
revolution 30 Mar 2024, 23:02
If you are not yet using a debugger then you should definitely look into using one.

Those kinds of silly mistakes can be identified and fixed effortlessly.
Post 30 Mar 2024, 23:02
View user's profile Send private message Visit poster's website Reply with quote
MatQuasar



Joined: 25 Oct 2023
Posts: 105
MatQuasar 31 Mar 2024, 09:05
I took note, revolution.

bitRAKE wrote:
Display the console mode for each interface. I'd wager they are different. Best to use SetConsoleMode as AsmGuru62 suggests.


I run a test and there is no difference in console mode value returned across CMD, PS, and Windows Terminal's CMD. (see screenshot)

0x1F7 is the initial value, 0x1FF after setting ENABLE_WINDOW_INPUT.

But the behavior still the same, PS and Windows Terminal's CMD don't wait for keypress. Or even I click Run in FASMW.EXE, the CMD popped up also doesn't wait for keypress.
Only work when I run the program by typing the filename in CMD.

So ENABLE_WINDOW_INPUT is not the answer.


Description: 0x1F7 before, 0x1FF after setting ENABLE_WINDOW_INPUT
Filesize: 104.95 KB
Viewed: 3676 Time(s)

Capture.PNG




Last edited by MatQuasar on 06 Apr 2024, 12:45; edited 1 time in total
Post 31 Mar 2024, 09:05
View user's profile Send private message Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1619
Location: Toronto, Canada
AsmGuru62 31 Mar 2024, 12:12
Please try to set Mode to 0.
Post 31 Mar 2024, 12:12
View user's profile Send private message Send e-mail Reply with quote
MatQuasar



Joined: 25 Oct 2023
Posts: 105
MatQuasar 31 Mar 2024, 14:17
AsmGuru62 wrote:
Please try to set Mode to 0.


Thanks for your reply. I set to mode 0 for screen input buffer handle, but the same behavior is reported.
Post 31 Mar 2024, 14:17
View user's profile Send private message Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1619
Location: Toronto, Canada
AsmGuru62 31 Mar 2024, 21:58
I looked closely at the code you provided in post #1.
I am sure you are using GetConsoleInput function in a wrong way.
Let me paste some of your code here:
Code:
    push     _dummy
    push     2
    push     _buffer
    push     eax
    call     [ReadConsoleInputA]
    

I think you do not need the 'A' in the name of this function.
And now, lets check your variables:
Code:
    _dummy    dd   ?
    _buffer   rb   2
    

ReadConsoleInput function is reading structures, so that "_buffer" should be a structure, at least one structure of type INPUT_RECORD. Lets assume that we will read just one structure at a time, so then we do not care about the size of this structure. You can find it all in the description of the function. The size of INPUT_RECORD is definitely not 2 bytes. Lets get safe here and make it a good 32 bytes piece of memory. When the function returns -- the first 16-bit value of the structure will indicate the type of the Console Input Event that was returned. It can be a key, menu command, mouse event or event if you resize the Console window -- that is also reported using this function. In your case -- you want the key, and that is a value of 1 in that 16-bit WORD at the beginning of the structure. So, basically, you have to LOOP this function until it returns a key event. So, these lines of code I pasted will look like this:
Code:
_dummy    dd 0
_buffer   dw 0
_event    rb 30         ; <-- this is where Windows will put details about the key

...

; --> call SetConsoleMode with '0' mode

waiting:
push     -10
call     [GetStdHandle]
push     _dummy
push     1
push     _buffer
push     eax
call     [ReadConsoleInputA]

cmp              [_buffer], 1
jne              waiting

...

; key was pressed at this point
    
Post 31 Mar 2024, 21:58
View user's profile Send private message Send e-mail Reply with quote
bitRAKE



Joined: 21 Jul 2003
Posts: 4024
Location: vpcmpistri
bitRAKE 31 Mar 2024, 22:21
This is what I am currently using:
Code:
invoke GetConsoleMode, [.hIn], addr .oldInMode
mov edx, not (ENABLE_LINE_INPUT or ENABLE_ECHO_INPUT)
and edx, [.oldInMode]
invoke SetConsoleMode, [.hIn], rdx    
... and a working example program (fasm2).


Description: getch() function
Download
Filename: press_key.zip
Filesize: 1.6 KB
Downloaded: 98 Time(s)


_________________
¯\(°_o)/¯ “languages are not safe - uses can be” Bjarne Stroustrup
Post 31 Mar 2024, 22:21
View user's profile Send private message Visit poster's website Reply with quote
MatQuasar



Joined: 25 Oct 2023
Posts: 105
MatQuasar 01 Apr 2024, 06:18
Thank you so much, bitRAKE for the detailed analysis. I forgot it is a key event. It is eye opening for me to know need to allocate 32-bytes for buffer (dw 0 + rb 30).
Your code modificiation now is better, work in FASMW.EXE's Run command as well besides CMD, but still not working in PowerShell and Windows Terminal.

But your press_key.exe is amazing! It works perfectly on all command processors! You are truly an advanced programmer.
Post 01 Apr 2024, 06:18
View user's profile Send private message Reply with quote
MatQuasar



Joined: 25 Oct 2023
Posts: 105
MatQuasar 01 Apr 2024, 16:18
Whoops....! It was AsmGuru62 who replied with code modification! I thought both posts were made by bitRAKE.

Thank you AsmGuru62. Yes, I did set mode to 0 or with 0x0008.
Post 01 Apr 2024, 16:18
View user's profile Send private message Reply with quote
bitRAKE



Joined: 21 Jul 2003
Posts: 4024
Location: vpcmpistri
bitRAKE 01 Apr 2024, 19:18
MatQuasar wrote:
It works perfectly on all command processors! You are truly an advanced programmer.
Thank you. As you test the example, you'll notice many keys don't trigger (i.e. ESC, F#, ...). If those keys are important to your implementation then using ReadConsoleInput() is needed. Blocking the program with a getch() like function is usually sufficient.

_________________
¯\(°_o)/¯ “languages are not safe - uses can be” Bjarne Stroustrup
Post 01 Apr 2024, 19:18
View user's profile Send private message Visit poster's website Reply with quote
MatQuasar



Joined: 25 Oct 2023
Posts: 105
MatQuasar 01 Apr 2024, 21:44
You're welcome, bitRAKE. I think I still need to figure ReadConsoleInput....

AsmGuru62 wrote:
I looked closely at the code you provided in post #1.
I am sure you are using GetConsoleInput function in a wrong way.
Let me paste some of your code here:
Code:
    push     _dummy
    push     2
    push     _buffer
    push     eax
    call     [ReadConsoleInputA]
    

I think you do not need the 'A' in the name of this function.
And now, lets check your variables:
Code:
    _dummy    dd   ?
    _buffer   rb   2
    

ReadConsoleInput function is reading structures, so that "_buffer" should be a structure, at least one structure of type INPUT_RECORD. Lets assume that we will read just one structure at a time, so then we do not care about the size of this structure. You can find it all in the description of the function. The size of INPUT_RECORD is definitely not 2 bytes. Lets get safe here and make it a good 32 bytes piece of memory. When the function returns -- the first 16-bit value of the structure will indicate the type of the Console Input Event that was returned. It can be a key, menu command, mouse event or event if you resize the Console window -- that is also reported using this function. In your case -- you want the key, and that is a value of 1 in that 16-bit WORD at the beginning of the structure. So, basically, you have to LOOP this function until it returns a key event. So, these lines of code I pasted will look like this:
Code:
_dummy    dd 0
_buffer   dw 0
_event    rb 30         ; <-- this is where Windows will put details about the key

...

; --> call SetConsoleMode with '0' mode

waiting:
push     -10
call     [GetStdHandle]
push     _dummy
push     1
push     _buffer
push     eax
call     [ReadConsoleInputA]

cmp              [_buffer], 1
jne              waiting

...

; key was pressed at this point
    


I know why your code still doesn't work in PowerShell and Windows Terminal. Thank you for pointing out the improvement. This time I read the Win32 API documentation and example properly.

As screenshot shows, the first event registered is "key released" when running PowerShell and Windows Terminal, while command prompt wait for key press than echo "key pressed". I look at the MS example C code, need to check KEY_EVENT_RECORD for key down, just the key event is not enough.

But I don't know the exact length of INPUT_RECORD, I am going to disassemble the example C code I compiled, using IDA Freeware. Will post here tomorrow or later.


Description: Only CMD receive key pressed initially, PS and Windows Terminal all register key released during startup
Filesize: 33.38 KB
Viewed: 3514 Time(s)

Capture.PNG


Post 01 Apr 2024, 21:44
View user's profile Send private message Reply with quote
MatQuasar



Joined: 25 Oct 2023
Posts: 105
MatQuasar 02 Apr 2024, 07:30
I fixed it!!! Thanks AsmGuru62 for initially pointing to INPUT_RECORD.

I found the length of INPUT_RECORD struct as dissassembled by IDA Freeware:
Code:
00000000 _INPUT_RECORD   struc ; (sizeof=0x14, align=0x4, copyof_152)
00000000                                         ; XREF: _main/r
00000000 EventType       dw ?
00000002                 db ? ; undefined
00000003                 db ? ; undefined
00000004 Event           _INPUT_RECORD::$56C9F45CD5AB058B1B4FD5A9AB9216C4 ?
00000014 _INPUT_RECORD   ends
00000014

00000000 _INPUT_RECORD::$56C9F45CD5AB058B1B4FD5A9AB9216C4 union ; (sizeof=0x10, align=0x4, copyof_153)
00000000                                         ; XREF: _INPUT_RECORD/r
00000000 KeyEvent        KEY_EVENT_RECORD ? 10+2
00000000 MouseEvent      MOUSE_EVENT_RECORD ? 10
00000000 WindowBufferSizeEvent WINDOW_BUFFER_SIZE_RECORD ?  4
00000000 MenuEvent       MENU_EVENT_RECORD ? 4
00000000 FocusEvent      FOCUS_EVENT_RECORD ? 4
00000000 _INPUT_RECORD::$56C9F45CD5AB058B1B4FD5A9AB9216C4 ends
00000000

00000000 KEY_EVENT_RECORD struc ; (sizeof=0x10, align=0x4, copyof_154)
00000000                                         ; XREF: _INPUT_RECORD::$56C9F45CD5AB058B1B4FD5A9AB9216C4/r
00000000 bKeyDown        dd ?
00000004 wRepeatCount    dw ?
00000006 wVirtualKeyCode dw ?
00000008 wVirtualScanCode dw ?
0000000A uChar           _KEY_EVENT_RECORD::$0E79EC4DAC98A4AB202537FA8C3F69FC ?
0000000C dwControlKeyState dd ?
00000010 KEY_EVENT_RECORD ends
00000010

00000000 _KEY_EVENT_RECORD::$0E79EC4DAC98A4AB202537FA8C3F69FC union ; (sizeof=0x2, align=0x2, copyof_156)
00000000                                         ; XREF: KEY_EVENT_RECORD/r
00000000 UnicodeChar     dw ?
00000000 AsciiChar       db ?
00000000 _KEY_EVENT_RECORD::$0E79EC4DAC98A4AB202537FA8C3F69FC ends
00000000

00000000 MOUSE_EVENT_RECORD struc ; (sizeof=0x10, align=0x4, copyof_158)
00000000                                         ; XREF: _INPUT_RECORD::$56C9F45CD5AB058B1B4FD5A9AB9216C4/r
00000000 dwMousePosition COORD ?
00000004 dwButtonState   dd ?
00000008 dwControlKeyState dd ?
0000000C dwEventFlags    dd ?
00000010 MOUSE_EVENT_RECORD ends
00000010

00000000 MENU_EVENT_RECORD struc ; (sizeof=0x4, align=0x4, copyof_165)
00000000                                         ; XREF: _INPUT_RECORD::$56C9F45CD5AB058B1B4FD5A9AB9216C4/r
00000000 dwCommandId     dd ?
00000004 MENU_EVENT_RECORD ends
00000004

00000000 WINDOW_BUFFER_SIZE_RECORD struc ; (sizeof=0x4, align=0x2, copyof_163)
00000000                                         ; XREF: _INPUT_RECORD::$56C9F45CD5AB058B1B4FD5A9AB9216C4/r
00000000 dwSize          COORD ?
00000004 WINDOW_BUFFER_SIZE_RECORD ends
00000004

00000000 FOCUS_EVENT_RECORD struc ; (sizeof=0x4, align=0x4, copyof_168)
00000000                                         ; XREF: _INPUT_RECORD::$56C9F45CD5AB058B1B4FD5A9AB9216C4/r
00000000 bSetFocus       dd ?
00000004 FOCUS_EVENT_RECORD ends
00000004    


I updated the code at post #1 accordingly, now it works on all interfaces. I just need to run additional check for keydown after initial check for keyevent.


Description: The newly updated code now works properly in Windows Terminal (and PowerShell).
Filesize: 11.23 KB
Viewed: 3442 Time(s)

Capture.PNG




Last edited by MatQuasar on 02 Apr 2024, 11:01; edited 1 time in total
Post 02 Apr 2024, 07:30
View user's profile Send private message Reply with quote
MatQuasar



Joined: 25 Oct 2023
Posts: 105
MatQuasar 02 Apr 2024, 10:37
But I don't understand, there is padded bytes automatically added by compiler.

For example:
Code:
typedef struct _INPUT_RECORD {
  WORD  EventType;
  union {
    KEY_EVENT_RECORD          KeyEvent;
    MOUSE_EVENT_RECORD        MouseEvent;
    WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
    MENU_EVENT_RECORD         MenuEvent;
    FOCUS_EVENT_RECORD        FocusEvent;
  } Event;
} INPUT_RECORD;    

....is become:
Code:
00000000 _INPUT_RECORD   struc ; (sizeof=0x14, align=0x4, copyof_152)
00000000                                         ; XREF: _main/r
00000000 EventType       dw ?
00000002                 db ? ; undefined
00000003                 db ? ; undefined
00000004 Event           _INPUT_RECORD::$56C9F45CD5AB058B1B4FD5A9AB9216C4 ?
00000014 _INPUT_RECORD   ends    


Two undefined "db ? " added. Is it because needed by alignment , which is 32-bit?

And what about this:
Code:
00000000 _KEY_EVENT_RECORD::$0E79EC4DAC98A4AB202537FA8C3F69FC union ; (sizeof=0x2, align=0x2, copyof_156)
00000000                                         ; XREF: KEY_EVENT_RECORD/r
00000000 UnicodeChar     dw ?
00000000 AsciiChar       db ?
00000000 _KEY_EVENT_RECORD::$0E79EC4DAC98A4AB202537FA8C3F69FC ends    


Is the size of struct 3 bytes or 4 bytes?


Description: Unlike CMD, PowerShell PAUSE command is actually "Press Enter to continue".
Filesize: 12.23 KB
Viewed: 3386 Time(s)

Capture.PNG




Last edited by MatQuasar on 03 Apr 2024, 14:45; edited 1 time in total
Post 02 Apr 2024, 10:37
View user's profile Send private message Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  
Goto page 1, 2  Next

< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2024, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.