Microsoft Development
--
Questions
--
Followers
Top Experts
I've created a test sample, which uses GetSecurityInfo() with the DACL flag, and then try to re-assign the very same DACL with SetSecurityInfo().
Before applying SetSecurityInfo(), the descriptor's 'Control' is: 0xA004 , SE_DACL_PRESENT , SE_SACL_PROTECTED , SE_SELF_RELATIVE.
After applying SetSecurityInfo(), the descriptor's 'Control' is: 0x8404 , SE_DACL_AUTO_INHERITED , SE_DACL_PRESENT , SE_SELF_RELATIVE.
Is there a way to store a file descriptor's state, and re-store it IDENTICALLY?
Here is the sample:
#define _WIN32_WINNT 0x0501
#define WINVER 0x0501
#include <stdio.h>
#include <windows.h>
#include "accctrl.h"
#include "aclapi.h"
#include "sddl.h"
int main (void)
{
PSECURITY_DESCRIPTOR PSecurityD;
HANDLE hFile;
PACL dacl;
int ret = 0;
DWORD lasterror;
hFile = CreateFile("test.txt", READ_CONTROL | ACCESS_SYSTEM_SECURITY ,
FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
fprintf(stderr,"CreateFile() failed. Error: INVALID_HANDLE_VALUE\n");
return 1;
}
lasterror = GetSecurityInfo(hFile, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
NULL, NULL, &dacl, NULL, &PSecurityD);
if (lasterror != ERROR_SUCCESS) {
fprintf(stderr,"GetSecurityInfo() failed. Error: %lu;\n", lasterror);
ret = 1;
goto ret1;
}
CloseHandle(hFile);
hFile = CreateFile("test.txt",READ_CONTROL | WRITE_OWNER | WRITE_DAC | ACCESS_SYSTEM_SECURITY ,
0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
fprintf(stderr,"CreateFile() failed. Error: INVALID_HANDLE_VALUE\n");
ret = 2;
goto ret2;;
}
lasterror = SetSecurityInfo(hFile, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION , NULL, NULL, dacl, NULL);
if (lasterror != ERROR_SUCCESS) {
fprintf(stderr,"SetSecurityInfo() failed. Last error: %lu;\n", lasterror);
ret = 2;
goto ret2;
}
CloseHandle(hFile);
ret2:
free(dacl);
free(PSecurityD);
ret1:
return ret;
}
Zero AI Policy
We believe in human intelligence. Our moderation policy strictly prohibits the use of LLM content in our Q&A threads.
class Win32_SecurityDescriptor : __SecurityDescriptor
{
 uint32     ControlFlags;
 Win32_ACE   DACL[];
 Win32_Trustee Group;
 Win32_Trustee Owner;
 Win32_ACE   SACL[];
};
do you mean that ONLY a few bits of ControlFlags are changed after re-applying the same SecurityInfo, and no ANY other changes??
Here are the steps in greater detail:
1. I create a text file using notepad.exe - "test.txt".
2. I extract "test.txt"'s security descriptor in its native state, writing it to output1.bin.
3. I try to re-write "test.txt"'s security descriptor (DACL) using SetSecurityInfo() (the code sample I originally posted).
4. I extract "test.txt"'s security descriptor in its native state, writing it to output2.bin.
5. I start comparing output1.bin and output2.bin, according to: http://msdn.microsoft.com/en-us/library/cc230366%28PROT.10%29.aspx
The first byte is REVISION, which is set to 0x01 for both.
The second byte is RM CONTROL (resource manager), which is set 0x00 for both.
The following two bytes are CONTROL, "04 A0" for output1.bin - while "04 84" for output2.bin
All the rest of the bytes are identical.
If I repeat this flow again, while re-using the current "test.txt" - output1.bin and output2.bin will be identical.
I hope I clarified it for you, if you need further details be my guest - I'm lost in the dark.
Note: I added another sample of code which I used to extract the descriptor to a file, for the purpose of comparing them.
Lubster
#define _WIN32_WINNT 0x0501
#define WINVER 0x0501
#include <stdio.h>
#include <windows.h>
#include "accctrl.h"
#include "aclapi.h"
#include "sddl.h"
int main (void)
{
DWORD lasterror;
PSECURITY_DESCRIPTOR PSecurityD;
HANDLE hFile;
PSID owner;
PSID group;
PACL dacl;
PACL sacl;
HANDLE hFile2;
DWORD wrote = 0;
int ret = 0;
HANDLE ProcessHandle = NULL;
TOKEN_PRIVILEGES TPrivileges;
LUID luid;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &ProcessHandle)) {
printf("OpenProcessToken failed.\n");
return 1;
}
if (LookupPrivilegeValue(NULL, SE_BACKUP_NAME, &luid) == 0) {
lasterror = GetLastError();
printf("LookupPrivilegeValue() error: %lu\n", lasterror);
return 1;
}
TPrivileges.PrivilegeCount = 1;
TPrivileges.Privileges[0].Luid = luid;
TPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (AdjustTokenPrivileges(ProcessHandle, FALSE, &TPrivileges, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES) NULL, (PDWORD) NULL) == 0) {
lasterror = GetLastError();
printf("AdjustTokenPrivileges() error: %lu\n", lasterror);
return 1;
}
lasterror = GetLastError();
if (lasterror == ERROR_NOT_ALL_ASSIGNED) {
printf("AdjustTokenPrivilege() failed.\n");
return 1;
}
CloseHandle(ProcessHandle);
/* --- */
hFile = CreateFile("test.txt", READ_CONTROL | ACCESS_SYSTEM_SECURITY ,
FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
fprintf(stderr,"CreateFile(test.txt) failed. Error: INVALID_HANDLE_VALUE\n");
return 1;
}
lasterror = GetSecurityInfo(hFile, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION ,
&owner, &group, &dacl, &sacl, &PSecurityD);
if (lasterror != ERROR_SUCCESS) {
fprintf(stderr,"GetSecurityInfo() failed. Error: %lu;\n", lasterror);
ret = 1;
goto ret1;
}
/* --- */
hFile2 = CreateFile("output.bin", GENERIC_WRITE ,
0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
fprintf(stderr,"CreateFile(output.bin) hFile2 failed. Error: INVALID_HANDLE_VALUE\n");
return 1;
}
lasterror = WriteFile(hFile2, PSecurityD, GetSecurityDescriptorLength(PSecurityD), &wrote, NULL);
if (wrote != GetSecurityDescriptorLength(PSecurityD)) {
fprintf(stderr,"WriteFile() error");
return 1;
}
CloseHandle(hFile2);
free(owner);
free(group);
free(dacl);
free(sacl);
free(PSecurityD);
ret1:
CloseHandle(hFile);
return ret;
}
Lubster






