def calculate(self):
        addr_space = malware.get_malware_space(self._config)
        addr_space.profile.add_types(zeus_types)

        RC4_KEYSIZE = 0x102

        # cycle the processes
        for p in self.filter_tasks(tasks.pslist(addr_space)):

            # get the process address space
            ps_ad = p.get_process_address_space()
            if ps_ad == None:
                continue

            rules  = yara.compile(sources = zeus_key_sigs)

            # traverse the VAD
            for vad in p.VadRoot.traverse():

                if vad == None:
                    continue

                # find the start and end range
                
                ## for Volatility 2.0 use the following
                start = vad.StartingVpn << 12
                end   = ((vad.EndingVpn + 1) << 12) - 1
                data  = malware.get_vad_data(ps_ad, start, end) 
                ## For Volatility >= 2.1 use the following
                #start = vad.get_start()
                #end   = vad.get_end()
                #data  = vad.get_data()

                # last check for PE headers at the base 
                if data[0:2] != 'MZ':
                    continue

                # check for the signature with YARA, both hits must be present
                matches = rules.match(data=data)

                if len(matches) != 4:
                    continue

                # get the NT header
                dos_header = obj.Object("_IMAGE_DOS_HEADER", start, ps_ad)
                nt_header = dos_header.get_nt_header()

                # there must be more than 2 sections 
                if nt_header.FileHeader.NumberOfSections < 2:
                    continue

                # get the last PE section's data 
                sections = list(nt_header.get_sections(unsafe=False))
                
                last_sec = sections[-1]
                last_sec_data = ps_ad.read((last_sec.VirtualAddress + start), last_sec.Misc.VirtualSize)
                if len(last_sec_data) == 0:
                    continue

                # contains C2 URL, RC4 key for decoding local.ds and the magic buffer
                decoded_config = ''
                # contains hw lock info, the user.ds RC4 key, and XOR key
                encoded_magic  = ''
                # contains BO_LOGIN_KEY
                longinKey = ''
                # contains Salt RC4 Init key
                salt_rc4_initKey = ''

                for match in matches:
                    sigaddr = (match.strings[0][0] + start)
                    debug.debug('Found {0} at {1:#x}'.format(match.rule, sigaddr))

                    if match.rule == 'z1':
                        loginKey = ps_ad.read(
                            obj.Object('unsigned long', offset = sigaddr + 30, vm = ps_ad),0x20)
                    elif match.rule == 'z2':
                        encoded_config = ps_ad.read(
                            obj.Object('unsigned long', offset = sigaddr + 8, vm = ps_ad),
                            obj.Object('unsigned long', offset = sigaddr + 2, vm = ps_ad))
                        decoded_config = self.decode_config(encoded_config, last_sec_data)
                    elif match.rule == 'z3':
                        encoded_magic = ps_ad.read(
                            obj.Object('unsigned long', offset = sigaddr + 31, vm = ps_ad),
                            addr_space.profile.get_obj_size('_ZEUS_MAGIC'))
                    elif match.rule == 'z4':
                        encoded_magic = ps_ad.read(
                            obj.Object('unsigned long', offset = sigaddr + 30, vm = ps_ad),
                            addr_space.profile.get_obj_size('_ZEUS_MAGIC'))
                    elif match.rule == 'z5':
                        salt_rc4_initKey = ps_ad.read(sigaddr + 5,0x4)
                    
                if not decoded_config or not encoded_magic:
                    continue

                debug.debug("encoded_config:\n{0}\n".format(self.get_hex(encoded_config)))
                debug.debug("decoded_config:\n{0}\n".format(self.get_hex(decoded_config)))
                debug.debug("encoded_magic:\n{0}\n".format(self.get_hex(encoded_magic)))

                offset = 0 

                decoded_magic = ''
                config_key = ''
                aes_key = ''
                rc4_comKey = ''

                found = False

                while offset < len(decoded_config) - RC4_KEYSIZE:
                    
                    config_key = decoded_config[offset:offset+RC4_KEYSIZE]
                    decoded_magic = self.rc4(config_key, encoded_magic, loginKey)

                    # When the first four bytes of the decoded magic buffer equal the size
                    # of the magic buffer, then we've found a winning RC4 key
                    (struct_size,) = struct.unpack("=I", decoded_magic[0:4])

                    if struct_size == addr_space.profile.get_obj_size('_ZEUS_MAGIC'):
                        found = True
                        # With the RC4 key and the BO_LOGIN_KEY, we can now calculate the AES Key
                        aes_key = self.rc4(config_key,hashlib.md5(loginKey).digest(),loginKey)
                        # Initialize the RC4 communication key
                        rc4_comKey = self.rc4_init(aes_key,salt_rc4_initKey)
                        break
                    offset += 1

                if not found:
                    debug.debug('Error, cannot decode magic')
                    continue
                
                debug.debug("decoded_magic:\n{0}\n".format(self.get_hex(decoded_magic)))
                debug.debug("config_key:\n{0}\n".format(self.get_hex(config_key)))

                # grab the URLs from the decoded buffer
                urls = []
                while "http" in decoded_config:
                    url = decoded_config[decoded_config.find("http"):]
                    urls.append(url[:url.find('\x00')])
                    decoded_config = url[url.find('\x00'):]
                
                yield p, start, urls, config_key, decoded_config, decoded_magic, loginKey, aes_key, rc4_comKey
