Windows Reparse Point

感觉很久都没写文章了,以后还是要养成记录学习过程的习惯,这样才不会老摸鱼。。。

Reparse Points

概念

之前学习的概念如下:

Windows仅仅支持两种类型的文件:普通文件以及文件目录。这两种文件都可以作为一个NTFS重解析点,一种特殊的文件,拥有一个修改的头部和一个可变的数据块。头部包括了一个表示当前重解析点的类型,这个tag将会被文件系统过滤驱动处理;或者包含内置的重解析点类型类型,即I/O管理器本身

为什么叫做重解析呢?这里要看一个例子:

1
C:\Symlink_to_File\File_to_SYS
  • 首先解析C:\,发现是一个驱动
  • 然后解析Symlink_to_File,发现其实是一个符号链接,于是重新解析当前路径,得到的路径其实为File Path
  • 继续解析File_to_SYS,发现也是一个符号链接,于是再次发生重新解析,得到路径为SYS
  • 实际发生解析的路径变成了C:\File Path\SYS

由于这个过程 重新解析了文件信息,所以称为重解析点。重解析通常用于符号链接或者挂载。在2019年之后,微软修改了符号链接的权限,现在规定只有管理员权限才能够随意创建符号链接,并且经过观察发现,即使是管理员,默认的创建权限也是关闭的,需要特殊开启

然而不知为何,挂载点MountPoint的创建却没有被限制。于是可以利用挂载点进行重解析,来对路径进行类似符号链接的重定向。

实验:如何创建重解析点

这边参考了ProjectZero的仓库,核心代码如左。

这里首先介绍一下核心代码:
首先,需要使用一个结构体:

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
typedef struct _REPARSE_DATA_BUFFER {
ULONG ReparseTag;
USHORT ReparseDataLength;
USHORT Reserved;
union {
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
ULONG Flags;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
struct {
UCHAR DataBuffer[1];
} GenericReparseBuffer;
} DUMMYUNIONNAME;
} REPARSE_DATA_BUFFER, * PREPARSE_DATA_BUFFER;

这个结构体是一个union,同时处理了SymbolicLinkMountPoint两个点。同时需要引进一些常量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

#define REPARSE_DATA_BUFFER_HEADER_LENGTH FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer)

#define IO_REPARSE_TAG_MOUNT_POINT (0xA0000003L) // winnt
#define IO_REPARSE_TAG_HSM (0xC0000004L) // winnt
#define IO_REPARSE_TAG_DRIVE_EXTENDER (0x80000005L)
#define IO_REPARSE_TAG_HSM2 (0x80000006L) // winnt
#define IO_REPARSE_TAG_SIS (0x80000007L) // winnt
#define IO_REPARSE_TAG_WIM (0x80000008L) // winnt
#define IO_REPARSE_TAG_CSV (0x80000009L) // winnt
#define IO_REPARSE_TAG_DFS (0x8000000AL) // winnt
#define IO_REPARSE_TAG_FILTER_MANAGER (0x8000000BL)
#define IO_REPARSE_TAG_SYMLINK (0xA000000CL) // winnt
#define IO_REPARSE_TAG_IIS_CACHE (0xA0000010L)
#define IO_REPARSE_TAG_DFSR (0x80000012L) // winnt
#define IO_REPARSE_TAG_DEDUP (0x80000013L) // winnt
#define IO_REPARSE_TAG_APPXSTRM (0xC0000014L)
#define IO_REPARSE_TAG_NFS (0x80000014L) // winnt
#define IO_REPARSE_TAG_FILE_PLACEHOLDER (0x80000015L) // winnt
#define IO_REPARSE_TAG_DFM (0x80000016L)
#define IO_REPARSE_TAG_WOF (0x80000017L) // winn