EARN REWARDS FOR ASKING, ANSWERING, AND MORE.
Earn free swag for participating on the platform.
Â
From the little I understand of BackupRead() - It's "All In Wonder" kind of method, which don't seem to answer my need, the ability of extracting and restoring a security descriptor of a file, while not extracting the content of the file, nor any properties which are not security descriptor related.
Regards Privileges, I tried SE_RESTORE_NAME , SE_BACKUP_NAME , SE_SECURITY_NAME. (Combined, apart, and mixed)
While CreateFile()'s desired access, I pretty much followed the MSDN logic regards RESTORE and BACKUP (respectfully). The only adjustment I had to add while restoring, was adding "READ_CONTROL".
Tried your "GENERIC_READ" suggestion - it seems like it didn't affect the results.
I think this whole mess, comes down to one note over SECURITY_DESCRIPTOR data type:
http://msdn.microsoft.com/en-us/library/aa379573%28VS.85%29.aspx
"Some SECURITY_INFORMATION members work only with the SetNamedSecurityInfo function. These members are not returned in the structure returned by other security functions such as GetNamedSecurityInfo or ConvertStringSecurityDescr
Is this for real?
"Some" members?
Which members? Why? How?
"These members are not returned by other functions"?
Which functions? Why? How?
Needless to say, this shows up ONLY at the data type. I -can not- find such suggestion on ANY of the related functions.
Any MSDN decryptors capable of decrypting this Remark?
(Maybe it wasn't my fault after all...)
Lubster

Get a FREE t-shirt when you ask your first question.
We believe in human intelligence. Our moderation policy strictly prohibits the use of LLM content in our Q&A threads.
Very appreciate everybody's effort to assist. If you believe my conclusion is false - feel free to correct me.
Lubster
Lubster






EARN REWARDS FOR ASKING, ANSWERING, AND MORE.
Earn free swag for participating on the platform.
<<Â From the little I understand of BackupRead() - It's "All In Wonder" kind of method >>
Not exactly you can enumerate or seek to any of the streams to read/restore the information. If your only interested in security information seek to the BACKUP_SECURITY_DATA stream this allows you to read or restore the security information in the file you can ignore all other streams. The real data begins at BACKUP_DATA this is the real contents of the file which can be ignored along with the other streams.
Whats your output if you add LABEL_SECURITY_INFORMATION
Thanks for correcting my premature assumption of BackupRead(). If your description of it's flexibility is accurate, then I suppose it suits my needs.
Unfortunately, I can't seem to find any such sample, nor can I understand how to achieve said behavior using the documentation.
The best I managed to achieve, is the attached code sample, of a basic operation for using BackupRead() and writing its buffer to a file. But I can't understand how to specify "Security Descriptor" only?
Regards your flag of GetSecurityinfo() - it actually doesn't matter, due to the fact that GetSecurityInfo() extracts the identical descriptor. The issue is actually with the SetSecurityInfo() , which actually TOGGLES the flags, therefore it can't set its default state. The default state is 0 for auto-inherit, and 0 for protected. while SetSecurityInfo() may set (1=auto-inherit AND 0=protected) , or  (0=auto-inherit AND 1=protected), but it can't set 0 for both (no such flag).
Lubster
#define _WIN32_WINNT 0x0501
#define WINVER 0x0501
#include <stdio.h>
#include <windows.h>
int SetPriv(HANDLE *ProcessHandle, LPCTSTR priv);
int SetPriv(HANDLE *ProcessHandle, LPCTSTR priv)
{
TOKEN_PRIVILEGES TPrivileges;
LUID luid;
DWORD lasterror;
if (LookupPrivilegeValue(NULL, priv, &luid) == 0) {
lasterror = GetLastError();
printf("Error: LookupPrivilegeValue() - %lu.\n", lasterror);
return 1;
}
TPrivileges.PrivilegeCount = 1;
TPrivileges.Privileges[0].Luid = luid;
TPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (AdjustTokenPrivileges(ProcessHandle, FALSE, &TPrivileges, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES) NULL, (PDWORD) NULL) == 0) {
lasterror = GetLastError();
printf("Error: AdjustTokenPrivileges() - %lu.\n", lasterror);
return 1;
}
lasterror = GetLastError();
if (lasterror == ERROR_NOT_ALL_ASSIGNED) {
printf("Error: AdjustTokenPrivilege().\n");
return 1;
}
return 0;
}
int main (void)
{
HANDLE hFile1;
HANDLE hFile2;
/* --- */
HANDLE ProcessHandle = NULL;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &ProcessHandle)) {
printf("OpenProcessToken failed.\n");
return 1;
}
if (SetPriv(ProcessHandle, SE_RESTORE_NAME) != 0) return 1;
if (SetPriv(ProcessHandle, SE_BACKUP_NAME) != 0) return 1;
if (SetPriv(ProcessHandle, SE_SECURITY_NAME) != 0) return 1;
CloseHandle(ProcessHandle);
/* --- */
hFile1 = CreateFile("test1.txt", GENERIC_READ | WRITE_OWNER | WRITE_DAC | ACCESS_SYSTEM_SECURITY ,
0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hFile1 == INVALID_HANDLE_VALUE) {
fprintf(stderr,"Error: CreateFile() - INVALID_HANDLE_VALUE.\n");
return 1;
}
hFile2 = CreateFile("test2.txt", GENERIC_WRITE | WRITE_OWNER | WRITE_DAC | ACCESS_SYSTEM_SECURITY ,
0, NULL, OPEN_ALWAYS, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hFile2 == INVALID_HANDLE_VALUE) {
fprintf(stderr,"Error: CreateFile() - INVALID_HANDLE_VALUE.\n");
return 1;
}
unsigned char buf[sizeof(WIN32_STREAM_ID) + 1];
DWORD bytes_read;
DWORD bytes_written;
LPVOID context = NULL;
while (1) {
if (!BackupRead(hFile1, buf, sizeof(buf), &bytes_read, FALSE, TRUE, &context ) ) {
printf("Error: BackupRead() - %d.",GetLastError());
return 1;
}
if (bytes_read == 0) {
fprintf(stderr,"bytes_read = 0.\n");
break;
}
if (!WriteFile (hFile2, buf, bytes_read, &bytes_written, NULL)) {
fprintf(stderr,"Error: WriteFile() - %d.\n", GetLastError());
return 1;
}
}
CloseHandle(hFile1);
CloseHandle(hFile2);
return 0;
}
I haven't been able to get the same bytes as the original but the security information is still identical and works as indeded.
Yes examples of BackupRead and BackupWrite are extremely rare and the documentation isn't the greatest. I have posted alot of information about each of them on MSDN section. I was under the impression you could implement these since you might want to end up backing up the file but if you don't want to use them it's not required.
There is an example here enumerating the streams
http://msdn.microsoft.com/en-us/magazine/cc163677.aspx

