481 lines
14 KiB
C
481 lines
14 KiB
C
|
#ifndef WIN32_LEAN_AND_MEAN
|
||
|
#define WIN32_LEAN_AND_MEAN
|
||
|
#endif
|
||
|
|
||
|
#ifndef STRICT
|
||
|
#define STRICT
|
||
|
#endif
|
||
|
|
||
|
#ifndef _WIN32_WINNT
|
||
|
#define _WIN32_WINNT 0x0500
|
||
|
#endif
|
||
|
|
||
|
#include <windows.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdarg.h>
|
||
|
#include "detours.h"
|
||
|
|
||
|
//
|
||
|
// Registry information used by RWSpy
|
||
|
//
|
||
|
WCHAR RWSpyKey[] = L"Software\\Microsoft\\RWSpy";
|
||
|
|
||
|
WCHAR DeviceValueName[] = L"FileToSpyOn";
|
||
|
WCHAR DeviceNameToSpyOn[MAX_PATH] = L"";
|
||
|
|
||
|
WCHAR LogFileValueName[] = L"Log File";
|
||
|
WCHAR DefaultLogFileName[] = L"%SystemRoot%\\rwspy.log";
|
||
|
WCHAR LogFileName[MAX_PATH] = L"\0";
|
||
|
|
||
|
//
|
||
|
// Globals
|
||
|
//
|
||
|
HANDLE g_hDeviceToSpyOn = INVALID_HANDLE_VALUE;
|
||
|
HANDLE g_hLogFile = INVALID_HANDLE_VALUE;
|
||
|
|
||
|
//
|
||
|
// Detours trampolines
|
||
|
//
|
||
|
DETOUR_TRAMPOLINE(HANDLE WINAPI Real_CreateFileW(LPCWSTR a0, DWORD a1, DWORD a2,
|
||
|
LPSECURITY_ATTRIBUTES a3, DWORD a4, DWORD a5, HANDLE a6), CreateFileW);
|
||
|
|
||
|
DETOUR_TRAMPOLINE(BOOL WINAPI Real_WriteFile(HANDLE hFile, LPCVOID lpBuffer,
|
||
|
DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped), WriteFile);
|
||
|
|
||
|
DETOUR_TRAMPOLINE(BOOL WINAPI Real_WriteFileEx(HANDLE hFile, LPCVOID lpBuffer, DWORD dwNumberOfBytesToWrite,
|
||
|
LPOVERLAPPED lpOverlapped, LPOVERLAPPED_COMPLETION_ROUTINE lpCompletion), WriteFileEx);
|
||
|
|
||
|
DETOUR_TRAMPOLINE(BOOL WINAPI Real_ReadFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToRead,
|
||
|
LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped), ReadFile);
|
||
|
|
||
|
DETOUR_TRAMPOLINE(BOOL WINAPI Real_ReadFileEx(HANDLE hFile, LPCVOID lpBuffer, DWORD dwNumberOfBytesToRead,
|
||
|
LPOVERLAPPED lpOverlapped, LPOVERLAPPED_COMPLETION_ROUTINE lpCompletion), ReadFileEx);
|
||
|
|
||
|
DETOUR_TRAMPOLINE(BOOL WINAPI Real_DeviceIoControl(HANDLE hFile, DWORD code, LPVOID inBuffer, DWORD cbIn,
|
||
|
LPVOID outBuffer, DWORD cbOutSize, LPDWORD cbOutActual, LPOVERLAPPED lpOverlapped), DeviceIoControl);
|
||
|
|
||
|
DETOUR_TRAMPOLINE(BOOL WINAPI Real_CloseHandle(HANDLE hObject), CloseHandle);
|
||
|
|
||
|
|
||
|
// closes log and makes further writing impossible until log reopened
|
||
|
void CloseLog(void)
|
||
|
{
|
||
|
if(g_hLogFile != INVALID_HANDLE_VALUE) {
|
||
|
Real_CloseHandle(g_hLogFile);
|
||
|
g_hLogFile = INVALID_HANDLE_VALUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Attempts to open log file. Assumes that LogFileName[] is already set
|
||
|
// elsewhere.
|
||
|
BOOL OpenLog(void)
|
||
|
{
|
||
|
BOOL success = FALSE;
|
||
|
|
||
|
CloseLog();
|
||
|
|
||
|
g_hLogFile = Real_CreateFileW(LogFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL,
|
||
|
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||
|
|
||
|
if(g_hLogFile != INVALID_HANDLE_VALUE) {
|
||
|
success = TRUE;
|
||
|
}
|
||
|
|
||
|
return success;
|
||
|
}
|
||
|
|
||
|
// Writes specified number of characters to the log file
|
||
|
BOOL WriteToLog(CHAR *bytes, DWORD len)
|
||
|
{
|
||
|
BOOL success = FALSE;
|
||
|
|
||
|
if(g_hLogFile != INVALID_HANDLE_VALUE && len && bytes) {
|
||
|
DWORD cbWritten;
|
||
|
if(Real_WriteFile(g_hLogFile, bytes, len, &cbWritten, NULL) && len == cbWritten) {
|
||
|
success = TRUE;
|
||
|
} else {
|
||
|
CloseLog();
|
||
|
}
|
||
|
}
|
||
|
return success;
|
||
|
}
|
||
|
|
||
|
// writes printf-style string to the log file
|
||
|
void Log(LPCSTR fmt, ...)
|
||
|
{
|
||
|
va_list marker;
|
||
|
CHAR buffer[1024];
|
||
|
DWORD cbToWrite;
|
||
|
|
||
|
if(g_hLogFile == INVALID_HANDLE_VALUE || fmt == NULL)
|
||
|
return;
|
||
|
|
||
|
va_start(marker, fmt);
|
||
|
_vsnprintf(buffer, sizeof(buffer), fmt, marker);
|
||
|
cbToWrite = lstrlenA(buffer);
|
||
|
|
||
|
WriteToLog(buffer, cbToWrite);
|
||
|
}
|
||
|
|
||
|
// writes db-style bytes to the log file
|
||
|
void LogBytes(BYTE *pBytes, DWORD dwBytes)
|
||
|
{
|
||
|
DWORD nBytes = min(dwBytes, 8192L);
|
||
|
const static CHAR hex[] = "0123456789ABCDEF";
|
||
|
CHAR buffer[80];
|
||
|
DWORD cbToWrite = 75;
|
||
|
DWORD byte = 0;
|
||
|
int pos;
|
||
|
|
||
|
if(g_hLogFile == INVALID_HANDLE_VALUE || pBytes == NULL || nBytes == 0)
|
||
|
return;
|
||
|
|
||
|
while(byte < nBytes) {
|
||
|
|
||
|
if((byte % 16) == 0) {
|
||
|
|
||
|
if(byte != 0) {
|
||
|
// write previous line into file
|
||
|
if(!WriteToLog(buffer, cbToWrite))
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
memset(buffer, ' ', cbToWrite - 1);
|
||
|
buffer[cbToWrite - 1] = '\n';
|
||
|
|
||
|
buffer[0] = hex[(byte >> 12) & 0xF];
|
||
|
buffer[1] = hex[(byte >> 8) & 0xF];
|
||
|
buffer[2] = hex[(byte >> 4) & 0xF];
|
||
|
buffer[3] = hex[byte & 0xF];
|
||
|
}
|
||
|
|
||
|
pos = (byte % 16 < 8 ? 5 : 6)+ (byte % 16) * 3;
|
||
|
|
||
|
buffer[pos] = hex[(pBytes[byte] >> 4) & 0xF];
|
||
|
buffer[pos + 1] = hex[pBytes[byte] & 0xF];
|
||
|
|
||
|
pos = 5 + 16 * 3 + 2 + (byte % 16);
|
||
|
buffer[pos] = pBytes[byte] >= ' ' && pBytes[byte] <= 127 ? pBytes[byte] : '.';
|
||
|
|
||
|
byte++;
|
||
|
}
|
||
|
|
||
|
// write one final line
|
||
|
WriteToLog(buffer, cbToWrite);
|
||
|
}
|
||
|
|
||
|
|
||
|
HANDLE WINAPI
|
||
|
My_CreateFileW(LPCWSTR a0,
|
||
|
DWORD a1,
|
||
|
DWORD a2,
|
||
|
LPSECURITY_ATTRIBUTES a3,
|
||
|
DWORD a4,
|
||
|
DWORD a5,
|
||
|
HANDLE a6)
|
||
|
{
|
||
|
HANDLE hResult;
|
||
|
|
||
|
__try {
|
||
|
hResult = Real_CreateFileW(a0, a1, a2, a3, a4, a5, a6);
|
||
|
}
|
||
|
|
||
|
__finally {
|
||
|
// if we have a file to spy on
|
||
|
if(DeviceNameToSpyOn[0]) {
|
||
|
WCHAR *pw = DeviceNameToSpyOn;
|
||
|
LPCWSTR p = a0;
|
||
|
|
||
|
while(*p && *pw) {
|
||
|
WCHAR w = *p;
|
||
|
if(w != *pw)
|
||
|
break;
|
||
|
p++;
|
||
|
pw++;
|
||
|
}
|
||
|
|
||
|
if(*p == L'\0' && *pw == L'\0') {
|
||
|
// we got our file
|
||
|
if(hResult == INVALID_HANDLE_VALUE) {
|
||
|
Log("Tried creating '%S', LastError() = : %d\n", a0 ? a0 : L"NULL", GetLastError());
|
||
|
} else {
|
||
|
Log("Created '%S', handle: %x\n", a0 ? a0 : L"NULL", hResult);
|
||
|
}
|
||
|
|
||
|
if(hResult != INVALID_HANDLE_VALUE) {
|
||
|
// successsfully created
|
||
|
if(g_hDeviceToSpyOn != INVALID_HANDLE_VALUE && hResult != g_hDeviceToSpyOn) {
|
||
|
// hmm... it was already open. Let user know
|
||
|
// this happened:
|
||
|
Log("Note: we were already spying on this device with handle %x. Changing to %x\n",
|
||
|
g_hDeviceToSpyOn, hResult);
|
||
|
}
|
||
|
g_hDeviceToSpyOn = hResult;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
// simply record the file name into output file
|
||
|
Log("Creating file: '%S', result: %x\n", a0 ? a0 : L"NULL", hResult);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return hResult;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL WINAPI
|
||
|
My_WriteFile(HANDLE hFile,
|
||
|
LPCVOID lpBuffer,
|
||
|
DWORD nNumberOfBytesToWrite,
|
||
|
LPDWORD lpNumberOfBytesWritten,
|
||
|
LPOVERLAPPED lpOverlapped)
|
||
|
{
|
||
|
BOOL bresult;
|
||
|
DWORD bytesWritten;
|
||
|
|
||
|
__try {
|
||
|
bresult = Real_WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
|
||
|
}
|
||
|
|
||
|
__finally {
|
||
|
if(g_hDeviceToSpyOn != INVALID_HANDLE_VALUE && hFile == g_hDeviceToSpyOn) {
|
||
|
bytesWritten = lpNumberOfBytesWritten ? *lpNumberOfBytesWritten :
|
||
|
nNumberOfBytesToWrite;
|
||
|
if(bresult) {
|
||
|
Log("Wrote %d bytes:\n", bytesWritten);
|
||
|
} else {
|
||
|
Log("Failure writing %d bytes, LastError() = %d:\n",
|
||
|
nNumberOfBytesToWrite, GetLastError());
|
||
|
}
|
||
|
LogBytes((BYTE *)lpBuffer, bytesWritten);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return bresult;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL WINAPI
|
||
|
My_WriteFileEx(HANDLE hFile,
|
||
|
LPCVOID lpBuffer,
|
||
|
DWORD nNumberOfBytesToWrite,
|
||
|
LPOVERLAPPED lpOverlapped,
|
||
|
LPOVERLAPPED_COMPLETION_ROUTINE lpOverlappedCompletion)
|
||
|
{
|
||
|
BOOL bresult;
|
||
|
|
||
|
__try {
|
||
|
bresult = Real_WriteFileEx(hFile, lpBuffer, nNumberOfBytesToWrite, lpOverlapped, lpOverlappedCompletion);
|
||
|
}
|
||
|
|
||
|
__finally {
|
||
|
if(g_hDeviceToSpyOn != INVALID_HANDLE_VALUE && hFile == g_hDeviceToSpyOn) {
|
||
|
if(bresult) {
|
||
|
Log("Submitted %d bytes to WriteEx:\n", nNumberOfBytesToWrite);
|
||
|
} else {
|
||
|
Log("Failed to submit %d bytes to WriteEx, LastError() = %d:\n",
|
||
|
nNumberOfBytesToWrite, GetLastError());
|
||
|
}
|
||
|
LogBytes((BYTE *)lpBuffer, nNumberOfBytesToWrite);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return bresult;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
BOOL WINAPI
|
||
|
My_ReadFile(HANDLE hFile,
|
||
|
LPCVOID lpBuffer,
|
||
|
DWORD nNumberOfBytesToRead,
|
||
|
LPDWORD lpNumberOfBytesRead,
|
||
|
LPOVERLAPPED lpOverlapped)
|
||
|
{
|
||
|
BOOL bresult;
|
||
|
DWORD bytesRead;
|
||
|
|
||
|
__try {
|
||
|
bresult = Real_ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped);
|
||
|
}
|
||
|
|
||
|
__finally {
|
||
|
if(g_hDeviceToSpyOn != INVALID_HANDLE_VALUE && hFile == g_hDeviceToSpyOn) {
|
||
|
bytesRead = lpNumberOfBytesRead ? *lpNumberOfBytesRead :
|
||
|
nNumberOfBytesToRead;
|
||
|
if(bresult) {
|
||
|
Log("Read %d bytes:\n", bytesRead);
|
||
|
LogBytes((BYTE *)lpBuffer, bytesRead);
|
||
|
} else {
|
||
|
Log("Failure to read %d bytes, LastError() = %d:\n",
|
||
|
nNumberOfBytesToRead, GetLastError());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return bresult;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Please, note that to see ReadEx bytes one needs to detour
|
||
|
// the completion routine (we don't do it here).
|
||
|
//
|
||
|
BOOL WINAPI
|
||
|
My_ReadFileEx(HANDLE hFile,
|
||
|
LPCVOID lpBuffer,
|
||
|
DWORD nNumberOfBytesToRead,
|
||
|
LPOVERLAPPED lpOverlapped,
|
||
|
LPOVERLAPPED_COMPLETION_ROUTINE lpOverlappedCompletion)
|
||
|
{
|
||
|
BOOL bresult;
|
||
|
|
||
|
__try {
|
||
|
bresult = Real_ReadFileEx(hFile, lpBuffer, nNumberOfBytesToRead, lpOverlapped, lpOverlappedCompletion);
|
||
|
}
|
||
|
|
||
|
__finally {
|
||
|
if(g_hDeviceToSpyOn != INVALID_HANDLE_VALUE && hFile == g_hDeviceToSpyOn) {
|
||
|
if(bresult) {
|
||
|
Log("Submitted ReadEx for %d bytes:\n", nNumberOfBytesToRead);
|
||
|
} else {
|
||
|
Log("Failed to sumbit ReadEx for %d bytes, LastError() = %d:\n",
|
||
|
nNumberOfBytesToRead, GetLastError());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return bresult;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
BOOL WINAPI
|
||
|
My_DeviceIoControl(HANDLE hFile, DWORD code, LPVOID inBuffer, DWORD cbIn,
|
||
|
LPVOID outBuffer, DWORD cbOutSize, LPDWORD pcbOutActual, LPOVERLAPPED lpOverlapped)
|
||
|
{
|
||
|
BOOL result;
|
||
|
DWORD outBytes;
|
||
|
DWORD Function;
|
||
|
__try {
|
||
|
result = Real_DeviceIoControl(hFile, code, inBuffer, cbIn, outBuffer, cbOutSize, pcbOutActual, lpOverlapped);
|
||
|
}
|
||
|
__finally {
|
||
|
if(g_hDeviceToSpyOn != INVALID_HANDLE_VALUE && hFile == g_hDeviceToSpyOn) {
|
||
|
outBytes = pcbOutActual ? *pcbOutActual : cbOutSize;
|
||
|
|
||
|
Function = (code >> 2) & 0xFFF;
|
||
|
|
||
|
Log("DeviceIoControl code = %x, Function = %x, %d bytes in:\n",
|
||
|
code, Function, cbIn);
|
||
|
LogBytes((BYTE *)inBuffer, cbIn);
|
||
|
if(outBytes) {
|
||
|
Log(" %d bytes out:\n", outBytes);
|
||
|
LogBytes((BYTE *)outBuffer, outBytes);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL WINAPI
|
||
|
My_CloseHandle(HANDLE hObject)
|
||
|
{
|
||
|
BOOL bresult;
|
||
|
|
||
|
__try {
|
||
|
bresult = Real_CloseHandle(hObject);
|
||
|
}
|
||
|
|
||
|
__finally {
|
||
|
if(g_hDeviceToSpyOn != INVALID_HANDLE_VALUE && hObject == g_hDeviceToSpyOn) {
|
||
|
Log("Closed handle %x\n", hObject);
|
||
|
g_hDeviceToSpyOn = INVALID_HANDLE_VALUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return bresult;
|
||
|
}
|
||
|
|
||
|
|
||
|
void PrepareLogger()
|
||
|
{
|
||
|
HKEY hKey;
|
||
|
WCHAR buffer[MAX_PATH];
|
||
|
|
||
|
// retrieve our configuration from the registry
|
||
|
if(ERROR_SUCCESS == RegCreateKeyExW(HKEY_LOCAL_MACHINE, RWSpyKey,
|
||
|
0, NULL, 0, KEY_ALL_ACCESS, NULL, &hKey, NULL))
|
||
|
{
|
||
|
DWORD dwType, cbData = sizeof(buffer);
|
||
|
|
||
|
cbData = sizeof(buffer);
|
||
|
if(ERROR_SUCCESS == RegQueryValueExW(hKey, LogFileValueName, 0, &dwType, (BYTE *) buffer, &cbData) &&
|
||
|
cbData)
|
||
|
{
|
||
|
ExpandEnvironmentStringsW(buffer, LogFileName, MAX_PATH);
|
||
|
} else {
|
||
|
// no log file name value found, create it so that users
|
||
|
// would know what its name is
|
||
|
cbData = lstrlenW(DefaultLogFileName) * sizeof(DefaultLogFileName[0]);
|
||
|
RegSetValueExW(hKey, LogFileValueName, 0, REG_EXPAND_SZ, (BYTE *) DefaultLogFileName, cbData);
|
||
|
}
|
||
|
|
||
|
DeviceNameToSpyOn[0] = L'\0';
|
||
|
cbData = sizeof(DeviceNameToSpyOn);
|
||
|
if(ERROR_SUCCESS != RegQueryValueExW(hKey, DeviceValueName, NULL, &dwType, (LPBYTE) DeviceNameToSpyOn, &cbData)
|
||
|
|| !DeviceNameToSpyOn[0])
|
||
|
{
|
||
|
// no "FileToSpyOn" value found, create it so that users
|
||
|
// would know what the value name is
|
||
|
RegSetValueExW(hKey, DeviceValueName, 0, REG_SZ, (BYTE *)DeviceNameToSpyOn, sizeof(WCHAR));
|
||
|
}
|
||
|
RegCloseKey(hKey);
|
||
|
}
|
||
|
|
||
|
// if we still don't have file name, use the default one
|
||
|
if(!LogFileName[0]) {
|
||
|
ExpandEnvironmentStringsW(DefaultLogFileName, LogFileName, MAX_PATH);
|
||
|
}
|
||
|
|
||
|
OpenLog();
|
||
|
|
||
|
DetourFunctionWithTrampoline((PBYTE) Real_CreateFileW, (PBYTE) My_CreateFileW);
|
||
|
DetourFunctionWithTrampoline((PBYTE) Real_WriteFile, (PBYTE) My_WriteFile);
|
||
|
DetourFunctionWithTrampoline((PBYTE) Real_WriteFileEx, (PBYTE) My_WriteFileEx);
|
||
|
DetourFunctionWithTrampoline((PBYTE) Real_ReadFile, (PBYTE) My_ReadFile);
|
||
|
DetourFunctionWithTrampoline((PBYTE) Real_ReadFileEx, (PBYTE) My_ReadFileEx);
|
||
|
DetourFunctionWithTrampoline((PBYTE) Real_DeviceIoControl, (PBYTE) My_DeviceIoControl);
|
||
|
DetourFunctionWithTrampoline((PBYTE) Real_CloseHandle, (PBYTE) My_CloseHandle);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD dwReason, PVOID lpReserved)
|
||
|
{
|
||
|
switch (dwReason) {
|
||
|
case DLL_PROCESS_ATTACH:
|
||
|
PrepareLogger();
|
||
|
break;
|
||
|
case DLL_PROCESS_DETACH:
|
||
|
CloseLog();
|
||
|
break;
|
||
|
case DLL_THREAD_ATTACH:
|
||
|
case DLL_THREAD_DETACH:
|
||
|
break;
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
// This is necessary for detours static injection code (see detours
|
||
|
// code if you need to understand why)
|
||
|
void NullExport()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|