这些都是一些在Reparse处理过程中可能用到的一些变量。
然后再之后的代码中,我们首先打开两个目录,一个叫做srcPath,一个叫做targetPath,其中我们假定一个如下的场景:

  • 目前srcPath是我们有写权限的目录,但是targetPath我们没有
  • 通过重解析,我们需要让srcPath解析绑定到targetPath上
  • 当发生重解析之后,我们往srcPath中写入文件,最终会写入到targetPath目录中

当需要将当前的文件作为重解析句柄打开的时候,需要使用如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
HANDLE handle = CreateFile(path,
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
0);
if (handle == INVALID_HANDLE_VALUE)
{
printf("Create Reparse Point failed with error code %d\n", GetLastError());
return NULL;
}

关键在于FILE_FLAG_OPEN_REPARSE_POINT,这个变量表明当前打开的句柄需要作为重解析点去处理。
然后代码需要构建之前提到的_REPARSE_DATA_BUFFER:

1
2
3
4
5
6
7
std::wstring target = FixupPath(wszTargetFullDir);
const size_t target_byte_size = target.size() * 2;
// print name the symbolic show name,it's not necessary
std::wstring printname = L"";
const size_t printname_byte_size = printname.size() * 2;
const size_t path_buffer_size = target_byte_size + printname_byte_size + 4 + 8;
const size_t total_size = path_buffer_size + REPARSE_DATA_BUFFER_HEADER_LENGTH;

其中printname_byte_size之所以还要跟着4+8是因为,首先路径末尾需要预留\0的位置(Unicode所以是2个字节大小)然后成员变量里面有两个Unicode对象,所以需要4,而8则是来自ntoskrnl!FsRtlValidateReparsePointBuffer的分析和调试。之后写如下逻辑:

1
2
3
4
5
6
7
8
9
10
buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
buffer->ReparseDataLength = static_cast<USHORT>(path_buffer_size);
buffer->Reserved = 0;

buffer->MountPointReparseBuffer.SubstituteNameOffset = 0;
buffer->MountPointReparseBuffer.SubstituteNameLength = static_cast<USHORT>(target_byte_size);
memcpy(buffer->MountPointReparseBuffer.PathBuffer, target.c_str(), target_byte_size + 2);
buffer->MountPointReparseBuffer.PrintNameOffset = static_cast<USHORT>(target_byte_size + 2);
buffer->MountPointReparseBuffer.PrintNameLength = static_cast<USHORT>(printname_byte_size);
memcpy(buffer->MountPointReparseBuffer.PathBuffer + target.size() + 1, printname.c_str(), printname_byte_size + 2);

由于PrintName只是一个展示的数据,所以这个位置的数据可以为空字符串。

之后对之前打开的Reparse文件描述符,使用IOCTL发送请求数据:

1
2
3
4
5
6
bool ret = DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT,
reparse_buffer, dwReparseSize, nullptr, 0, &cb, nullptr) == TRUE;
if (!ret)
{
printf("SetReparsePoint failed with error code:%d\n", GetLastError());
}

其中dwReparseSize为之前算好的total_size,表示此时发送的数据大小。一旦调用成功之后,srcPath就会被设置成重解析点,此时就能够起到类似符号链接的作用:

未设置重解析点

设置了重解析点

图标发生了变化

可以看到,设置重解析点之后,srcDir拥有了一个link属性,此时再srcDir中创建的所有文件其实等价于再targetDir中创建文件。

当完成了工作之后,可以通过如下的方式将这个重解析数据头删除:

1
2
3
4
5
6
7
8
9
10
11
12
buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
buffer->ReparseDataLength = 0;

bool ret = false;
DWORD dwIOCTLOutSize = 0;
ret = DeviceIoControl(hSrc, FSCTL_DELETE_REPARSE_POINT, buffer,
REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, NULL, NULL, &dwIOCTLOutSize, NULL);
if (!ret)
{
printf("Reset the Reparse Point failed with error:%d\n", GetLastError());
return -1;
}

关于CVE的相关分析