Get a FREE t-shirt when you ask your first question.
We believe in human intelligence. Our moderation policy strictly prohibits the use of LLM content in our Q&A threads.
From SetSecurityInfo() POV, the Control bits can be assigned passively - using it's flags.
If I want the Control to be true for X, I need to assign Y (a flag which implies setting a X).
X: SE_DACL_PRESENT, Y: DACL_SECURITY_INFORMATION.
X: SE_DACL_AUTO_INHERITED, Y: UNPROTECTED_DACL_SECURITY_
X: SE_DACL_PROTECTED, Y: PROTECTED_DACL_SECURITY_IN
X represents the actual desired Control, while Y represents the needed flag to achieve it.
Now you see the contradiction? this method of setting the Control bits, by design, won't allow me to set the default state of 0:0. It may allow me to set it either 1:0 , or 0:1.
There is no flag for 0:0. There is no flag which says: "Default state".
It's either it's AUTO_INHERITED, or it's PROTECTED (while the default state, is neither).
(I'm still trying my best to understand your last reference of BackupSeek)
Lubster
for the mean while I found this sample:
http://win32.mvps.org/ntfs/dump_ntfs_streams.cpp
From: http://win32.mvps.org/ntfs/streams.html
Isn't your reference suggests that 20 bytes might be aligned to 24 bytes? the fact that he hardcoded '20' might be wrong?
(Should I open a new question regards the 'new' subject?)
Lubster
GetSecurityInfo() is able to extract a whole Security Descriptor. Its SecurityInfo flags suggests which components you would like to retrieve.
While SetSecurityInfo() is able to restore said components. Its SecurityInfo flags are actually being used as passive toggles for the Security Descriptor's Control.
If we desire Control X, we need to use a Y flag:
X: SE_DACL_PRESENT, Y: DACL_SECURITY_INFORMATION.
X: SE_DACL_AUTO_INHERITED, Y: UNPROTECTED_DACL_SECURITY_
X: SE_DACL_PROTECTED, Y: PROTECTED_DACL_SECURITY_IN
X: SE_SACL_PRESENT, Y: SACL_SECURITY_INFORMATION.
X: SE_SACL_AUTO_INHERITED, Y: UNPROTECTED_SACL_SECURITY_
X: SE_SACL_PROTECTED, Y: PROTECTED_SACL_SECURITY_IN
It means, that by design, it can not restore an identical state of Control. A default state indicates the AUTO_INHERITED and PROTECTED to be 0. While SetSecurityInfo() have no such flag for setting a 0 - it may only toggle them. (set the requested flag to 1, while the other being set to 0)
egl1044's suggestion of BackupRead() and BackupWrite() seems capable to extract and restore a Security Descriptor's  identical state - I'm moving it to a new Question.
@egl1044: Thanks once again, I'm still on the topic - but I've decided it wouldn't be fair to EE's rules, because this is way off-topic from my initial question, so I will open a follow-up question.
@evilrix, @ModernMatt, @(and the other helpers): I can't thank you enough for your effort. I guess my requirement using the native API functions was indeed impossible by design, while egl1044's suggestion indeed answers my needs.
I will open a new Question for the new topic.
You guys are the best! :)
Lubster






