In order to celebrate the creation of this technique, I modified my
ExtractBinaryFile subroutine to use the pure Batch method to write the binary file instead of the original JScript code. I like ExtractBinaryFile subroutine because you just take the hexadecimal representation of the binary file and insert it in the Batch file surrounded by a pair of delimiting lines, and it also compress groups of binary zeros.
I also wrote a new auxiliary program for this occasion; this program, called GetInput.exe, allows to get input from both keyboard and mouse and return it via errorlevel. A key press is returned as a positive value and a mouse click as a negative value that indicate the mouse cursor position in the screen using this formula: errorlevel = -(line<<16 + column). An interesting point of this program (besides the fact that it allows to use the mouse as input device in Batch files) is that it can detect
any keystroke, that is, besides all extended keys it also report keys that usually are not pressed alone, like Shift, Ctrl, Alt, Caps-Lock, etc. and even a Ctrl-C combination without cancel the Batch file; you may consult
Virtual-Key Codes for a complete description of the values returned this way. Just for a change, I included the assembly source code of this program below.
It is interesting to note that although a .cab compressed file is much lesser than the original file, in the particular case of small .exe files they contain many groups of binary zeros that are compressed/expanded by ExtractBinaryFile subroutine, so the makecab-compression/expand-expantion steps are a waste in this case. For example, GetInput.exe file is 1536 bytes long and GetInput.exe.cab is just 552, but GetInput.exe.hex is 1080 bytes and GetInput.exe.cab.hex is 1069 bytes, about 1% lesser!
EDIT - 2014/01/31: I fixed a detail in GetInput.exe auxiliary program: it now cancel Quick Edit mode and preserve the original mode at exit; both the hexadecimal version and the source code at end include this change now.
EDIT - 2014/02/01: I modified GetInput.exe program to also report right-button mouse clicks and modified the Batch code in order to achieve a more precise identification of the key pressed.
EDIT - 2014/02/04: I modified GetInput.exe program to also report Alt-key and Shift-extended key combinations and to omit non-useful keys, like Shift-, Ctrl- or Alt- keys alone, or CapsLock. Full details in both Batch and assembler source code.
Code: Select all
@echo off
rem Example of GetInput.exe keyboard/mouse input auxiliary program
rem Antonio Perez Ayala
if not exist GetInput.exe call :ExtractBinaryFile GetInput.exe.cab
setlocal DisableDelayedExpansion
set Ascii= !"#$%%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
setlocal EnableDelayedExpansion
for %%a in ("7=Bell" "8=BackSpc" "9=Tab" "10=LineFeed" "13=Enter" "26=Sub" "27=Esc") do (
for /F "tokens=1,2 delims==" %%b in (%%a) do set "control[%%b]=(%%c)"
)
for %%a in ("33=PageUp" "34=PageDown" "35=End" "36=Home" "37=LeftArrow" "38=UpArrow" "39=RightArrow" "40=DownArrow"
"45=Ins" "46=Del"
"112=F1" "113=F2" "114=F3" "115=F4" "116=F5" "117=F6" "118=F7" "119=F8" "120=F9" "121=F10" "122=F11" "123=F12"
) do (
for /F "tokens=1,2 delims==" %%b in (%%a) do set "extended[%%b]=%%c"
)
cls
echo To end, click in the "X": [X]
echo/
echo Press a key (ESC to end) or click a mouse button
echo/
:nextInput
GetInput
set /A "input=%errorlevel%, ctrl=input+32, asc=input-32, alt=input-192, ext=input-256&0xFF"
if %input% gtr 0 (
rem Key pressed
set input= %input%
set input=!input:~-3!
if %input% lss 32 (
echo !input!: Ctrl-!Ascii:~%ctrl%,1! !control[%input%]!
) else if %input% lss 192 (
echo !input!: Ascii: !Ascii:~%asc%,1!
) else if %input% lss 256 (
echo !input!: Alt-!Ascii:~%alt%,1!
) else (
if %input% lss 512 (
set extnd= !extended[%ext%]!
) else (
set extnd= Shift-!extended[%ext%]!
)
echo !input!: !extnd:~-16!
)
) else ( rem %input% leq 0
rem Mouse button clicked
set /A "input=-input, row=input >> 16, col=input & 0xFFFF"
if !col! lss 32768 (
echo LEFT @ !row!,!col!
) else (
set /A col-=32768
echo @ !row!,!col! RIGHT
)
)
if %input% neq 27 goto nextInput
goto :EOF
rem Extract Binary File from hexadecimal digits placed in a "resource" in this .bat file
:ExtractBinaryFile filename.ext[.cab]
setlocal EnableDelayedExpansion
ECHO Creating %1, please wait...
set "start="
set "end="
for /F "tokens=1,3 delims=:=>" %%a in ('findstr /N /B "</*resource" "%~F0"') do (
if not defined start (
if "%%~b" equ "%~1" set start=%%a
) else if not defined end set end=%%a
)
(for /F "skip=%start% tokens=1* delims=:" %%a in ('findstr /N "^" "%~F0"') do (
if "%%a" == "%end%" goto decodeHexFile
echo %%b
)) > "%~1.hex"
:decodeHexFile
rem Modified code based on :genchr subroutine
type nul > t.tmp
(for /F "usebackq" %%a in ("%~1.hex") do (
set input=%%a
set i=0
for /L %%I in (0,2,120) do for %%i in (!i!) do if "!input:~%%i,1!" neq "" (
set hex=!input:~%%i,2!
set /A i+=2
if "!hex:~0,1!" neq "[" (
set /A chr=0x!hex!
if not exist !chr!.chr call :genchr !chr!
type !chr!.chr
) else (
for /L %%J in (1,1,5) do for %%i in (!i!) do if "!input:~%%i,1!" neq "]" (
set "hex=!hex!!input:~%%i,1!"
set /A i+=1
)
if not exist 0.chr call :genchr 0
for /L %%J in (1,1,!hex:~1!) do type 0.chr
set /A i+=1
)
)
)) > "%~1"
del *.chr
del t.tmp temp.tmp
del "%~1.hex"
rem Expand created file if extension is .cab
set "filename=%~1"
if /I "%filename:~-4%" equ ".cab" (
expand "%filename%" "%filename:~0,-4%" > NUL
del "%filename%"
)
exit /B
:genchr
REM This code creates one single byte. Parameter: int
REM Teamwork of carlos, penpen, aGerman, dbenham
REM Tested under Win2000, XP, Win7, Win8
set "options=/d compress=off /d reserveperdatablocksize=26"
if %~1 neq 26 (
makecab %options% /d reserveperfoldersize=%~1 t.tmp %~1.chr > nul
type %~1.chr | ( (for /l %%N in (1,1,38) do pause)>nul & findstr "^" > temp.tmp )
>nul copy /y temp.tmp /a %~1.chr /b
) else (
copy /y nul + nul /a 26.chr /a >nul
)
exit /B
<resource id="GetInput.exe.cab">
4d534346[4]8302[6]2c[7]0301010001[7]49[3]010001[2]06[8]4444b8962000476574496e7075742e6578650077a0cada32020006434bf38d9a
c0c0ccc0c0c002c4ffff3330ec6080000706c2600310f3c9efe263d8c279567107a3cf59c5908ccc628582a2fcf4a2c45c85e4c4bcbcfc1285a45485
a2d23c85cc3c0517ff6085dcfc94543d5e5e2e15a819b1a97f4f48b24c9e0dc3d3a51b660b02e9a72a8db325807450667206481c6667802b03830f23
13c3ecee8f4130b1070cfc8cdc8cac3c0c4c400e135450008a15a0be1180c8b140a561348301420f334ca300320da7c04002689e0611614332009a2b
81475aaf24b5a20448f33032c0fd02f72bc28804bda294c4924406863d5001b01a1654750e48d11bdafda6f9c883ac6f2fde02399dae7f7a5d7f04fc
2ffdf3e20690dbedfa837702906e7eb00f2c760dc8ee757d1090c5d8ebfa042cb20f2892d65cfb84b134b3b9f60543c943fe6dae9fd2b80f94c87f77
fd02b2b9a42aadf947557d491aeb0286345b06c69aacb4e6170aaf53d2ba5ddf0165146bd603498d3a0920a95bb31848ead5f100c9829ae940b2ba7e
621a2b0323d02890874bf4811ca6d79a20fb98f85b8bffffffffddf50d28cef85bd2809c6ed717403e28784a991bce347cbfd17de37fe90f902be580
62c12f40befdaf2aa0e0002419c0240b98e400933c0ac4a4f8e107d28069a402883b80780e10472840d3880242cd3a05583e225eff6c06d78acc9280
a2fce4d4e262864f0ceea925cef979c5f939a9bec0128081218b1128125c92e29198979203e49b3005a526a6409578e61594963832e43105a3e9ca4e
2dca4bcd3136d24bc9c91991d145750000
</resource>
This is GetInput.asm:
Code: Select all
; GetInput
;
; Get console input from both keyboard and mouse
;
; Antonio Perez Ayala - Version 1.0 - Jan/30/2014
; - Version 1.1 - Jan/31/2014 - Preserve console mode, disable quick edit
; - Version 1.2 - Feb/01/2014 - Get right button mouse clicks
; - Version 1.3 - Feb/04/2014 - Get Alt-keys and Shift-extended keys
include \masm32\include\masm32rt.inc
Main PROTO
GetStdHandle PROTO
GetConsoleMode PROTO
SetConsoleMode PROTO ;http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033
(v=vs.85).aspx
ReadConsoleInput PROTO ;http://msdn.microsoft.com/en-
us/library/windows/desktop/ms684961%28v=vs.85%29.aspx
.code
Main PROC
LOCAL hInput :DWORD
LOCAL lpMode :DWORD
LOCAL lpBuffer :INPUT_RECORD
LOCAL lpEvents :DWORD
invoke GetStdHandle, STD_INPUT_HANDLE ;EAX = console input handle
mov hInput, eax ;store it
;
invoke GetConsoleMode, hInput, ADDR lpMode ;get current console mode
;
mov eax, lpMode ;EAX = current mode
or eax, ENABLE_MOUSE_INPUT + ENABLE_EXTENDED_FLAGS ;enable mouse events + value...
; ;- required to disable quick edit
and eax, NOT (ENABLE_QUICK_EDIT_MODE OR ENABLE_PROCESSED_INPUT) ;disable quick edit and Ctrl-C
invoke SetConsoleMode, hInput, eax ;change console mode
;
get_event: ;get the next input event
invoke ReadConsoleInput, hInput, ADDR lpBuffer, 1, ADDR lpEvents
;
cmp lpBuffer.EventType, KEY_EVENT ;is a key event?
jne SHORT check_mouse ;no: continue
;
cmp lpBuffer.KeyEvent.bKeyDown,FALSE;the key was pressed?
je SHORT get_event ;no: ignore key releases
;
movzx eax, lpBuffer.KeyEvent.AsciiChar;get the key Ascii code
or ax, ax ;is zero?
jz SHORT virtual_key ;yes: is extended key
;
test lpBuffer.KeyEvent.dwControlKeyState,LEFT_ALT_PRESSED ;Alt- pressed?
jz SHORT end_main ;no: continue
;
cmp ax, 'z' ;is digit/letter?
jg SHORT end_main ;no: continue
;
add ax, 160 ;add 160 as Alt-key signature (Alt-A=225)
cmp ax, 256 ;was lowcase letter?
jl SHORT end_main ;no: continue
sub ax, 'a'-'A' ;else: convert to upcase
jmp SHORT end_main ;and return it
;
virtual_key:
mov ax, lpBuffer.KeyEvent.wVirtualKeyCode ;get Virtual Key Code
;
cmp ax, VK_PRIOR ;less than 1st useful block?
jl SHORT get_event ;yes: ignore it
cmp ax, VK_DOWN ;in 1st useful block?
jle SHORT @F ;yes continue
;
cmp ax, VK_INSERT ;less than 2nd useful block?
jl SHORT get_event ;yes: ignore it
cmp ax, VK_DELETE ;in 2nd useful block?
jle SHORT @F ;yes: continue
;
cmp ax, VK_F1 ;less than 3rd useful block?
jl SHORT get_event ;yes: ignore it
cmp ax, VK_F12 ;greater than 3rd useful block?
jg SHORT get_event ;yes: ignore it
@@:
add ax, 256 ;add 256 as extended-key signature
;
test lpBuffer.KeyEvent.dwControlKeyState,SHIFT_PRESSED ;Shift- pressed?
jz SHORT end_main ;no: continue
add ax, 512 ;else: add 512 as Shift- signature
jmp SHORT end_main ;and return it
;
check_mouse:
cmp lpBuffer.EventType, MOUSE_EVENT ;is a mouse event?
jne get_event ;no: ignore event
; ;left or right button pressed?
test lpBuffer.MouseEvent.dwButtonState, FROM_LEFT_1ST_BUTTON_PRESSED OR RIGHTMOST_BUTTON_PRESSED
jz get_event ;no: ignore button releases
;
mov eax, lpBuffer.MouseEvent.dwMousePosition;get click position as row<<16 + col
test lpBuffer.MouseEvent.dwButtonState, FROM_LEFT_1ST_BUTTON_PRESSED ;left button?
jnz SHORT neg_pos ;yes: continue
or ah, 80H ;else: add 32768 to col for right button
neg_pos:
neg eax ;change result sign as Mouse signature
;
end_main:
mov ebx, eax ;pass result value to EBX
invoke SetConsoleMode, hInput, lpMode ;recover original console mode
invoke ExitProcess, ebx ;and return result value
Main ENDP
end Main
Antonio