CVE-2022-22718其实是关于printer一个老漏洞CVE-2020–1030的一个新的思路。这里简单介绍一下漏洞详情,以及上述提到的重解析漏洞在这个地方的利用方式。

漏洞成因


实际控制的进程

核心原因在于:对于文件夹的权限检查和创建没有再同一个时刻完成。

出现问题的API是SetPrinterDataEx

1
2
3
4
5
6
7
8
DWORD SetPrinterDataEx(
_In_ HANDLE hPrinter,
_In_ LPCTSTR pKeyName,
_In_ LPCTSTR pValueName,
_In_ DWORD Type,
_In_ LPBYTE pData,
_In_ DWORD cbData
);

这个API实际上是一个COM调用,通过这个调用能够对打印机的一些注册表配置进行修改,其中修改的注册表其实就是左边Printers展开后的这些打印机

由于调用这个API,需要用户对这个打印机存在PRINTER_ACCESS_ADMINISTER的权限。如果无法打开现有的打印机的话,可以通过添加一个新的打印机来规避这个权限的月书。调用这个API可以往这些位置添加对应的注册表项。

这个API在SpoolDirectory这个值设置的时候,首先会检查相关权限(COM调用的场合都是没问题的),之后会进入相关的逻辑如下:

这个地方会尝试创建我们传入的目录,并且是具备可写的权限。如果可以创建,则检查这个文件的符号链接数量是否为1,具体逻辑如下

首先调用AdjustFileName将路径调整为CanonicalPath,也就是如下的形式:

1
\\?\C:\\CanonicalPath

然后调用下列逻辑,对当前的路径链接数进行比对

或者这个文件之前就存在了,那么就会把这个路径写入注册表,否则就会离开当前逻辑。

总结以下,上述的整体逻辑如下:

  • 检查当前写入SpoolDirectory的路径是否可以创建
  • 如果可以创建,则创建路径,并且将这个路径写入注册表

于是这里就出现了一个逻辑问题:对目录进行check的时候,和创建文件并不是发生在同一个时机,这里就存在一个类似竞争的问题。举个例子

  • 首先,我们假设我们可以写入的路径是C:\\My\\Dir
  • 我们将这个路径传入注册表,此时必然是可以通过检查的,包括是否可写,以及是否为符号链接
  • 当调用玩SetPrinterDataEx函数之后,将这个路径用重解析的方式重定向到C:\\Windows\\System32

此时,C:\\My\\Dir其实本质上就指向了C:\\Windows\\System32。然后我们通过让spool进程重启,他就会去调用BuildPrinterInfo ,就会尝试将注册表中的路径读出来,并且尝试去创建对应的文件:

于是通过Repase Point,我们就能够获得一个任意目录创建的机会。

漏洞利用点

通过上述方法,我们能够获得一个任意目录写的机会,那么如何利用这个漏洞呢?这个就要扯到打印机的一个特性:

  • 再任意一个打印机注册表目录项下,如果存在CopyFiles开头的键,那么这个键对应的Modules值中填写一个DLL路径,这个DLL将会被当前COM服务加载,并且load到打印机服务中。这个进程是SYSTEM权限。
  • 打印机在加载DLL的时候,会在函数IsModuleFilePathAllowed检查加载的DLL路径是否为C:\\Windows\\System32或者C:\Windows\System32\spool\drivers\<ARCH>这两个目录,否则的话DLL不会加载。
  • 为了实现上一步,首先需要能够修改对应的Module值,然后需要能够往这个目录下写入DLL

修改Module值可以用通过直接使用APISetPrinterDataEx进行调用。然而在这个过程中有一个小问题:在BuildPrinterInfo中,会检查注册表中读出来的路径,是否为DriverPath,而我们的目标就是C:\Windows\System32\spool\drivers\<ARCH>,也就是DriverPath,于是我们需要一个绕过的策略

上图为匹配逻辑,可以很容易的发现,他这边是直接简单的比较路径,其中PrinterPathName就是C:\Windows\System32\spool\drivers\<ARCH>

