Mouse Events are detected far less in a GOTO loop vs FOR /L loop
Posted: 27 Feb 2024 22:39
Hey guys,
Recently I have been making a cmd extension that takes mouse and keyboard input implicitly into the variable CMDCMDLINE. I got inspiration when I saw this post viewtopic.php?f=3&t=4312 about CMDCMDLINE. It works well, except, when used in a GOTO loop, many mouse events seem missed / lag, but not the keyboard events. Here the exe code
It works by getting the RTL_USE_PROCESS_PARAMETER of a CMD process it launches, and continually updates the Commandline parameter with the keyboard / mouse input. And since the CMDCMDLINE variable reads directly from this field, the variable is updated by itself implicitly. Here is the working Batch file I use to test
However, when I replace the above loop with GOTO, the mouse events become laggy and unresponsive, but not the keyboard events. Like so
I have attached the EXE file (64 bit) here, along with the working / not working test batch files. I was wondering if you guys had any idea what the difference between the two loops are, and why it would cause such a difference? If I take out the Sleep in the loop, it works, but CPU usage skyrockets. Not sure why that is. Perhaps the console mode is being reset? Thanks!
Recently I have been making a cmd extension that takes mouse and keyboard input implicitly into the variable CMDCMDLINE. I got inspiration when I saw this post viewtopic.php?f=3&t=4312 about CMDCMDLINE. It works well, except, when used in a GOTO loop, many mouse events seem missed / lag, but not the keyboard events. Here the exe code
Code: Select all
#include <stdio.h>
#include <stdlib.h>
#include "ntdll/ntdll.h"
#include <windows.h>
// compile : doskey ds=cl radish.c "ntdll/ntdll_x64.lib" ^& test.bat
// ntdll lib : https://github.com/x64dbg/ScyllaHide/blob/master/3rdparty/ntdll/ntdll.h
// debug : printf("Radish Failed with Error : %d", GetLastError());\
// Bugs :
// High CPU usage -> Sleep fixes it but will lag Mouse Events in GOTO loop
// Unfocus creates sticky keys FIXED
// Scroll Wheel doesn't end BY DESIGN
#define IF_ERR_EXIT(status) do {\
if (!(status)) {\
exit(EXIT_FAILURE);\
}\
} while(0)
#define KEY_SET_MAX (0xFE + 1)
#define KEY_BUF_MAX 49
#define NULL_TERM_LEN 1
#define SPACE_LEN 1
#define QUOTE_PAIR 2
#define ARG_NUM 3
#define INPUT_NUM 50
#define OUTPUT_BUFFER "%comspec%\\##########################\\..\\..\\cmd.exe /k " // 123.123.12.12.-123-123-123-123-123-123-123-123-123-123-123-123-
void set_con_mode(HANDLE std_in);
RTL_USER_PROCESS_PARAMETERS get_rtl_param(HANDLE con, PROCESS_BASIC_INFORMATION pbi);
wchar_t* get_buffer(HANDLE con, PROCESS_BASIC_INFORMATION pbi, size_t* max_len);
int
main(int argc, char* argv[]) {
IF_ERR_EXIT(argc == ARG_NUM);
char* com = calloc(strlen(OUTPUT_BUFFER) + strlen(argv[1]) + QUOTE_PAIR + SPACE_LEN + strlen(argv[2]) + NULL_TERM_LEN, sizeof(char));
IF_ERR_EXIT(com);
sprintf(com, "%s\"%s\" %s", OUTPUT_BUFFER, argv[1], argv[2]);
STARTUPINFO si = {
.cb = sizeof(si)
};
PROCESS_INFORMATION pi = {0};
IF_ERR_EXIT(
CreateProcess(
NULL,
com,
NULL,
NULL,
FALSE,
CREATE_NEW_PROCESS_GROUP,
NULL,
NULL,
&si,
&pi
)
);
free(com);
Sleep(200); // make sure process starts first BEFORE querying info
SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS);
PROCESS_BASIC_INFORMATION pbi = {0};
NtQueryInformationProcess(pi.hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
size_t max_len;
wchar_t* buffer = get_buffer(pi.hProcess, pbi, &max_len);
RTL_USER_PROCESS_PARAMETERS rtl = get_rtl_param(pi.hProcess, pbi);
IF_ERR_EXIT(WriteProcessMemory(pi.hProcess, rtl.CommandLine.Buffer, buffer, sizeof(wchar_t) * max_len , NULL));
HANDLE std_in = GetStdHandle(STD_INPUT_HANDLE);
HANDLE std_out = GetStdHandle(STD_OUTPUT_HANDLE);
set_con_mode(std_in);
SHORT mouse_x = 0;
SHORT mouse_y = 0;
DWORD mouse_b = 0;
BOOL focus = TRUE;
BOOL key_set[KEY_SET_MAX] = {0};
char key_buf[KEY_BUF_MAX + NULL_TERM_LEN] = {0};
key_buf[0] = '-';
int written = 0;
INPUT_RECORD rec[INPUT_NUM] = {0};
CreateFileA("RADISH_READY", 0, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
while (TRUE) {
set_con_mode(std_in);
DWORD num_event = 0;
GetNumberOfConsoleInputEvents(std_in, &num_event);
if (num_event) {
DWORD num_read;
ReadConsoleInputW(std_in, rec, INPUT_NUM, &num_read);
for (int i = 0; i < num_read; i++) {
if (rec[i].EventType == MOUSE_EVENT) {
mouse_x = rec[i].Event.MouseEvent.dwMousePosition.X;
mouse_y = rec[i].Event.MouseEvent.dwMousePosition.Y;
CONSOLE_SCREEN_BUFFER_INFO con_info = {0};
GetConsoleScreenBufferInfo(std_out, &con_info);
SHORT h = con_info.srWindow.Bottom - con_info.srWindow.Top;
if (con_info.dwCursorPosition.Y >= h) {
mouse_y -= (con_info.dwCursorPosition.Y - h);
}
if (rec[i].Event.MouseEvent.dwEventFlags == MOUSE_WHEELED) {
mouse_b = (short) HIWORD(rec[i].Event.MouseEvent.dwButtonState) / 120 * 6; // 6 or -6 for up / down scroll
} else {
mouse_b = rec[i].Event.MouseEvent.dwButtonState;
}
} else if (rec[i].EventType == KEY_EVENT) {
key_set[rec[i].Event.KeyEvent.wVirtualKeyCode] = rec[i].Event.KeyEvent.bKeyDown;
} else if (rec[i].EventType == FOCUS_EVENT) {
focus = rec[i].Event.FocusEvent.bSetFocus;
if (!focus) {
for (int i = 0; i < KEY_SET_MAX; i++) {
key_set[i] = FALSE;
}
}
}
}
}
char* key_write = key_buf + 1; // 1 = '-'
memset(key_write, 0, KEY_BUF_MAX);
for (int i = 0; i < KEY_SET_MAX; i++) {
if (key_set[i] == TRUE) {
key_write += sprintf(key_write, "%d-", i);
if (key_write - key_buf > KEY_BUF_MAX) {
break;
}
}
}
memset(buffer, 0, sizeof(wchar_t) * written);
if ((key_write - key_buf) == 1) {
written = swprintf(buffer, max_len, L"%d.%d.%d", mouse_x, mouse_y, mouse_b);
} else {
written = swprintf(buffer, max_len, L"%d.%d.%d.%hs", mouse_x, mouse_y, mouse_b, key_buf);
}
if (written > 0) {
RTL_USER_PROCESS_PARAMETERS rtl = get_rtl_param(pi.hProcess, pbi);
IF_ERR_EXIT(WriteProcessMemory(pi.hProcess, rtl.CommandLine.Buffer, buffer, sizeof(wchar_t) * (written + NULL_TERM_LEN), NULL));
}
Sleep(30);
}
free(buffer);
return EXIT_SUCCESS;
}
void
set_con_mode(HANDLE std_in) {
DWORD mode = 0;
GetConsoleMode(std_in, &mode);
SetConsoleMode(std_in, ENABLE_MOUSE_INPUT | ENABLE_EXTENDED_FLAGS | (mode & ~ENABLE_QUICK_EDIT_MODE));
}
wchar_t*
get_buffer(HANDLE con, PROCESS_BASIC_INFORMATION pbi, size_t* max_len) {
RTL_USER_PROCESS_PARAMETERS rtl = get_rtl_param(con, pbi);
*max_len = rtl.CommandLine.Length / sizeof(wchar_t);
wchar_t* buffer = calloc(*max_len, sizeof(wchar_t));
IF_ERR_EXIT(buffer);
return buffer;
}
RTL_USER_PROCESS_PARAMETERS
get_rtl_param(HANDLE con, PROCESS_BASIC_INFORMATION pbi) {
PEB peb = {0};
ReadProcessMemory(con, pbi.PebBaseAddress, &peb, sizeof(peb), NULL);
RTL_USER_PROCESS_PARAMETERS rtl = {0};
IF_ERR_EXIT(ReadProcessMemory(con, peb.ProcessParameters, &rtl, sizeof(rtl), NULL));
return rtl;
}
Code: Select all
@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
(CHCP 65001)>NUL
IF not "%~1" == "" (
GOTO :%~1
)
FOR /F %%A in ('ECHO PROMPT $E^| CMD') DO SET "ESC=%%A"
CALL :RADISH GAME
ECHO Finished
EXIT /B
:GAME
CLS
CALL :RADISH_WAIT
FOR /L %%Q in () DO (
ECHO %ESC%[1;1HPress P to quit%ESC%[2;1H%ESC%[2KKEYS : [!CMDCMDLINE!]
FOR /F "tokens=1-4 delims=." %%A in ("!CMDCMDLINE!") DO (
SET "keys=%%D "
IF not "!keys!" == "!keys:-80-=!" (
%RADISH_END%
)
)
)
:RADISH
IF exist "RADISH_READY" (DEL /F /Q "RADISH_READY")
SET "RADISH_END=(TASKKILL /F /IM "RADISH.exe")>NUL & EXIT"
RADISH "%~nx0" %~1
GOTO :EOF
:RADISH_WAIT
IF exist "RADISH_READY" (DEL /F /Q "RADISH_READY" & GOTO :EOF)
(TIMEOUT /T 1)>NUL
GOTO :RADISH_WAIT
Code: Select all
@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
(CHCP 65001)>NUL
IF not "%~1" == "" (
GOTO :%~1
)
FOR /F %%A in ('ECHO PROMPT $E^| CMD') DO SET "ESC=%%A"
CALL :RADISH GAME
ECHO Finished
EXIT /B
:GAME
CLS
CALL :RADISH_WAIT
:G
ECHO %ESC%[1;1HPress P to quit%ESC%[2;1H%ESC%[2KKEYS : [!CMDCMDLINE!]
FOR /F "tokens=1-4 delims=." %%A in ("!CMDCMDLINE!") DO (
SET "keys=%%D "
IF not "!keys!" == "!keys:-80-=!" (
%RADISH_END%
)
)
GOTO :G
:RADISH
IF exist "RADISH_READY" (DEL /F /Q "RADISH_READY")
SET "RADISH_END=(TASKKILL /F /IM "RADISH.exe")>NUL & EXIT"
RADISH "%~nx0" %~1
GOTO :EOF
:RADISH_WAIT
IF exist "RADISH_READY" (DEL /F /Q "RADISH_READY" & GOTO :EOF)
(TIMEOUT /T 1)>NUL
GOTO :RADISH_WAIT