CozyCode
CozyCode

Reputation: 504

How to read the values of IconLayouts REG_BINARY registry file

I want to make a program that gets the positions of the icons on the screen. And with some research I found out that the values I needed were in a registry binary file called IconLayouts (Located in HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\Shell\Bags\1\Desktop) I used python to get the positions using the winreg module. And succeeded on getting the values.

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)
value = value.replace(b'\x00', b'')

This is the code I have. But the problem is I don't know what to do with these values. The program returns something like:

b'\x03\x01\x01\x01\x04,::{20D04FE0-3AEA-1069-A2D8-08002B30309D}>  ,::{645FF040-5081-101B-9F08-00AA002F954E}>  \x13Timetables.jpeg>  \nfolder>\\ \x01\x02\x01\x01\x02\x01\x0c\x04\x01\x04\x80?\x01@\x020A\x03'

I would appreciate if you would help me decipher this output and get the positions from it.

Upvotes: 4

Views: 1422

Answers (3)

zahiko
zahiko

Reputation: 33

Building on @JosefZ's answer, I've analyzed the sequence of row values (and it's the same for column values) and found a cool pattern in the differences between consecutive terms. Starting from the third term, the differences between consecutive terms follow this sequence:

  • 128, 64, 64, 32, 32, 32, 32, 16, ...

By taking the base-2 logarithm of these differences, we get the sequence of values:

  • 7, 6, 6, 5, 5, 5, 5, 4, ...

What stands out is that the logarithmic value 7 appears once, 6 appears twice, 5 appears four times, and so on. In general, for a difference of (2^n), it appears (2^{7-n}) times.

To quantify this, I derived a formula for the difference between the (n)-th and ((n-1))-th terms of the sequence:

difference = math.pow(2, 7 - math.floor(math.log2(n - 2)))

Then, I created a function to generate a mapping of the sequence using this formula:

import math

def generate_mapping(n: int) -> dict:
    mapping = {0: 1, 16256: 2}
    tmp = 16256

    for i in range(3, n + 1):
        tmp += math.pow(2, 7 - math.floor(math.log2(i - 2)))
        mapping[tmp] = i
    return mapping

To optimize this, we can replace the expression math.pow(2, 7 - math.floor(math.log2(n - 2))) with a bit-shift operation:

1 << (7 - (i - 2).bit_length() + 1)

This reduces computation time by avoiding floating-point operations. Here's the updated version of the function with the optimization:

def generate_mapping(n: int) -> dict:
    mapping = {0: 1, 16256: 2}
    tmp = 16256

    for i in range(3, n + 1):
        tmp += 1 << (7 - (i - 2).bit_length() + 1)
        mapping[tmp] = i
    return mapping

As you can see, the latter is much faster though this kind of optimization may be needless. contrast

After applying the optimized mapping, I get the following result:

The result As you can see, the strange row and column values have been successfully converted into indexes.

Upvotes: 2

JosefZ
JosefZ

Reputation: 30183

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):

result

Upvotes: 4

vpz
vpz

Reputation: 1044

it seems that you have an binary file that must be tranformed in floats, strings, etc.

Here you have a function that I have made that uses struct unpack to read an amount of bytes and then transform then in float or int or str, etc.

# Import
import numpy as np
import struct

def ReadBytes(file, fmt, size):
    """
    Function to read an amount of bytes and convert to the desired format.

    Parameters
    ----------
    file : Stream of file.
        Stream from open file.
         - `file = open("file.bin")`
    
    fmt : str
        String containing the format type of the object to be converted.
        - <https://docs.python.org/3/library/struct.html>
    
    size : int
        How many bytes should be read.

    Returns
    -------
    oc : object{int, float, str, ...}
        Desired object read from bytes and converted to desired type.

    """
    
    if size<0:
        # If you have an array you can use size = -n indicating that the array has n positions.
        # Length to be read.
        bs  = file.read(abs(size))
        # Converting length.
        size = struct.unpack('B',bs)[0]
        # Format considering the len.
        fmt = str(size)+fmt
          
    # Object to be read, in bytes.
    o = file.read(size)
    # Converting the object to the format desired.
    oc = struct.unpack(fmt, o)[0]
  
    return oc

In a simple usage, you can just use the struct unpack itself. The following shows how to unpack 4 bytes to int:

struct.unpack('i', b'\x03\x01\x01\x01')

# Output-> (16843011,)

Upvotes: 1

Related Questions