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