/* * Copyright (c) 2001-2004 Andy Polyakov * * Build with: * * cl -Ox -GD -GF -Zl -MD -LD DLL_PRELOAD.c advapi32.lib kernel32.lib * * See http://fy.chalmers.se/~appro/nt/DLL_PRELOAD/ for further details. * */ #ifndef _DLL #error "_DLL is not defined." #endif #ifdef _WIN64 #pragma comment(linker,"/entry:DllMain") #pragma comment(linker,"/base:0x7E000000") #pragma comment(linker,"/merge:.rdata=.text") #else #pragma comment(linker,"/entry:DllMain@12") #pragma comment(linker,"/base:0x7F000000") #pragma comment(linker,"/align:0x2000") #pragma comment(linker,"/section:.text,erw") #pragma comment(linker,"/merge:.rdata=.text") #pragma comment(linker,"/merge:.data=.text") #endif #define UNICODE #define _UNICODE #if defined(WIN32) && !defined(_WIN32) #define _WIN32 #endif #include #include #include #include #include #define SIZEOF_A(a) (sizeof(a)/sizeof(a[0])) /* * Implement some trivia in order to be excused from linking with MSVCRT. */ static void _memmove (void *dst, void *src, size_t n) { unsigned char *d=dst,*s=src; while (n--) *d++ = *s++; return; } static size_t _lstrlen (TCHAR *str) { int len=0; while (*str) { str++, len++; } return len; } static TCHAR *_lstrrchr (TCHAR *str,TCHAR c) { TCHAR *p = NULL; while (*str) { if (*str == c) p = str; str++; } return p; } static TCHAR *_lstrchr (TCHAR *str,TCHAR c) { TCHAR *p = NULL; while (*str) { if (*str == c) { p = str; break; } str++; } return p; } static TCHAR *_lstrncpy (TCHAR *dst,TCHAR *src,size_t n) { TCHAR *ret=dst; while(--n && *src) { *dst++ = *src++; } *dst=_T('\0'); return ret; } static HINSTANCE loaded_libs [256]; static int n_loaded_libs = 0; static void _load_lib (TCHAR *str,DWORD regtype) { HINSTANCE h; if (n_loaded_libs < SIZEOF_A(loaded_libs)) #if 1 { TCHAR path[MAX_PATH]; DWORD len; if (regtype==REG_EXPAND_SZ) { len=ExpandEnvironmentStrings (str,path,SIZEOF_A(path)); if (len==0 || len>SIZEOF_A(path)) { OutputDebugString (_T("Pathname is too long, ignoring...")); return; } str=path; } if (h=LoadLibrary (str)) loaded_libs [n_loaded_libs++] = h; } #else { TCHAR path[MAX_PATH],*bgn,*end,*p=path; size_t sz=SIZEOF_A(path),l; while ( sz>1 && (bgn=_lstrchr(str,_T('%'))) && (end=_lstrchr(bgn+1,_T('%'))) ) { *bgn=_T('\0'); _lstrncpy(p,str,sz); *bgn=_T('%'); l=_lstrlen(p); p+=l, sz-=l; *end=_T('\0'); GetEnvironmentVariable(bgn+1,p,sz); *end=_T('%'); l=_lstrlen(p); p+=l, sz-=l; str=end+1; } if (p==path) p=str; else _lstrncpy(p,str,sz), p=path; if (h=LoadLibrary (p)) loaded_libs [n_loaded_libs++] = h; } #endif else OutputDebugString (_T("Too many DLLs to load, ignoring...")); return; } static int _load_libs (TCHAR *str,DWORD regtype) { TCHAR *p; while (p = _lstrchr (str,_T(';'))) { if (p[1]==_T('-')) return -1; p[0] = _T('\0'); _load_lib (str,regtype); p[0] = _T(';'); str = p+1; } if (str[0]==_T('-')) return -1; _load_lib (str,regtype); return 0; } static int _load_by_key (HKEY hkey,TCHAR *val) { TCHAR str[1024]; LONG sz,ret; DWORD regtype; sz = sizeof(str); ret = RegQueryValueEx (hkey,val,NULL,®type,(BYTE *)str,&sz); if (ret == ERROR_FILE_NOT_FOUND) return 1; /* retry */ else if (ret == ERROR_SUCCESS) { if (regtype==REG_SZ || regtype==REG_EXPAND_SZ) sz /= sizeof(str[0]), sz = sz>=SIZEOF_A(str) ? SIZEOF_A(str)-1 : sz, str[sz]=_T('\0'), ret=_load_libs (str,regtype); } return ret; } static BOOL SkipHKCU() /* most notably when running as local system */ { HANDLE htoken=NULL; struct { TOKEN_USER tu; BYTE sid[64]; } token; DWORD sz; void *localsyssid; BOOL ret; if (!OpenProcessToken (GetCurrentProcess(),TOKEN_QUERY,&htoken)) return TRUE; ret=GetTokenInformation (htoken,TokenUser,&token,sizeof(token),&sz); CloseHandle (htoken); if (!ret) return TRUE; { SID_IDENTIFIER_AUTHORITY ntauth = SECURITY_NT_AUTHORITY; if (!AllocateAndInitializeSid (&ntauth, 1,SECURITY_LOCAL_SYSTEM_RID, 0,0,0,0,0,0,0,&localsyssid)) return TRUE; } ret=EqualSid (localsyssid,token.tu.User.Sid); FreeSid(localsyssid); return ret; } #define MY_ENVNAM _T("DLL_PRELOAD") #define MY_REGKEY \ _T("Software\\Microsoft\\Windows NT\\CurrentVersion\\DLL_PRELOAD") #if defined(_M_IX86) #define I_BUNDLE 8 /* (2*sizeof(unsigned long)) */ #elif defined(_M_IA64) #define I_BUNDLE 64 /* 3 bundles + function pointer + gp */ #elif defined(_M_AMD64) #define I_BUNDLE 16 /* mov rax,imm64; call *rax */ #endif static unsigned char saved_code [I_BUNDLE]; void _pre_load (void *entry_code) { DWORD acc; TCHAR str[256],exe[64],*s; int ret; HKEY hkey,hkey1; /* * Put the original machine code back to entry point. We managed * to make the code segment writable once, we should manage fine * now too, so I don't care about the return value... */ VirtualProtect (entry_code,I_BUNDLE,PAGE_EXECUTE_WRITECOPY,&acc); _memmove (entry_code,saved_code,I_BUNDLE); VirtualProtect (entry_code,I_BUNDLE,acc,&acc); FlushInstructionCache (GetCurrentProcess(),entry_code,I_BUNDLE); /* * Load libraries */ if (!GetModuleFileName (NULL,str,SIZEOF_A(str))) return; (s = _lstrrchr (str,_T('\\'))) ? s++ : (s=str); _lstrncpy (exe,s,SIZEOF_A(exe)); if (RegOpenKeyEx (HKEY_LOCAL_MACHINE,MY_REGKEY,0,KEY_QUERY_VALUE,&hkey) == ERROR_SUCCESS) { if ((ret=_load_by_key (hkey,_T("")))>=0 && /* all apps!!! */ (ret=_load_by_key (hkey,exe))>0 ) { if (RegOpenKeyEx (hkey,exe,0,KEY_QUERY_VALUE,&hkey1) == ERROR_SUCCESS) ret=_load_by_key (hkey1,_T("")), RegCloseKey(hkey1); } RegCloseKey (hkey); } if (ret<0) return; /* '-' was spotted, back off */ if (SkipHKCU()) return; if (RegOpenKeyEx (HKEY_CURRENT_USER,MY_REGKEY,0,KEY_QUERY_VALUE,&hkey) == ERROR_SUCCESS) { if (_load_by_key (hkey,exe)) { if (RegOpenKeyEx (hkey,exe,0,KEY_QUERY_VALUE,&hkey1) == ERROR_SUCCESS) _load_by_key (hkey1,_T("")), RegCloseKey(hkey1); } RegCloseKey (hkey); } if ((ret = GetEnvironmentVariable (MY_ENVNAM,str,SIZEOF_A(str))) > 0) if (ret < SIZEOF_A(str)) _load_libs (str,REG_SZ); else OutputDebugString (_T("DLL_PRELOAD: Environment variable is too long")); return; } #if defined(_M_IA64) void _my_entry_code(),_my_cutin_code(); #elif defined(_M_AMD64) void _my_entry_code(); #elif defined(_M_IX86) #pragma optimize("",off) static void _my_entry_code (unsigned long arg) { _asm { pushfd pushad lea eax,arg mov ebx,[eax-4] ; fetch the return address sub ebx,I_BUNDLE ; and rebias it back to the mov [eax-4],ebx ; original entry point push ebx ; NB!!! it's _pre_load that is call _pre_load ; responsible for putting the add esp,4 ; original machine code back!!! popad popfd } } #pragma optimize("",on) #endif BOOL WINAPI DllMain (HINSTANCE h, DWORD reason, LPVOID junk) { DWORD acc; HMODULE hmod; IMAGE_DOS_HEADER *dos_header; IMAGE_NT_HEADERS *nt_headers; void *entry_code; int i; switch (reason) { case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls(h); if ((hmod = GetModuleHandle (NULL)) == NULL) { OutputDebugString (_T("DLL_PRELOAD: Unable to GetModuleHandle(NULL)")); return FALSE; } dos_header = (IMAGE_DOS_HEADER *)hmod; /* Sanity check */ if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) { OutputDebugString (_T("DLL_PRELOAD: DOS Signature mismatch")); return FALSE; } nt_headers = (IMAGE_NT_HEADERS *)((char *)hmod + dos_header->e_lfanew); /* More sanity checks */ if (nt_headers->Signature != IMAGE_NT_SIGNATURE) { OutputDebugString (_T("DLL_PRELOAD: NT/PE Signature mismatch")); return FALSE; } /* * Find the entry point and distance to _my_entry_code() */ entry_code = ((unsigned char *)hmod + nt_headers->OptionalHeader.AddressOfEntryPoint); #if defined(_WIN64) && defined(_M_IA64) if (nt_headers->FileHeader.Machine != IMAGE_FILE_MACHINE_IA64) { OutputDebugString (_T("DLL_PRELOAD: Unsupported architecture")); return FALSE; } entry_code = *(void **)entry_code; #elif defined(_WIN64) && defined(_M_AMD64) if (nt_headers->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64) { OutputDebugString (_T("DLL_PRELOAD: Unsupported architecture")); return FALSE; } #elif defined(_WIN32) && defined(_M_IX86) if (nt_headers->FileHeader.Machine != IMAGE_FILE_MACHINE_I386) { OutputDebugString (_T("DLL_PRELOAD: Unsupported architecture")); return FALSE; } #else #error "Unsupported architecture." #endif _memmove (saved_code,entry_code,I_BUNDLE); if (VirtualProtect (entry_code,I_BUNDLE,PAGE_EXECUTE_WRITECOPY,&acc) == 0) { OutputDebugString (_T("DLL_PRELOAD: Unable to modify the code segment")); return FALSE; } #if defined(_M_IA64) /* * Pick first three bundles at _my_entry_code. Note that * _my_entry_code as seen by C code is not a pointer to first * instruction, but a pointer to a structure comprising * pointer to instruction and corresponding gp value. */ _memmove (entry_code,*(void**)_my_entry_code,48); /* ...append _my_cutin_code's descriptor. */ _memmove ((void**)entry_code+48/sizeof(void*),_my_cutin_code,16); #elif defined(_M_AMD64) { unsigned long *machine_code=entry_code; void *proc64 = _my_entry_code; /* * Replace first 16 bytes at entry point with * "nop; nop; mov $proc64,%rax; nop; nop; call *%rax" */ machine_code [0] = 0xB8489090U; machine_code [1] = (unsigned long)proc64; machine_code [2] = (unsigned long)(proc64>>32); machine_code [3] = 0xD0FF9090U; } #elif defined(_M_IX86) { unsigned long *machine_code=entry_code; size_t e8_offset; /* * Replace first 8 bytes at entry point with * "lea 0(%esi),%esi; call _my_entry_point" intructions */ e8_offset = ( (size_t)_my_entry_code - ((size_t)entry_code + I_BUNDLE) ); machine_code [0] = 0xE800768DU; machine_code [1] = e8_offset; } #endif VirtualProtect (entry_code,I_BUNDLE,acc,&acc); FlushInstructionCache (GetCurrentProcess(),entry_code,I_BUNDLE); break; case DLL_PROCESS_DETACH: for (i=n_loaded_libs-1;i>=0;i--) FreeLibrary (loaded_libs[i]); n_loaded_libs=0; break; } return TRUE; }