Bilgus

Desktop Shell Bags Parser

2 posts in this topic

#1 ·  Posted (edited)

Here is a preliminary Shellbag parser for desktop icons

I used information found at http://www.williballenthin.com/forensics/shellbags/http://forensicswiki.org/wiki/Shell_Item#Format, and https://github.com/libyal/libfwsi/blob/master/documentation/Windows%20Shell%20Item%20format.asciidoc

 

So far This only works with the desktop ShItem Specs although, I admit this is the only one I tried it with.

The code makes a file called data.txt containing the data from the registry key and

a file with the name 'RegKeyValNameXXXXxyyyy'_FileData for each ItemPos Value found

The Information is returned in an INI file With [SHITEM#] as the section name

This Code is not commented very well yet nor is it a final version

Please let me know what kind of errors you find and Post the improvements you make.

 

Sample Entry For Computer:

[SHITEM0]
FileStart= 0x0000000000000010
FileEnd= 0x000000000000002C
RecordLen= 28
FilePtr= 16
SHIconX= 36
SHIconY= -1
Size= 20
Flags= 501F
GUID= 20D04FE0-3AEA-1069-A2D8-08002B30309D
Shortname= Computer
Longname= Computer

Sample Entry for Google Chrome

[SHITEM3]
FileStart= 0x00000000000000C4
FileEnd= 0x000000000000013A
RecordLen= 118
FilePtr= 196
SHIconX= 36
SHIconY= 368
Size= 108
Flags= 003A
FileSize= 2193
ModifiedDate= 02/19/2016
ModifiedTime= 22:56.10
FileAttribs= 8224
Shortname= GOOGLE~1.LNK
ExtSize= 80
ExtVer= 8
ExtSIG= 0xBEEF0004
CreatedDate= 09/26/2014
CreatedTime= 03:09.38
AccessDate= 09/26/2014
AccessTime= 03:09.38
Unknown2= 42
64FileRef= 000300000000F689
Unknown3= 0
LongNameSize= 0
Unknown4= 0
LongName= Google Chrome.lnk
LongNameAddl= 
Unknown5= 0024001C

The Code

#include <WinAPIFiles.au3> ;FileOpen
Opt("MustDeclareVars", 1)
Local $sSubKey = ""
Local $aRegKeys[30]
Local $iRegCount = 0
For $i = 1 To 30
    $sSubKey = RegEnumVal("HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Bags\1\Desktop\", $i)
    If @error = -1 Then ExitLoop
    If StringInStr($sSubKey, "ItemPos", 0) Then
        $aRegKeys[$iRegCount + 1] = $sSubKey
        $iRegCount += 1
    EndIf
Next
$aRegKeys[0] = $iRegCount

Local $sFilePath = ""
Local $PathResultsIni = ""
Local $sRegEntry = ""
For $i = 1 To $aRegKeys[0]
    $sSubKey = $aRegKeys[$i]

    $sRegEntry = RegRead("HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Bags\1\Desktop", $sSubKey)
    If Not (@extended = 3) Then
        MsgBox(0, "Incorrect Key Type", $sSubKey & @CRLF & "Key Not REG_BINARY ") ;Const $REG_BINARY = 3
    EndIf

    If Not (StringLeft($sRegEntry, 34) = "0x00000000000000000000000000000000") Then
        MsgBox(0, "Incorrect File Header", $sSubKey & @CRLF & "Header does not match ShItem Header " & @CRLF & StringLeft($sRegEntry, 32) & @CRLF & "0x00000000000000000000000000000000")
    EndIf

    $sFilePath = @ScriptDir & "\RegData.txt"
    $PathResultsIni = @ScriptDir & "\" & $sSubKey & "_" & "FileData.Ini"


    FileDelete($PathResultsIni)
    FileDelete($sFilePath)

    If Not FileWrite($sFilePath, $sRegEntry) Then ; Create a file to read data from.
        MsgBox("0", @ScriptName, "An error occurred while writing the temporary data file.")
    EndIf

    $sRegEntry = ""
    $iRegCount = ParseSH_ItemToIni($sFilePath, $PathResultsIni)
    $aRegKeys[$i] = $iRegCount & " Entries saved to:" & @CRLF & $PathResultsIni

Next
$sSubKey = ""
For $i = 1 To $aRegKeys[0]
    $sSubKey &= $aRegKeys[$i] & @CRLF
Next

MsgBox(0, @ScriptName & " Finished Parsing", $sSubKey)

;-------------------------------------------------------------------
Func ParseSH_ItemToIni($sFilePath, $PathResultsIni)
    Local Const $hFile = FileOpen($sFilePath, $FO_BINARY + $FO_FULLFILE_DETECT)

    Local $iFilePos0 = 0 ;File Pointer
    FileSetPos($hFile, 0, 2)
    Local $iFileLast = FileGetPos($hFile) ;End Of File
    FileSetPos($hFile, $iFilePos0, 0)

    Local $iFileStart = 0, $FileEnd = 0, $iRecordStart = 0, $iRecordNumber = 0, $iRecordEnd
    Local $iSHIconX = 0, $iSHIconY = 0
    Local $iSize = 0, $iFlags = 0, $iSortOrderIndex = 0 ;added
    Local $sGUID = 0
    Local $iFileSize = 0
    Local $sModDate = "", $sModTime = ""
    Local $iFileAttribs = 0
    Local $sShortname = ""
    Local $iExtSize = 0, $iExtVer = 0
    Local $iExtSig = 0
    Local $iUnknown0 = 0, $iUnknown1 = 0
    Local $sCreDate = 0, $sCreTime = 0
    Local $sAccDate = 0, $sAccTime = 0
    Local $iUnknown2 = 0
    Local $i64FileRef = 0
    Local $iUnknown3 = 0
    Local $iLongNameSize = 0
    Local $iUnknown4 = 0
    Local $sLongName = "", $sLongNameAddl = ""
    Local $iUnknown5 = 0


    For $iPos = 0 To $iFileLast - 2 ;Step 2 Removed causes the loss of the last item
        ;Set all fields to default Values
        $iFileStart = 0
        $FileEnd = 0
        $iRecordStart = 0

        $iSHIconX = 0
        $iSHIconY = 0
        $iSize = 0
        $iFlags = 0
        $iSortOrderIndex = 0 ;added
        $sGUID = 0
        $iFileSize = 0
        $sModDate = ""
        $sModTime = ""
        $iFileAttribs = 0
        $sShortname = ""
        $iExtSize = 0
        $iExtVer = 0
        $iExtSig = 0
        $sCreDate = 0
        $sCreTime = 0
        $sAccDate = 0
        $sAccTime = 0
        $iUnknown2 = 0
        $i64FileRef = 0
        $iUnknown3 = 0
        $iLongNameSize = 0
        $iUnknown4 = 0
        $sLongName = ""
        $sLongNameAddl = ""
        $iUnknown5 = 0
        ;$iUnknown0 = 0 Replaced By $iExtSig
        ;$iUnknown1 = 0 Replaced By $iExtSig

        $iFileStart = FileGetPos($hFile)
        $iSHIconX = ReadRegHexStringValue($hFile, 32, "INT")
        $iSHIconY = ReadRegHexStringValue($hFile, 32, "INT")
        $iRecordStart = FileGetPos($hFile)

        $iSize = ReadRegHexStringValue($hFile, 16, "INT")
        $iFlags = ReadRegHexStringValue($hFile, 8, "INT") ;16 changed to 8
        $iSortOrderIndex = ReadRegHexStringValue($hFile, 8, "INT") ;Added

        If $iSize <= 21 Then
            If Not (BitAND($iFlags, 0x00FF) = 0x1F) Then
                $iFilePos0 += 2
                FileSetPos($hFile, $iFilePos0, 0)
                ContinueLoop
            ElseIf $iSize > 15 And $iSortOrderIndex > 0 Then

                $sGUID = ReadRegHexStringValue($hFile, 128, "GUID")

                $sShortname = RegRead("HKEY_CLASSES_ROOT\CLSID\{" & $sGUID & "}", "")
                If Not (@error) Then

                    $iRecordEnd = FileGetPos($hFile)
                    ConsoleWrite(@CRLF & "[SHITEM" & $iRecordNumber & "] " & $sShortname & ":" & $iExtSig & ",")
                    FileWrite($PathResultsIni, _
                            @CRLF & "[SHITEM" & $iRecordNumber & "]" & _
                            @CRLF & "FileStart= 0x" & Hex($iFileStart, 16) & _
                            @CRLF & "FileEnd= 0x" & Hex($iRecordEnd, 16) & _
                            @CRLF & "RecordLen= " & ($iRecordEnd - $iFileStart) & _
                            @CRLF & "FilePtr= " & ($iFilePos0) & _ ; the second part of FilePtr is SortOrderIndex
                            @CRLF & "SHIconX= " & $iSHIconX & _
                            @CRLF & "SHIconY= " & $iSHIconY & _
                            @CRLF & "Size= " & $iSize & _
                            @CRLF & "Flags= " & Hex($iFlags, 2) & _
                            @CRLF & "SortOrderIndex= " & $iSortOrderIndex & _
                            @CRLF & "GUID= " & $sGUID & _
                            @CRLF & "Shortname= " & $sShortname & _
                            @CRLF & "Longname= " & $sShortname & @CRLF & @CRLF)

                    $iRecordNumber += 1
                    $iFilePos0 = FileGetPos($hFile)
                Else
                    $iFilePos0 += 2
                    FileSetPos($hFile, $iFilePos0, 0)
                    MsgBox(0, "INVALID GUID", "Invalid GUID encountered", 10)
                EndIf
                ContinueLoop
            EndIf
        EndIf

        $iFileSize = ReadRegHexStringValue($hFile, 32, "INT")
        $sModDate = ReadRegHexStringValue($hFile, 16, "DOSDATE")
        $sModTime = ReadRegHexStringValue($hFile, 16, "DOSTIME")
        $iFileAttribs = ReadRegHexStringValue($hFile, 16, "INT")
        $sShortname = ReadRegHexStringValue($hFile, $iSize, "String")

        $iExtSize = ReadRegHexStringValue($hFile, 16, "INT")
        $iExtVer = ReadRegHexStringValue($hFile, 16, "INT")
        $iExtSig = "0x" & Hex(ReadRegHexStringValue($hFile, 32, "INT"))
        ;msgbox(0,"",$iExtSig & " " & HEX(BitAND($iExtSig, 0xFFFF0000)))
        If ($iSize <= 21 Or Not (BitAND($iExtSig, 0xFFFF0000) = 0xBEEF0000) Or Not (BitAND($iFlags, 0x70)) = 0x30) Then
            $iFilePos0 += 2
            FileSetPos($hFile, $iFilePos0, 0)
            ContinueLoop
        EndIf
        ;FileRead($hFile,2)

        If $iExtVer >= 0x0003 Then
            ;$iUnknown0 = ReadRegHexStringValue($hFile, 16, "INT") Replaced By $iExtSig
            ;$iUnknown1 = ReadRegHexStringValue($hFile, 16, "INT") Replaced By $iExtSig
            $sCreDate = ReadRegHexStringValue($hFile, 16, "DOSDATE")
            $sCreTime = ReadRegHexStringValue($hFile, 16, "DOSTIME")
            $sAccDate = ReadRegHexStringValue($hFile, 16, "DOSDATE")
            $sAccTime = ReadRegHexStringValue($hFile, 16, "DOSTIME")
            $iUnknown2 = ReadRegHexStringValue($hFile, 32, "INT")
        EndIf

        If $iExtVer >= 0x0007 Then

            $i64FileRef = Hex(ReadRegHexStringValue($hFile, 64, "INT"), 16)
            $iUnknown3 = ReadRegHexStringValue($hFile, 64, "INT")
            $iLongNameSize = ReadRegHexStringValue($hFile, 16, "INT")
            If $iExtVer <= 0x0008 Then ;Changed to <= from >= Not working right otherwise
                $iUnknown4 = ReadRegHexStringValue($hFile, 32, "INT")
            EndIf

            $sLongName = ReadRegHexStringValue($hFile, 0, "WSTRING")
            If $iLongNameSize > 0 Then
                ;MsgBox(0, "$iLongNameSize", $iLongNameSize)
                $sLongNameAddl = ReadRegHexStringValue($hFile, $iLongNameSize + 2, "WString")
            EndIf
        ElseIf $iExtVer >= 0x0003 Then

            $iUnknown5 = ReadRegHexStringValue($hFile, 16, "INT")

        EndIf

        $iUnknown5 = ReadRegHexStringValue($hFile, 32, "HEX")
        $iRecordEnd = FileGetPos($hFile)

        If Not (($iRecordEnd - $iRecordStart) = ($iSize + 2)) Then
            $iFilePos0 += 2
            FileSetPos($hFile, $iFilePos0, 0)

            ContinueLoop
        ElseIf ($iFilePos0 > $iFileLast - 4) Then
            ExitLoop

        ElseIf $iSize < $iFileLast Then
            ConsoleWrite(@CRLF & "[SHITEM" & $iRecordNumber & "] " & $sLongName & ":" & $iExtSig & ",")

        EndIf

        FileWrite($PathResultsIni, _
                @CRLF & "[SHITEM" & $iRecordNumber & "]" & _
                @CRLF & "FileStart= 0x" & Hex($iFileStart, 16) & @CRLF & "FileEnd= 0x" & Hex($iRecordEnd, 16) & _
                @CRLF & "RecordLen= " & ($iRecordEnd - $iFileStart) & _
                @CRLF & "FilePtr= " & ($iFilePos0) & _
                @CRLF & "SHIconX= " & $iSHIconX & @CRLF & "SHIconY= " & $iSHIconY & _
                @CRLF & "Size= " & $iSize & _
                @CRLF & "Flags= " & Hex($iFlags, 2) & _
                @CRLF & "SortOrderIndex= " & $iSortOrderIndex & _ ;added
                @CRLF & "FileSize= " & $iFileSize & _
                @CRLF & "ModifiedDate= " & $sModDate & @CRLF & "ModifiedTime= " & $sModTime & _
                @CRLF & "FileAttribs= " & $iFileAttribs & _
                @CRLF & "Shortname= " & $sShortname & _
                @CRLF & "ExtSize= " & $iExtSize & _
                @CRLF & "ExtVer= " & $iExtVer & _
                @CRLF & "ExtSIG= " & ($iExtSig) & _                  
                @CRLF & "CreatedDate= " & $sCreDate & @CRLF & "CreatedTime= " & $sCreTime & _
                @CRLF & "AccessDate= " & $sAccDate & @CRLF & "AccessTime= " & $sAccTime & _
                @CRLF & "Unknown2= " & $iUnknown2 & _
                @CRLF & "64FileRef= " & $i64FileRef & _
                @CRLF & "Unknown3= " & $iUnknown3 & _
                @CRLF & "LongNameSize= " & $iLongNameSize & _
                @CRLF & "Unknown4= " & $iUnknown4 & _
                @CRLF & "LongName= " & $sLongName & _
                @CRLF & "LongNameAddl= " & $sLongNameAddl & _
                @CRLF & "Unknown5= " & $iUnknown5 & @CRLF & @CRLF)
;@CRLF & "Unknown0= " & $iUnknown0 & _;@CRLF & "Unknown1= " & $iUnknown1 & _ (Replaced By $iExtSig)
        $iRecordNumber += 1
    Next

    FileClose($hFile)

    Return $iRecordNumber
EndFunc   ;==>ParseSH_ItemToIni

Func ReadRegHexStringValue(Const $hFile, $iLength, $sType)
    $sType = StringUpper($sType)
    Local $sData = ""
    If Not ($sType = "WSTRING" Or $sType = "STRING" Or $sType = "GUID") Then
        $iLength /= 8
        $sData = FileRead($hFile, $iLength)
    EndIf
    Local $vReturn = ""
    Local $iCount = 0
    Local $aTemp, $iTemp, $sTemp
    Switch $sType
        Case "STRING"
            $vReturn = ""
            For $i = 0 To 260
                ;$iFilePos += 8
                $sData = FileRead($hFile, 1)
                If $sData = Chr(00) Then
                    If Not (FileRead($hFile, 1) = Chr(00)) Then FileSetPos($hFile, FileGetPos($hFile) - 1, 0) ;Sometimes Double Null Terminated
                    ExitLoop
                EndIf
                $vReturn &= BinaryToString($sData)

            Next
        Case "WSTRING"

            $vReturn = ""
            For $i = 0 To 260
                $sData = FileRead($hFile, 2)
                If $sData = Chr(00) & Chr(00) Then ExitLoop
                $vReturn &= BinaryToString($sData, $SB_UTF16LE)

            Next
            $aTemp = 0
        Case "INT"
            $vReturn = Int($sData)
        Case "DOSDATE"

            $vReturn &= StringFormat("%02d/%02d/%04d", BitShift(BitAND($sData, 0x01e0), 5), BitAND($sData, 0x1F), BitShift($sData, 9) + 1980)
        Case "DOSTIME" ;UTC

            $vReturn &= StringFormat("%02d:%02d.%02d", BitShift($sData, 11), BitShift(BitAND($sData, 0x07e0), 5), BitAND($sData, 0x1F) * 2)
        Case "HEX"
            $vReturn = Hex(Int($sData), $iLength * 2)
        Case "HEXSTR"
            $vReturn = Hex($sData, $iLength * 2)
        Case "GUID"
            If $iLength = 128 Then
                $sData = FileRead($hFile, 4)
                $vReturn = Hex(Int($sData), 8) & "-"
                $sData = FileRead($hFile, 2)
                $vReturn &= Hex(Int($sData), 4) & "-"
                $sData = FileRead($hFile, 2)
                $vReturn &= Hex(Int($sData), 4) & "-"
                $sData = FileRead($hFile, 2)
                $vReturn &= Hex($sData, 2) & "-"
                $sData = FileRead($hFile, 6)
                $vReturn &= Hex($sData, 4)
            Else
                $vReturn = 0
            EndIf
            #cs
                ;Old "GUID"
                ;= Hex(ReadRegHexStringValue($hFile, 32, "INT"), 8) & "-" & _
                ;Hex(ReadRegHexStringValue($hFile, 16, "INT"), 4) & "-" & _
                ;Hex(ReadRegHexStringValue($hFile, 16, "INT"), 4) & "-" & _
                ;ReadRegHexStringValue($hFile, 16, "Hex") & "-" & _
                ;ReadRegHexStringValue($hFile, 48, "HEX")
            #CE

        Case Else
            MsgBox(0, "ReadRegHexStringValue Error", "Datatype " & $sType & " not found.")
    EndSwitch


    Return $vReturn
EndFunc   ;==>ReadRegHexStringValue

 

Edited by Bilgus
Split Flags into Flags and SortOrderIndex also added mask of 0x70 on Flags for the longer shitem entry
1 person likes this

Share this post


Link to post
Share on other sites



#2 ·  Posted (edited)

As of today this code is 1 day old. I haven't tried it on anything but Win7 x64 and only the Desktop Bags located in HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Bags\1\Desktop. I'd consider this ALPHA and for sure not use it in any script that is anything near critical.

It should be noted that this program only works by trying every position in the file until the Structure matches the data. Furthermore,  it only depends on a few Items for verification. Namely: matching $iSize+2 to the record size, $iExtSig= 0xBEEF0000 , ($iFlags & 0x70) = 0x30 and That size is Greater than 20\21.

For the builtin icons Flags = 0x1F, $iSortOrderIndex > 0 and GUID entry from the registry, This is probably pretty robust but I had to rearrange the GUID data BEndian and LEndian from the Reg Entry so maybe there is some kind of format to it or it won't work on every system IDK.

I am not sure how robust any of this will be so hopefully someone has an epiphany :P

 

Edited by Bilgus
added info

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now