#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 #include #include #include #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() { }