Exemple #2
0
    def calculate(self):
        addr_space = malware.get_malware_space(self._config)
        addr_space.profile.add_types(zeus_types)

        RC4_KEYSIZE = 0x102

        # cycle the processes
        for p in self.filter_tasks(tasks.pslist(addr_space)):

            # get the process address space
            ps_ad = p.get_process_address_space()
            if ps_ad == None:
                continue

            rules = yara.compile(sources=zeus_key_sigs)

            # traverse the VAD
            for vad in p.VadRoot.traverse():

                if vad == None:
                    continue

                # find the start and end range

                ## for Volatility 2.0 use the following
                start = vad.StartingVpn << 12
                end = ((vad.EndingVpn + 1) << 12) - 1
                data = malware.get_vad_data(ps_ad, start, end)
                ## For Volatility >= 2.1 use the following
                #start = vad.get_start()
                #end   = vad.get_end()
                #data  = vad.get_data()

                # last check for PE headers at the base
                if data[0:2] != 'MZ':
                    continue

                # check for the signature with YARA, both hits must be present
                matches = rules.match(data=data)

                if len(matches) != 2:
                    continue

                # get the NT header
                dos_header = obj.Object("_IMAGE_DOS_HEADER", start, ps_ad)
                nt_header = dos_header.get_nt_header()

                # there must be more than 2 sections
                if nt_header.FileHeader.NumberOfSections < 2:
                    continue

                # get the last PE section's data
                sections = list(nt_header.get_sections(unsafe=False))

                last_sec = sections[-1]
                last_sec_data = ps_ad.read((last_sec.VirtualAddress + start),
                                           last_sec.Misc.VirtualSize)

                # contains C2 URL, RC4 key for decoding local.ds and the magic buffer
                decoded_config = ''
                # contains hw lock info, the user.ds RC4 key, and XOR key
                encoded_magic = ''

                for match in matches:
                    sigaddr = (match.strings[0][0] + start)
                    debug.debug('Found {0} at {1:#x}'.format(
                        match.rule, sigaddr))
                    if match.rule == 'z1':
                        encoded_config = ps_ad.read(
                            obj.Object('unsigned long',
                                       offset=sigaddr + 8,
                                       vm=ps_ad),
                            obj.Object('unsigned long',
                                       offset=sigaddr + 2,
                                       vm=ps_ad))
                        decoded_config = self.decode_config(
                            encoded_config, last_sec_data)
                    elif match.rule == 'z2':
                        config_ptr = obj.Object('unsigned long',
                                                offset=sigaddr + 26,
                                                vm=ps_ad)
                        config_ptr = obj.Object('unsigned long',
                                                offset=config_ptr,
                                                vm=ps_ad)
                        encoded_config = ps_ad.read(config_ptr, 0x3c8)
                        decoded_config = self.rc4(
                            self.rc4_init(encoded_config), last_sec_data[2:])
                    elif match.rule == 'z5':
                        encoded_config = ps_ad.read(
                            obj.Object('unsigned long',
                                       offset=sigaddr + 8,
                                       vm=ps_ad),
                            obj.Object('unsigned long',
                                       offset=sigaddr + 2,
                                       vm=ps_ad))
                        decoded_config = self.decode_config(
                            encoded_config, last_sec_data)
                    elif match.rule == 'z3':
                        encoded_magic = ps_ad.read(
                            obj.Object('unsigned long',
                                       offset=sigaddr + 30,
                                       vm=ps_ad),
                            addr_space.profile.get_obj_size('_ZEUS_MAGIC'))
                    elif match.rule == 'z4':
                        encoded_magic = ps_ad.read(
                            obj.Object('unsigned long',
                                       offset=sigaddr + 31,
                                       vm=ps_ad),
                            addr_space.profile.get_obj_size('_ZEUS_MAGIC'))

                if not decoded_config or not encoded_magic:
                    continue

                debug.debug("encoded_config:\n{0}\n".format(
                    self.get_hex(encoded_config)))
                debug.debug("decoded_config:\n{0}\n".format(
                    self.get_hex(decoded_config)))
                debug.debug("encoded_magic:\n{0}\n".format(
                    self.get_hex(encoded_magic)))

                offset = 0

                decoded_magic = ''
                config_key = ''

                found = False

                while offset < len(decoded_config) - RC4_KEYSIZE:

                    config_key = decoded_config[offset:offset + RC4_KEYSIZE]
                    decoded_magic = self.rc4(config_key, encoded_magic)

                    # when the first four bytes of the decoded magic buffer equal the size
                    # of the magic buffer, then we've found a winning RC4 key
                    (struct_size, ) = struct.unpack("=I", decoded_magic[0:4])

                    if struct_size == addr_space.profile.get_obj_size(
                            '_ZEUS_MAGIC'):
                        found = True
                        break

                    offset += 1

                if not found:
                    debug.debug('Error, cannot decode magic')
                    continue

                debug.debug("decoded_magic:\n{0}\n".format(
                    self.get_hex(decoded_magic)))
                debug.debug("config_key:\n{0}\n".format(
                    self.get_hex(config_key)))

                # grab the URL from the decoded buffer
                url = decoded_config[decoded_config.find("http"):]
                url = url[:url.find('\x00')]

                # report what we've found
                rc4_offset = addr_space.profile.get_obj_offset(
                    '_ZEUS_MAGIC', 'rc4key')
                creds_key = decoded_magic[rc4_offset:rc4_offset + RC4_KEYSIZE]
                yield p, start, url, config_key, creds_key, decoded_config, decoded_magic
    def calculate(self):
        addr_space = malware.get_malware_space(self._config)

        # temporary fix until issue 144 is resolved (http://code.google.com/p/volatility/issues/detail?id=144)
        addr_space.profile.add_types({
            '_MMVAD_FLAGS': [
                0x4, {
                    'CommitCharge':
                    [0x0, ['BitField',
                           dict(start_bit=0, end_bit=19)]],
                    'PhysicalMapping':
                    [0x0, ['BitField',
                           dict(start_bit=19, end_bit=20)]],
                    'ImageMap':
                    [0x0, ['BitField',
                           dict(start_bit=20, end_bit=21)]],
                    'UserPhysicalPages':
                    [0x0, ['BitField',
                           dict(start_bit=21, end_bit=22)]],
                    'NoChange':
                    [0x0, ['BitField',
                           dict(start_bit=22, end_bit=23)]],
                    'WriteWatch':
                    [0x0, ['BitField',
                           dict(start_bit=23, end_bit=24)]],
                    'Protection':
                    [0x0, ['BitField',
                           dict(start_bit=24, end_bit=29)]],
                    'LargePages':
                    [0x0, ['BitField',
                           dict(start_bit=29, end_bit=30)]],
                    'MemCommit':
                    [0x0, ['BitField',
                           dict(start_bit=30, end_bit=31)]],
                    'PrivateMemory':
                    [0x0, ['BitField',
                           dict(start_bit=31, end_bit=32)]],
                }
            ]
        })

        RC4_KEYSIZE = 0x102

        # cycle through the active processes
        for p in self.filter_tasks(tasks.pslist(addr_space)):

            # get the process address space
            ps_ad = p.get_process_address_space()
            if ps_ad == None:
                continue

            # enumerate DLLs (inherited from DllList)
            mods = p.list_modules()

            # traverse the VAD
            for vad in p.VadRoot.traverse():

                if vad == None:
                    continue

                # only looking for short VADs (non-memory mapped)
                if (vad.Tag != "VadS"):
                    continue

                # only looking for private, virtually-alocated memory
                if vad.u.VadFlags.PrivateMemory == 0:
                    continue

                # we want the memory to be executable, but right now we can only
                # "see" the original protection not the current protection...and
                # the original protection can be anything. this version of zeus
                # happens to use PAGE_NOACCESS so that's what we'll look for instead.
                # see http://code.google.com/p/volatility/issues/detail?id=143.
                if malware.PROTECT_FLAGS[vad.Flags.Protection
                                         >> 24] != 'PAGE_NOACCESS':
                    continue

                ## for Volatility 2.0 use the following
                start = vad.StartingVpn << 12
                end = ((vad.EndingVpn + 1) << 12) - 1
                data = malware.get_vad_data(ps_ad, start, end)
                ## For Volatility >= 2.1 use the following
                #start = vad.get_start()
                #end   = vad.get_end()
                #data  = vad.get_data()

                # check for PE headers at the base
                if data[0:2] != 'MZ':
                    continue

                # locate the winsock2 module
                winsock = malware.find_module_by_name(mods, "ws2_32.dll")

                if winsock == None:
                    continue

                # resolve the address of closesocket export
                closesocket = winsock.getprocaddress("closesocket")

                if closesocket == None:
                    continue

                # scan for calls to imported functions (inherited from ImpScan)
                calls = list(self.call_scan(ps_ad, data, start))

                for (addr_of_call, const, call_dest) in calls:

                    if call_dest != closesocket:
                        continue

                    # read the DWORD directly after closesocket
                    struct_base = obj.Object('Pointer',
                                             offset=const + 4,
                                             vm=ps_ad)

                    # to be valid, it must point within the vad segment
                    if (struct_base < start) or (struct_base > (start + end)):
                        continue

                    # grab the key data
                    key = ps_ad.read(struct_base + 0x2a, RC4_KEYSIZE)

                    # greg's sanity check
                    if len(key) != RC4_KEYSIZE or key[-2:] != "\x00\x00":
                        continue

                    yield p, struct_base, key
