denyhosts/clamav/common/scanmem.c

711 lines
21 KiB
C

/*
* Copyright (C) 2021-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
* Copyright (C) 2005-2010 Gianluigi Tiesi <sherpya@netfarm.it>
*
* 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 <windows.h>
#include <tlhelp32.h>
#include <psapi.h>
#include <windns.h>
#include <clamav.h>
#include <others.h>
#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;
}