恶意程序的沙盒执行部分

前面介绍了对执行恶意程序采集到log 进行分析的过程, 现在我们介绍下沙盒执行部分, 与前面的代码是运行在 host 机器上不同, 这部分代码是运行在 虚拟机 guest 机器上的 。
[analyzer\windows\analyzer.py]

一、注入cuckoomon.dll 到恶意程序进程空间, cuckoomon.dll 将监视恶意程序的行为如 API 调用及其参数

创建 PipeServer 用来和注入的模块通信。
以 exe 为例, 创建进程运行恶意程序
注入cuckoomon.dll到恶意程序进程。[代码察看 analyzer\windows\modules\packages]

如果pipe收到 " PROCESS:" 命令, 表示恶意程序正在创建子进程 或者修改别的进程空间内容,那么这个进程也会被注入cuckoomon.dll。
注入 cuckoomon.dll 到malware 程序。 注入的步骤是:
  在恶意程序的进程空间分配一块内存
  在分配的内存内填写 cuckoomon.dll 的路径
  使用CreateRemoteThread 或者 QueueUserAPC调用系统函数 loadLibrary load cuckoomon.dll 进恶意程序的进程空间. 注意 loadlibrary 函数在kernel32.dll 中, 而 kernel32.dll 影射的地址在每个进程中是一样的. 所以我们可以直接将 loadlibrary 函数地址直接用在恶意程序的进程空间, 但是loadlibrary(path) 的参数 path 的地址一定是在恶意程序的地址空间.


以下是 dll 注入的python 代码
class Process:
   """Windows process."""


   def inject(self, dll=os.path.join("dll", "cuckoomon.dll"), apc=False):
       """Cuckoo DLL injection.
       @param dll: Cuckoo DLL path.
       @param apc: APC use.
       """
       if self.pid == 0:
           log.warning("No valid pid specified, injection aborted")
           return False

       if not self.is_alive():
           log.warning("The process with pid %d is not alive, injection "
                       "aborted" % self.pid)
           return False

       dll = randomize_dll(dll)

       if not dll or not os.path.exists(dll):
           log.warning("No valid DLL specified to be injected in process "
                       "with pid %d, injection aborted" % self.pid)
           return False

       arg = KERNEL32.VirtualAllocEx(self.h_process,
                                     None,
                                     len(dll) + 1,
                                     MEM_RESERVE | MEM_COMMIT,
                                     PAGE_READWRITE)

       if not arg:
           log.error("VirtualAllocEx failed when injecting process with "
                     "pid %d, injection aborted (Error: %s)"
                     % (self.pid, get_error_string(KERNEL32.GetLastError())))
           return False

       bytes_written = c_int(0)
       if not KERNEL32.WriteProcessMemory(self.h_process,
                                          arg,
                                          dll + "\x00",
                                          len(dll) + 1,
                                          byref(bytes_written)):
           log.error("WriteProcessMemory failed when injecting process "
                     "with pid %d, injection aborted (Error: %s)"
                     % (self.pid, get_error_string(KERNEL32.GetLastError())))
           return False

       kernel32_handle = KERNEL32.GetModuleHandleA("kernel32.dll")
       load_library = KERNEL32.GetProcAddress(kernel32_handle,
                                              "LoadLibraryA")

       config_path = os.path.join(os.getenv("TEMP"), "%s.ini" % self.pid)
       with open(config_path, "w") as config:
           cfg = Config("analysis.conf")

           config.write("host-ip={0}\n".format(cfg.ip))
           config.write("host-port={0}\n".format(cfg.port))
           config.write("pipe={0}\n".format(PIPE))
           config.write("results={0}\n".format(PATHS["root"]))
           config.write("analyzer={0}\n".format(os.getcwd()))
           config.write("first-process={0}\n".format(Process.first_process))

           Process.first_process = False

       if apc or self.suspended:
           log.info("Using QueueUserAPC injection")
           if not self.h_thread:
               log.info("No valid thread handle specified for injecting "
                        "process with pid %d, injection aborted" % self.pid)
               return False

           if not KERNEL32.QueueUserAPC(load_library, self.h_thread, arg):
               log.error("QueueUserAPC failed when injecting process "
                         "with pid %d (Error: %s)"
                         % (self.pid,
                            get_error_string(KERNEL32.GetLastError())))
               return False
           log.info("Successfully injected process with pid %d" % self.pid)
       else:
           event_name = "CuckooEvent%d" % self.pid
           self.event_handle = KERNEL32.CreateEventA(None,
                                                     False,
                                                     False,
                                                     event_name)
           if not self.event_handle:
               log.warning("Unable to create notify event..")
               return False

           log.info("Using CreateRemoteThread injection")
           new_thread_id = c_ulong(0)
           thread_handle = KERNEL32.CreateRemoteThread(self.h_process,
                                                       None,
                                                       0,
                                                       load_library,
                                                       arg,
                                                       0,
                                                       byref(new_thread_id))
           if not thread_handle:
               log.error("CreateRemoteThread failed when injecting " +
                   "process with pid %d (Error: %s)" % (self.pid,
                   get_error_string(KERNEL32.GetLastError())))
               KERNEL32.CloseHandle(self.event_handle)
               self.event_handle = None
               return False
           else:
               KERNEL32.CloseHandle(thread_handle)

       return True