Exemple #4
0
    def calculate(self):
        addr_space = malware.get_malware_space(self._config)

        # temporary fix until issue 144 is resolved (http://code.google.com/p/volatility/issues/detail?id=144)
        addr_space.profile.add_types(
            {
                "_MMVAD_FLAGS": [
                    0x4,
                    {
                        "CommitCharge": [0x0, ["BitField", dict(start_bit=0, end_bit=19)]],
                        "PhysicalMapping": [0x0, ["BitField", dict(start_bit=19, end_bit=20)]],
                        "ImageMap": [0x0, ["BitField", dict(start_bit=20, end_bit=21)]],
                        "UserPhysicalPages": [0x0, ["BitField", dict(start_bit=21, end_bit=22)]],
                        "NoChange": [0x0, ["BitField", dict(start_bit=22, end_bit=23)]],
                        "WriteWatch": [0x0, ["BitField", dict(start_bit=23, end_bit=24)]],
                        "Protection": [0x0, ["BitField", dict(start_bit=24, end_bit=29)]],
                        "LargePages": [0x0, ["BitField", dict(start_bit=29, end_bit=30)]],
                        "MemCommit": [0x0, ["BitField", dict(start_bit=30, end_bit=31)]],
                        "PrivateMemory": [0x0, ["BitField", dict(start_bit=31, end_bit=32)]],
                    },
                ]
            }
        )

        RC4_KEYSIZE = 0x102

        # cycle through the active processes
        for p in self.filter_tasks(tasks.pslist(addr_space)):

            # get the process address space
            ps_ad = p.get_process_address_space()
            if ps_ad == None:
                continue

            # enumerate DLLs (inherited from DllList)
            mods = p.list_modules()

            # traverse the VAD
            for vad in p.VadRoot.traverse():

                if vad == None:
                    continue

                # only looking for short VADs (non-memory mapped)
                if vad.Tag != "VadS":
                    continue

                # only looking for private, virtually-alocated memory
                if vad.u.VadFlags.PrivateMemory == 0:
                    continue

                # we want the memory to be executable, but right now we can only
                # "see" the original protection not the current protection...and
                # the original protection can be anything. this version of zeus
                # happens to use PAGE_NOACCESS so that's what we'll look for instead.
                # see http://code.google.com/p/volatility/issues/detail?id=143.
                if malware.PROTECT_FLAGS[vad.Flags.Protection >> 24] != "PAGE_NOACCESS":
                    continue

                ## for Volatility 2.0 use the following
                start = vad.StartingVpn << 12
                end = ((vad.EndingVpn + 1) << 12) - 1
                data = malware.get_vad_data(ps_ad, start, end)
                ## For Volatility >= 2.1 use the following
                # start = vad.get_start()
                # end   = vad.get_end()
                # data  = vad.get_data()

                # check for PE headers at the base
                if data[0:2] != "MZ":
                    continue

                # locate the winsock2 module
                winsock = malware.find_module_by_name(mods, "ws2_32.dll")

                if winsock == None:
                    continue

                # resolve the address of closesocket export
                closesocket = winsock.getprocaddress("closesocket")

                if closesocket == None:
                    continue

                # scan for calls to imported functions (inherited from ImpScan)
                calls = list(self.call_scan(ps_ad, data, start))

                for (addr_of_call, const, call_dest) in calls:

                    if call_dest != closesocket:
                        continue

                    # read the DWORD directly after closesocket
                    struct_base = obj.Object("Pointer", offset=const + 4, vm=ps_ad)

                    # to be valid, it must point within the vad segment
                    if (struct_base < start) or (struct_base > (start + end)):
                        continue

                    # grab the key data
                    key = ps_ad.read(struct_base + 0x2A, RC4_KEYSIZE)

                    # greg's sanity check
                    if len(key) != RC4_KEYSIZE or key[-2:] != "\x00\x00":
                        continue

                    yield p, struct_base, key