The following code snippet could help. It's very, very simplified code combined from Windows Shellbag Forensics article and shellbags.py script (the latter is written in Python 2.7 hence unserviceable for me).
import struct
from winreg import *
aReg = ConnectRegistry(None, HKEY_CURRENT_USER)
aKey = OpenKey(aReg, r"Software\Microsoft\Windows\Shell\Bags\1\Desktop", REG_BINARY)
name, value, type_ = EnumValue(aKey, 9)
offset = 0x10
head = [struct.unpack_from("<H", value[offset:],0)[0],
struct.unpack_from("<H", value[offset:],2)[0],
struct.unpack_from("<H", value[offset:],4)[0],
struct.unpack_from("<H", value[offset:],6)[0]]
number_of_items = struct.unpack_from("<I", value[offset:],8)[0] # 4 bytes (dword)
offset += 12
for x in range( number_of_items):
uint16_size = struct.unpack_from("<H", value[offset:],0)[0];
uint16_flags = struct.unpack_from("<H", value[offset:],2)[0];
uint32_filesize = struct.unpack_from("<I", value[offset:],4)[0];
dosdate_date = struct.unpack_from("<H", value[offset:],8)[0];
dostime_time = struct.unpack_from("<H", value[offset:],10)[0];
fileattr16_ = struct.unpack_from("<H", value[offset:],12)[0];
offset += 12
entry_name = value[offset:(offset + (2 * uint32_filesize - 8))].decode('utf-16-le')
offset += (2 * uint32_filesize - 4 )
if offset % 2:
offset += 1
print( x, uint32_filesize, entry_name)
print( '\nThere is', (len(value) - offset), 'bytes left' )
Output (truncated): .\SO\70039190.py
0 44 ::{59031A47-3F72-44A7-89C5-5595FE6B30EE}
1 44 ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}
2 44 ::{5399E694-6CE5-4D6C-8FCE-1D8870FDCBA0}
3 31 Software602 Form Filler.lnk
4 8 test
5 32 Virtual Russian Keyboard.lnk
…
49 22 WTerminalAdmin.lnk
50 22 AVG Secure VPN.lnk
51 44 ::{645FF040-5081-101B-9F08-00AA002F954E}
52 29 powershell - Shortcut.lnk
There is 1176 bytes left
Honestly, I don't fully comprehend offset around entry_name…
Edit
I have found (partial) structure for the rest of value. There are two tables containing row and column along with an index to desktop_items list for each desktop icon.
Current row and column assignment is in the second table (see the picture below). The first table supposedly contains default assignments for automatic sort by (from desktop context menu).
Unfortunately, I have no clue for interpretation of row and column values (e.g. 16256, 16384) to icon indexes (3rd row, 2nd column).
import struct
import winreg
import pprint
aReg = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
aKey = winreg.OpenKey(aReg,
r"Software\Microsoft\Windows\Shell\Bags\1\Desktop",
winreg.REG_BINARY)
name, value, type_ = winreg.EnumValue(aKey, 9)
aKey.Close()
aReg.Close()
offset = 0x10
head = [struct.unpack_from("<H", value[offset:],0)[0], # 2 bytes (word)
struct.unpack_from("<H", value[offset:],2)[0],
struct.unpack_from("<H", value[offset:],4)[0],
struct.unpack_from("<H", value[offset:],6)[0],
struct.unpack_from("<I", value[offset:],8)[0] # 4 bytes (dword)
]
number_of_items = head[-1]
offset += 12
desktop_items = []
for x in range( number_of_items):
uint16_size = struct.unpack_from("<H", value[offset:],0)[0];
uint16_flags = struct.unpack_from("<H", value[offset:],2)[0];
uint32_filesize = struct.unpack_from("<I", value[offset:],4)[0];
dosdate_date = struct.unpack_from("<H", value[offset:],8)[0];
dostime_time = struct.unpack_from("<H", value[offset:],10)[0];
fileattr16_ = struct.unpack_from("<H", value[offset:],12)[0];
offset += 12
entry_name = value[offset:(offset + (2 * uint32_filesize - 8))].decode('utf-16-le')
offset += (2 * uint32_filesize - 4 )
# uint16_size = location
# 0x20 = %PUBLIC%\Desktop
# 0x7c = %USERPROFILE%\Desktop
desktop_items.append([x,
'{:04x}'.format(uint16_size),
0, 0,
'{:04x}'.format(fileattr16_),
entry_name])
print('{:2}'.format(x),
'{:04x}'.format(uint16_size),
# '{:04x}'.format(uint16_flags), # always zero
# '{:04x}'.format(dosdate_date), # always zero
# '{:04x}'.format(dostime_time), # always zero
'{:04x}'.format(fileattr16_),
entry_name)
print( '\nThere is', (len(value) - offset), 'bytes left' )
print('head (12 bytes):', head)
offs = offset
head2 = []
for x in range( 32):
head2.append(struct.unpack_from("<H", value[offs:],2*x)[0])
offs += 64
print( 'head2 (64 bytes):', head2)
for x in range( number_of_items):
item_list = [
struct.unpack_from("<H", value[offs:],0)[0], # 0
struct.unpack_from("<H", value[offs:],2)[0], # column
struct.unpack_from("<H", value[offs:],4)[0], # 0
struct.unpack_from("<H", value[offs:],6)[0], # row
struct.unpack_from("<H", value[offs:],8)[0] ] # index to desktop_items
# print( x, item_list)
desktop_items[item_list[-1]][2] = int( item_list[1])
desktop_items[item_list[-1]][3] = int( item_list[3])
offs += 10
print(len(value), offset, offs, (offs - offset), '1st table, from start:')
table_1st = desktop_items
table_1st.sort(key=lambda k: (k[2], k[3]))
pprint.pprint(table_1st)
#pprint.pprint(desktop_items)
# 2nd table from behind
offs = len(value)
for x in range( number_of_items):
offs -= 10
item_list = [
struct.unpack_from("<H", value[offs:],0)[0], # 0
struct.unpack_from("<H", value[offs:],2)[0], # column
struct.unpack_from("<H", value[offs:],4)[0], # 0
struct.unpack_from("<H", value[offs:],6)[0], # row
struct.unpack_from("<H", value[offs:],8)[0] ] # index to desktop_items
# print(item_list)
desktop_items[item_list[-1]][2] = int( item_list[1])
desktop_items[item_list[-1]][3] = int( item_list[3])
print(len(value), offset, offs, (offs - offset), '2nd table, from behind:')
table_2nd = desktop_items
table_2nd.sort(key=lambda k: (k[2], k[3]))
pprint.pprint(table_2nd)
# pprint.pprint(desktop_items)
Result (an illustrative picture):
