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