hackedteam/test-av

View on GitHub
analyzer/windows/lib/api/process.py

Summary

Maintainability
C
1 day
Test Coverage
# Copyright (C) 2010-2012 Cuckoo Sandbox Developers.
# This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
# See the file 'docs/LICENSE' for copying permission.

import os
import string
import random
import logging
from time import time
from ctypes import *
from shutil import copy

from lib.common.defines import *
from lib.common.paths import PATHS
from lib.core.errors import get_error_string

log = logging.getLogger(__name__)

def randomize_dll(dll_path):
    """Randomize DLL name.
    @return: new DLL path.
    """
    new_dll_name = "".join(random.choice(string.ascii_letters) for x in range(6))
    new_dll_path = os.path.join("dll", "%s.dll" % new_dll_name)

    try:
        copy(dll_path, new_dll_path)
        return new_dll_path
    except:
        return dll_path

class Process:
    """Windows process."""

    def __init__(self, pid=0, h_process=0, thread_id=0, h_thread=0):
        """@param pid: PID.
        @param h_process: process handle.
        @param thread_id: thread id.
        @param h_thread: thread handle.
        """
        self.pid = pid
        self.h_process = h_process
        self.thread_id = thread_id
        self.h_thread = h_thread
        self.suspended = False

    def get_system_info(self):
        """Get system information."""
        self.system_info = SYSTEM_INFO()
        KERNEL32.GetSystemInfo(byref(self.system_info))

    def open(self):
        """Open a process.
        @return: operation status.
        """
        if self.pid == 0:
            return False

        self.h_process = KERNEL32.OpenProcess(PROCESS_ALL_ACCESS,
                                              False,
                                              int(self.pid))
        return True

    def exit_code(self):
        """Get process exit code.
        @return: exit code value.
        """
        if not self.h_process:
            self.open()

        exit_code = c_ulong(0)
        KERNEL32.GetExitCodeProcess(self.h_process, byref(exit_code))

        return exit_code.value

    def is_alive(self):
        """Process is alive?
        @return: process status.
        """
        if self.exit_code() == STILL_ACTIVE:
            return True
        else:
            return False

    def execute(self, path=None, args=None, suspended=False):
        """Execute sample process.
        @param path: sample path.
        @param args: process args.
        @param suspended: is suspended.
        @return: operation status.
        """
        if not os.access(path, os.X_OK):
            log.info("Problem Accessing to %s." % path)
            return False

        startup_info = STARTUPINFO()
        startup_info.cb = sizeof(startup_info)
        process_info = PROCESS_INFORMATION()

        if args:
            arguments = "\"" + path + "\" " + args
        else:
            arguments = None

        creation_flags = CREATE_NEW_CONSOLE
        if suspended:
            self.suspended = True
            creation_flags += CREATE_SUSPENDED

        created = KERNEL32.CreateProcessA(path,
                                          arguments,
                                          None,
                                          None,
                                          None,
                                          creation_flags,
                                          None,
                                          None,
                                          byref(startup_info),
                                          byref(process_info))

        if created:
            self.pid = process_info.dwProcessId
            self.h_thread = process_info.hThread
            log.info("Successfully executed process from path \"%s\" with arguments \"%s\" with pid %d"
                     % (path, args, self.pid))
            return True
        else:
            log.error("Failed to execute process from path \"%s\" with arguments \"%s\"" % (path, args))
            return False

    def resume(self):
        """Resume a suspended thread.
        @return: operation status.
        """
        if not self.suspended:
            log.warning("The process with pid %d was not suspended at creation" % self.pid)
            return False

        if self.h_thread == 0:
            return False

        KERNEL32.Sleep(2000)

        if KERNEL32.ResumeThread(self.h_thread) != -1:
            log.info("Successfully resumed process with pid %d" % self.pid)
            return True
        else:
            log.error("Failed to resume process with pid %d" % self.pid)
            return False

    def terminate(self):
        """Terminate process.
        @return: operation status.
        """
        if self.h_process == 0:
            self.open()

        if KERNEL32.TerminateProcess(self.h_process, 1):
            log.info("Successfully terminated process with pid %d" % self.pid)
            return True
        else:
            log.error("Failed to terminate process with pid %d" % self.pid)
            return False

    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

        KERNEL32.Sleep(2000)

        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")

        if apc or self.suspended:
            log.info("Using QueueUserAPC injection")
            if self.h_thread == 0:
                log.info("No valid thread handle specified for injecting process with pid %d, injection aborted" % self.pid)
                return False
            
            if KERNEL32.QueueUserAPC(load_library, self.h_thread, arg) == 0:
                log.error("QueueUserAPC failed when injecting process with pid %d (Error: %s)"
                          % (self.pid, get_error_string(KERNEL32.GetLastError())))
                return False
        else:
            log.info("Using CreateRemoteThread injection")
            new_thread_id = c_ulong(0)
            if not KERNEL32.CreateRemoteThread(self.h_process,
                                               None,
                                               0,
                                               load_library,
                                               arg,
                                               0,
                                               byref(new_thread_id)):
                log.error("CreateRemoteThread 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)

        return True

    def dump_memory(self):
        """Dump process memory.
        @return: operation status.
        """
        if self.pid == 0:
            log.warning("No valid pid specified, memory dump aborted")
            return False

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

        self.get_system_info()

        page_size = self.system_info.dwPageSize
        min_addr = self.system_info.lpMinimumApplicationAddress
        max_addr = self.system_info.lpMaximumApplicationAddress
        mem = min_addr

        root = os.path.join(PATHS["memory"], str(self.pid))
        root = os.path.join(root, str(int(time())))

        if not os.path.exists(root):
            os.makedirs(root)

        while(mem < max_addr):
            mbi = MEMORY_BASIC_INFORMATION()
            count = c_ulong(0)

            if KERNEL32.VirtualQueryEx(self.h_process, mem, byref(mbi), sizeof(mbi)) < sizeof(mbi):
                mem += page_size
                continue

            if mbi.State == 0x1000 and mbi.Type == 0x20000:
                buf = create_string_buffer(mbi.RegionSize)
                if KERNEL32.ReadProcessMemory(self.h_process,
                                              mem,
                                              buf,
                                              mbi.RegionSize,
                                              byref(count)):
                    path = os.path.join(root, "0x%.8x.dmp" % mem)
                    chunk = open(path, "wb")
                    chunk.write(buf.raw)
                    chunk.close()
                mem += mbi.RegionSize
            else:
                mem += page_size

        log.info("Memory dump of process with pid %d completed" % self.pid)

        return True