于是现在的问题变成:

  • 为了让SplLoadLibraryTheCopyFileModule加载DLL,我们的DLL路径必须要指向C:\Windows\System32\spool\drivers\<ARCH>
  • 为了让BuildPrinterInfo通过,此时我们利用任意文件创建的攻击原语(attack primitive)创建的路径必须与字符串字符串C:\Windows\System32\spool\drivers\<ARCH>不同

可以看到,第二项check比较的是字符串,而第一项要求的只是路径能够只想对应位置的DLL,因为在这个SplLoadLibraryTheCopyFileModule加载逻辑如下:

可以看到,在检查文件DLL是否合法(满足两个路径,就是IsModuleFilePathAllowed这个地方)之前,调用了一个关键函数MakeCanonicalPath,这个函数跟进去可以看到

可以看到这边会尝试获取这个文件,并且调用GetFinalPathNameByHandleW,这个API的作用是能够解析符号链接,也就是说,在真正的检查之前,这个路径会被先规范化,比如说

1
C:\My\Dir

上述路径被我们用重解析点解析到了

1
C:\Windows\system32

那么这个规范化解析就会变成

1
\\?\C:\Windows\system32

然后实际IsModuleFilePathAllowed比较的时候会先比较前四个字节是否为\\?\,如果是的话,会对其进行跳过,然后比较之后的路径是否为C:\Windows\system32

为了这里可以使用另一种路径的表达方式:

1
\\localhost\C$\spooldir\printers\

根据文章提到的,在某些版本中比较路径的时候,在进行路径比较之前会将\\?\删除,所以这边如果使用上述的路径的话,当结构化处理的时候会转换成

1
\\?\UNC\C:\spooldir\printers

于是当进行比较的时候,就永远不会相等了。于是这一个攻击流程就是

  • 首先调用SetPinterDataEx设置SpoolDirectory,此时填入任意一个可控的目录的UNC目录,此时可以通过第一层check->符号链接数量为1
  • 然后使用ReparsePoint,将路径重定向到C:\Windows\system32\spool\driver\<ARCH>
  • 然后第二次调用SetPinterDataEx,此时增添\\CopyFiles,并且指定DLL路径为system32下的APPVTerminator.dll
  • 程序重启,读取SpoolDirectory之后进行第二次比较,此时由于路径为UNC目录,可以绕过第二个比较->此时不能为print driver目录,并且创建对应的路径4
  • 此时往重解析点中,新建的目录4为任意用户可写,于是把攻击用的dll写入
  • 然后第二次调用SetPinterDataEx,此时增添\\CopyFiles,并且写入攻击dll,攻击完成


修复后(修复后,在此处创建文件会提示没有权限,具体原因不明)
同时删除了AppVTerminator.dll

附录

完整的Reparse实验代码:

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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
#include<Windows.h>
#include<iostream>
#include<stdio.h>


// Taken from ntifs.h
#define SYMLINK_FLAG_RELATIVE 1

typedef struct _REPARSE_DATA_BUFFER {
ULONG ReparseTag;
USHORT ReparseDataLength;
USHORT Reserved;
union {
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
ULONG Flags;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
struct {
UCHAR DataBuffer[1];
} GenericReparseBuffer;
} DUMMYUNIONNAME;
} REPARSE_DATA_BUFFER, * PREPARSE_DATA_BUFFER;

#define REPARSE_DATA_BUFFER_HEADER_LENGTH FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer)

