取证的时候没软件有点坐牢,准备写几个小脚本加快手搓速度
演示检材:2025獬豸杯PC镜像
环境:python3.12
FTK
获取基本信息
照例需要获取计算机的Build版本,计算机名称,系统名称、安装时间、时区、最后一次关机时间、上次登录用户,这些基本信息,这些信息可以直接从注册表中拿到,不需要进行特殊处理(除了时间戳)
收集一下这些信息在注册表中的位置,以及对应的键值
因为只有在我本机和镜像中找到了,就放在了这里,未进行多次实验,有不当支持欢迎斧正。
1 2 3 4 5 6 7 8
| Build信息 SOFTWARE SOFTWARE\Microsoft\Windows NT\CurrentVersion BuildLabEx Build版本 SOFTWARE SOFTWARE\Microsoft\Windows NT\CurrentVersion CurrentBuildNumber 计算机名称 SYSTEM SYSTEM\ControlSet001\Control\ComputerName\ComputerName ComputerName 系统名称 SOFTWARE SOFTWARE\Microsoft\Windows NT\CurrentVersion ProductName 系统安装时间 SOFTWARE SOFTWARE\Microsoft\Windows NT\CurrentVersion InstallDate 系统时区 SYSTEM SYSTEM\ControlSet001\Control\TimeZoneInformation TimeZoneKeyName 最后一次关机时间 SYSTEM SYSTEM\ControlSet001\Control\Windows ShutdownTime 上次登录用户 SOFTWARE SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI LastLoggedOnUser
|
使用regipy库进行处理,先将dd镜像用FTK挂载到本地,这里是挂在了H盘
regipy:https://github.com/mkorman90/regipy
使用 RegistryHive 来确定注册表文件的路径,使用get_key指定键值路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import re from regipy import RegistryHive from regipy.exceptions import RegistryKeyNotFoundException from datetime import datetime, timedelta
def system_get(hive, key_path, name, def_name=None): key = hive.get_key(key_path) try: date = key.get_value(name) if def_name == "byte_to_str": return byte_to_str(date) else: return date except RegistryKeyNotFoundException as e: print(f"未找到 {name} 键。 {e}")
def analyze_software_registry(registry_path):
result = [] with open('../system_path', 'r', encoding="UTF-8") as file: lines = file.readlines() for line in lines: res = {} line = line.strip('\n').split(' ') if len(line) == 4: res[line[0]] = system_get(RegistryHive(f"{registry_path}/{line[1]}"), line[2], line[3]) else: res[line[0]] = system_get(RegistryHive(f"{registry_path}/{line[1]}"), line[2], line[3], line[4]) result.append(res) return result
if __name__ == "__main__": registry_path = r'H:/[root]/Windows/System32/config' result = analyze_software_registry(registry_path)
|
直接获取的信息中,系统安装时间是unix时间戳,最后一次关机时间是FILETIME 时间戳,还需要处理时间戳。中间还有一个字节序的转换,简单来说就是,大端字节序就是我们日常的阅读顺序
10 2c 05 80。 这个十六进制数据如果按照大端字节序转换成十进制就是 271,320,448
如果是小端字节序,就需要从后往前读,两个十六进制数为一组: 80 05 2c 10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| def byte_to_str(byte_time): if isinstance(byte_time, bytes): res = little_to_big(byte_time.hex()) / 10000000 unix_seconds = res - 11644473600 unix_seconds = datetime.fromtimestamp(unix_seconds) return unix_seconds.strftime("%Y-%m-%d %H:%M:%S") else: unix_seconds = datetime.fromtimestamp(byte_time) return unix_seconds.strftime("%Y-%m-%d %H:%M:%S")
def little_to_big(little_hex): little_bytes = bytes.fromhex(little_hex) num = int.from_bytes(little_bytes, byteorder='little') big_bytes = num.to_bytes(len(little_bytes), byteorder='big') big_hex = int(big_bytes.hex(), 16) return big_hex
|
输出结果
1
| [{'Build信息': '18362.1.amd64fre.19h1_release.190318-1202'}, {'Build版本': '18363'}, {'计算机名称': 'DESKTOP-SBTC549'}, {'系统名称': 'Windows 10 Education'}, {'系统安装时间': '2024-06-12 19:02:06'}, {'系统时区': 'China Standard Time'}, {'最后一次关机时间': '2025-02-10 16:15:30'}, {'上次登录用户': '.\\TTT'}]
|
获取账户的信息
账户信息存在SAM文件中,这一部分主要对主要对 SAM\SAM\Domains\Account\Users
目录中的 V 键值对进行分析
这个是镜像 TTT 用户的V值
1
| 00000000f400000003000100f40000000600000000000000fc00000008000000000000000401000000000000000000000401000000000000000000000401000000000000000000000401000000000000000000000401000000000000000000000401000000000000000000000401000000000000000000000401000000000000000000000401000015000000a80000001c01000008000000010000002401000018000000000000003c01000038000000000000007401000018000000000000008c010000180000000000000001001480d4000000e40000001400000044000000020030000200000002c014004400050101010000000000010000000002c01400ff070f0001010000000000050700000002009000040000000000240044000200010500000000000515000000064a693cc7d5a71f57e1f80de8030000000038001b030200010a00000000000f0300000000040000dea22867213ed2af19ad5d79b0c107292756fc20d8ad66f610f268fadf2af80f00001800ff070f0001020000000000052000000020020000000014005b030200010100000000000100000000010200000000000520000000200200000102000000000005200000002002000054005400540000004600610063006500ffffffffffffffffffffffffffffffffffffffffffe1a1a401020000070000000200020000000000e4cf9dd0d3d5d720b26d66d9dc712cb50200020010000000af5788fbd9902cb7182bd4f96e6271cf30bdb1ec83090591fec6c0de090fa8b0e7013610157b17c9bc7b2d313bda45a602000200000000004c2a126f2dceb6ee0b14ed1e27995aff02000200000000005249e2b181774f9b48f9c306ca163d46
|
获取RID
SAM\SAM\Domains\Account\Users
下的目录代表账户,names目录除外,其余目录的名称就是对应账户的RID值,例如:Administrator账户,对应的RID就是500,那么他的目录名就是 000001F4
(这个转换成十进制就是500)
再记录一个小信息,F的值转换成十六进制,十六进制数的 96-104 位以小端字节序的形式记录了RID。
获取SID
从这个值中找到 0105 这样的数据,05后面还会有几个0,这一串代表的是该用户(非系统用户,系统用户的我不太明白)的 SID值,
拿出来0105以及之后的数据
1
| 010500000000000515000000064a693cc7d5a71f57e1f80de80300000000380…………………………
|
这个 01
是固定的,表示SID版本号后面的 05
表示的是 子权限数量 ,我是直接找的 0105,从网上也没有发现具体的说法,AI更扯,说起始位置可能为 0x30 0x38。
从 0105 之后再获取6字节,这里就是 00 00 00 00 00 05 呈现为大端字节序,是标识符权威值,然后根据子权限数量,来获取 (x-1)段 4字节的子权限列表,这里为 15 00 00 00
06 4a 69 3c
c7 d5 a7 1f
57 e1 f8 0d
,将获取的四字节从小端序转成大端序,然后转成十进制,得到结果 21
1013533190
531092935
234414423
,为什么是 x-1呢,一般子权限列表中的最后一个是 RID。
拼接得出SID S-1-5-21-1013533190-531092935-234414423-1000
。完美
获取user
user的存储前面是有标识符的,结尾需要自己判断,我使用了 5200000002002000001020000000000052000000020020000
来进行判断,那到这个标识符后面的数据
1
| 54005400540000004600610063006500ffffffffffffffffffffffffffffffffffffffffffe1a1a401020000070000000200020000000000e4cf9dd0d3d5d720b26d66d9dc712cb50200020010000000af5788fbd9902cb7182bd4f96e6271cf30bdb1ec83090591fec6c0de090fa8b0e7013610157b17c9bc7b2d313bda45a602000200000000004c2a126f2dceb6ee0b14ed1e27995aff02000200000000005249e2b181774f9b48f9c306ca163d46
|
结尾的判断并不是使用标识符,而是从 00 下手,用户名有一个规律,两位十六进制数之后,间隔一个 00 才是下一个十六进制数,我们判断用户名结束就可以使用这样的方法,先将 0000 之前的数据提取出来,防止获取过长,因为出现0000很显然是结束了,节省时间,这样拿到数据 5400540054
而我们需要即对 00 这个间隔来进行判断,有需要将00取出,避免转码是出现乱码。提取结束是 545454
hex解码正是 TTT。
PS:至于密码的NT hash我也找到对应的了,但是还不知道怎么处理成取证软件中的形式,下次再说


