/* * Copyright (C) 2021-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved. * Copyright (C) 2005-2010 Gianluigi Tiesi * * Authors: Gianluigi Tiesi * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include #include #include #include #include #include #include "actions.h" #include "output.h" #include "clamdcom.h" #include "exescanner.h" #include "scanmem.h" typedef int (*proc_callback)(PROCESSENTRY32 ProcStruct, MODULEENTRY32 me32, void *data, struct mem_info *info); int sock; struct optstruct *clamdopts; static inline int lookup_cache(filelist_t **list, const char *filename) { filelist_t *current = *list; while (current) { /* Cache hit */ if (!_stricmp(filename, current->filename)) { return current->res; } current = current->next; } return -1; } static inline void insert_cache(filelist_t **list, const char *filename, int res) { filelist_t *current = *list, *prev = NULL; if (!current) /* New */ *list = current = malloc(sizeof(filelist_t)); else { while (current->next) current = current->next; prev = current; prev->next = current = malloc(sizeof(filelist_t)); } current->next = NULL; current->res = res; current->filename[0] = 0; strncat(current->filename, filename, MAX_PATH - 1 - strlen(current->filename)); current->filename[MAX_PATH - 1] = 0; } static inline void free_cache(filelist_t **list) { filelist_t *current, *prev; current = prev = *list; if (!current) return; do { prev = current; current = prev->next; free(prev); } while (current); } static inline char *wc2mb(const wchar_t *wc, DWORD flags) { BOOL invalid = FALSE; DWORD len = 0, res = 0; char *mb = NULL; len = WideCharToMultiByte(CP_ACP, flags, wc, -1, NULL, 0, NULL, &invalid); if (!len && (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { fprintf(stderr, "WideCharToMultiByte() failed with %d\n", GetLastError()); return NULL; } mb = cli_malloc(len + 1); if (!mb) return NULL; res = WideCharToMultiByte(CP_ACP, flags, wc, -1, mb, len, NULL, &invalid); if (res && ((!invalid || (flags != WC_NO_BEST_FIT_CHARS)))) return mb; free(mb); return NULL; } /* Needed to Scan System Processes */ int EnablePrivilege(LPCSTR PrivilegeName, DWORD yesno) { HANDLE hToken; TOKEN_PRIVILEGES tp; LUID luid; if (!LoadLibraryA("advapi32.dll")) { logg(LOGG_WARNING, "EnablePrivilege functions are missing\n"); return 0; } if (!OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_READ, &hToken)) return 0; if (!LookupPrivilegeValue(NULL, PrivilegeName, &luid)) return 0; tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = yesno; AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL); CloseHandle(hToken); return (GetLastError() == ERROR_SUCCESS) ? 1 : 0; } static char *getaltpath(const wchar_t *filename) { WIN32_FIND_DATAW wfdw; HANDLE hf = INVALID_HANDLE_VALUE; wchar_t *part = _wcsdup(filename); wchar_t comprev[MAX_PATH + 1] = L"", compose[MAX_PATH + 1]; wchar_t *rev = comprev, *slash = part, *c = NULL; size_t l, la; size_t i; do { if (slash != part) *slash = 0; /* c: d: etc */ if ((wcslen(part) == 2) && (part[1] == L':')) { *rev++ = L':'; *rev++ = part[0]; break; } hf = FindFirstFileW(part, &wfdw); if (hf == INVALID_HANDLE_VALUE) /* Network path */ { for (i = wcslen(part); i > 0; i--) *rev++ = part[i - 1]; break; } FindClose(hf); l = wcslen(wfdw.cFileName); la = wcslen(wfdw.cAlternateFileName); if (la) for (i = la; i > 0; i--) *rev++ = *(wfdw.cAlternateFileName + i - 1); else for (i = l; i > 0; i--) *rev++ = *(wfdw.cFileName + i - 1); *rev++ = '\\'; } while ((slash = wcsrchr(part, L'\\'))); rev = comprev; c = compose; for (i = wcslen(rev); i > 0; i--) *c++ = *(rev + i - 1); *c = 0; free(part); return wc2mb(compose, WC_NO_BEST_FIT_CHARS); } int walkmodules_th(proc_callback callback, void *data, struct mem_info *info) { HANDLE hSnap = INVALID_HANDLE_VALUE, hModuleSnap = INVALID_HANDLE_VALUE; PROCESSENTRY32 ps; MODULEENTRY32 me32; logg(LOGG_INFO, " *** Memory Scan: using ToolHelp ***\n\n"); hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnap == INVALID_HANDLE_VALUE) return -1; ps.dwSize = sizeof(PROCESSENTRY32); if (!Process32First(hSnap, &ps)) { CloseHandle(hSnap); return -1; } do { /* system process */ if (!ps.th32ProcessID) continue; hModuleSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, ps.th32ProcessID); if (hModuleSnap == INVALID_HANDLE_VALUE) continue; me32.dwSize = sizeof(MODULEENTRY32); if (!Module32First(hModuleSnap, &me32)) { CloseHandle(hModuleSnap); continue; } /* Check and transform non ANSI filenames to ANSI using altnames */ if (GetModuleFileNameEx) { HANDLE hFile = CreateFile( me32.szExePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL); if (hFile == INVALID_HANDLE_VALUE) { DWORD err = GetLastError(); wchar_t name[MAX_PATH + 1]; char *converted = NULL; HANDLE p; if (err == ERROR_BAD_NETPATH) { logg(LOGG_WARNING, "Warning scanning files on non-ansi network paths is not " "supported\n"); logg(LOGG_WARNING, "File: %s\n", me32.szExePath); continue; } if ((err != ERROR_INVALID_NAME) && (err != ERROR_PATH_NOT_FOUND)) { logg(LOGG_WARNING, "Expected ERROR_INVALID_NAME/ERROR_PATH_NOT_FOUND but got %d\n", err); continue; } p = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, ps.th32ProcessID); if (!GetModuleFileNameEx(p, NULL, name, MAX_PATH)) { logg(LOGG_WARNING, "GetModuleFileNameExW() failed %d\n", GetLastError()); CloseHandle(p); continue; } CloseHandle(p); if (!(converted = getaltpath(name))) { logg(LOGG_WARNING, "Cannot map filename to ANSI codepage\n"); continue; } strcpy(me32.szExePath, converted); free(converted); } else CloseHandle(hFile); } do if (callback(ps, me32, data, info)) break; while (Module32Next(hModuleSnap, &me32)); CloseHandle(hModuleSnap); } while (Process32Next(hSnap, &ps)); CloseHandle(hSnap); return 0; } int walkmodules_psapi(proc_callback callback, void *data, struct mem_info *info) { DWORD procs[1024], needed, nprocs, mneeded; HANDLE hProc; HMODULE mods[1024]; PROCESSENTRY32 ps; MODULEENTRY32 me32; MODULEINFO mi; int i, j; logg(LOGG_INFO, " *** Memory Scan: using PsApi ***\n\n"); if (!EnumProcesses(procs, sizeof(procs), &needed)) return -1; nprocs = needed / sizeof(DWORD); memset(&ps, 0, sizeof(PROCESSENTRY32)); memset(&me32, 0, sizeof(MODULEENTRY32)); ps.dwSize = sizeof(PROCESSENTRY32); me32.dwSize = sizeof(MODULEENTRY32); for (i = 0; i < nprocs; i++) { if (!procs[i]) continue; /* System process */ hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, procs[i]); if (!hProc) continue; if (!EnumProcessModules(hProc, mods, sizeof(mods), &mneeded)) { CloseHandle(hProc); continue; } if (!GetModuleBaseName(hProc, mods[0], ps.szExeFile, MAX_PATH - 1)) { CloseHandle(hProc); continue; } ps.th32ProcessID = procs[i]; for (j = 0; j < (mneeded / sizeof(HMODULE)); j++) { if (!GetModuleBaseNameA(hProc, mods[j], me32.szModule, MAX_PATH - 1)) continue; if (!GetModuleFileNameExA(hProc, mods[j], me32.szExePath, MAX_PATH - 1)) continue; if (!GetModuleInformation(hProc, mods[j], &mi, sizeof(mi))) continue; me32.hModule = mods[j]; me32.th32ProcessID = procs[i]; me32.modBaseAddr = mi.lpBaseOfDll; me32.modBaseSize = mi.SizeOfImage; if (callback(ps, me32, data, info)) break; } CloseHandle(hProc); } return 0; } int kill_process(DWORD pid) { HANDLE hProc; if (GetCurrentProcessId() == pid) { logg(LOGG_WARNING, "Don't want to kill myself\n"); return 1; } if ((hProc = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, pid))) { TerminateProcess(hProc, 0); if (WaitForSingleObject(hProc, TIMEOUT_MODULE) != WAIT_OBJECT_0) logg(LOGG_WARNING, "Unable to unload process from memory\n"); CloseHandle(hProc); } else logg(LOGG_WARNING, "OpenProcess() failed %lu\n", GetLastError()); return 1; /* Skip to next process anyway */ } /* Not so safe ;) */ int unload_module(DWORD pid, HANDLE hModule) { DWORD rc = 1; HANDLE ht; HANDLE hProc; if (GetCurrentProcessId() == pid) { logg(LOGG_WARNING, "Don't want to unload modules from myself\n"); return 1; } hProc = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, pid); if (!hProc) { logg(LOGG_WARNING, "OpenProcess() failed %lu\n", GetLastError()); return 1; /* Skip to next process */ } if ((ht = CreateRemoteThread( hProc, 0, 0, (LPTHREAD_START_ROUTINE)FreeLibrary, hModule, 0, &rc))) { if (WaitForSingleObject(ht, TIMEOUT_MODULE) == WAIT_TIMEOUT) { CloseHandle(ht); CloseHandle(hProc); logg(LOGG_INFO, "The module may trying to trick us, killing the process, please " "rescan\n"); return kill_process(pid); } CloseHandle(ht); rc = 0; /* Continue scanning this process */ } else { DWORD res = GetLastError(); if (res == ERROR_CALL_NOT_IMPLEMENTED) { logg(LOGG_WARNING, "Module unloading is not supported on this OS\n"); rc = -1; /* Don't complain about removing/moving the file */ } else { logg(LOGG_ERROR, "CreateRemoteThread() failed %lu\n", res); rc = 1; /* Skip to next process */ } } CloseHandle(hProc); return rc; } #define FILLBYTES(dst) \ if (IsBadReadPtr(seek, sizeof(dst))) { \ logg(LOGG_ERROR, "ScanMem Align: Bad pointer!!!\n"); \ return 1; \ } \ memcpy(&dst, seek, sizeof(dst)) /* PE Realignment - FIXME: a lot of code is copy/paste from exeScanner.c */ int align_pe(unsigned char *buffer, size_t size) { int i = 0; uint16_t e_mz; uint32_t e_lfanew, e_magic; unsigned char *seek = buffer; PIMAGE_FILE_HEADER pehdr; PIMAGE_OPTIONAL_HEADER32 opthdr; PIMAGE_SECTION_HEADER sechdr; FILLBYTES(e_mz); if (e_mz != IMAGE_DOS_SIGNATURE) { /* cli_dbgmsg("ScanMem Align: DOS Signature not found\n"); */ return 0; } seek += 0x3c; FILLBYTES(e_lfanew); if (!e_lfanew) { /* cli_dbgmsg("ScanMem Align: Invalid PE offset\n"); */ return 0; } seek = buffer + e_lfanew; /* PE Signature 'PE' */ FILLBYTES(e_magic); if (e_magic != IMAGE_NT_SIGNATURE) { /* cli_dbgmsg("ScanMem Align: PE Signature not found\n"); */ return 0; } seek += sizeof(e_magic); if (IsBadReadPtr(seek, sizeof(IMAGE_FILE_HEADER))) return 0; pehdr = (PIMAGE_FILE_HEADER)seek; seek += sizeof(IMAGE_FILE_HEADER); if (IsBadReadPtr(seek, sizeof(IMAGE_OPTIONAL_HEADER32))) return 0; opthdr = (PIMAGE_OPTIONAL_HEADER32)seek; seek += sizeof(IMAGE_OPTIONAL_HEADER32); /* Invalid sections number */ if ((pehdr->NumberOfSections < 1) || (pehdr->NumberOfSections > 32)) { /* cli_dbgmsg("ScanMem Align: Invalid sections number\n"); */ return 0; } for (i = 0; i < pehdr->NumberOfSections; i++) { if (IsBadWritePtr(seek, sizeof(IMAGE_SECTION_HEADER))) return 0; sechdr = (PIMAGE_SECTION_HEADER)seek; seek += sizeof(IMAGE_SECTION_HEADER); sechdr->PointerToRawData = sechdr->VirtualAddress; sechdr->SizeOfRawData = sechdr->Misc.VirtualSize; } return 1; } int dump_pe(const char *filename, PROCESSENTRY32 ProcStruct, MODULEENTRY32 me32) { #ifdef _WIN64 /* MinGW has a broken header for ReadProcessMemory() */ size_t bytesread = 0; #else DWORD bytesread = 0; #endif DWORD byteswrite = 0; int ret = -1; HANDLE hFile = INVALID_HANDLE_VALUE, hProc = NULL; unsigned char *buffer = NULL; if (!(hProc = OpenProcess(PROCESS_VM_READ, FALSE, ProcStruct.th32ProcessID))) return -1; buffer = malloc((size_t)me32.modBaseSize); if (!ReadProcessMemory(hProc, me32.modBaseAddr, buffer, (size_t)me32.modBaseSize, &bytesread)) { free(buffer); CloseHandle(hProc); return ret; } CloseHandle(hProc); /* PE Realignment */ align_pe(buffer, me32.modBaseSize); hFile = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { logg(LOGG_INFO, "Error creating %s\n", filename); free(buffer); return ret; } if (WriteFile(hFile, buffer, (DWORD)bytesread, &byteswrite, NULL)) ret = _open_osfhandle((intptr_t)hFile, O_RDONLY | O_BINARY); free(buffer); return ret; } int scanfile(const char *filename, scanmem_data *scan_data, struct mem_info *info) { int fd; int scantype; int ret = CL_CLEAN; const char *virname = NULL; logg(LOGG_DEBUG, "Scanning %s\n", filename); if ((fd = safe_open(filename, O_RDONLY | O_BINARY)) == -1) { logg(LOGG_WARNING, "Can't open file %s, %s\n", filename, strerror(errno)); return -1; } if (info->d) { // clamdscan if (optget(info->opts, "stream")->enabled) scantype = STREAM; else if (optget(info->opts, "multiscan")->enabled) scantype = MULTI; else if (optget(info->opts, "allmatch")->enabled) scantype = ALLMATCH; else scantype = CONT; if ((sock = dconnect(clamdopts)) < 0) { info->errors++; return -1; } if (dsresult(sock, scantype, filename, NULL, &info->errors, clamdopts) > 0) { info->ifiles++; ret = CL_VIRUS; } } else { // clamscan ret = cl_scandesc(fd, filename, &virname, &info->blocks, info->engine, info->options); if (ret == CL_VIRUS) { logg(LOGG_INFO, "%s: %s FOUND\n", filename, virname); info->ifiles++; } else if (scan_data->printclean) { logg(LOGG_INFO, "%s: OK \n", filename); } } close(fd); return ret; } int scanmem_cb(PROCESSENTRY32 ProcStruct, MODULEENTRY32 me32, void *data, struct mem_info *info) { scanmem_data *scan_data = data; int rc = 0; int isprocess = 0; char modulename[MAX_PATH] = ""; char expandmodule[MAX_PATH] = ""; if (!scan_data) return 0; scan_data->res = CL_CLEAN; modulename[0] = 0; /* Special case, btw why I get \SystemRoot\ in process szExePath? There are also other cases? */ if ((strlen(me32.szExePath) > 12) && !strncmp(me32.szExePath, "\\SystemRoot\\", 12)) { expandmodule[0] = 0; strncat(expandmodule, me32.szExePath, MAX_PATH - 1 - strlen(expandmodule)); expandmodule[MAX_PATH - 1] = 0; snprintf(expandmodule, MAX_PATH - 1, "%%SystemRoot%%\\%s", &me32.szExePath[12]); expandmodule[MAX_PATH - 1] = 0; ExpandEnvironmentStrings(expandmodule, modulename, MAX_PATH - 1); modulename[MAX_PATH - 1] = 0; } if (!modulename[0]) { strncpy(modulename, me32.szExePath, MAX_PATH - 1); modulename[MAX_PATH - 1] = 0; } scan_data->res = lookup_cache(&scan_data->files, modulename); isprocess = !_stricmp(ProcStruct.szExeFile, modulename) || !_stricmp(ProcStruct.szExeFile, me32.szModule); if (scan_data->res == -1) { if (isprocess) scan_data->processes++; else scan_data->modules++; info->files++; /* check for module exclusion */ scan_data->res = CL_CLEAN; if (!(scan_data->exclude && chkpath(modulename, clamdopts))) scan_data->res = scanfile(modulename, scan_data, info); if ((scan_data->res != CL_VIRUS) && is_packed(modulename)) { char *dumped = cli_gentemp(NULL); int fd = -1; if ((fd = dump_pe(dumped, ProcStruct, me32)) > 0) { close(fd); scan_data->res = scanfile(dumped, scan_data, info); DeleteFile(dumped); } free(dumped); } insert_cache(&scan_data->files, modulename, scan_data->res); } if (scan_data->res == CL_VIRUS) { if (isprocess && scan_data->kill) { logg(LOGG_INFO, "Unloading program %s from memory\n", modulename); rc = kill_process(ProcStruct.th32ProcessID); } else if (scan_data->unload) { logg(LOGG_INFO, "Unloading module %s from %s\n", me32.szModule, modulename); if ((rc = unload_module(ProcStruct.th32ProcessID, me32.hModule)) == -1) /* CreateProcessThread() is not implemented */ return 0; } if (action) action(modulename); return rc; } return rc; } int scanmem(struct mem_info *info) { scanmem_data data; data.files = NULL; data.printclean = 1; data.kill = 0; data.unload = 0; data.exclude = 0; data.res = CL_CLEAN; data.processes = 0; data.modules = 0; HMODULE psapi_ok = LoadLibrary("psapi.dll"); HMODULE k32_ok = LoadLibrary("kernel32.dll"); if (!(psapi_ok || k32_ok)) { logg(LOGG_INFO, " *** Memory Scanning is not supported on this OS ***\n\n"); return -1; } if (optget(info->opts, "infected")->enabled) data.printclean = 0; if (optget(info->opts, "kill")->enabled) data.kill = 1; if (optget(info->opts, "unload")->enabled) data.unload = 1; if (optget(info->opts, "exclude")->enabled) data.exclude = 1; if (info->d) { if ((sock = dconnect(clamdopts)) < 0) { info->errors++; return -1; } } logg(LOGG_INFO, " *** Scanning Programs in Computer Memory ***\n"); if (!EnablePrivilege(SE_DEBUG_NAME, SE_PRIVILEGE_ENABLED)) logg(LOGG_INFO, "---Please login as an Administrator to scan System processes loaded " "in computer memory---\n"); if (k32_ok) walkmodules_th(scanmem_cb, (void *)&data, info); else walkmodules_psapi(scanmem_cb, (void *)&data, info); free_cache(&data.files); logg(LOGG_INFO, "\n *** Scanned %lu processes - %lu modules ***\n", data.processes, data.modules); logg(LOGG_INFO, " *** Computer Memory Scan Completed ***\n\n"); return data.res; }