1#!/bin/python
2#
3# This implements the composite value types used in settings.dat registry hive
4# used to store AppContainer settings in Windows Apps aka UWP.
5#
6# ApplicationDataCompositeValue class is documented here:
7# https://docs.microsoft.com/en-us/uwp/api/windows.storage.applicationdatacompositevalue
8#
9# The internals of types, values and structures had to be reverse engineered.
10#
11# Copyright (c) 2019 Yogesh Khatri <yogesh@swiftforensics.com>
12#
13# Permission is hereby granted, free of charge, to any person obtaining a copy
14# of this software and associated documentation files (the "Software"), to deal
15# in the Software without restriction, including without limitation the rights
16# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17# copies of the Software, and to permit persons to whom the Software is
18# furnished to do so, subject to the following conditions:
19#
20# The above copyright notice and this permission notice shall be included in all
21# copies or substantial portions of the Software.
22#
23# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29# SOFTWARE.
30from __future__ import print_function
31from __future__ import unicode_literals
32
33from datetime import datetime, timedelta
34from uuid import UUID
35import binascii
36import struct
37
38REG_COMPOSITE_TYPE = 0x100
39# When used in registry to denote value type, the REG_COMPOSITE_TYPE is or'd with one of the
40# values below. Example: RegUInt8 will be (REG_COMPOSITE_TYPE | RegUint8)
41# In the serialized ApplicationDataCompositeValue stream, the REG_COMPOSITE_TYPE is not present.
42RegUint8 = 0x001
43RegInt16 = 0x002
44RegUint16 = 0x003
45RegInt32 = 0x004
46RegUint32 = 0x005
47RegInt64 = 0x006
48RegUint64 = 0x007
49RegFloat = 0x008 # aka Single
50RegDouble = 0x009
51RegUnicodeChar = 0x00A
52RegBoolean = 0x00B
53RegUnicodeString = 0x00C
54RegCompositeValue = 0x00D # Application Data Composite Value (Dictionary Object)
55RegDateTimeOffset = 0x00E # Date as FILETIME
56RegTimeSpan = 0x00F #  Span in 100ns ticks
57RegGUID = 0x010
58RegUnk111 = 0x011
59RegUnk112 = 0x012
60RegUnk113 = 0x013
61RegBytesArray = 0x014
62RegInt16Array = 0x015
63RegUint16Array = 0x016
64RegInt32Array = 0x017
65RegUInt32Array = 0x018
66RegInt64Array = 0x019
67RegUInt64Array = 0x01A
68RegFloatArray = 0x01B
69RegDoubleArray = 0x01C
70RegUnicodeCharArray = 0x01D
71RegBooleanArray = 0x01E
72RegUnicodeStringArray = 0x01F
73
74def parse_windows_timestamp(qword):
75    # see http://integriography.wordpress.com/2010/01/16/using-phython-to-parse-and-present-windows-64-bit-timestamps/
76    return datetime.utcfromtimestamp(float(qword) * 1e-7 - 11644473600 )
77
78def ReadUnicodeStringArray(buf):
79    """Read a buffer containing an array of struct { int size; wchar string[size]; }
80       Returns a list of utf8 encoded strings
81    """
82    strings = []
83    buf_len = len(buf)
84    pos = 0
85    while pos < buf_len:
86        item_byte_len = struct.unpack_from(str("<I"), buf, pos)[0]
87        pos += 4
88        strings.append(buf[pos:pos+(item_byte_len)].decode('utf-16').rstrip('\0'))
89        pos += item_byte_len
90    return strings
91
92def ReadGuid(buf):
93    guid = UUID(bytes_le=buf[0:16])
94    return guid
95
96def ParseAppDataCompositeValue(item_type, data, data_size):
97    """
98    Reads an individual Composite type entry from buffer
99    Arguments:
100        - `item_type`: composite data type (from registry value type)
101        - `data`: Byte string containing a single CompositeData object
102        - `data_size`: size of data
103    """
104    value = None
105    if   item_type == RegUint8: value = struct.unpack_from(str("<B"), data, 0)[0]
106    elif item_type == RegInt16: value = struct.unpack_from(str("<h"), data, 0)[0]
107    elif item_type == RegUint16: value = struct.unpack_from(str("<H"), data, 0)[0]
108    elif item_type == RegInt32: value = struct.unpack_from(str("<i"), data, 0)[0]
109    elif item_type == RegUint32: value = struct.unpack_from(str("<I"), data, 0)[0]
110    elif item_type == RegInt64: value = struct.unpack_from(str("<q"), data, 0)[0]
111    elif item_type == RegUint64: value = struct.unpack_from(str("<Q"), data, 0)[0]
112    elif item_type == RegFloat: value = struct.unpack_from(str("<f"), data, 0)[0]
113    elif item_type == RegDouble: value = struct.unpack_from(str("<d"), data, 0)[0]
114    elif item_type == RegUnicodeChar: value = data[0:2].decode('utf-16')
115    elif item_type == RegBoolean: value = True if data[0:1] != b'\x00' else False
116    elif item_type == RegUnicodeString: value = data.decode('utf-16')
117    elif item_type == RegCompositeValue: value = ParseAppDataCompositeStream(data)
118    elif item_type == RegDateTimeOffset: value = parse_windows_timestamp(struct.unpack_from(str("<Q"), data, 0)[0])
119    elif item_type == RegTimeSpan: value = timedelta(seconds= 10e-8 * struct.unpack_from(str("<Q"), data, 0)[0])
120    elif item_type == RegGUID: value = ReadGuid(data)
121    #elif item_type in ( RegUnk111, RegUnk112, RegUnk113): value = "UNKNOWN TYPE"
122    elif item_type == RegBytesArray: value = struct.unpack_from(str("<{}B").format(data_size), data, 0)
123    elif item_type == RegInt16Array: value = struct.unpack_from(str("<{}h").format(data_size//2), data, 0)
124    elif item_type == RegUint16Array: value = struct.unpack_from(str("<{}H").format(data_size//2), data, 0)
125    elif item_type == RegInt32Array: value = struct.unpack_from(str("<{}i").format(data_size//4), data, 0)
126    elif item_type == RegUInt32Array: value = struct.unpack_from(str("<{}I").format(data_size//4), data, 0)
127    elif item_type == RegInt64Array: value = struct.unpack_from(str("<{}q").format(data_size//8), data, 0)
128    elif item_type == RegUInt64Array: value = struct.unpack_from(str("<{}Q").format(data_size//8), data, 0)
129    elif item_type == RegFloatArray: value = struct.unpack_from(str("<{}f").format(data_size//4), data, 0)
130    elif item_type == RegDoubleArray: value = struct.unpack_from(str("<{}d").format(data_size//8), data, 0)
131    elif item_type == RegUnicodeCharArray: value = data.decode('utf-16')
132    elif item_type == RegBooleanArray: value = [True if data[x:x+1] != b'\x00' else False for x in range(data_size)]
133    elif item_type == RegUnicodeStringArray: value = ReadUnicodeStringArray(data)
134    else:
135        print("UNKNOWN TYPE FOUND 0x{:X} data={} \nPlease report to developers!".format(item_type, str(data)))
136        value = str(data)
137    return value
138
139def ParseAppDataCompositeStream(buf):
140    """
141    Read a buffer containing an ApplicationDataCompositeData binary object
142    and returns a dictionary of items present.
143    Arguments:
144        - `buf`: Byte string containing the serialized ApplicationDataCompositeData object
145    """
146    composite_data = {}
147    buf_len = len(buf)
148    pos = 0
149    item_pos = 0
150    while pos < buf_len:
151        item_byte_len, item_type, item_name_len = struct.unpack_from(str("<III"), buf, pos)
152        item_pos = pos
153        pos += 12
154        item_name = buf[pos:pos+item_name_len*2].decode('utf-16')
155        pos += (item_name_len + 1) * 2
156        data_size = item_byte_len - 12 - (item_name_len + 1) * 2
157        data = buf[pos:pos+data_size]
158        value = ParseAppDataCompositeValue(item_type, data, data_size)
159        composite_data[item_name] = value
160
161        pos = item_pos + item_byte_len
162        if pos % 8: # alignment to 8 byte boundary
163            pos += (8 - (pos % 8))
164
165    return composite_data
166