ansicon/ANSI.c

1375 lines
39 KiB
C

/*
ANSI.c - ANSI escape sequence console driver.
Jason Hood, 21 & 22 October, 2005.
Derived from ANSI.xs by Jean-Louis Morel, from his Perl package
Win32::Console::ANSI. I removed the codepage conversion ("\e(") and added
WriteConsole hooking.
v1.01, 11 & 12 March, 2006:
disable when console has disabled processed output;
\e[5m (blink) is the same as \e[4m (underline);
do not conceal control characters (0 to 31);
\e[m will restore original color.
v1.10, 22 February, 2009:
fix MyWriteConsoleW for strings longer than the buffer;
initialise attributes to current;
hook into child processes.
v1.11, 28 February, 2009:
fix hooking into child processes (only do console executables).
v1.12, 9 March, 2009:
really fix hooking (I didn't realise MinGW didn't generate relocations).
v1.13, 21 & 27 March, 2009:
alternate injection method, to work with DEP;
use Unicode and the current output code page (not OEMCP).
v1.14, 3 April, 2009:
fix test for empty import section.
v1.15, 17 May, 2009:
properly update lpNumberOfCharsWritten in MyWriteConsoleA.
v1.20, 26 & 29 May, 17 to 21 June, 2009:
create an ANSICON environment variable;
hook GetEnvironmentVariable to create ANSICON dynamically;
use another injection method.
v1.22, 5 October, 2009:
hook LoadLibrary to intercept the newly loaded functions.
v1.23, 11 November, 2009:
unload gracefully;
conceal characters by making foreground same as background;
reverse the bold/underline attributes, too.
v1.25, 15, 20 & 21 July, 2010:
hook LoadLibraryEx (now cscript works);
Win7 support.
v1.30, 3 August to 7 September, 2010:
x64 support.
v1.31, 13 & 19 November, 2010:
fix multibyte conversion problems.
v1.32, 4 to 22 December, 2010:
test for lpNumberOfCharsWritten/lpNumberOfBytesWritten being NULL;
recognise DSR and xterm window title;
ignore sequences starting with \e[? & \e[>;
close the handles opened by CreateProcess.
25 February, 2011:
hook GetProcAddress, addresses issues with .NET (work with PowerShell).
*/
#include "ansicon.h"
#include <tlhelp32.h>
#define isdigit(c) ('0' <= (c) && (c) <= '9')
// ========== Global variables and constants
// Macro for adding pointers/DWORDs together without C arithmetic interfering
#define MakePtr( cast, ptr, addValue ) (cast)((DWORD_PTR)(ptr)+(DWORD)(addValue))
const char APIKernel[] = "kernel32.dll";
const char APIKernelBase[] = "kernelbase.dll";
const char APIConsole[] = "API-MS-Win-Core-Console-L1-1-0.dll";
const char APIProcessThreads[] = "API-MS-Win-Core-ProcessThreads-L1-1-0.dll";
const char APIProcessEnvironment[] = "API-MS-Win-Core-ProcessEnvironment-L1-1-0.dll";
const char APILibraryLoader[] = "API-MS-Win-Core-LibraryLoader-L1-1-0.dll";
const char APIFile[] = "API-MS-Win-Core-File-L1-1-0.dll";
PCSTR APIs[] =
{
APIKernel,
APIConsole,
APIProcessThreads,
APIProcessEnvironment,
APILibraryLoader,
APIFile,
NULL
};
HMODULE hKernel; // Kernel32 module handle
HINSTANCE hDllInstance; // Dll instance handle
HANDLE hConOut; // handle to CONOUT$
#define ESC '\x1B' // ESCape character
#define BEL '\x07'
#define MAX_ARG 16 // max number of args in an escape sequence
int state; // automata state
TCHAR prefix; // escape sequence prefix ( '[', ']' or '(' );
TCHAR prefix2; // secondary prefix ( '?' or '>' );
TCHAR suffix; // escape sequence suffix
int es_argc; // escape sequence args count
int es_argv[MAX_ARG]; // escape sequence args
TCHAR Pt_arg[MAX_PATH*2]; // text parameter for Operating System Command
int Pt_len;
// color constants
#define FOREGROUND_BLACK 0
#define FOREGROUND_WHITE FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE
#define BACKGROUND_BLACK 0
#define BACKGROUND_WHITE BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE
WORD foregroundcolor[8] =
{
FOREGROUND_BLACK, // black foreground
FOREGROUND_RED, // red foreground
FOREGROUND_GREEN, // green foreground
FOREGROUND_RED | FOREGROUND_GREEN, // yellow foreground
FOREGROUND_BLUE, // blue foreground
FOREGROUND_BLUE | FOREGROUND_RED, // magenta foreground
FOREGROUND_BLUE | FOREGROUND_GREEN, // cyan foreground
FOREGROUND_WHITE // white foreground
};
WORD backgroundcolor[8] =
{
BACKGROUND_BLACK, // black background
BACKGROUND_RED, // red background
BACKGROUND_GREEN, // green background
BACKGROUND_RED | BACKGROUND_GREEN, // yellow background
BACKGROUND_BLUE, // blue background
BACKGROUND_BLUE | BACKGROUND_RED, // magenta background
BACKGROUND_BLUE | BACKGROUND_GREEN, // cyan background
BACKGROUND_WHITE, // white background
};
// screen attributes
WORD org_fg, org_bg, org_bold, org_ul; // original attributes
WORD foreground;
WORD background;
WORD bold;
WORD underline;
WORD rvideo;
WORD concealed;
// saved cursor position
COORD SavePos;
// ========== Hooking API functions
//
// References about API hooking (and dll injection):
// - Matt Pietrek ~ Windows 95 System Programming Secrets.
// - Jeffrey Richter ~ Programming Applications for Microsoft Windows 4th ed.
typedef struct
{
PCSTR lib;
PSTR name;
PROC newfunc;
PROC oldfunc;
PROC apifunc;
} HookFn, *PHookFn;
HookFn Hooks[];
//-----------------------------------------------------------------------------
// HookAPIOneMod
// Substitute a new function in the Import Address Table (IAT) of the
// specified module.
// Return FALSE on error and TRUE on success.
//-----------------------------------------------------------------------------
BOOL HookAPIOneMod(
HMODULE hFromModule, // Handle of the module to intercept calls from
PHookFn Hooks, // Functions to replace
BOOL restore // Restore the original functions
)
{
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNTHeader;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
PIMAGE_THUNK_DATA pThunk;
PHookFn hook;
// Tests to make sure we're looking at a module image (the 'MZ' header)
pDosHeader = (PIMAGE_DOS_HEADER)hFromModule;
if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
DEBUGSTR( L"error: %S(%d)", __FILE__, __LINE__ );
return FALSE;
}
// The MZ header has a pointer to the PE header
pNTHeader = MakePtr( PIMAGE_NT_HEADERS, pDosHeader, pDosHeader->e_lfanew );
// One more test to make sure we're looking at a "PE" image
if (pNTHeader->Signature != IMAGE_NT_SIGNATURE)
{
DEBUGSTR( L"error: %S(%d)", __FILE__, __LINE__ );
return FALSE;
}
// We now have a valid pointer to the module's PE header.
// Get a pointer to its imports section.
pImportDesc = MakePtr( PIMAGE_IMPORT_DESCRIPTOR,
pDosHeader,
pNTHeader->OptionalHeader.
DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].
VirtualAddress );
// Bail out if the RVA of the imports section is 0 (it doesn't exist)
if (pImportDesc == (PIMAGE_IMPORT_DESCRIPTOR)pDosHeader)
{
return TRUE;
}
// Iterate through the array of imported module descriptors, looking
// for the module whose name matches the pszFunctionModule parameter.
for (; pImportDesc->Name; pImportDesc++)
{
PCSTR* lib;
PSTR pszModName = MakePtr( PSTR, pDosHeader, pImportDesc->Name );
for (lib = APIs; *lib; ++lib)
if (_stricmp( pszModName, *lib ) == 0)
break;
if (*lib == NULL)
continue;
// Get a pointer to the found module's import address table (IAT).
pThunk = MakePtr( PIMAGE_THUNK_DATA, pDosHeader, pImportDesc->FirstThunk );
// Blast through the table of import addresses, looking for the ones
// that match the original addresses.
while (pThunk->u1.Function)
{
for (hook = Hooks; hook->name; ++hook)
{
PROC patch = 0;
if (restore)
{
if ((PROC)pThunk->u1.Function == hook->newfunc)
patch = (lib == APIs) ? hook->oldfunc : hook->apifunc;
}
else if ((PROC)pThunk->u1.Function == hook->oldfunc ||
(PROC)pThunk->u1.Function == hook->apifunc)
{
patch = hook->newfunc;
}
if (patch)
{
DWORD flOldProtect, flNewProtect, flDummy;
MEMORY_BASIC_INFORMATION mbi;
DEBUGSTR( L" %S", hook->name );
// Get the current protection attributes.
VirtualQuery( &pThunk->u1.Function, &mbi, sizeof(mbi) );
// Take the access protection flags.
flNewProtect = mbi.Protect;
// Remove ReadOnly and ExecuteRead flags.
flNewProtect &= ~(PAGE_READONLY | PAGE_EXECUTE_READ);
// Add on ReadWrite flag
flNewProtect |= (PAGE_READWRITE);
// Change the access protection on the region of committed pages in the
// virtual address space of the current process.
VirtualProtect( &pThunk->u1.Function, sizeof(PVOID),
flNewProtect, &flOldProtect );
// Overwrite the original address with the address of the new function.
if (!WriteProcessMemory( GetCurrentProcess(),
&pThunk->u1.Function,
&patch, sizeof(patch), NULL ))
{
DEBUGSTR( L"error: %S(%d)", __FILE__, __LINE__ );
return FALSE;
}
// Put the page attributes back the way they were.
VirtualProtect( &pThunk->u1.Function, sizeof(PVOID),
flOldProtect, &flDummy );
}
}
pThunk++; // Advance to next imported function address
}
}
return TRUE; // Function not found
}
//-----------------------------------------------------------------------------
// HookAPIAllMod
// Substitute a new function in the Import Address Table (IAT) of all
// the modules in the current process.
// Return FALSE on error and TRUE on success.
//-----------------------------------------------------------------------------
BOOL HookAPIAllMod( PHookFn Hooks, BOOL restore )
{
HANDLE hModuleSnap;
MODULEENTRY32 me;
BOOL fOk;
// Take a snapshot of all modules in the current process.
hModuleSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE,
GetCurrentProcessId() );
if (hModuleSnap == INVALID_HANDLE_VALUE)
{
DEBUGSTR( L"error: %S(%d)", __FILE__, __LINE__ );
return FALSE;
}
// Fill the size of the structure before using it.
me.dwSize = sizeof(MODULEENTRY32);
// Walk the module list of the modules.
for (fOk = Module32First( hModuleSnap, &me ); fOk;
fOk = Module32Next( hModuleSnap, &me ))
{
// We don't hook functions in our own module.
if (me.hModule != hDllInstance && me.hModule != hKernel)
{
DEBUGSTR( (restore) ? L"Unhooking from %s" : L"Hooking in %s",
me.szModule );
// Hook this function in this module.
if (!HookAPIOneMod( me.hModule, Hooks, restore ))
{
CloseHandle( hModuleSnap );
return FALSE;
}
}
}
CloseHandle( hModuleSnap );
return TRUE;
}
// ========== Print Buffer functions
int nCharInBuffer;
LPCTSTR ChBuffer;
//-----------------------------------------------------------------------------
// FlushBuffer()
// Writes the buffer to the console and empties it.
//-----------------------------------------------------------------------------
void FlushBuffer( void )
{
DWORD nWritten;
if (nCharInBuffer <= 0) return;
WriteConsole( hConOut, ChBuffer, nCharInBuffer, &nWritten, NULL );
nCharInBuffer = 0;
}
//-----------------------------------------------------------------------------
// PushBuffer( LPCTSTR s )
// Adds a character in the "buffer".
//-----------------------------------------------------------------------------
void PushBuffer( LPCTSTR s )
{
if (nCharInBuffer++ == 0)
ChBuffer = s;
}
//-----------------------------------------------------------------------------
// SendSequence( LPTSTR seq )
// Send the string to the input buffer.
//-----------------------------------------------------------------------------
void SendSequence( LPTSTR seq )
{
DWORD out;
INPUT_RECORD in;
HANDLE hStdIn = GetStdHandle( STD_INPUT_HANDLE );
in.EventType = KEY_EVENT;
in.Event.KeyEvent.bKeyDown = TRUE;
in.Event.KeyEvent.wRepeatCount = 1;
in.Event.KeyEvent.wVirtualKeyCode = 0;
in.Event.KeyEvent.wVirtualScanCode = 0;
in.Event.KeyEvent.dwControlKeyState = 0;
for (; *seq; ++seq)
{
in.Event.KeyEvent.uChar.UnicodeChar = *seq;
WriteConsoleInput( hStdIn, &in, 1, &out );
}
}
// ========== Print functions
//-----------------------------------------------------------------------------
// InterpretEscSeq()
// Interprets the last escape sequence scanned by ParseAndPrintString
// prefix escape sequence prefix
// es_argc escape sequence args count
// es_argv[] escape sequence args array
// suffix escape sequence suffix
//
// for instance, with \e[33;45;1m we have
// prefix = '[',
// es_argc = 3, es_argv[0] = 33, es_argv[1] = 45, es_argv[2] = 1
// suffix = 'm'
//-----------------------------------------------------------------------------
void InterpretEscSeq( void )
{
int i;
WORD attribut;
CONSOLE_SCREEN_BUFFER_INFO Info;
DWORD len, NumberOfCharsWritten;
COORD Pos;
SMALL_RECT Rect;
CHAR_INFO CharInfo;
// Just ignore \e[? & \e[> sequences.
if (prefix2 != 0)
return;
if (prefix == '[')
{
GetConsoleScreenBufferInfo( hConOut, &Info );
switch (suffix)
{
case 'm':
if (es_argc == 0) es_argv[es_argc++] = 0;
for (i = 0; i < es_argc; i++)
{
switch (es_argv[i])
{
case 0:
foreground = org_fg;
background = org_bg;
bold = (es_argc == 1) ? org_bold : 0;
underline = (es_argc == 1) ? org_ul : 0;
rvideo = 0;
concealed = 0;
break;
case 1: bold = FOREGROUND_INTENSITY; break;
case 5: /* blink */
case 4: underline = BACKGROUND_INTENSITY; break;
case 7: rvideo = 1; break;
case 8: concealed = 1; break;
case 21: bold = 0; break;
case 25:
case 24: underline = 0; break;
case 27: rvideo = 0; break;
case 28: concealed = 0; break;
}
if (30 <= es_argv[i] && es_argv[i] <= 37) foreground = es_argv[i]-30;
if (40 <= es_argv[i] && es_argv[i] <= 47) background = es_argv[i]-40;
}
if (concealed)
{
if (rvideo)
{
attribut = foregroundcolor[foreground]
| backgroundcolor[foreground];
if (bold)
attribut |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY;
}
else
{
attribut = foregroundcolor[background]
| backgroundcolor[background];
if (underline)
attribut |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY;
}
}
else if (rvideo)
{
attribut = foregroundcolor[background] | backgroundcolor[foreground];
if (bold)
attribut |= BACKGROUND_INTENSITY;
if (underline)
attribut |= FOREGROUND_INTENSITY;
}
else
attribut = foregroundcolor[foreground] | backgroundcolor[background]
| bold | underline;
SetConsoleTextAttribute( hConOut, attribut );
return;
case 'J':
if (es_argc == 0) es_argv[es_argc++] = 0; // ESC[J == ESC[0J
if (es_argc != 1) return;
switch (es_argv[0])
{
case 0: // ESC[0J erase from cursor to end of display
len = (Info.dwSize.Y - Info.dwCursorPosition.Y - 1) * Info.dwSize.X
+ Info.dwSize.X - Info.dwCursorPosition.X - 1;
FillConsoleOutputCharacter( hConOut, ' ', len,
Info.dwCursorPosition,
&NumberOfCharsWritten );
FillConsoleOutputAttribute( hConOut, Info.wAttributes, len,
Info.dwCursorPosition,
&NumberOfCharsWritten );
return;
case 1: // ESC[1J erase from start to cursor.
Pos.X = 0;
Pos.Y = 0;
len = Info.dwCursorPosition.Y * Info.dwSize.X
+ Info.dwCursorPosition.X + 1;
FillConsoleOutputCharacter( hConOut, ' ', len, Pos,
&NumberOfCharsWritten );
FillConsoleOutputAttribute( hConOut, Info.wAttributes, len, Pos,
&NumberOfCharsWritten );
return;
case 2: // ESC[2J Clear screen and home cursor
Pos.X = 0;
Pos.Y = 0;
len = Info.dwSize.X * Info.dwSize.Y;
FillConsoleOutputCharacter( hConOut, ' ', len, Pos,
&NumberOfCharsWritten );
FillConsoleOutputAttribute( hConOut, Info.wAttributes, len, Pos,
&NumberOfCharsWritten );
SetConsoleCursorPosition( hConOut, Pos );
return;
default:
return;
}
case 'K':
if (es_argc == 0) es_argv[es_argc++] = 0; // ESC[K == ESC[0K
if (es_argc != 1) return;
switch (es_argv[0])
{
case 0: // ESC[0K Clear to end of line
len = Info.srWindow.Right - Info.dwCursorPosition.X + 1;
FillConsoleOutputCharacter( hConOut, ' ', len,
Info.dwCursorPosition,
&NumberOfCharsWritten );
FillConsoleOutputAttribute( hConOut, Info.wAttributes, len,
Info.dwCursorPosition,
&NumberOfCharsWritten );
return;
case 1: // ESC[1K Clear from start of line to cursor
Pos.X = 0;
Pos.Y = Info.dwCursorPosition.Y;
FillConsoleOutputCharacter( hConOut, ' ',
Info.dwCursorPosition.X + 1, Pos,
&NumberOfCharsWritten );
FillConsoleOutputAttribute( hConOut, Info.wAttributes,
Info.dwCursorPosition.X + 1, Pos,
&NumberOfCharsWritten );
return;
case 2: // ESC[2K Clear whole line.
Pos.X = 0;
Pos.Y = Info.dwCursorPosition.Y;
FillConsoleOutputCharacter( hConOut, ' ', Info.dwSize.X, Pos,
&NumberOfCharsWritten );
FillConsoleOutputAttribute( hConOut, Info.wAttributes,
Info.dwSize.X, Pos,
&NumberOfCharsWritten );
return;
default:
return;
}
case 'L': // ESC[#L Insert # blank lines.
if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[L == ESC[1L
if (es_argc != 1) return;
Rect.Left = 0;
Rect.Top = Info.dwCursorPosition.Y;
Rect.Right = Info.dwSize.X - 1;
Rect.Bottom = Info.dwSize.Y - 1;
Pos.X = 0;
Pos.Y = Info.dwCursorPosition.Y + es_argv[0];
CharInfo.Char.UnicodeChar = ' ';
CharInfo.Attributes = Info.wAttributes;
ScrollConsoleScreenBuffer( hConOut, &Rect, NULL, Pos, &CharInfo );
return;
case 'M': // ESC[#M Delete # lines.
if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[M == ESC[1M
if (es_argc != 1) return;
if (es_argv[0] > Info.dwSize.Y - Info.dwCursorPosition.Y)
es_argv[0] = Info.dwSize.Y - Info.dwCursorPosition.Y;
Rect.Left = 0;
Rect.Top = Info.dwCursorPosition.Y + es_argv[0];
Rect.Right = Info.dwSize.X - 1;
Rect.Bottom = Info.dwSize.Y - 1;
Pos.X = 0;
Pos.Y = Info.dwCursorPosition.Y;
CharInfo.Char.UnicodeChar = ' ';
CharInfo.Attributes = Info.wAttributes;
ScrollConsoleScreenBuffer( hConOut, &Rect, NULL, Pos, &CharInfo );
return;
case 'P': // ESC[#P Delete # characters.
if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[P == ESC[1P
if (es_argc != 1) return;
if (Info.dwCursorPosition.X + es_argv[0] > Info.dwSize.X - 1)
es_argv[0] = Info.dwSize.X - Info.dwCursorPosition.X;
Rect.Left = Info.dwCursorPosition.X + es_argv[0];
Rect.Top = Info.dwCursorPosition.Y;
Rect.Right = Info.dwSize.X - 1;
Rect.Bottom = Info.dwCursorPosition.Y;
CharInfo.Char.UnicodeChar = ' ';
CharInfo.Attributes = Info.wAttributes;
ScrollConsoleScreenBuffer( hConOut, &Rect, NULL, Info.dwCursorPosition,
&CharInfo );
return;
case '@': // ESC[#@ Insert # blank characters.
if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[@ == ESC[1@
if (es_argc != 1) return;
if (Info.dwCursorPosition.X + es_argv[0] > Info.dwSize.X - 1)
es_argv[0] = Info.dwSize.X - Info.dwCursorPosition.X;
Rect.Left = Info.dwCursorPosition.X;
Rect.Top = Info.dwCursorPosition.Y;
Rect.Right = Info.dwSize.X - 1 - es_argv[0];
Rect.Bottom = Info.dwCursorPosition.Y;
Pos.X = Info.dwCursorPosition.X + es_argv[0];
Pos.Y = Info.dwCursorPosition.Y;
CharInfo.Char.UnicodeChar = ' ';
CharInfo.Attributes = Info.wAttributes;
ScrollConsoleScreenBuffer( hConOut, &Rect, NULL, Pos, &CharInfo );
return;
case 'A': // ESC[#A Moves cursor up # lines
if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[A == ESC[1A
if (es_argc != 1) return;
Pos.Y = Info.dwCursorPosition.Y - es_argv[0];
if (Pos.Y < 0) Pos.Y = 0;
Pos.X = Info.dwCursorPosition.X;
SetConsoleCursorPosition( hConOut, Pos );
return;
case 'B': // ESC[#B Moves cursor down # lines
if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[B == ESC[1B
if (es_argc != 1) return;
Pos.Y = Info.dwCursorPosition.Y + es_argv[0];
if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1;
Pos.X = Info.dwCursorPosition.X;
SetConsoleCursorPosition( hConOut, Pos );
return;
case 'C': // ESC[#C Moves cursor forward # spaces
if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[C == ESC[1C
if (es_argc != 1) return;
Pos.X = Info.dwCursorPosition.X + es_argv[0];
if (Pos.X >= Info.dwSize.X) Pos.X = Info.dwSize.X - 1;
Pos.Y = Info.dwCursorPosition.Y;
SetConsoleCursorPosition( hConOut, Pos );
return;
case 'D': // ESC[#D Moves cursor back # spaces
if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[D == ESC[1D
if (es_argc != 1) return;
Pos.X = Info.dwCursorPosition.X - es_argv[0];
if (Pos.X < 0) Pos.X = 0;
Pos.Y = Info.dwCursorPosition.Y;
SetConsoleCursorPosition( hConOut, Pos );
return;
case 'E': // ESC[#E Moves cursor down # lines, column 1.
if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[E == ESC[1E
if (es_argc != 1) return;
Pos.Y = Info.dwCursorPosition.Y + es_argv[0];
if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1;
Pos.X = 0;
SetConsoleCursorPosition( hConOut, Pos );
return;
case 'F': // ESC[#F Moves cursor up # lines, column 1.
if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[F == ESC[1F
if (es_argc != 1) return;
Pos.Y = Info.dwCursorPosition.Y - es_argv[0];
if (Pos.Y < 0) Pos.Y = 0;
Pos.X = 0;
SetConsoleCursorPosition( hConOut, Pos );
return;
case 'G': // ESC[#G Moves cursor column # in current row.
if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[G == ESC[1G
if (es_argc != 1) return;
Pos.X = es_argv[0] - 1;
if (Pos.X >= Info.dwSize.X) Pos.X = Info.dwSize.X - 1;
if (Pos.X < 0) Pos.X = 0;
Pos.Y = Info.dwCursorPosition.Y;
SetConsoleCursorPosition( hConOut, Pos );
return;
case 'f': // ESC[#;#f
case 'H': // ESC[#;#H Moves cursor to line #, column #
if (es_argc == 0)
es_argv[es_argc++] = 1; // ESC[H == ESC[1;1H
if (es_argc == 1)
es_argv[es_argc++] = 1; // ESC[#H == ESC[#;1H
if (es_argc > 2) return;
Pos.X = es_argv[1] - 1;
if (Pos.X < 0) Pos.X = 0;
if (Pos.X >= Info.dwSize.X) Pos.X = Info.dwSize.X - 1;
Pos.Y = es_argv[0] - 1;
if (Pos.Y < 0) Pos.Y = 0;
if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1;
SetConsoleCursorPosition( hConOut, Pos );
return;
case 's': // ESC[s Saves cursor position for recall later
if (es_argc != 0) return;
SavePos = Info.dwCursorPosition;
return;
case 'u': // ESC[u Return to saved cursor position
if (es_argc != 0) return;
SetConsoleCursorPosition( hConOut, SavePos );
return;
case 'n': // ESC[#n Device status report
if (es_argc != 1) return; // ESC[n == ESC[0n -> ignored
switch (es_argv[0])
{
case 5: // ESC[5n Report status
SendSequence( L"\33[0n" ); // "OK"
return;
case 6: // ESC[6n Report cursor position
{
TCHAR buf[32];
wsprintf( buf, L"\33[%d;%dR", Info.dwCursorPosition.Y + 1,
Info.dwCursorPosition.X + 1 );
SendSequence( buf );
}
return;
default:
return;
}
case 't': // ESC[#t Window manipulation
if (es_argc != 1) return;
if (es_argv[0] == 21) // ESC[21t Report xterm window's title
{
TCHAR buf[MAX_PATH*2];
DWORD len = GetConsoleTitle( buf+3, lenof(buf)-3-2 );
// Too bad if it's too big or fails.
buf[0] = ESC;
buf[1] = ']';
buf[2] = 'l';
buf[3+len] = ESC;
buf[3+len+1] = '\\';
buf[3+len+2] = '\0';
SendSequence( buf );
}
return;
default:
return;
}
}
else // (prefix == ']')
{
if (es_argc == 1 && es_argv[0] == 0) // ESC]0;titleST
{
SetConsoleTitle( Pt_arg );
}
}
}
//-----------------------------------------------------------------------------
// ParseAndPrintString(hDev, lpBuffer, nNumberOfBytesToWrite)
// Parses the string lpBuffer, interprets the escapes sequences and prints the
// characters in the device hDev (console).
// The lexer is a three states automata.
// If the number of arguments es_argc > MAX_ARG, only the MAX_ARG-1 firsts and
// the last arguments are processed (no es_argv[] overflow).
//-----------------------------------------------------------------------------
BOOL
ParseAndPrintString( HANDLE hDev,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten
)
{
DWORD i;
LPCTSTR s;
if (hDev != hConOut) // reinit if device has changed
{
hConOut = hDev;
state = 1;
}
for (i = nNumberOfBytesToWrite, s = (LPCTSTR)lpBuffer; i > 0; i--, s++)
{
if (state == 1)
{
if (*s == ESC) state = 2;
else PushBuffer( s );
}
else if (state == 2)
{
if (*s == ESC) ; // \e\e...\e == \e
else if ((*s == '[') || (*s == ']')) // || (*s == '('))
{
FlushBuffer();
prefix = *s;
prefix2 = 0;
state = 3;
Pt_len = 0;
*Pt_arg = '\0';
}
else state = 1;
}
else if (state == 3)
{
if (isdigit( *s ))
{
es_argc = 0;
es_argv[0] = *s - '0';
state = 4;
}
else if (*s == ';')
{
es_argc = 1;
es_argv[0] = 0;
es_argv[1] = 0;
state = 4;
}
else if (*s == '?' || *s == '>')
{
prefix2 = *s;
}
else
{
es_argc = 0;
suffix = *s;
InterpretEscSeq();
state = 1;
}
}
else if (state == 4)
{
if (isdigit( *s ))
{
es_argv[es_argc] = 10 * es_argv[es_argc] + (*s - '0');
}
else if (*s == ';')
{
if (es_argc < MAX_ARG-1) es_argc++;
es_argv[es_argc] = 0;
if (prefix == ']')
state = 5;
}
else
{
es_argc++;
suffix = *s;
InterpretEscSeq();
state = 1;
}
}
else if (state == 5)
{
if (*s == BEL)
{
Pt_arg[Pt_len] = '\0';
InterpretEscSeq();
state = 1;
}
else if (*s == '\\' && Pt_len > 0 && Pt_arg[Pt_len-1] == ESC)
{
Pt_arg[--Pt_len] = '\0';
InterpretEscSeq();
state = 1;
}
else if (Pt_len < lenof(Pt_arg)-1)
Pt_arg[Pt_len++] = *s;
}
}
FlushBuffer();
if (lpNumberOfBytesWritten != NULL)
*lpNumberOfBytesWritten = nNumberOfBytesToWrite - i;
return( i == 0 );
}
// ========== Child process injection
// Inject code into the target process to load our DLL.
void Inject( LPPROCESS_INFORMATION pinfo, LPPROCESS_INFORMATION lpi,
DWORD dwCreationFlags )
{
int type = ProcessType( pinfo );
if (type != 0)
{
WCHAR dll[MAX_PATH];
#ifdef _WIN64
DWORD len = GetModuleFileName( GetModuleHandleA( "ANSI64.dll" ),
dll, lenof(dll) );
if (type == 32)
{
dll[len-6] = '3';
dll[len-5] = '2';
InjectDLL32( pinfo, dll );
}
else
{
InjectDLL64( pinfo, dll );
}
#else
GetModuleFileName( GetModuleHandleA( "ANSI32.dll" ), dll, lenof(dll) );
InjectDLL32( pinfo, dll );
#endif
}
if (!(dwCreationFlags & CREATE_SUSPENDED))
ResumeThread( pinfo->hThread );
if (lpi)
{
memcpy( lpi, pinfo, sizeof(PROCESS_INFORMATION) );
}
else
{
CloseHandle( pinfo->hProcess );
CloseHandle( pinfo->hThread );
}
}
BOOL WINAPI MyCreateProcessA( LPCSTR lpApplicationName,
LPSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCSTR lpCurrentDirectory,
LPSTARTUPINFOA lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation )
{
PROCESS_INFORMATION pi;
if (!CreateProcessA( lpApplicationName,
lpCommandLine,
lpThreadAttributes,
lpProcessAttributes,
bInheritHandles,
dwCreationFlags | CREATE_SUSPENDED,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
&pi ))
return FALSE;
DEBUGSTR( L"CreateProcessA: \"%S\", \"%S\"",
(lpApplicationName == NULL) ? "" : lpApplicationName,
(lpCommandLine == NULL) ? "" : lpCommandLine );
Inject( &pi, lpProcessInformation, dwCreationFlags );
return TRUE;
}
BOOL WINAPI MyCreateProcessW( LPCWSTR lpApplicationName,
LPWSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCWSTR lpCurrentDirectory,
LPSTARTUPINFOW lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation )
{
PROCESS_INFORMATION pi;
if (!CreateProcessW( lpApplicationName,
lpCommandLine,
lpThreadAttributes,
lpProcessAttributes,
bInheritHandles,
dwCreationFlags | CREATE_SUSPENDED,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
&pi ))
return FALSE;
DEBUGSTR( L"CreateProcessW: \"%s\", \"%s\"",
(lpApplicationName == NULL) ? L"" : lpApplicationName,
(lpCommandLine == NULL) ? L"" : lpCommandLine );
Inject( &pi, lpProcessInformation, dwCreationFlags );
return TRUE;
}
FARPROC WINAPI MyGetProcAddress( HMODULE hModule, LPCSTR lpProcName )
{
PHookFn hook;
FARPROC proc;
proc = GetProcAddress( hModule, lpProcName );
if (proc)
{
if (hModule == hKernel)
{
// Ignore LoadLibrary so other hooks continue to work (our version
// might end up at a different address).
if (proc == Hooks[0].oldfunc || proc == Hooks[1].oldfunc)
return proc;
for (hook = Hooks+2; hook->name; ++hook)
{
if (proc == hook->oldfunc)
{
DEBUGSTR( L"GetProcAddress: %S", lpProcName );
return hook->newfunc;
}
}
}
else if (Hooks[0].apifunc) // assume if one is defined, all are
{
if (proc == Hooks[0].apifunc || proc == Hooks[1].apifunc)
return proc;
for (hook = Hooks+2; hook->name; ++hook)
{
if (proc == hook->apifunc)
{
DEBUGSTR( L"GetProcAddress: %S", lpProcName );
return hook->newfunc;
}
}
}
}
return proc;
}
HMODULE WINAPI MyLoadLibraryA( LPCSTR lpFileName )
{
HMODULE hMod = LoadLibraryA( lpFileName );
if (hMod && hMod != hKernel)
{
DEBUGSTR( L"Hooking in %S (LoadLibraryA)", lpFileName );
HookAPIOneMod( hMod, Hooks, FALSE );
}
return hMod;
}
HMODULE WINAPI MyLoadLibraryW( LPCWSTR lpFileName )
{
HMODULE hMod = LoadLibraryW( lpFileName );
if (hMod && hMod != hKernel)
{
DEBUGSTR( L"Hooking in %s (LoadLibraryW)", lpFileName );
HookAPIOneMod( hMod, Hooks, FALSE );
}
return hMod;
}
HMODULE WINAPI MyLoadLibraryExA( LPCSTR lpFileName, HANDLE hFile,
DWORD dwFlags )
{
HMODULE hMod = LoadLibraryExA( lpFileName, hFile, dwFlags );
if (hMod && hMod != hKernel && !(dwFlags & LOAD_LIBRARY_AS_DATAFILE))
{
DEBUGSTR( L"Hooking in %S (LoadLibraryExA)", lpFileName );
HookAPIOneMod( hMod, Hooks, FALSE );
}
return hMod;
}
HMODULE WINAPI MyLoadLibraryExW( LPCWSTR lpFileName, HANDLE hFile,
DWORD dwFlags )
{
HMODULE hMod = LoadLibraryExW( lpFileName, hFile, dwFlags );
if (hMod && hMod != hKernel && !(dwFlags & LOAD_LIBRARY_AS_DATAFILE))
{
DEBUGSTR( L"Hooking in %s (LoadLibraryExW)", lpFileName );
HookAPIOneMod( hMod, Hooks, FALSE );
}
return hMod;
}
//-----------------------------------------------------------------------------
// MyWrite...
// It is the new function that must replace the original Write... function.
// This function have exactly the same signature as the original one.
//-----------------------------------------------------------------------------
BOOL
WINAPI MyWriteConsoleA( HANDLE hCon, LPCVOID lpBuffer,
DWORD nNumberOfCharsToWrite,
LPDWORD lpNumberOfCharsWritten, LPVOID lpReserved )
{
DWORD Mode;
LPWSTR buf;
DWORD len;
BOOL rc = TRUE;
// if we write in a console buffer with processed output
if (GetConsoleMode( hCon, &Mode ) && (Mode & ENABLE_PROCESSED_OUTPUT))
{
UINT cp = GetConsoleOutputCP();
DEBUGSTR( L"\33WriteConsoleA: %lu \"%.*S\"",
nNumberOfCharsToWrite, nNumberOfCharsToWrite, lpBuffer );
len = MultiByteToWideChar( cp, 0, lpBuffer, nNumberOfCharsToWrite, NULL, 0 );
buf = malloc( len * sizeof(WCHAR) );
if (buf == NULL)
{
if (lpNumberOfCharsWritten != NULL)
*lpNumberOfCharsWritten = 0;
return (nNumberOfCharsToWrite == 0);
}
MultiByteToWideChar( cp, 0, lpBuffer, nNumberOfCharsToWrite, buf, len );
rc = ParseAndPrintString( hCon, buf, len, lpNumberOfCharsWritten );
free( buf );
if (rc && lpNumberOfCharsWritten != NULL &&
*lpNumberOfCharsWritten != nNumberOfCharsToWrite)
{
// Converting a multibyte character to Unicode results in a different
// "character" count. This causes some programs to think not everything
// was written, so the difference is sent again. Fudge the (presumably)
// correct count.
TCHAR env[2048];
if (GetEnvironmentVariable( L"ANSICON_API", env, lenof(env) ))
{
BOOL not;
not = (*env == '!');
if (not && env[1] == '\0')
{
*lpNumberOfCharsWritten = nNumberOfCharsToWrite;
}
else
{
TCHAR path[MAX_PATH];
LPTSTR name, exe;
GetModuleFileName( NULL, path, lenof(path) );
name = wcsrchr( path, '\\' );
if (name == NULL)
name = path;
else
++name;
exe = wcsrchr( name, '.' );
if (exe != NULL && exe != name)
*exe = '\0';
for (exe = wcstok( env + not, L";" ); exe; exe = wcstok( NULL, L";" ))
{
if (_wcsicmp( name, exe ) == 0)
break;
}
if ((exe && !not) || (!exe && not))
*lpNumberOfCharsWritten = nNumberOfCharsToWrite;
}
}
}
return rc;
}
else
{
return WriteConsoleA( hCon, lpBuffer,
nNumberOfCharsToWrite,
lpNumberOfCharsWritten,
lpReserved );
}
}
BOOL
WINAPI MyWriteConsoleW( HANDLE hCon, LPCVOID lpBuffer,
DWORD nNumberOfCharsToWrite,
LPDWORD lpNumberOfCharsWritten, LPVOID lpReserved )
{
DWORD Mode;
if (GetConsoleMode( hCon, &Mode ) && (Mode & ENABLE_PROCESSED_OUTPUT))
{
DEBUGSTR( L"\33WriteConsoleW: %lu \"%.*s\"",
nNumberOfCharsToWrite, nNumberOfCharsToWrite, lpBuffer );
return ParseAndPrintString( hCon, lpBuffer,
nNumberOfCharsToWrite,
lpNumberOfCharsWritten );
}
else
{
return WriteConsoleW( hCon, lpBuffer,
nNumberOfCharsToWrite,
lpNumberOfCharsWritten,
lpReserved );
}
}
BOOL
WINAPI MyWriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped )
{
DWORD Mode;
if (GetConsoleMode( hFile, &Mode ) && (Mode & ENABLE_PROCESSED_OUTPUT))
{
DEBUGSTR( L"WriteFile->" );
return MyWriteConsoleA( hFile, lpBuffer,
nNumberOfBytesToWrite,
lpNumberOfBytesWritten,
lpOverlapped );
}
else // here, WriteFile is the old function (this module is not hooked)
{
return WriteFile( hFile, lpBuffer,
nNumberOfBytesToWrite,
lpNumberOfBytesWritten,
lpOverlapped );
}
}
// ========== Environment variable
void set_ansicon( PCONSOLE_SCREEN_BUFFER_INFO pcsbi )
{
CONSOLE_SCREEN_BUFFER_INFO csbi;
TCHAR buf[64];
if (pcsbi == NULL)
{
HANDLE hConOut;
hConOut = CreateFile( L"CONOUT$", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, 0 );
GetConsoleScreenBufferInfo( hConOut, &csbi );
CloseHandle( hConOut );
pcsbi = &csbi;
}
wsprintf( buf, L"%dx%d (%dx%d)",
pcsbi->dwSize.X, pcsbi->dwSize.Y,
pcsbi->srWindow.Right - pcsbi->srWindow.Left + 1,
pcsbi->srWindow.Bottom - pcsbi->srWindow.Top + 1 );
SetEnvironmentVariable( L"ANSICON", buf );
}
DWORD
WINAPI MyGetEnvironmentVariableA( LPCSTR lpName, LPSTR lpBuffer, DWORD nSize )
{
if (lstrcmpiA( lpName, "ANSICON" ) == 0)
set_ansicon( NULL );
return GetEnvironmentVariableA( lpName, lpBuffer, nSize );
}
DWORD
WINAPI MyGetEnvironmentVariableW( LPCWSTR lpName, LPWSTR lpBuffer, DWORD nSize )
{
if (lstrcmpi( lpName, L"ANSICON" ) == 0)
set_ansicon( NULL );
return GetEnvironmentVariableW( lpName, lpBuffer, nSize );
}
// ========== Initialisation
HookFn Hooks[] = {
// These two are expected first!
{ APILibraryLoader, "LoadLibraryA", (PROC)MyLoadLibraryA, NULL, NULL },
{ APILibraryLoader, "LoadLibraryW", (PROC)MyLoadLibraryW, NULL, NULL },
{ APIProcessThreads, "CreateProcessA", (PROC)MyCreateProcessA, NULL, NULL },
{ APIProcessThreads, "CreateProcessW", (PROC)MyCreateProcessW, NULL, NULL },
{ APIProcessEnvironment, "GetEnvironmentVariableA", (PROC)MyGetEnvironmentVariableA, NULL, NULL },
{ APIProcessEnvironment, "GetEnvironmentVariableW", (PROC)MyGetEnvironmentVariableW, NULL, NULL },
{ APILibraryLoader, "GetProcAddress", (PROC)MyGetProcAddress, NULL, NULL },
{ APILibraryLoader, "LoadLibraryExA", (PROC)MyLoadLibraryExA, NULL, NULL },
{ APILibraryLoader, "LoadLibraryExW", (PROC)MyLoadLibraryExW, NULL, NULL },
{ APIConsole, "WriteConsoleA", (PROC)MyWriteConsoleA, NULL, NULL },
{ APIConsole, "WriteConsoleW", (PROC)MyWriteConsoleW, NULL, NULL },
{ APIFile, "WriteFile", (PROC)MyWriteFile, NULL, NULL },
{ NULL, NULL, NULL, NULL }
};
//-----------------------------------------------------------------------------
// OriginalAttr()
// Determine the original attributes for use by \e[m.
//-----------------------------------------------------------------------------
void OriginalAttr( void )
{
static const char attr2ansi[8] = // map console attribute to ANSI number
{
0, 4, 2, 6, 1, 5, 3, 7
};
HANDLE hConOut;
CONSOLE_SCREEN_BUFFER_INFO csbi;
hConOut = CreateFile( L"CONOUT$", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, 0 );
if (!GetConsoleScreenBufferInfo( hConOut, &csbi ))
csbi.wAttributes = 7;
CloseHandle( hConOut );
foreground = org_fg = attr2ansi[csbi.wAttributes & 7];
background = org_bg = attr2ansi[(csbi.wAttributes >> 4) & 7];
bold = org_bold = csbi.wAttributes & FOREGROUND_INTENSITY;
underline = org_ul = csbi.wAttributes & BACKGROUND_INTENSITY;
set_ansicon( &csbi );
}
//-----------------------------------------------------------------------------
// DllMain()
// Function called by the system when processes and threads are initialized
// and terminated.
//-----------------------------------------------------------------------------
__declspec(dllexport) // to stop MinGW exporting everything
BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved )
{
BOOL bResult = TRUE;
HMODULE api;
PHookFn hook;
if (dwReason == DLL_PROCESS_ATTACH)
{
hDllInstance = hInstance; // save Dll instance handle
DEBUGSTR( L"hDllInstance = %p", hDllInstance );
// Get the entry points to the original functions.
hKernel = GetModuleHandleA( APIKernel );
for (hook = Hooks; hook->name; ++hook)
{
hook->oldfunc = GetProcAddress( hKernel, hook->name );
api = GetModuleHandleA( hook->lib );
if (api)
hook->apifunc = GetProcAddress( api, hook->name );
}
bResult = HookAPIAllMod( Hooks, FALSE );
OriginalAttr();
DisableThreadLibraryCalls( hInstance );
}
else if (dwReason == DLL_PROCESS_DETACH)
{
if (lpReserved == NULL)
{
DEBUGSTR( L"Unloading" );
HookAPIAllMod( Hooks, TRUE );
}
else
{
DEBUGSTR( L"Terminating" );
}
}
return( bResult );
}