- commit
- 1f3a146
- parent
- 3e0413f
- author
- Neale Pickett
- date
- 2021-05-06 14:51:18 -0600 MDT
Add release wording and remove redundant thing
1 files changed,
+100,
-36
+100,
-36
1@@ -1,13 +1,37 @@
2 #! /usr/bin/python3
3
4 # Neale Pickett <neale@lanl.gov>
5-# Unclassified/FOUO
6 #
7 # Created: 2020-12-14 16:49:51
8-# Last-modified: 2020-12-22 21:42:40
9+# Last-modified: 2021-05-06 11:09:55
10 #
11 # Based on work by @RedDrip7 (twitter),
12-# who should be getting more credit in the English-speaking world.
13+# and Prevasio (https://blog.prevasio.com/2020/12/sunburst-backdoor-deeper-look-into.html)
14+
15+# This is public domain software. The public may copy, distribute, prepare derivative works and
16+# publicly display this software without charge, provided that this Notice, the statement
17+# of reserved government right, and any statement of authorship are reproduced on all copies.
18+# If software is modified to produce derivative works, such modified software should
19+# be clearly marked, so as not to confuse it with the version available from LANL.
20+
21+# NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE.
22+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
23+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25+# NEITHER THE UNITED STATES NOR THE UNITED STATES DEPARTMENT OF ENERGY/NATIONAL
26+# NUCLEAR SECURITY ADMINSTRATION, NOR Triad National Security, LLC. NOR ANY OF THEIR
27+# EMPLOYEES, MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR ASSUMES ANY LEGAL LIABILITY
28+# OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR USEFULNESS OF ANY INFORMATION,
29+# APPARATUS, PRODUCT, OR PROCESS DISCLOSED, OR REPRESENTS THAT ITS USE WOULD NOT
30+# INFRINGE PRIVATELY OWNED RIGHTS.
31+
32+# IN NO EVENT SHALL THE U.S. GOVERNMENT OR ITS CONTRACTORS BE LIABLE FOR ANY DIRECT,
33+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
34+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
36+# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
37+# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
38+# OF THE POSSIBILITY OF SUCH DAMAGE.
39
40 import argparse
41 import base64
42@@ -16,6 +40,7 @@ import csv
43 import itertools
44 import re
45 import sys
46+import time
47
48
49 knownDomains = [
50@@ -23,7 +48,6 @@ knownDomains = [
51 "appsync-api.us-east-2.avsvmcloud.com",
52 "appsync-api.us-west-2.avsvmcloud.com",
53 "appsync-api.eu-west-1.avsvmcloud.com",
54- "avsvmcloud.com",
55 ]
56
57
58@@ -34,6 +58,18 @@ def xor(key, buf):
59 Esab32Alphabet = "ph2eifo3n5utg1j8d94qrvbmk0sal76c"
60 SubstitutionAlphabet = 'rq3gsalt6u1iyfzop572d49bnx8cvmkewhj'
61 SubstitutionAlphabet0 = '0_-.'
62+SequenceAlphabet = '0123456789abcdefghijklmnopqrstuvwxyz'
63+
64+Apps = [
65+ "Windows Live OneCare / Windows Defender",
66+ "Windows Defender Advanced Threat Protection",
67+ "Microsoft Defender for Identity",
68+ "Carbon Black",
69+ "CrowdStrike",
70+ "FireEye",
71+ "ESET",
72+ "F-Secure",
73+]
74
75
76 def DecodeBase32(s: str):
77@@ -74,15 +110,16 @@ def DecodeSubst(s: str) -> str:
78 out = []
79 for c in s:
80 if c == '0':
81+ # Use alternate alphabet next time
82 alphabet = SubstitutionAlphabet0
83- else:
84- try:
85- pos = (SubstitutionAlphabet.index(c) - 4) % len(alphabet)
86- except ValueError:
87- raise RuntimeError(
88- "Not a subst encoded character: %c in %s" % (c, s))
89- out.append(alphabet[pos])
90- alphabet = SubstitutionAlphabet
91+ continue
92+ try:
93+ pos = (SubstitutionAlphabet.index(c) - 4) % len(alphabet)
94+ except ValueError:
95+ raise RuntimeError(
96+ "Not a subst encoded character: %c in %s" % (c, s))
97+ out.append(alphabet[pos])
98+ alphabet = SubstitutionAlphabet
99 return "".join(out)
100
101
102@@ -153,33 +190,61 @@ def DecodeDomain(domain: str) -> (Guid, int, str):
103 foundDomain = d
104 break
105 if not foundDomain:
106- raise RuntimeError("Can't find domain for %s" % s)
107+ return (None, None, "[Probably not a Sunburst domain]")
108 s = s[:-len(foundDomain)]
109 if not s:
110 return (None, None, "[no data transmitted]")
111 assert(s[-1] == '.')
112 s = s[:-1]
113
114- if foundDomain == "avsvmcloud.com":
115- return (None, None, "[Probably not a Sunburst domain]")
116 if len(s) < 16:
117 return (None, None, "[too short]")
118
119- dec, _ = DecodeEsab32(s[:15])
120- eguid = dec.to_bytes(10, 'little')[:9]
121- guid = int.from_bytes(xor(eguid[0:1], eguid[1:]), 'big')
122-
123- unknown_a = s[15]
124- payload = s[16:]
125-
126- decoder = DecodersByGuid.get(guid)
127- if not decoder:
128- decoder = DGADecoder(guid)
129- DecodersByGuid[guid] = decoder
130-
131- decoded = decoder.decode(payload)
132-
133- return (guid, unknown_a, decoded)
134+ c0 = s.encode("ASCII")[0]
135+ # https://blog.prevasio.com/2020/12/sunburst-backdoor-part-iii-dga-security.html
136+ sequence = (c0 % 36) - SequenceAlphabet.index(s[15])
137+
138+ if 0 <= sequence < 3:
139+ dec, _ = DecodeEsab32(s[:15])
140+ eguid = dec.to_bytes(10, 'little')[:9]
141+ guid = xor(eguid[0:1], eguid[1:])
142+ payload = s[16:]
143+ decoder = DecodersByGuid.get(guid)
144+ if not decoder:
145+ decoder = DGADecoder(guid)
146+ DecodersByGuid[guid] = decoder
147+ decoded = decoder.decode(payload)
148+ else:
149+ # https://blog.prevasio.com/2020/12/sunburst-backdoor-part-iii-dga-security.html
150+ print(s, c0, sequence)
151+ dec1num, bits = DecodeEsab32(s)
152+ dec1len = bits // 8
153+ dec1 = dec1num.to_bytes(dec1len+1, "little")[:dec1len]
154+
155+ dec2 = xor(dec1[:1], dec1[1:])
156+
157+ guid = xor(dec2[9:11], dec2[0:8])
158+ tslen = int.from_bytes(dec2[8:11], "big")
159+ length = tslen >> (8+8+4)
160+ epoch = 1262329200 + ((tslen & 0xfffff) << 2) # 4s intervals since 2010-01-01
161+ timestamp = time.gmtime(epoch)
162+
163+ print(dec2[8:], tslen, length, timestamp)
164+ state = int.from_bytes(dec2[15:17], "big")
165+ decodedStrings = []
166+ for i in range(len(Apps)):
167+ appState = state >> (i * 2)
168+ app = Apps[i]
169+ appStateString = []
170+ if appState & 0b01:
171+ appStateString.append("running")
172+ if appState & 0b10:
173+ appStateString.append("stopped")
174+ if appStateString:
175+ decodedStrings.append("%s [%s]" % (app, ",".join(appStateString)))
176+ decoded = "\n".join(decodedStrings)
177+
178+ return (guid, sequence, decoded)
179
180
181 class TextReader:
182@@ -195,8 +260,7 @@ class TextReader:
183 class CsvReader:
184 def __init__(self, infile):
185 self.reader = csv.DictReader(infile)
186- self.fieldnames = self.reader.fieldnames + \
187- ["guid", "unknown a", "decode"]
188+ self.fieldnames = self.reader.fieldnames
189
190 def __iter__(self):
191 for record in self.reader:
192@@ -230,14 +294,14 @@ def main():
193 parser.print_help()
194 return
195
196- fieldnames = reader.fieldnames + ["guid", "unknown a", "decode"]
197+ fieldnames = reader.fieldnames + ["guid", "sequence", "decode"]
198 writer = csv.DictWriter(args.outfile, fieldnames)
199 writer.writeheader()
200 for record in reader:
201 name = record.get("name") or record.get("fqdn")
202- guid, unknown_a, ptext = DecodeDomain(name)
203- record["guid"] = guid
204- record["unknown a"] = unknown_a
205+ guid, sequence, ptext = DecodeDomain(name)
206+ record["guid"] = int.from_bytes(guid, "big")
207+ record["sequence"] = sequence
208 record["decode"] = ptext
209 writer.writerow(record)
210