#define IO_REPARSE_TAG_MOUNT_POINT (0xA0000003L) // winnt
#define IO_REPARSE_TAG_HSM (0xC0000004L) // winnt
#define IO_REPARSE_TAG_DRIVE_EXTENDER (0x80000005L)
#define IO_REPARSE_TAG_HSM2 (0x80000006L) // winnt
#define IO_REPARSE_TAG_SIS (0x80000007L) // winnt
#define IO_REPARSE_TAG_WIM (0x80000008L) // winnt
#define IO_REPARSE_TAG_CSV (0x80000009L) // winnt
#define IO_REPARSE_TAG_DFS (0x8000000AL) // winnt
#define IO_REPARSE_TAG_FILTER_MANAGER (0x8000000BL)
#define IO_REPARSE_TAG_SYMLINK (0xA000000CL) // winnt
#define IO_REPARSE_TAG_IIS_CACHE (0xA0000010L)
#define IO_REPARSE_TAG_DFSR (0x80000012L) // winnt
#define IO_REPARSE_TAG_DEDUP (0x80000013L) // winnt
#define IO_REPARSE_TAG_APPXSTRM (0xC0000014L)
#define IO_REPARSE_TAG_NFS (0x80000014L) // winnt
#define IO_REPARSE_TAG_FILE_PLACEHOLDER (0x80000015L) // winnt
#define IO_REPARSE_TAG_DFM (0x80000016L)
#define IO_REPARSE_TAG_WOF (0x80000017L) // winnt

static int g_last_error = 0;



bool CreateMyDirectory(const WCHAR path[])
{
wprintf(L"Create Directory:%s\n", path);
if (CreateDirectory(path, NULL) || (GetLastError() == ERROR_ALREADY_EXISTS))
{
return true;
}
else
{
return false;
}

}


HANDLE OpenReparsePoint(const WCHAR path[])
{
HANDLE handle = CreateFile(path,
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
0);
if (handle == INVALID_HANDLE_VALUE)
{
printf("Create Reparse Point failed with error code %d\n", GetLastError());
return NULL;
}

return handle;

}

bool SetReparsePoint(HANDLE handle, REPARSE_DATA_BUFFER* reparse_buffer, DWORD dwReparseSize)
{
DWORD cb;

bool ret = DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT,
reparse_buffer, dwReparseSize, nullptr, 0, &cb, nullptr) == TRUE;
if (!ret)
{
printf("SetReparsePoint failed with error code:%d\n", GetLastError());
}

return ret;
}

std::wstring FixupPath(std::wstring str)
{
if (str[0] != '\\')
{
return L"\\??\\" + str;
}

return str;
}


