From 394853542f512171e50e09947e44acc7a3976931 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 20 Jul 2018 15:04:08 +0100 Subject: [PATCH] New SmokeLoader signature and parser. --- README.md | 8 +- data/yara/CAPE/SmokeLoader.yar | 12 +++ .../parsers/mwcp/parsers/SmokeLoader.py | 96 +++++++++++++++++++ 3 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 data/yara/CAPE/SmokeLoader.yar create mode 100644 modules/processing/parsers/mwcp/parsers/SmokeLoader.py diff --git a/README.md b/README.md index 3a6671291..7741f912a 100644 --- a/README.md +++ b/README.md @@ -43,19 +43,19 @@ CAPE has config parsers/decoders for the following malware families, whose paylo - PoisonIvy - Screech - TSCookie +- Dridex +- SmokeLoader Many other malware families have their payloads automatically extracted by behavioural packages, for which CAPE uses Yara signatures to detect the payloads. This list is growing, and includes: -- Gootkit, QtBot, ZeroT, WanaCry, NetTraveler, Locky, BadRabbit, Magniber, Redsip, RCSession, Kronos, PetrWrap, Kovter, Azer, Petya, Dreambot, Atlas, NanoLocker, Mole, Codoso, Cryptoshield, Loki, Jaff, Dridex, IcedID, Scarab, Cutlet, RokRat, OlympicDestroyer, Gandcrab, Fareit, ZeusPanda, AgentTesla, Imminent, Arkei. +- Gootkit, QtBot, ZeroT, WanaCry, NetTraveler, Locky, BadRabbit, Magniber, Redsip, RCSession, Kronos, PetrWrap, Kovter, Azer, Petya, Dreambot, Atlas, NanoLocker, Mole, Codoso, Cryptoshield, Loki, Jaff, IcedID, Scarab, Cutlet, RokRat, OlympicDestroyer, Gandcrab, Fareit, ZeusPanda, AgentTesla, Imminent, Arkei. Configuration data may be output from either family packages, or in payloads resulting from behavioural packages. Configuration parsing may then be performed on this by virtue of Yara-based detection, and config parsing based on either of CAPE's config parsing frameworks, the RATDecoders framework from malwareconfig.com and DC3-MWCP (Defense Cyber Crime Center - Malware Configuration Parser). The many parsers/decoders from malwareconfig.com are also included, comprising among many others: Sakula, Trickbot, DarkComet, PredatorPain and PoisonIvy. Thanks to Kevin Breen/TechAnarchy for this framework and parsers (https://github.com/kevthehermit/RATDecoders), and to DC3 for their framework (https://github.com/Defense-Cyber-Crime-Center/DC3-MWCP). Utility packages are also included: 'DumpOnAPI' allows a module to be dumped when it calls a specific API function which can be specified in the web interface. 'DumpConfigRegion' allows the memory region containing C2 information or other config data to be dumped for commonly used API calls. These packages can be useful for quickly unpacking/dumping novel samples or configs. The 'Trace' package allows quick access to the debugger by accepting four breakpoints (RVA values) to set on instructions, whereupon a short instruction trace will be output. An optional 'base-on-api' parameter allows the image base to be set by API call. -Packages can be written based on API hooks, the CAPE debugger, or a combination of both. There are a number of other behavioural and malware family packages and parsers currently in the works, so watch this space. - The CAPE debugger allows breakpoints to be set on read, write or execute of a memory address or region, as well as single-step mode. This allows fine control over malware execution until it is possible to dump the memory regions of interest, containing code or configuration data. Breakpoints can be set dynamically by package code, API hooks or Yara signatures. Thanks to the embedded distorm library the debugger can output the disassembly of instructions during single-step mode or when breakpoints are hit, resulting in instruction traces. -Processes, modules and memory regions can variously be dumped by CAPE through use of a simple API. These dumps can then be scanned and parsed for configuration information. Executable modules are fixed on being dumped, and may also have their imports automatically reconstructed (based on Scylla: https://github.com/NtQuery/Scylla). +Processes, modules and memory regions can variously be dumped by CAPE through use of a simple API. These dumps can then be scanned and parsed for configuration information. Executable modules are fixed on being dumped, and may also have their imports automatically reconstructed (based on Scylla: https://github.com/NtQuery/Scylla). Packages can be written based on API hooks, the CAPE debugger, or a combination of both. There are a number of other behavioural and malware family packages and parsers currently in the works, so watch this space. The repository containing the code for the monitor DLLs which form the basis of these packages is a distinct one: https://github.com/ctxis/capemon. This repository is organised in branches for the various packages. diff --git a/data/yara/CAPE/SmokeLoader.yar b/data/yara/CAPE/SmokeLoader.yar new file mode 100644 index 000000000..76ee5841f --- /dev/null +++ b/data/yara/CAPE/SmokeLoader.yar @@ -0,0 +1,12 @@ +rule SmokeLoader +{ + meta: + author = "kev" + description = "SmokeLoader C2 decryption function" + cape_type = "SmokeLoader Payload" + strings: + $decrypt1 = {44 0F B6 CF 48 8B D0 49 03 D9 4C 2B D8 8B 4B 01 41 8A 04 13 41 BA 04 00 00 00 0F C9 32 C1 C1 F9 08 49 FF CA 75 F6 F6 D0 88 02 48 FF C2 49 FF C9 75 DB 49 8B C0 48 8B 5C 24 30 48 83 C4 20 5F C3} + //$ref1 = {3D 00 10 00 00 0F 8E ?? ?? 00 00 39 07 0F 85 ?? 02 00 00 8B 4F 04 81 F1 ?? ?? ?? ?? 0F 85 ?? 02 00 00 44 8A 67 0C} + condition: + $decrypt1 // and (any of ($ref*)) +} \ No newline at end of file diff --git a/modules/processing/parsers/mwcp/parsers/SmokeLoader.py b/modules/processing/parsers/mwcp/parsers/SmokeLoader.py new file mode 100644 index 000000000..e88fe27b5 --- /dev/null +++ b/modules/processing/parsers/mwcp/parsers/SmokeLoader.py @@ -0,0 +1,96 @@ +# Copyright (C) 2018 Kevin O'Reilly (kevin.oreilly@contextis.co.uk) +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from mwcp.malwareconfigparser import malwareconfigparser +import struct, socket +import yara + +rule_source = ''' +rule SmokeLoader +{ + meta: + author = "kev" + description = "SmokeLoader C2 decryption function" + cape_type = "SmokeLoader Payload" + strings: + $decrypt1 = {44 0F B6 CF 48 8B D0 49 03 D9 4C 2B D8 8B 4B 01 41 8A 04 13 41 BA 04 00 00 00 0F C9 32 C1 C1 F9 08 49 FF CA 75 F6 F6 D0 88 02 48 FF C2 49 FF C9 75 DB 49 8B C0 48 8B 5C 24 30 48 83 C4 20 5F C3} + $ref1 = {3D 00 10 00 00 0F 8E ?? ?? 00 00 39 07 0F 85 ?? 02 00 00 8B 4F 04 81 F1 ?? ?? ?? ?? 0F 85 ?? 02 00 00 44 8A 67 0C 44 88 65 78 45 84 E4 0F 84 CD 02 00 00 48 8D 0D ?? E4 FF FF E8} + $ref2 = {3D 00 10 00 00 0F 8E ?? ?? 00 00 39 07 0F 85 ?? 02 00 00 8B 4F 04 81 F1 ?? ?? ?? ?? 0F 85 ?? 02 00 00 44 8A 67 0C 45 84 E4 0F 84 C7 02 00 00 48 8D 0D} + condition: + $decrypt1 and (any of ($ref*)) +} +''' + +def yara_scan(raw_data, rule_name): + addresses = {} + yara_rules = yara.compile(source=rule_source) + matches = yara_rules.match(data=raw_data) + for match in matches: + if match.rule == 'SmokeLoader': + for item in match.strings: + if item[1] == rule_name: + addresses[item[1]] = item[0] + return addresses + +def xor_decode(buffer, key): + byte_key = 0xff + for i in range(0, 4): + byte_key = byte_key^(key >> (i * 8) & 0xff) + return ''.join(chr(ord(x)^byte_key) for x in buffer) + +class SmokeLoader(malwareconfigparser): + def __init__(self, reporter=None): + malwareconfigparser.__init__(self, description='SmokeLoader configuration parser.', author='kevoreilly', reporter=reporter) + + def run(self): + filebuf = self.reporter.data + c2ref = yara_scan(filebuf, '$ref1') + if c2ref: + c2ref_offset = int(c2ref['$ref1']) + c2_delta = struct.unpack('i', filebuf[c2ref_offset+54:c2ref_offset+58])[0] + c2_offset = c2ref_offset + c2_delta + 58 + # First C2 URL + c2_size = struct.unpack('B', filebuf[c2_offset:c2_offset+1])[0] + c2_key = struct.unpack('I', filebuf[c2_offset+c2_size+1:c2_offset+c2_size+5])[0] + c2_url = xor_decode(filebuf[c2_offset+1:c2_offset+c2_size+1], c2_key) + if c2_url: + self.reporter.add_metadata('address', c2_url) + # Second C2 URL + c2_offset = c2_offset + c2_size + 9 + c2_size = struct.unpack('B', filebuf[c2_offset:c2_offset+1])[0] + c2_key = struct.unpack('I', filebuf[c2_offset+c2_size+1:c2_offset+c2_size+5])[0] + c2_url = xor_decode(filebuf[c2_offset+1:c2_offset+c2_size+1], c2_key) + if c2_url: + self.reporter.add_metadata('address', c2_url) + return + else: + c2ref = yara_scan(filebuf, '$ref2') + if c2ref: + c2ref_offset = int(c2ref['$ref2']) + c2_delta = struct.unpack('i', filebuf[c2ref_offset+50:c2ref_offset+54])[0] + c2_offset = c2ref_offset + c2_delta + 54 + # First C2 URL + c2_size = struct.unpack('B', filebuf[c2_offset:c2_offset+1])[0] + c2_key = struct.unpack('I', filebuf[c2_offset+c2_size+1:c2_offset+c2_size+5])[0] + c2_url = xor_decode(filebuf[c2_offset+1:c2_offset+c2_size+1], c2_key) + if c2_url: + self.reporter.add_metadata('address', c2_url) + # Second C2 URL + c2_offset = c2_offset + c2_size + 9 + c2_size = struct.unpack('B', filebuf[c2_offset:c2_offset+1])[0] + c2_key = struct.unpack('I', filebuf[c2_offset+c2_size+1:c2_offset+c2_size+5])[0] + c2_url = xor_decode(filebuf[c2_offset+1:c2_offset+c2_size+1], c2_key) + if c2_url: + self.reporter.add_metadata('address', c2_url) + return \ No newline at end of file