小工具代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
| import re from regipy import RegistryHive from regipy.exceptions import RegistryKeyNotFoundException from datetime import datetime, timedelta
def system_get(hive, key_path, name, def_name=None): key = hive.get_key(key_path) try: date = key.get_value(name) if def_name == "byte_to_str": return byte_to_str(date) else: return date except RegistryKeyNotFoundException as e: print(f"未找到 SOFTWARE 键。 {e}")
def analyze_software_registry(registry_path):
result = [] with open('../system_path', 'r', encoding="UTF-8") as file: lines = file.readlines() for line in lines: res = {} line = line.strip('\n').split(' ') if len(line) == 4: res[line[0]] = system_get(RegistryHive(f"{registry_path}/{line[1]}"), line[2], line[3]) else: res[line[0]] = system_get(RegistryHive(f"{registry_path}/{line[1]}"), line[2], line[3], line[4]) result.append(res) return result
def get_sid_name(registry_path): user_list = [] hive_sam = RegistryHive(f"{registry_path}/SAM") users_key = hive_sam.get_key('SAM\\SAM\\Domains\\Account\\Users')
for subkey in users_key.iter_subkeys(): user = {} user_rid = subkey.name
pattern = r'^0{1,5}' if bool(re.match(pattern, user_rid)): try: f_full_name_key = subkey.get_value('F') rid = little_to_big(f_full_name_key.hex()[96:104]) user['RID'] = rid except RegistryKeyNotFoundException: username = f"User_{user_rid}"
try: v_key = subkey.get_value('V') print(v_key.hex()) sid = v_key.hex()[576:624] sid = f"S-{get_sid(sid)}-{rid}" user['SID'] = sid
pattern_name = r'(5200000002002000001020000000000052000000020020000.*)' match = re.search(pattern_name, v_key.hex())
if match: result = match.group(1) temp = result[49:].split('0000')[0] temp = temp.split('00') res = "" for i in temp: if len(i) > 2: break else: res += i res = bytes.fromhex(res).decode('utf-8') user['username'] = res else: print("未找到匹配的内容。") except RegistryKeyNotFoundException: print(f"用户名: {username}, 无法找到 SID 信息") user_list.append(user) else: continue return user_list
def get_network(): print()
def byte_to_str(byte_time): if isinstance(byte_time, bytes): res = little_to_big(byte_time.hex()) / 10000000 unix_seconds = res - 11644473600 unix_seconds = datetime.fromtimestamp(unix_seconds) return unix_seconds.strftime("%Y-%m-%d %H:%M:%S") else: unix_seconds = datetime.fromtimestamp(byte_time) return unix_seconds.strftime("%Y-%m-%d %H:%M:%S")
def little_to_big(little_hex): little_bytes = bytes.fromhex(little_hex) num = int.from_bytes(little_bytes, byteorder='little') big_bytes = num.to_bytes(len(little_bytes), byteorder='big') big_hex = int(big_bytes.hex(), 16) return big_hex
def get_sid(data): sid = [] temp = str(int(data[:2], 16)) sid.append(temp) num = int(data[2:4], 16) value = str(int(data[4:16], 16)) sid.append(value) for i in range(num-1): test = str(little_to_big(data[16+i*8:24+i*8])) sid.append(test) sid = '-'.join(sid) return sid
if __name__ == "__main__": registry_path = r'H:/[root]/Windows/System32/config' result = analyze_software_registry(registry_path) res = get_sid_name(registry_path) print(res) print(result)
|
运行结果
系统用户的SID处理有点问题,这里的用户的是正常的,还需要大量的验证,时区没进行处理,常见的形式是 (UTC+08:00)北京
1 2 3
| [{'RID': 500, 'SID': 'S-1-1-500', 'username': 'Administrator'}, {'RID': 501, 'SID': 'S-1-1-501', 'username': 'Guest'}, {'RID': 503, 'SID': 'S-1-1-503', 'username': 'DefaultAccount'}, {'RID': 504, 'SID': 'S-1-1-504', 'username': 'WDAGUtilityAccount'}, {'RID': 1000, 'SID': 'S-1-5-21-1013533190-531092935-234414423-1000', 'username': 'TTT'}]
[{'Build信息': '18362.1.amd64fre.19h1_release.190318-1202'}, {'Build版本': '18363'}, {'计算机名称': 'DESKTOP-SBTC549'}, {'系统名称': 'Windows 10 Education'}, {'系统安装时间': '2024-06-12 19:02:06'}, {'系统时区': 'China Standard Time'}, {'最后一次关机时间': '2025-02-10 16:15:30'}, {'上次登录用户': '.\\TTT'}]
|