int main()
{
std::cout << "[+] Reparse Test! [+]" << std::endl;

WCHAR wcsFullDir[MAX_PATH] = { 0 };
GetCurrentDirectory(MAX_PATH, wcsFullDir);
WCHAR srcDir[] = L"\\srcDir";
WCHAR targetDir[] = L"\\targetDir";

std::wstring wszSrcFullDir = wcsFullDir;
wszSrcFullDir += srcDir;
std::wstring wszTargetFullDir = wcsFullDir;
wszTargetFullDir += targetDir;

std::wcout << L"Current FullDir is " << wcsFullDir << std::endl;



if (!CreateMyDirectory(wszSrcFullDir.c_str()))
{
std::wcout << "Create " << wszSrcFullDir << "Failed!" << std::endl;
return -1;
}

if (!CreateMyDirectory(wszTargetFullDir.c_str()))
{
std::wcout << "Create " << wszTargetFullDir << "Failed!" << std::endl;
return -1;
}

puts("[+] Now open directory as reparse point [+] \n");
// return 0;

HANDLE hSrc = OpenReparsePoint(wszSrcFullDir.c_str());
// HANDLE hFake = OpenReparsePoint(srcDir);
if (!hSrc)
{
puts("[+] Open handle failed\n");
return -1;
}

// Here we build the target binary
std::wstring target = FixupPath(wszTargetFullDir);
const size_t target_byte_size = target.size() * 2;
// print name the symbolic show name,it's not necessary
std::wstring printname = L"";
const size_t printname_byte_size = printname.size() * 2;
const size_t path_buffer_size = target_byte_size + printname_byte_size + 4 + 8;
const size_t total_size = path_buffer_size + REPARSE_DATA_BUFFER_HEADER_LENGTH;
printf("The ReparseDataLength = %d \n", path_buffer_size);
printf("The total_size = %d \n", total_size);

REPARSE_DATA_BUFFER* buffer = new REPARSE_DATA_BUFFER();
/*
* WARNING!Here could not create the SYMBOLIC
*
* If using whoami /all ,it will show the privilege SeCreateSymbolicLinkPrivilege has been disabled
* so if here we couldn't use code below:
* but we could try to using the reparse point with Mount
* it will reparse the directory as mount, so it can try to
* work like Symbolic


// typed_buffer_ptr<REPARSE_DATA_BUFFER> buffer(total_size);

buffer->ReparseTag = IO_REPARSE_TAG_SYMLINK;
buffer->ReparseDataLength = static_cast<USHORT>(path_buffer_size);
buffer->Reserved = 0;

buffer->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0;
buffer->SymbolicLinkReparseBuffer.SubstituteNameLength = static_cast<USHORT>(target_byte_size);
memcpy(buffer->SymbolicLinkReparseBuffer.PathBuffer, target.c_str(), target_byte_size + 2);
buffer->SymbolicLinkReparseBuffer.PrintNameOffset = static_cast<USHORT>(target_byte_size + 2);
buffer->SymbolicLinkReparseBuffer.PrintNameLength = static_cast<USHORT>(printname_byte_size);
memcpy(buffer->SymbolicLinkReparseBuffer.PathBuffer + target.size() + 1, printname.c_str(), printname_byte_size + 2);
buffer->SymbolicLinkReparseBuffer.Flags = SYMLINK_FLAG_RELATIVE;
*/
buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
buffer->ReparseDataLength = static_cast<USHORT>(path_buffer_size);
buffer->Reserved = 0;

buffer->MountPointReparseBuffer.SubstituteNameOffset = 0;
buffer->MountPointReparseBuffer.SubstituteNameLength = static_cast<USHORT>(target_byte_size);
memcpy(buffer->MountPointReparseBuffer.PathBuffer, target.c_str(), target_byte_size + 2);
buffer->MountPointReparseBuffer.PrintNameOffset = static_cast<USHORT>(target_byte_size + 2);
buffer->MountPointReparseBuffer.PrintNameLength = static_cast<USHORT>(printname_byte_size);
memcpy(buffer->MountPointReparseBuffer.PathBuffer + target.size() + 1, printname.c_str(), printname_byte_size + 2);
// Next, send IOCTL to the NTFS file handle to make it symbolic
if(!SetReparsePoint(hSrc, buffer, total_size))
{
printf("Set Reparse Point failed with error :%d\n", GetLastError());
return -1;
}

// Now the src will become the target symbolic, we will try to create
// file at src path. If success, the file will appear at target file path
puts("[+] Create Symbolic success! now try create one file at src path [+]");

std::wstring TargetFilePath = wszSrcFullDir;
TargetFilePath += L"\\TestFile";
HANDLE hFile = CreateFile(
TargetFilePath.c_str(),
GENERIC_READ | GENERIC_WRITE,
0, 0,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("Create file failed with error code:%d\n",GetLastError());
return -1;
}

std::string content = "Success!";
DWORD dwByteWrite = 0;
WriteFile(hFile,
content.c_str(),
content.size(),
&dwByteWrite,
NULL);

CloseHandle(hFile);

// succcess

puts("[+] Success! enter any character to delete symbolic\n");
char c = getchar();

buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
buffer->ReparseDataLength = 0;

bool ret = false;
DWORD dwIOCTLOutSize = 0;
ret = DeviceIoControl(hSrc, FSCTL_DELETE_REPARSE_POINT, buffer,
REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, NULL, NULL, &dwIOCTLOutSize, NULL);
if (!ret)
{
printf("Reset the Reparse Point failed with error:%d\n", GetLastError());
return -1;
}

CloseHandle(hSrc);
puts("[+] Reparse Success!\n");

return 0;
}