// Derived from https://github.com/Shadow0ps/solorigate_sample_source/
val sunburst_cpg = importCpg(“/XXX/sunburst.bin.zip")
Fetch all hardcoded literals in the trojanized DLL . The system needs to set a threshold to determine if there are one too many hashed literals in the list. To determine the encoding scheme follow the method that is dereferencing each literal to attempt a reversal (the scheme could be base64 encode, SHA-x, FNV-1a). In cases like these, attempt to brute force the decoding process by running this analysis in a separate thread (could be computationally intensive). Skipping the decoding process as FNV-1a was determined to be the encoding scheme (by FireEye and adjunct community) in the GetHash(..) function
val all_literals = cpg.literal.code.l.distinct.map(_.replaceAll("^\"|\"$", "")).toSet
"80zT9cvPS9X1TSxJzgAA",
"1970",
"300000UL",
"UyotTi3yTFGyUqo2qFXSAQA=",
"UypOLS7OzM/zTFGyUqo2qFXSAQA=",
"UyouSS0oVrKKBgA=",
"512",
"2UL",
"18446744073709551613UL",
"UwrJzE0tLknMLVCyUorRd0ksSdWoNqjVjNFX0gEA",
"U/LMS0mtULKqNqjVAQA=",
"U3ItS80rCaksSFWyUvIvyszPU9IBAA==",
"U3ItS80r8UvMTVWyUgKzfRPzEtNTi5R0AA==",
"U3IpLUosyczP8y1Wsqo2qNUBAA==",
"UwouTU5OTU1JTVGyKikqTdUBAA==",
"U/JNLS5OTE9VslKqNqhVAgA=",
….
There are many encoded literals in the code .. Let's attempt a base64 decode to detect literals. Other than keeping hardcoded values in hashed form, the malware has used DEFLATE compression to keep strings like WMI queries, registry entries and tokens.
decodeBase64()
For keeping the hardcoded values in the file like the list of processes, services, etc., the malware used a variant of the FNV-1a hashing algorithm by XORing the computed hash of the string with a hardcoded value at the end. In order to determine functions using variant Hashing techniques, ideintiy all code blocks using AND, XOR .. operators
cpg.method.name("<operator>.(and|xor|or)").caller.fullName.l.distinct
Based on the functions using such operators as indicated above, identify fan-in (all other functions calling this function). This list can determine suspicious encode/decode operations
cpg.method.name("<operator>.(and|xor|or)").caller.fullName.l.distinct.map {
item => (item, cpg.method.fullNameExact(item).caller.fullName.l.distinct)
}
cpg.call.name(Operators.equals).where(_.argument.order(2).isLiteral).code.l
Spawning threads as a background task could be both benign and suspicious at the same time, especially if the instance spawned is dealing with obfuscated hashes
val all_methods_spawning_threads = cpg.method.fullName(".*Thread.*").caller.fullName.l.distinct
res28: List[String] = List(
"SolarWinds.Orion.Core.BusinessLayer.OrionImprovementBusinessLayer.Update:System.Void()",
"SolarWinds.Orion.Core.BusinessLayer.BackgroundInventory.InventoryManager.RefreshInternal:System.Void()",
"SolarWinds.Orion.Core.BusinessLayer.OrionImprovementBusinessLayer.DelayMs:System.Void(System.Double,System.Double)",
"SolarWinds.Orion.Core.BusinessLayer.OrionImprovementBusinessLayer.HttpHelper.Close:System.Void(SolarWinds.Orion.Core.BusinessLayer.OrionImprovementBusinessLayer.HttpHelper,System.Threading.Thread)"
)
val instance_spawned = cpg.call.code(".*Thread.*").map(_.argument.isMethodRef.referencedMethod.fullName.l).l.flatten
instance_spawned: List[String] = List(
"SolarWinds.Orion.Core.BusinessLayer.OrionImprovementBusinessLayer.Initialize:System.Void()",
"SolarWinds.Orion.Core.BusinessLayer.OrionImprovementBusinessLayer.HttpHelper.Initialize:System.Void()"
)
val checking_process_name = cpg.call.name(Operators.equals).where(_.argument.order(2).isLiteral).where(_.argument.order(1).isCall.argument.code(".*Process.GetCurrentProcess.*ProcessName.*")).code.l
checking_process_name: List[String] = List(
"OrionImprovementBusinessLayer.GetHash(Process.GetCurrentProcess().ProcessName.ToLower()) == 17291806236368054941UL"
)
Conducting date interval checks in a control loop is always suspicious especially if the control block is executing obfuscated commands
val time_checks = cpg.call.code(".*DateTime.*Now.*CompareTo.*").callee.caller.map(i => (i.fullName, i.lineNumber, i.filename)).l
…
time_checks: List[(String, Option[Integer], String)] = List(
(
"SolarWinds.Orion.Core.BusinessLayer.OrionImprovementBusinessLayer.Initialize:System.Void()",
Some(110),
"SolarWinds.Orion.Core.BusinessLayer/BusinessLayer/OrionImprovementBusinessLayer.cs"
),
(
"SolarWinds.Orion.Core.BusinessLayer.OrionImprovementBusinessLayer.IsNullOrInvalidName:System.Boolean(System.String)",
Some(419),
"SolarWinds.Orion.Core.BusinessLayer/BusinessLayer/OrionImprovementBusinessLayer.cs"
),
(
"SolarWinds.Orion.Core.BusinessLayer.OrionImprovementBusinessLayer.DelayMs:System.Void(System.Double,System.Double)",
Some(447),
"SolarWinds.Orion.Core.BusinessLayer/BusinessLayer/OrionImprovementBusinessLayer.cs"
),
….
Creating a NamedPipeServer stream for async/sync R/W operations is a RED FLAG. Get all methods creating a named pipe
val methods_with_namedpipes = cpg.call.code(".*NamedPipeServerStream.*").callee.caller.map(i => (i.fullName, i.lineNumber, i.filename)).l
…
methods_with_namedpipes: List[(String, Option[Integer], String)] = List(
(
"SolarWinds.Orion.Core.BusinessLayer.OrionImprovementBusinessLayer.Initialize:System.Void()",
Some(110),
"SolarWinds.Orion.Core.BusinessLayer/BusinessLayer/OrionImprovementBusinessLayer.cs"
))
…
object FNV {
private val INIT32 = BigInt("811c9dc5", 16);
private val INIT64 = BigInt("cbf29ce484222325", 16);
private val PRIME32 = BigInt("01000193", 16);
private val PRIME64 = BigInt("100000001b3", 16);
private val MOD32 = BigInt("2").pow(32);
private val MOD64 = BigInt("2").pow(64);
private val MASK = 0xff
@inline private final def calc(prime: BigInt, mod: BigInt)(hash: BigInt, b: Byte): BigInt = ((hash * prime) % mod) ^ (b & MASK)
@inline private final def calcA(prime: BigInt, mod: BigInt)(hash: BigInt, b: Byte): BigInt = ((hash ^ (b & MASK)) * prime) % mod
/**
* Calculates 32bit FNV-1 hash
* @param data the data to be hashed
* @return a 32bit hash value
*/
@inline final def hash32(data: Array[Byte]): BigInt = data.foldLeft(INIT32)(calc(PRIME32, MOD32))
/**
* Calculates 32bit FNV-1a hash
* @param data the data to be hashed
* @return a 32bit hash value
*/
@inline final def hash32a(data: Array[Byte]): BigInt = data.foldLeft(INIT32)(calcA(PRIME32, MOD32))
/**
* Calculates 64bit FNV-1 hash
* @param data the data to be hashed
* @return a 64bit hash value
*/
@inline final def hash64(data: Array[Byte]): BigInt = data.foldLeft(INIT64)(calc(PRIME64, MOD64))
/**
* Calculates 64bit FNV-1a hash
* @param data the data to be hashed
* @return a 64bit hash value
*/
@inline final def hash64a(data: Array[Byte]): BigInt = data.foldLeft(INIT64)(calcA(PRIME64, MOD64))
}
import scala.collection.immutable._
val process_blacklist = Set(
"apimonitor-x64",
"apimonitor-x86",
"autopsy64",
"autopsy",
"autoruns64",
"autoruns",
"autorunsc64",
"autorunsc",
"binaryninja",
"blacklight",
"cff explorer",
"cutter",
"de4dot",
"debugview",
"diskmon",
"dnsd",
"dnspy",
"dotpeek32",
"dotpeek64",
"dumpcap",
"evidence center",
"exeinfope",
"fakedns",
"fakenet",
"ffdec",
"fiddler",
"fileinsight",
"floss",
"gdb",
"hiew32demo",
"hiew32",
"hollows_hunter",
"idaq64",
"idaq",
"idr",
"ildasm",
"ilspy",
"jd-gui",
"lordpe",
"officemalscanner",
"ollydbg",
"pdfstreamdumper",
"pe-bear",
"pebrowse64",
"peid",
"pe-sieve32",
"pe-sieve64",
"pestudio",
"peview",
"ppee",
"procdump64",
"procdump",
"processhacker",
"procexp64",
"procexp",
"procmon",
"prodiscoverbasic",
"py2exedecompiler",
"r2agent",
"rabin2",
"radare2",
"ramcapture64",
"ramcapture",
"reflector",
"regmon",
"resourcehacker",
"retdec-ar-extractor",
"retdec-bin2llvmir",
"retdec-bin2pat",
"retdec-config",
"retdec-fileinfo",
"retdec-getsig",
"retdec-idr2pat",
"retdec-llvmir2hll",
"retdec-macho-extractor",
"retdec-pat2yara",
"retdec-stacofin",
"retdec-unpacker",
"retdec-yarac",
"rundotnetdll",
"sbiesvc",
"scdbg",
"scylla_x64",
"scylla_x86",
"shellcode_launcher",
"solarwindsdiagnostics",
"sysmon64",
"task explorer",
"task explorer-x64",
"tcpdump",
"tcpvcon",
"tcpview",
"vboxservice",
"win32_remote",
"win64_remotex64",
"windbg",
"windump",
"winhex64",
"winhex",
"winobj",
"wireshark",
"x32dbg",
"x64dbg",
"xwforensics64",
"xwforensics",
"redcloak",
"avgsvc",
"avgui",
"avgsvca",
"avgidsagent",
"avgsvcx",
"avgwdsvcx",
"avgadminclientservice",
"afwserv",
"avastui",
"avastsvc",
"aswidsagent",
"aswidsagenta",
"aswengsrv",
"avastavwrapper",
"bccavsvc",
"psanhost",
"psuaservice",
"psuamain",
"avp",
"avpui",
"ksde",
"ksdeui",
"tanium",
"taniumclient",
"taniumdetectengine",
"taniumendpointindex",
"taniumtracecli",
"taniumtracewebsocketclient64"
)
val process_fnv_tuples = process_blacklist.map { i =>
val seed = 6605813339339102567L
val op = FNV.hash64a(i.getBytes) ^ seed
(i, op, op.toString + "L")
}
val obfuscated_process_checks = process_fnv_tuples.map(_._3).toSet.intersect(all_literals)
val services_blacklist : Map[String, Set[String]] = Map(
"Windows Defender" -> Set("msmpeng","windefend"),
"Windows Sense" -> Set("mssense","sense"),
"Windows Sensor" -> Set("microsoft.tri.sensor","microsoft.tri.sensor.updater"),
"NIST" -> Set("cavp"),
"Carbon Black" -> Set(
"cb",
"carbonblack",
"carbonblackk",
"cbcomms",
"cbstream"),
"CrowdStrike" -> Set(
"csfalconservice",
"csfalconcontainer",
"csagent",
"csdevicecontrol",
"csfalconservice"),
"FireEye" -> Set(
"xagt",
"xagtnotif",
"fe_avk",
"fekern",
"feelam",
"fewscservice"),
"ESET" -> Set(
"ekrn",
"eguiproxy",
"egui",
"eamonm",
"eelam",
"ehdrv",
"ekrnepfw",
"epfwwfp",
"ekbdflt",
"epfw"),
"F-SECURE" -> Set(
"fsgk32st",
"fswebuid",
"fsgk32",
"fsma32",
"fssm32",
"fnrb32",
"fsaua",
"fsorsp",
"fsav32",
"f-secure gatekeeper handler starter",
"f-secure network request broker",
"f-secure webui daemon",
"fsma",
"fsorspclient",
"f-secure gatekeeper",
"f-secure hips",
"fsbts",
"fsni",
"fsvista",
"f-secure filter",
"f-secure recognizer",
"fses",
"fsfw",
"fsdfw",
"fsms",
"fsdevcon")
)
val process_fnv_services = services_blacklist.values.flatten.map { i =>
val seed = 6605813339339102567L
val op = FNV.hash64a(i.getBytes) ^ seed
op.toString + "L"
}
val obfuscated_services_checks = process_fnv_services.toSet.intersect(all_literals)
val driver_blacklist = Set(
"cybkerneltracker.sys",
"atrsdfw.sys",
"eaw.sys",
"rvsavd.sys",
"dgdmk.sys",
"sentinelmonitor.sys",
"hexisfsmonitor.sys",
"groundling32.sys",
"groundling64.sys",
"safe-agent.sys",
"crexecprev.sys",
"psepfilter.sys",
"cve.sys",
"brfilter.sys",
"brcow_x_x_x_x.sysv",
"lragentmf.sys",
"libwamf.sys");
val process_fnv_services = driver_blacklist.map { i =>
val seed = 6605813339339102567L
val op = FNV.hash64a(i.getBytes) ^ seed
op + "L"
}
val obfuscated_driver_checks = process_fnv_services.toSet.intersect(all_literals)
val domain_blacklist = Set(
"swdev.local",
"swdev.dmz",
"lab.localv",
"lab.na",
"emea.sales",
"cork.lab",
"dev.local",
"dmz.local",
"pci.local",
"saas.swi",
"lab.rio",
"lab.brno",
"apac.lab");
val process_fnv_domain = domain_blacklist.map { i =>
val seed = 6605813339339102567L
val op = FNV.hash64a(i.getBytes) ^ seed
op
}
val obfuscated_domain_checks = process_fnv_domain.toSet.intersect(all_literals)
val obfuscated_http_codes = Set(
"expect",
"content-type",
"accept",
"content-type",
"user-agent",
"100-continue",
"connection",
"referer",
"keep-alive",
"close",
"if-modified-since",
"date"
)
val http_codes = obfuscated_http_codes.map { i =>
val seed = 6605813339339102567L
val op = FNV.hash64a(i.getBytes) ^ seed
op
}
val http_comm_checks = http_codes.toSet.intersect(all_literals)