上面介绍了如何注入 cuckoomon.dll 到恶意程序的地址空间。 下面接着介绍 cuckoomon.dll工作原理。
二、cuckoomon.dll工作原理
Cuckoo Sandbox的核心模块是cuckoomon.dll, 使用 hook技术拦截恶意软件的执行。cuckoo sandbox 可以监控windows 可执行文件, office 文件 (Microsoft Word, Microsoft Excel), PDF 文件, 及其它运行在用户模式的各种程序。

他的位置在cockoo/analyzer/windows/dll/cuckoomon.dll. 他的源代码:https://github.com/cuckoobox/cuckoomon

cuckoomon.dll 的 DllMain() 会调用 set_hooks() , 然后 设置 CuckooEvent, 通知 成功hook , 让挂起的恶意程序继续运行。
先看看这个数据结构:

typedef struct _hook_t {
   const wchar_t *library; // 被hook的api的库
   const char *funcname; // 被hook的 api 名字

   // instead of a library/funcname combination, an address can be given
   // as well (this address has more priority than library/funcname)
   void *addr; // 被hook的api的地址

   // pointer to the new function
   void *new_func; //

   // "function" which jumps over the trampoline and executes the original
   // function call
   void **old_func; //成功hook后指向 gate

   // allow hook recursion on this hook?
   // (see comments @ hook_create_pre_gate)
   int allow_hook_recursion;

   // this hook has been performed
   int is_hooked;

   unsigned char gate[128]; // 存放callgate 代码
   unsigned char pre_gate[128]; //存放 pre_gate代码
   unsigned char hook_data[32]; // 间接跳转时候存放跳转目的地址
} hook_t;

然后分析函数
void set_hooks()
{
   // the hooks contain the gates as well, so they have to be RWX
   DWORD old_protect;
   VirtualProtect(g_hooks, sizeof(g_hooks), PAGE_EXECUTE_READWRITE,
       &old_protect);

   hook_disable();

   // now, hook each api : ) g_hooks是一个静态数组,数组内容是希望hook的 windows api
   for (int i = 0; i < ARRAYSIZE(g_hooks); i++) {
       hook_api(&g_hooks, HOOKTYPE);
   }

   hook_enable();
}


可见最重要的工作是在函数 hook_api 中执行。

int hook_api(hook_t *h, int type)
{
      // 创建调用门指令代码
      // 修改 原api 的第一条指令为跳转指令,跳转到调用门代码执行
}


具体看cuckoo的源代码, 短短几十行代码, 就实现了hook 机制。