-
Notifications
You must be signed in to change notification settings - Fork 9
/
filesystem_link.py
109 lines (94 loc) · 4.14 KB
/
filesystem_link.py
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
"""
This module provides utilities for creating an hardlink without write permissions.
More info about it can be found in this blogpost:
https://googleprojectzero.blogspot.com/2015/12/between-rock-and-hard-link.html
"""
import ctypes
import logging
import os.path
from ctypes import POINTER, wintypes
from ctypes.wintypes import BOOL, LPVOID, ULONG
import win32con
import win32file
NTSTATUS = wintypes.LONG
NTDLL = ctypes.WinDLL("ntdll", use_last_error=True)
FILE_LINK_INFORMATION = 72
class _IOStatusBlockResult(ctypes.Union):
_fields_ = [
("Status", NTSTATUS),
("Pointer", LPVOID),
]
class IOStatusBlock(ctypes.Structure):
"""
https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_io_status_block
"""
_fields_ = [
("dummy_union_name", _IOStatusBlockResult),
("Information", POINTER(ULONG)),
]
class _FileLinkInformationDummyUnionName(ctypes.Union):
_fields_ = [
("replace_if_exists", BOOL),
("Flags", ctypes.c_ulong)
]
NtSetInformationFile = NTDLL.NtSetInformationFile
NtSetInformationFile.argtypes = [ctypes.wintypes.HANDLE, # FileHandle
POINTER(IOStatusBlock), # IoStatusBlock
ctypes.wintypes.LPVOID, # FileInformation
ctypes.wintypes.ULONG, # Length
ctypes.c_uint, # FileInformationClass
]
NtSetInformationFile.restype = NTSTATUS
def file_link_information_factory(length_file_name):
"""
Generate dynamically sized struct FILE_LINK_INFORMATION
:param length_file_name: length of the field 'file_name'
:return: FILE_LINK_INFORMATION instance.
"""
class _FileLinkInformation(ctypes.Structure):
"""
https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_file_link_information
"""
_fields_ = [
("dummy_union_name", _FileLinkInformationDummyUnionName),
("root_directory", ctypes.wintypes.HANDLE),
("file_name_length", ctypes.c_ulong),
("file_name", ctypes.c_wchar * length_file_name),
]
return _FileLinkInformation()
def create_hard_link(src: str, dst: str):
"""
Create hardlink via NtSetInformationFile.
Followed the source code in:
https://github.com/googleprojectzero/symboliclink-testing-tools/blob/00c0fe4cefcd2a62c887fe6117abc02bc98bb9fb/CommonUtils/Hardlink.cpp
:param src: Path of the file that will be pointed by the hardlink
:param dst: Path of the hardlink that will be created
:return: Ntstatus code
"""
# Path is required to be in win32 device-namespace
source_path_absolute = rf"\??\\{os.path.abspath(dst)}"
try:
file_handle = win32file.CreateFile(src, win32file.GENERIC_READ,
win32file.FILE_SHARE_READ,
None,
win32con.OPEN_EXISTING, 0, None)
except Exception as e:
logging.error("Exception at create_hard_link %s",e)
raise
io_status = IOStatusBlock()
path_hard_link_unicode = ctypes.create_unicode_buffer(source_path_absolute)
size_hard_link_path = ctypes.sizeof(path_hard_link_unicode)
file_link_information = file_link_information_factory(size_hard_link_path)
file_link_information.file_name_length = size_hard_link_path - ctypes.sizeof(ctypes.c_wchar)
file_link_information.file_name = source_path_absolute
dummy_union_name = _FileLinkInformationDummyUnionName()
dummy_union_name.replace_if_exists = True
file_link_information.dummy_union_name = dummy_union_name
file_link_info_sizeof = ctypes.sizeof(file_link_information)
try:
nt_status = NtSetInformationFile(file_handle.handle, ctypes.byref(io_status),
ctypes.byref(file_link_information), file_link_info_sizeof,
FILE_LINK_INFORMATION)
finally:
file_handle.close()
return nt_status