通过程序行为追踪揪出木马病毒 【 大成天下 黄鑫 】

  • 文章出处:大成天下   发布时间:2006-02-02   网址:http://www.unnoo.com

 

  作者:黄鑫(glacier@unnoo.com

  对木马类程序处理多了,就渐渐觉得静/动态手工分析过程在很大程度上都是重复劳动。总要先花半个钟头了解程序特性,手工分析时还生怕漏掉某项隐蔽的关键操作,导致最终清除不彻底。其实只要在主动安装木马的时候将API调用序列及相应参数做完整记录,就能极大减轻分析和清除木马的工作量。

  以前曾写过一个利用 API HOOKING 原理记录可疑程序对文件、注册表、服务和网络操作的小工具。API HOOKING 方式的优点在于,当调用 CreateFile 时可将文件名与句柄关联,等调用 WriteFile 对句柄操作时便能轻易取到文件名,对 hKey、socket 等句柄操作亦是如此。但该方式的缺点也显而易见,首先必须为每个感兴趣的 API 函数编写代码,“体力工作”繁重;其次我们不可能 HOOK 所有的 API 函数,由于缺乏完整的 API 调用序列作参考,在分析日志时很可能漏掉某些小动作。
  另一种思路是采用调试技术,在所有被引入的 DLL 的各函数入口处预先设置断点,调试期间再通过堆栈信息获取参数。IDA pro 和 OllyDbg 都可用于动态调试,同时还提供了脚本/插件功能。上周在北京开会的时候,我利用酒醒的时间写了一个简单的 OllyDbg 插件,仅从 CALL 指令处通过 ESP 指针获取8个函数参数,不对函数返回后的 EAX 及堆栈内容进行记录,在对普通(未加壳)程序的测试中效果还算理想。只要先在“Search for -> All intermodular calls”窗口中执行“Set breakpoint on every command”设置断点,再运行插件的“Fast trace”功能即可。日志文件片断如下:

  注:本文所有单引号用’替代。

  -------------------------------------------------------------------
  004099EC: CALL DWORD PTR DS:[<&KERNEL32.GetModuleFileNameA>] (kernel32.GetModuleFileNameA)
  -------------------------------------------------------------------
    ESP+00 (0012F704): 00000000
    ESP+04 (0012F708): 0012F824    ""
    ESP+08 (0012F70C): 00000104    00000104 ???
    ESP+0C (0012F710): 0012FA6D    ""
    ESP+10 (0012F714): 00000001    00000001 ???
    ESP+14 (0012F718): 00000000
    ESP+18 (0012F71C): 575C3A43    575C3A43 ???
    ESP+1C (0012F720): 4F444E49    4F444E49 ???
  
  -------------------------------------------------------------------
  00409A00: CALL DWORD PTR DS:[<&KERNEL32.CopyFileA>] (kernel32.CopyFileA)
  -------------------------------------------------------------------
    ESP+00 (0012F704): 0012F824    "E: rojan.exe"
    ESP+04 (0012F708): 0012F71C    "C:WINDOWSsystem32 rojan.exe"
    ESP+08 (0012F70C): 00000000
    ESP+0C (0012F710): 0012FA6D    ""
    ESP+10 (0012F714): 00000001    00000001 ???
    ESP+14 (0012F718): 00000000
    ESP+18 (0012F71C): 575C3A43    575C3A43 ???
    ESP+1C (0012F720): 4F444E49    4F444E49 ???
  
  -------------------------------------------------------------------
  00409A94: CALL DWORD PTR DS:[<&ADVAPI32.OpenSCManagerA>] (ADVAPI32.OpenSCManagerA)
  -------------------------------------------------------------------
    ESP+00 (0012F704): 00000000
    ESP+04 (0012F708): 00000000
    ESP+08 (0012F70C): 000F003F    000F003F ???
    ESP+0C (0012F710): 0012FA6D    ""
    ESP+10 (0012F714): 00000001    00000001 ???
    ESP+14 (0012F718): 00000000
    ESP+18 (0012F71C): 575C3A43    575C3A43 ???
    ESP+1C (0012F720): 4F444E49    4F444E49 ???
  
  -------------------------------------------------------------------
  00409ACF: CALL DWORD PTR DS:[<&ADVAPI32.CreateServiceA>] (ADVAPI32.CreateServiceA)
  -------------------------------------------------------------------
    ESP+00 (0012F6DC): 0014F9C0
      F8 F9 14 00 98 BA DC FE 00 00 00 00 B4 F9 CC 53   ...............S
      82 6C FC 42 BF 8C 55 14 00 44 14 F4 AB AB AB AB   .l.B..U..D......
      AB AB AB AB EE FE EE FE 00 00 00 00 00 00 00 00   ................
      20 00 07 00 09 07 18 00 58 FA C3 77 EF CD AB 89   .......X..w....
      00 00 01 00 01 00 00 00 00 00 00 00 00 00 00 00   ................
      00 00 00 00 00 00 00 00 05 00 00 00 01 00 00 00   ................
      00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
      06 00 00 00 0A 00 00 00 00 00 00 00 30 FB 14 00   ............0...
    ESP+04 (0012F6E0): 0042008C    "trojan"
    ESP+08 (0012F6E4): 004200C0    "Back door for testing"
    ESP+0C (0012F6E8): 000F01FF    000F01FF ???
    ESP+10 (0012F6EC): 00000120    00000120 ???
    ESP+14 (0012F6F0): 00000002    00000002 ???
    ESP+18 (0012F6F4): 00000001    00000001 ???
    ESP+1C (0012F6F8): 0012F71C    "C:WINDOWSsystem32 rojan.exe -start"
  
    ......

  这种简单粗糙的日志对我来说已经够用了。若希望以更友好的形式显示参数信息,就必须有一些数据文件来描述各 API 函数的调用方式、返回值类型、参数个数等内容。比如这样:

  int LoadLibraryA([in] char *lpLibFileName);
  int LoadLibraryW([in] wchar *lpLibFileName);
  void *GetProcAddress([in] int hModule, [in] char *lpProcName);
  int GetModuleFileNameA([in] int hModule, [out] char *lpFilename, [in] int nSize);
  int GetModuleFileNameW([in] int hModule, [out] wchar *lpFilename, [in] int nSize);

  编写一个简单的词法解析模块直接解析 VC 自带的 .h 文件,对使用者来说就更省事了。经过参数类型解析后的输出信息会好看很多:

  ------------------------------------------------------
  004099F2 -> GetModuleFileNameA(
    int             hModule: 0 (unsigned = 0 / hex = 0),
    char*          lpFilename: [0012F824] = "",
    int              nSize: 260 (unsigned = 260 / hex = 104),
     13 << results
    int             hModule: 0 (unsigned = 0 / hex = 0),
    char*          lpFilename: [0012F824] = "e: rojan.exe" in stack of Thread,
    int              nSize: 260 (unsigned = 260 / hex = 104)
  );
  ------------------------------------------------------
  00409A06 -> CopyFileA(
    char*      lpExistingFileName: [0012F824] = "e: rojan.exe" in stack of Thread,
    char*         lpNewFileName: [0012F71C] = "C:WINDOWSsystem32 rojan.exe" in stack of Thread,
    int          bFailIfExists: 0 (unsigned = 0 / hex = 0),
      1 << results
    char*      lpExistingFileName: [0012F824] = "",
    char*         lpNewFileName: [0012F71C] = "",
    int          bFailIfExists: 0 (unsigned = 0 / hex = 0)
  );
  ------------------------------------------------------
  00409A9A -> OpenSCManagerA(
    char*         lpMachineName: [00000000] = (null),
    char*        lpDatabaseName: [00000000] = (null),
    int         dwDesiredAccess: 983103 (unsigned = 983103 / hex = F003F),
   1374656 << results
    char*         lpMachineName: [00000000] = "",
    char*        lpDatabaseName: [00000000] = "",
    int         dwDesiredAccess: 983103 (unsigned = 983103 / hex = F003F)
  );
  ------------------------------------------------------
  00409AD5 -> CreateServiceA(
    int           hSCManager: 1374656 (unsigned = 1374656 / hex = 14F9C0),
    char*         lpServiceName: [0042008C] = "trojan" in main image (.data),
    char*         lpDisplayName: [004200C0] = "Back door for testing" in main image (.data),
    int         dwDesiredAccess: 983551 (unsigned = 983551 / hex = F01FF),
    int          dwServiceType: 288 (unsigned = 288 / hex = 120),
    int           dwStartType: 2 (unsigned = 2 / hex = 2),
    int         dwErrorControl: 1 (unsigned = 1 / hex = 1),
    char*       lpBinaryPathName: [0012F71C] = "C:WINDOWSsystem32 rojan.exe -start" in stack of Thread,
    char*       lpLoadOrderGroup: [00000000] = (null),
    int*           lpdwTagId: 00000000,
    char*        lpDependencies: [004201C4] = "" in main image (.data),
    char*      lpServiceStartName: [00000000] = (null),
    char*          lpPassword: [00000000] = (null),
   1370392 << results
    int           hSCManager: 1374656 (unsigned = 1374656 / hex = 14F9C0),
    char*         lpServiceName: [0042008C] = "",
    char*         lpDisplayName: [004200C0] = "",
    int         dwDesiredAccess: 983551 (unsigned = 983551 / hex = F01FF),
    int          dwServiceType: 288 (unsigned = 288 / hex = 120),
    int           dwStartType: 2 (unsigned = 2 / hex = 2),
    int         dwErrorControl: 1 (unsigned = 1 / hex = 1),
    char*       lpBinaryPathName: [0012F71C] = "",
    char*       lpLoadOrderGroup: [00000000] = "",
    int*           lpdwTagId: 00000000,
    char*        lpDependencies: [004201C4] = "",
    char*      lpServiceStartName: [00000000] = "",
    char*          lpPassword: [00000000] = ""
  );
  ......

  dumbug 是一个开源的 API TRACING 工具,但被设计为仅对 trace 文件中定义的 API 调用进行跟踪。要想通过原始的 dumbug 获得完整 API 调用序列,工作量一点也不比 API HOOKING 方式小。而且就分析木马程序来说,我们并不需要记录 kernel32.dll 等系统链接库内部的 API 调用序列,所以还应根据 EXE 和 DLL 的入口地址、代码段长度进行过滤,最大限度减少冗余信息。在 dumbug 中,只要为 Tracer 对象的 ActivateTraces() 方法添加一些代码,并在其他地方也做相应的小修改,就可以输出上面的结果了。

  附1 - dumbug 的源代码可以从这里获得:

  http://www.phenoelit.de/dumbug/dumbugVegasRelease.zip

  附2 - 简单的 ApiTracing-plugin for OllyDbg 源代码:

  // ApiTracing.c
  
  #define STRICT             // Avoids some type mismatches
  #include
  #include
  #include
  #include "plugin.h"
  
  #define VERSIONHI    1        // High plugin version
  #define VERSIONLO    0        // Low plugin version
  #define LOG_FILENAME  "TraceApi.log" // Log filename
  
  static HINSTANCE hinst;         // DLL instance
  static BOOL bFastTracing = TRUE;
  static BOOL bStartTrace = FALSE;
  
  int Execute(char *text,char *answer);
  
  
  BOOL WINAPI DllEntryPoint(HINSTANCE hi, DWORD reason, LPVOID reserved)
  {
    FILE *fLog;
    if (reason == DLL_PROCESS_ATTACH) {
      hinst = hi;           // Mark plugin instance
      fLog = fopen(LOG_FILENAME, "w");
      if (fLog) {
        fprintf(fLog, "API tracing plugin v%i.%02i, written by glacier_at_xfocus.org ",
          VERSIONHI, VERSIONLO);
        fclose(fLog);
      }
  
    }
    return 1;              // Report success
  }
  
  // Report plugin name and return version of plugin interface.
  extc int _export cdecl ODBG_Plugindata(char shortname[32])
  {
    strcpy(shortname, "API tracing");  // Name of command line plugin
    return PLUGIN_VERSION;
  }
  
  extc int _export cdecl ODBG_Plugininit(int ollydbgversion, HWND hw, ulong *features)
  {
    // This plugin uses some newest features,
    // check that version of OllyDbg is correct.
    if (ollydbgversion < PLUGIN_VERSION)
      return -1;
  
    return 0;
  }
  
  extc void _export cdecl ODBG_Pluginmainloop(DEBUG_EVENT *debugevent) {
  }
  
  // Function adds items to main OllyDbg menu (origin=PM_MAIN).
  extc int _export cdecl ODBG_Pluginmenu(int origin, char data[4096], void *item)
  {
    if (origin != PM_MAIN)
      return 0;            // No pop-up menus in OllyDbg’s windows
    strcpy(data, "0 &Fast trace,1 &Slow trace|2 &About");
    return 1;
  }
  
  // Receives commands from main menu.
  extc void _export cdecl ODBG_Pluginaction(int origin, int action, void *item)
  {
    char szLine[MAX_PATH] = {0};
    if (origin != PM_MAIN)
      return;
  
    switch (action) {
    case 0:               // Fast tracing
      bFastTracing = TRUE;
      bStartTrace = TRUE;
      Sendshortcut(PM_MAIN, 0, WM_KEYDOWN, 0, 0, VK_F9);
      break;
    case 1:               // Slow tracing
      bFastTracing = FALSE;
      bStartTrace = TRUE;
      Sendshortcut(PM_MAIN, 0, WM_KEYDOWN, 0, 0, VK_F7);
      break;
    case 2:               // "About", displays plugin info
      sprintf(szLine, "API tracing plugin v%i.%02i",
        VERSIONHI, VERSIONLO);
      MessageBox(0, szLine, "API tracing", MB_OK|MB_ICONINFORMATION);
      break;
    default: break;
    }
  }
  
  // User opens new or restarts current application.
  extc void _export cdecl ODBG_Pluginreset(void)
  {
    bStartTrace = FALSE;
  }
  
  extc int _export cdecl ODBG_Pluginclose(void)
  {
    return 0;
  }
  
  extc void _export cdecl ODBG_Plugindestroy(void)
  {
  }
  
  // 记录二进制内容
  void LogBinToFile(char *szFileName, const char *pBuf, int nSize)
  {
    FILE *fLog;
    int i, j;
    unsigned const char *ptr = (unsigned const char *)pBuf;
  
    fLog = fopen(szFileName, "a+");
    if (!fLog) return;
  
    if (nSize == 0)
      nSize = strlen(pBuf);
    for (i=0; i      fprintf(fLog, " ");
      for (j=i; j        fprintf(fLog, "%02X ", ptr[j]);
      fprintf(fLog, " ");
      for (j=i; j        if (IsCharAlpha(ptr[j]) || (ptr[j]>=0x20 && ptr[j]<0x7F))
          fprintf(fLog, "%c", ptr[j]);
        else
          fprintf(fLog, "%c", ’.’);
      }
      fprintf(fLog, " ");
    }
    fclose(fLog);
  }
  
  // 格式化记录日志
  void LogToFile(char *szFileName, char *szFmt, ...)
  {
    FILE *fLog;
    char buff[1024];
    va_list arglist;
    va_start(arglist, szFmt);
    _vsnprintf(buff, sizeof(buff), szFmt, arglist);
    va_end(arglist);
  
    fLog = fopen(szFileName, "a+");
    if (!fLog) return;
  
    fprintf(fLog, "%s", buff);
    fclose(fLog);
  }
  
  // 检查是否为ASCII字符串
  BOOL CheckCharAlpha(char *szLine)
  {
    int i = 0;
    while (szLine[i]) {
      if (!IsCharAlpha(szLine[i]) && (szLine[i]<0x20 || szLine[i]>=0x7F))
        return FALSE;
      i++;
    }
    return TRUE;
  }
  
  extc int _export cdecl ODBG_Paused(int reason, t_reg *reg)
  {
    char szSrcDec[1024] = {0};
    char szLine[1024] = {0};
    unsigned long uEsp = 0, uAddr = 0, uTemp = 0;
    int nSize = 0, i = 0;
  
    if (!bStartTrace) return 0;
    if (!reg) {
      ShellExecute(0, "open", "notepad.exe", LOG_FILENAME, NULL, SW_SHOW);
      return 0;
    }
    
    // 读取断点处指令
    nSize = Readcommand(reg->ip, szLine);
    if (nSize > 0) {

      t_disasm disasm;
      
      // 反汇编二进制指令
      Disasm(szLine, nSize, reg->ip, szSrcDec, &disasm, DISASM_ALL, 0);
  
      if (strstr(disasm.result, "CALL ")) {  // 若为CALL指令
        LogToFile(LOG_FILENAME, " %s ",
          "------------------------------------------------------");
        LogToFile(LOG_FILENAME, "%08X: %s (%s)", reg->ip, disasm.result, disasm.comment);
        LogToFile(LOG_FILENAME, " %s ",
          "------------------------------------------------------");
  
        uEsp = reg->r[4];
  
        // 由ESP读取8个堆栈参数
        uTemp = uEsp;
        for (i=0; i<8; i++) {
          Readmemory(&uAddr, uTemp, sizeof(uAddr), MM_SILENT);
          LogToFile(LOG_FILENAME, " ESP+%02X (%08X): %08X", i*4, uTemp, uAddr);
  
          if (uAddr == 0) nSize = 0;
          else nSize = Decodeascii(uAddr, szLine, sizeof(szLine)-1, DASC_ASCII);
          if (nSize > 0 && CheckCharAlpha(szLine)) {
            LogToFile(LOG_FILENAME, " %s ", szLine);
          }
          else {
            memset(szLine, 0, sizeof(szLine));
            nSize = Readmemory(szLine, uAddr, 128, MM_SILENT);
            if (nSize > 0) {
              LogToFile(LOG_FILENAME, " ");
              LogBinToFile(LOG_FILENAME, szLine, nSize);
            }
            else
              LogToFile(LOG_FILENAME, " ");
          }
          uTemp += 4;
        }
      }

      // 继续执行
      if (bFastTracing)
        Sendshortcut(PM_MAIN, 0, WM_KEYDOWN, 0, 0, VK_F9);
      else {
        if (strstr(disasm.result, ".0")) {
          Go(0, reg->ip, STEP_IN, 1, 0);
        }
        else {
          Go(0, reg->ip, STEP_OVER, 1, 0);
        }
      }
    }
    return 0;
  }

 

大成天下网址:http://www.unnoo.com


 

  公司简介

  深圳市大成天下信息技术有限公司(简称大成天下)是一家专业从事信息安全产品与服务的高科技公司,凭借多年的安全从业经验,大成天下聚集了一批优秀的专业人才,在国内信息安全领域独树一帜。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在安全网上逛的时候,突然发现一篇很好的文章!详细阐述了当前流行的api hook技术,rootkit技术和主动防御技术,涉及到深入的操作系统知识,需要一定的功底才能看懂,真的很不错,可以说是一篇难得的教程!现摘选如下: 一. 黑匣子的原理 对于一般用户而言,一个程序从开始运行直到结束,这期间内都做过什么,并不是我们需要关心的事情,他们只要听到播放器里的音乐、看到电影画面、和远方的朋友用通讯工具聊天就可以了,有谁会去关心从用户点击播放器程序图标到音乐响起的时间里,这个程序具体做了什么事情呢?然而,如果面对的程序是恶意软件之流,用户就不得不关心一下它到底对自己的计算机造成什么影响了。 程序在运行期间所进行的操作被称为“程序行为”(Action),一般泛指程序进行的相对表现较明显的操作,例如创建读写文件、访问注册表、连接网络等,而在这些操作之外做的程序内部运算、判断、逻辑等操作并不是我们需要关心的,除非是对它进行复杂的分析如逆向工程。对程序行为进行监视记录的过程就是“跟踪”(Tracing),如果要进一步深入,则要使用调试器(Debugger)环境进行汇编级的指令分析,这就是“调试”(Debugging),也可视为更全面的跟踪,因为调试过程可观察到整个程序里的运算和每一步过程。 也许很多用户会觉得,这些复杂技术距离我们很远,甚至会想像为需要复杂设备和程序才能完成,其实,这些技术的应用范围,一直就在我们身边。如果你正在使用一款防毒产品,那么你系统里执行的程序就已经处于被记录行为的状态了;如果你使用HIPS产品,就会更强烈的感受到程序运行被监视着;如果你正在使用调试器,那就不用我说了吧…… 在Windows系统里,至少有三种技术可以实现程序行为的记录,甚至控制程序的某些行为,分别是“虚拟机”(VM)、“API钩子”(API Hooking)和“API跟踪”(API Tracing)。 应用广泛的虚拟机技术 经常提到的虚拟机技术有两种,一种是普遍应用上的虚拟机技术,它是通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统,此类虚拟机的概念比较广泛,可以是一种使用软件模拟一个完整的计算机硬件环境技术如VMWare,也可以是介于硬件和编译程序之间的交互介质,如Java虚拟机等;另一种则是反病毒产品中使用的“通用解密器”技术,为了检测一些复杂或者代码加密的病毒,杀毒引擎必须让它运行起来以便自我暴露危险程序行为,但是如果病毒真的在用户计算机里运行了,就违背反病毒产品的初衷了,因此反病毒产品也采取了一种虚拟环境检测方法,这就是虚拟机技术,但是这个技术并非是为病毒提供一套计算机仿真系统,这样就太庞大复杂和消耗资源了,这种虚拟机是指杀毒引擎模拟出一个仿真CPU,这个“CPU”具备和真正CPU等同的指令分析功能,杀毒引擎将待检测的程序代码读入“CPU”中逐条指令循环执行,直到出现特定情况才结束工作,在这个过程中探知程序是否具备病毒行为特征或者暴露出病毒特征码。这就是杀毒引擎的“虚拟机技术”,它的目的就是让程序文件在没有实际运行的情况下得到运行后的结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值