EARN REWARDS FOR ASKING, ANSWERING, AND MORE.
Earn free swag for participating on the platform.
I'm so pleased you found a solution. If there is anything else I can do for you just ping me, you can find my e-mail address in my profile.
Best regards,
-Rx.
Thanks for your suggestion, just posted the follow-up question here:
https://www.experts-exchange.com/questions/26206959/Extracting-a-NTFS-Security-Descriptor-using-BackupRead-and-BackupSeek.html
(I hope I selected the relevant zones)
Lubster

Get a FREE t-shirt when you ask your first question.
We believe in human intelligence. Our moderation policy strictly prohibits the use of LLM content in our Q&A threads.
Lubster
Long = DWORD in C, I put alot of comments so that if you have trouble you might be able to make it work in C
Public Type WIN32_STREAM_ID
dwStreamId As Long
dwStreamAttributes As Long
Size As LARGE_INTEGER
dwStreamNameSize As Long
'cStreamName As Byte
End Type
Public Sub CreateFileSecurityCopy()
Dim header As WIN32_STREAM_ID
Dim Buff() As Byte '// variable buffer
Dim hFile1 As Long '// source
Dim hFile2 As Long '// copy
Dim dwCtxRead As Long '// read context
Dim dwCtxWrite As Long '// write context
Dim dwNumberOfBytesRead As Long
Dim dwNumberOfBytesWritten As Long
Dim dwLowBytesSeeked As Long
Dim dwHighBytesSeeked As Long
'// The source file.
hFile1 = CreateFileW(StrPtr("\\?\e:\test.txt"), GENERIC_READ Or ACCESS_SYSTEM_SECURITY, 0, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)
'// The exact copy of source file with only security data.
hFile2 = CreateFileW(StrPtr("\\?\e:\test_sec.txt"), GENERIC_WRITE Or WRITE_DAC Or WRITE_OWNER Or ACCESS_SYSTEM_SECURITY, 0, 0, CREATE_ALWAYS, FILE_FLAG_BACKUP_SEMANTICS, 0)
'// Read WIN32_STREAM_ID (header)
Do While BackupRead(hFile1, VarPtr(header), 20, dwNumberOfBytesRead, 0, 1, dwCtxRead)
If dwNumberOfBytesRead = 0 Then
Exit Do ' _leave
End If
If header.dwStreamNameSize > 0 Then
'// Can't directly seek over a stream name using BackupSeek(), you must use BackupRead()
'// to move the the file position over the stream name before you can use BackupSeek()
'// Buff() is ignored and this call is only used to advance the file pointer position to prepare
'// for BackupSeek if the file contains ALTERNATE_DATA_STREAM.
Call BackupRead(hFile1, VarPtr(Buff(0)), header.dwStreamNameSize, dwNumberOfBytesRead, 0, 1, dwCtxRead)
End If
If header.dwStreamId = StreamType.SecurityData Then
'// We found the SECURITY stream
ReDim Buff(header.Size.LoPart) As Byte '// allocate buffer
'// Read the security data bytes
Call BackupRead(hFile1, VarPtr(Buff(0)), header.Size.LoPart, dwNumberOfBytesRead, 0, 1, dwCtxRead)
'// Write the security data header
BackupWrite hFile2, VarPtr(header), 20, dwNumberOfBytesWritten, 0, 1, dwCtxWrite
'// Write the security data bytes
BackupWrite hFile2, VarPtr(Buff(0)), header.Size.LoPart, dwNumberOfBytesWritten, 0, 1, dwCtxWrite
'// Just bail we completed the security copy.
Exit Do ' _leave
End If
'// If we haven't found the correct stream seek to the next stream header.
'// The dwNumberOfBytesRead and dwHighBytesSeeked ignored. Using a large seek
'// value will always place the position at the next stream header because you
'// can't seek over a stream header or ALTERNATE_DATA_STREAM name.
BackupSeek hFile1, &HFFFFFFFF, &H7FFFFFFF, dwLowBytesSeeked, dwHighBytesSeeked, dwCtxRead
Loop
'// CleanUp
BackupRead hFile1, 0, 0, dwNumberOfBytesRead, 1, 0, dwCtxRead
BackupWrite hFile2, 0, 0, dwNumberOfBytesWritten, 1, 0, dwCtxWrite
CloseHandle hFile1
CloseHandle hFile2
End Sub






EARN REWARDS FOR ASKING, ANSWERING, AND MORE.
Earn free swag for participating on the platform.
https://www.experts-exchange.com/questions/26206959/Extracting-a-NTFS-Security-Descriptor-using-BackupRead-and-BackupSeek.html
Lubster
Microsoft Development
--
Questions
--
Followers
Top Experts
Most development for the Microsoft platform is done utilizing the technologies supported by the.NET framework. Other development is done using Visual Basic for Applications (VBA) for programs like Access, Excel, Word and Outlook, with PowerShell for scripting, or with SQL for large databases.