I'm trying to enumerate 32bit process modules names from 64bit application using the following code:
if (EnumProcessModulesEx(hProcess, hMods, sizeof(hMods), &cbNeeded, LIST_MODULES_ALL))
{
for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++)
{
TCHAR szModName[MAX_PATH] = { 0 };
if (GetModuleFileNameEx(hProcess, hMods[i], szModName,
sizeof(szModName) / sizeof(TCHAR)))
{
printf("module name is: %S", szModName);
}
}
}
The code works as expected in Windows 7, as part of the results are:
...
C:\Windows\**SysWOW64**\ntdll.dll
...
In Windows 10 the above code returns the full path but with System32 instead of SysWOW64. e.g,
...
C:\Windows\**System32**\ntdll.dll
...
Looking deeper for the cause, I notice that GetModuleFileNameEx reads the remote process PEB and LDR_TABLE_ENTRY, and starting from Windows 10 the LDR_TABLE_ENTRY contains the full path with System32 and not SysWOW64 - also for 32bit applications.
I also tried to use GetMappedFileName but it isn't straight forward and efficient to translate the path from dos path (\device\harddiskvolume) to standard (c:\) path.
I wonder if there are any other easy way to extract the full syswow64 path.
for get valid win32 file path from file nt-path - simplest way - add L"\\\\?\\globalroot" (\\?\globalroot) prefix. this is because CreateFileW looked from \??\ directory and globalroot is symbolic link in \??\ which let as to jump to root of nt namespace.
for example - \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll is nt absolute path. and \\?\globalroot\Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll is valid win32 path for CreateFileW - this api convert well known prefix \\?\ to nt prefix \??\ and pass name \??\globalroot\Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll to kernel. when parsing this name - after process symbolic link globalroot which point to root of namespace - we again got \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll - correct nt path.
so if we need valid win32 path for use in CreateFileW - simply append this prefix to nt path. however some shell32 api not accept this form path. also it not nice looked in UI. if we want got DOS drive letter form path (this is subset of valid win32 paths) - we can use IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH which convert device name to drive letter. this ioctl take as input MOUNTDEV_NAME (declared in mountmgr.h) and output buffer is MOUNTMGR_VOLUME_PATHS. in MOUNTDEV_NAME buffer must be exactly device name, without file path. so we need break returned nt path to 2 components. for example in \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll :
\Device\HarddiskVolume9 - device path
\Windows\SysWOW64\ntdll.dll - file system path
correct way here first open file and call GetFileInformationByHandleEx with FileNameInfo - we got file system path in output. with this we can use wcsstr for separate device path. also if we open file handle - we can use it in call GetFinalPathNameByHandleW with VOLUME_NAME_DOS. this api do exactly which we will be do - query file path, separate device path and call IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH. + open/close mount manager.
but usual nt file path begin from \Device\HarddiskVolumeX. this allow first try fast way - avoid open file and query it path.
so first we need open mount manager:
#include <mountmgr.h>
HANDLE hMountManager = CreateFile(MOUNTMGR_DOS_DEVICE_NAME,
0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);
then we can run next code:
void dumpModules(HANDLE hMountManager, HANDLE hProcess)
{
ULONG cb = 0, cbNeeded = 16;
volatile static UCHAR guz;
PVOID stack = alloca(guz);
HMODULE *hMods, hmod;
__continue:
// cumulative allocate memory in stack, not need free it
cb = RtlPointerToOffset(hMods = (HMODULE*)alloca(cbNeeded - cb), stack);
if (EnumProcessModulesEx(hProcess, hMods, cb, &cbNeeded, LIST_MODULES_32BIT))
{
if (cb < cbNeeded)
{
goto __continue;
}
if (cbNeeded /= sizeof(HMODULE))
{
//i use hard coded size buffers, for reduce code and show main idea
#define FILE_NAME_INFO_buffer_size FIELD_OFFSET(FILE_NAME_INFO, FileName[MAX_PATH])
#define MOUNTDEV_NAME_buffer_size FIELD_OFFSET(MOUNTDEV_NAME, Name[MAX_PATH])
#define MOUNTMGR_VOLUME_PATHS_buffer_size FIELD_OFFSET(MOUNTMGR_VOLUME_PATHS, MultiSz[64])
// + space for 0 at the end
PFILE_NAME_INFO pfni = (PFILE_NAME_INFO)alloca(FILE_NAME_INFO_buffer_size + sizeof(WCHAR));
PMOUNTMGR_VOLUME_PATHS pmvp = (PMOUNTMGR_VOLUME_PATHS)alloca(MOUNTMGR_VOLUME_PATHS_buffer_size);
PMOUNTDEV_NAME pmdn = (PMOUNTDEV_NAME)alloca(MOUNTDEV_NAME_buffer_size);
static WCHAR globalroot[] = L"\\\\.\\globalroot";
alloca(sizeof(globalroot));
PWSTR win32Path = pmdn->Name - RTL_NUMBER_OF(globalroot) + 1;
memcpy(win32Path, globalroot, sizeof(globalroot));
USHORT NameLength = pmdn->NameLength;
do
{
hmod = *hMods++;
if (GetMappedFileNameW(hProcess, hmod, pmdn->Name, MAX_PATH))
{
DbgPrint("%p %S\n",hmod, pmdn->Name);
PWSTR c = 0;
static const WCHAR HarddiskVolume[] = L"\\Device\\HarddiskVolume";
// fast way
if (!memcmp(pmdn->Name, HarddiskVolume, sizeof(HarddiskVolume) - sizeof(WCHAR)))
{
c = wcschr(pmdn->Name + RTL_NUMBER_OF(HarddiskVolume) - 1, '\\');
}
// else - for demo
{
pmdn->NameLength = NameLength;
HANDLE hFile = CreateFile(win32Path, 0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
//++ just for demo
WCHAR DosPath[MAX_PATH];
if (GetFinalPathNameByHandleW(hFile, DosPath, RTL_NUMBER_OF(DosPath), VOLUME_NAME_DOS))
{
DbgPrint("%S\n", DosPath);
}
RtlGetLastNtStatus();
//-- just for demo
BOOL fOk = GetFileInformationByHandleEx(hFile, FileNameInfo, pfni, FILE_NAME_INFO_buffer_size);
CloseHandle(hFile);
if (fOk)
{
// FileName not 0 terminated
pfni->FileName[pfni->FileNameLength/sizeof(WCHAR)] = 0;
c = wcsstr(pmdn->Name, pfni->FileName);
}
}
}
if (c)
{
pmdn->NameLength = (USHORT)RtlPointerToOffset(pmdn->Name, c);
if (DeviceIoControl(hMountManager, IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH,
pmdn, MOUNTDEV_NAME_buffer_size,
pmvp, MOUNTMGR_VOLUME_PATHS_buffer_size, &cb, NULL))
{
DbgPrint("%S%S\n", pmvp->MultiSz, c);
}
}
}
} while (--cbNeeded);
}
}
}
and demo output for notepad:
0000000000170000 \Device\HarddiskVolume9\Windows\SysWOW64\notepad.exe
\\?\C:\Windows\SysWOW64\notepad.exe
C:\Windows\SysWOW64\notepad.exe
0000000077A90000 \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll
\\?\C:\Windows\SysWOW64\ntdll.dll
0000000075460000 \Device\HarddiskVolume9\Windows\SysWOW64\kernel32.dll
\\?\C:\Windows\SysWOW64\kernel32.dll
C:\Windows\SysWOW64\kernel32.dll
0000000074A30000 \Device\HarddiskVolume9\Windows\SysWOW64\KernelBase.dll
\\?\C:\Windows\SysWOW64\KernelBase.dll
C:\Windows\SysWOW64\KernelBase.dll
00000000749B0000 \Device\HarddiskVolume9\Windows\SysWOW64\advapi32.dll
\\?\C:\Windows\SysWOW64\advapi32.dll
Related
To test a corner case in our debugger, I need to come up with a program which has a DLL loaded above 2GB (0x80000000). Current test case is a multi-GB game which loads >700 DLLs, and I'd like to have something simpler and smaller. Is there a way to achieve it reliably without too much fiddling? I assume I need to use /LARGEADDRESSAWARE and somehow consume enough of the VA space to bump the new DLLs above 2GB but I'm fuzzy on the details...
Okay, it took me a few tries but I managed to come up with something working.
// cl /MT /Ox test.cpp /link /LARGEADDRESSAWARE
// occupy the 2 gigabytes!
#define ALLOCSIZE (64*1024)
#define TWOGB (2*1024ull*1024*1024)
#include <windows.h>
#include <stdio.h>
int main()
{
int nallocs = TWOGB/ALLOCSIZE;
for ( int i = 0; i < nallocs+200; i++ )
{
void * p = VirtualAlloc(NULL, ALLOCSIZE, MEM_RESERVE, PAGE_NOACCESS);
if ( i%100 == 0)
{
if ( p != NULL )
printf("%d: %p\n", i, p);
else
{
printf("%d: failed!\n", i);
break;
}
}
}
printf("finished VirtualAlloc. Loading a DLL.\n");
//getchar();
HMODULE hDll = LoadLibrary("winhttp");
printf("DLL base: %p.\n", hDll);
//getchar();
FreeLibrary(hDll);
}
On Win10 x64 produces:
0: 00D80000
100: 03950000
200: 03F90000
[...]
31800: 7FBC0000
31900: 00220000
32000: 00860000
32100: 80140000
32200: 80780000
32300: 80DC0000
32400: 81400000
32500: 81A40000
32600: 82080000
32700: 826C0000
32800: 82D00000
32900: 83340000
finished VirtualAlloc. Loading a DLL.
DLL base: 83780000.
for your own DLL you need set 3 linker option:
/LARGEADDRESSAWARE
/DYNAMICBASE:NO
/BASE:"0x********"
note that link.exe allow only image full located bellow 3GB (0xC0000000) for 32-bit image. in other word, he want that ImageBase + ImageSize <= 0xC0000000
so say /BASE:0xB0000000 will be ok, /BASE:0xBFFF0000 only if your image size <= 0x10000 and for /BASE:0xC0000000 and higher we always got error LNK1249 - image exceeds maximum extent with base address address and size size
also EXE mandatory must have /LARGEADDRESSAWARE too, because are all 4GB space available for wow64 process based only on EXE options.
if we want do this for external DLL - here question more hard. first of all - are this DLL can correct handle this situation (load base > 0x80000000) ? ok. let test this. any api (including most low level LdrLoadDll) not let specify base address, for DLL load. here only hook solution exist.
when library loaded, internal always called ZwMapViewOfSection and it 3-rd parameter BaseAddress - Pointer to a variable that receives the base address of the view. if we set this variable to 0 - system yourself select loaded address. if we set this to specific address - system map view (DLL image in our case) only at this address, or return error STATUS_CONFLICTING_ADDRESSES.
working solution - hook call to ZwMapViewOfSection and replace value of variable, to which point BaseAddress. for find address > 0x80000000 we can use VirtualAlloc with MEM_TOP_DOWN option. note - despite ZwMapViewOfSection also allow use MEM_TOP_DOWN in AllocationType parameter, here it will be have not needed effect - section anyway will be loaded by preferred address or top-down from 0x7FFFFFFF not from 0xFFFFFFFF. but with VirtualAlloc the MEM_TOP_DOWN begin search from 0xFFFFFFFF if process used 4Gb user space. for know - how many memory need for section - we can call ZwQuerySection with SectionBasicInformation - despite this is undocumented - for debug and test - this is ok.
for hook of course can be used some detour lib, but possible do hook with DRx breakpoint - set some Drx register to NtMapViewOfSection address. and set AddVectoredExceptionHandler - which handle exception. this will be perfect work, if process not under debugger. but under debugger it break - most debuggers alwas stop under single step exception and usually no option not handle it but pass to application. of course we can start program not under debugger, and attach it later - after dll load. or possible do this task in separate thread and hide this thread from debugger. disadvantage here - that debugger not got notify about dll load in this case and not load symbols for this. however for external (system dll) for which you have not src code - this in most case can be not a big problem. so solution exit, if we can implement it ). possible code:
PVOID pvNtMapViewOfSection;
LONG NTAPI OnVex(::PEXCEPTION_POINTERS ExceptionInfo)
{
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP &&
ExceptionInfo->ExceptionRecord->ExceptionAddress == pvNtMapViewOfSection)
{
struct MapViewOfSection_stack
{
PVOID ReturnAddress;
HANDLE SectionHandle;
HANDLE ProcessHandle;
PVOID *BaseAddress;
ULONG_PTR ZeroBits;
SIZE_T CommitSize;
PLARGE_INTEGER SectionOffset;
PSIZE_T ViewSize;
SECTION_INHERIT InheritDisposition;
ULONG AllocationType;
ULONG Win32Protect;
} * stack = (MapViewOfSection_stack*)(ULONG_PTR)ExceptionInfo->ContextRecord->Esp;
if (stack->ProcessHandle == NtCurrentProcess())
{
SECTION_BASIC_INFORMATION sbi;
if (0 <= ZwQuerySection(stack->SectionHandle, SectionBasicInformation, &sbi, sizeof(sbi), 0))
{
if (PVOID pv = VirtualAlloc(0, (SIZE_T)sbi.Size.QuadPart, MEM_RESERVE|MEM_TOP_DOWN, PAGE_NOACCESS))
{
if (VirtualFree(pv, 0, MEM_RELEASE))
{
*stack->BaseAddress = pv;
}
}
}
}
// RESUME_FLAG ( 0x10000) not supported by xp, but anyway not exist 64bit xp
ExceptionInfo->ContextRecord->EFlags |= RESUME_FLAG;
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
struct LOAD_DATA {
PCWSTR lpLibFileName;
HMODULE hmod;
ULONG dwError;
};
ULONG WINAPI HideFromDebuggerThread(LOAD_DATA* pld)
{
NtSetInformationThread(NtCurrentThread(), ThreadHideFromDebugger, 0, 0);
ULONG dwError = 0;
HMODULE hmod = 0;
if (PVOID pv = AddVectoredExceptionHandler(TRUE, OnVex))
{
::CONTEXT ctx = {};
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
ctx.Dr7 = 0x404;
ctx.Dr1 = (ULONG_PTR)pvNtMapViewOfSection;
if (SetThreadContext(GetCurrentThread(), &ctx))
{
if (hmod = LoadLibraryW(pld->lpLibFileName))
{
pld->hmod = hmod;
}
else
{
dwError = GetLastError();
}
ctx.Dr7 = 0x400;
ctx.Dr1 = 0;
SetThreadContext(GetCurrentThread(), &ctx);
}
else
{
dwError = GetLastError();
}
RemoveVectoredExceptionHandler(pv);
}
else
{
dwError = GetLastError();
}
pld->dwError = dwError;
return dwError;
}
HMODULE LoadLibHigh(PCWSTR lpLibFileName)
{
BOOL bWow;
HMODULE hmod = 0;
if (IsWow64Process(GetCurrentProcess(), &bWow) && bWow)
{
if (pvNtMapViewOfSection = GetProcAddress(GetModuleHandle(L"ntdll"), "NtMapViewOfSection"))
{
LOAD_DATA ld = { lpLibFileName };
if (IsDebuggerPresent())
{
if (HANDLE hThread = CreateThread(0, 0, (PTHREAD_START_ROUTINE)HideFromDebuggerThread, &ld, 0, 0))
{
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
}
}
else
{
HideFromDebuggerThread(&ld);
}
if (!(hmod = ld.hmod))
{
SetLastError(ld.dwError);
}
}
}
else
{
hmod = LoadLibrary(lpLibFileName);
}
return hmod;
}
I am aware that GetFinalPathNameByHandle can be used to obtain the target of a symbolic link or a reparse point, but there are situations where its use is not desirable:
If the target is not available, doesn't exist or cannot be opened, CreateFile on a symlink fails, and thus the path cannot be obtained.
If I point a symlink "a" to file "b" and create a symlink "b" to file "c", the function follows the whole chain, returning "c".
The function is not useful much when I already have the handle to the actual symlink at hand.
It seems that DeviceIoControl can be used together with FSCTL_GET_REPARSE_POINT to obtain the actual reparse data of the file, but that gets me the REPARSE_DATA_BUFFER, and I would have to parse that.
I don't know how the system actually processes reparse points, but I think that the target location is a piece of information that should be available at some point. The dir command, for example, can display the target path correctly for any reparse point... well I have already seen it handle just symlinks and mount points (junctions).
how the system actually processes reparse points
this is done inside file system and file system filter drivers. result depend from are FILE_FLAG_OPEN_REPARSE_POINT option used in call CreateFile (or FILE_OPEN_REPARSE_POINT in NT calls).
when FILE_FLAG_OPEN_REPARSE_POINT is specified - file system bypass normal reparse point processing for the file and attempts to directly open the reparse point file as is.
If the FILE_OPEN_REPARSE_POINT flag is not specified - file-system attempts to open a file to which reparse point is point (if fs understand format of reparse point - primary only Microsoft reparse points)
the data format saved in reparse point is REPARSE_DATA_BUFFER (Microsoft reparse point format) or REPARSE_GUID_DATA_BUFFER - need look for ReparseTag at begin.
to determine whether a reparse point tag corresponds to a tag owned by Microsoft we use IsReparseTagMicrosoft macro.
code for test/print reparse point data:
volatile UCHAR guz;
ULONG TestReparsePoint(PCWSTR FileName)
{
HANDLE hFile = CreateFile(FileName, 0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, 0);
if (hFile == INVALID_HANDLE_VALUE)
{
return GetLastError();
}
union {
PVOID pv;
PULONG ReparseTag;
PREPARSE_DATA_BUFFER prdb;
PREPARSE_GUID_DATA_BUFFER prgdb;
};
PVOID stack = alloca(guz);
ULONG cb = 0, rcb = sizeof(REPARSE_DATA_BUFFER) + 0x100, BytesReturned;
ULONG dwError;
do
{
if (cb < rcb) cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
if (DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, 0, 0, pv, cb, &BytesReturned, 0))
{
dwError = NOERROR;
if (IsReparseTagMicrosoft(*ReparseTag))
{
char cc[16];
LPCSTR name;
switch (*ReparseTag)
{
case IO_REPARSE_TAG_SYMLINK:
name = " SYMLINK";
stack = prdb->SymbolicLinkReparseBuffer.PathBuffer;
break;
case IO_REPARSE_TAG_MOUNT_POINT:
name = " MOUNT_POINT";
stack = prdb->MountPointReparseBuffer.PathBuffer;
break;
default:
sprintf(cc, " %08x", prdb->ReparseTag);
name = cc;
}
DbgPrint(" %s->%.*S <%.*S>\n", name,
prdb->MountPointReparseBuffer.SubstituteNameLength >> 1,
RtlOffsetToPointer(stack, prdb->MountPointReparseBuffer.SubstituteNameOffset),
prdb->MountPointReparseBuffer.PrintNameLength >> 1,
RtlOffsetToPointer(stack, prdb->MountPointReparseBuffer.PrintNameOffset)
);
}
else
{
PGUID g = &prgdb->ReparseGuid;
DbgPrint(" tag=%x {%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x} size=%x\n", *ReparseTag,
g->Data1, g->Data2, g->Data3,
g->Data4[0],g->Data4[1],g->Data4[2],g->Data4[3],g->Data4[4],g->Data4[5],g->Data4[6],g->Data4[7],
prgdb->ReparseDataLength);
}
break;
}
rcb = IsReparseTagMicrosoft(*ReparseTag)
? REPARSE_DATA_BUFFER_HEADER_SIZE + prdb->ReparseDataLength
: REPARSE_GUID_DATA_BUFFER_HEADER_SIZE + prgdb->ReparseDataLength;
} while((dwError = GetLastError()) == ERROR_MORE_DATA);
CloseHandle(hFile);
return dwError;
}
Microsoft reparse points can be read with REPARSE_DATA_BUFFER instead. The MS open protocol specification might also be useful.
Parsing other GUID based tags can only be done if you know the format.
In Windows 10 version 1607, processes can now opt in to long path awareness using a manifest attribute (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx#maxpath)
How can I programmatically check if the calling process is long path aware? Note that checking the OS version or the value of the registry key alone is insufficient due to the case where the Windows version is >= 1607, long paths are disabled system wide, and the process is not manifested for long paths.
Despite the documentation says that long path names could be enabled for both Win32 and UWP applications, it is broken for UWP. API KernelBase!BasepIsProcessLongPathAwareByManifest uses SxS API to obtain values from manifest, and this API is not functional for UWP.
The problem could be solved by manually setting proper bit in PEB:
NtCurrentTeb()->ProcessEnvironmentBlock->IsLongPathAwareProcess = 1;
definition of TEB could be copied from winternl.h, IsLongPathAwareProcess bit is the most significant bit of 4th byte, i.e this can be rewritten as
((unsigned char*)NtCurrentTeb()->ProcessEnvironmentBlock)[3] |= 0x80;
ntdll (in win10 1607) export next API BOOLEAN NTAPI RtlAreLongPathsEnabled(); - so you can call this. it return TRUE if LongPaths Enabled
here code spinet - if RtlAreLongPathsEnabled returned false - STATUS_NAME_TOO_LONG (c0000106) is returned
system need convert Win32 path to NT path before use it in any file functions, which call kernel. this is done by calling RtlDosPathNameTo*NtPathName* . this functions, if see that path exceeds MAX_PATH (~) - called RtlAreLongPathsEnabled() and continue work only if function return TRUE. in case false - STATUS_NAME_TOO_LONG returned.
code of RtlAreLongPathsEnabled is simply - when first time called - it check registry (and only registry) and save result. not looking for manifest at all. here exactly code of function:
BOOLEAN RtlAreLongPathsEnabled()
{
static BOOLEAN init;
static BOOLEAN elp;
if (!init)
{
init = true;
HANDLE hKey;
KEY_VALUE_PARTIAL_INFORMATION kvpi;
STATIC_OBJECT_ATTRIBUTES(FileSystemRegKeyName, "\\registry\\MACHINE\\SYSTEM\\CurrentControlSet\\Control\\FileSystem");
if (0 <= ZwOpenKey(&hKey, KEY_READ, &FileSystemRegKeyName))
{
STATIC_UNICODE_STRING(LongPathRegKeyValue, "LongPathsEnabled");
if (0 <= ZwQueryValueKey(hKey, &LongPathRegKeyValue, KeyValuePartialInformation, &kvpi, sizeof(kvpi), &kvpi.TitleIndex) &&
kvpi.Type == REG_DWORD && kvpi.DataLength == sizeof(DWORD))
{
elp = *(DWORD*)kvpi.Data != 0;
}
ZwClose(hKey);
}
}
return elp;
}
so my conclusion - in current build long path behavior dependent only from registry settings and absolute not depended from application manifest, despite MSDN.
for down votes - for me simply interesting - are somebody from you build test app (with and without manifest) and test this yourself , or you can only read documentation ?
for those who find it difficult, or too lazy to write the code yourself. you can test with this code:
BOOL CreateFolder(LPCWSTR lpPathName)
{
return CreateDirectoryW(lpPathName, 0) || GetLastError() == ERROR_ALREADY_EXISTS;
}
void LPT()
{
WCHAR name[128], path[0x8000], *c;
if (!SHGetFolderPath(0, CSIDL_PROFILE , 0, 0, path))
{
*name = '\\';
__stosw((PUSHORT)name + 1, '3', RTL_NUMBER_OF(name) - 2);
name[RTL_NUMBER_OF(name) - 1] = 0;
c = path + wcslen(path);
int n = 4;
do
{
memcpy(c, name, sizeof(name));
c += RTL_NUMBER_OF(name) - 1;
if (!CreateFolder(path))
{
break;
}
} while (--n);
if (!n)
{
wcscpy(c, L"\\1.txt");
HANDLE hFile = CreateFileW(path, FILE_GENERIC_WRITE, FILE_SHARE_VALID_FLAGS, 0, OPEN_ALWAYS, 0, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
return ;
}
}
}
GetLastError();
}
and test it with <ws2:longPathAware>true</ws2:longPathAware> in manifest and LongPathsEnabled==0 in registry. it fail ? and then test it without manifest but with LongPathsEnabled==1 in registry. worked ?
if so i test on windows 10. version 1607. build 14393.0
on win10 1709 implementation changed: now RtlAreLongPathsEnabled is very simply:
BOOLEAN RtlAreLongPathsEnabled()
{
return NtCurrentTeb()->ProcessEnvironmentBlock->IsLongPathAwareProcess;
}
in previous build was:
The MSDN document Naming Files, Paths, and Namespaces talks about the \\?\ prefix. To quote:
For file I/O, the "\?\" prefix to a path string tells the Windows APIs to disable all string parsing and to send the string that follows it straight to the file system.
Experimentation showed me that the \??\ prefix has the same effect, both disabling path parsing (.. handling) and enabling paths longer than MAX_PATH.
The MSDN refers to \\? as the "Win32 file namespace", so does it known purely by the Win32 usermode API and translated to \?? in the NT namespace? And anyway, through Winobj I see GLOBAL?? in the NT namespace, not ??.
The answer to your question is, yes there is a difference between passing \\?\ and \??\ to user mode functions.
Internally, NT always represents paths with the \??\ prefix. Normally, when you call a user mode function (e.g. CreateDirectoryW) with a normal path like C:\foo, the user mode functions call an internal function named RtlDosPathNameToNtPathName_U which converts this to an NT-style path prefixed with \??\. This conversion is done with a fixed size static buffer, which is where the famous MAX_PATH limitation comes from.
When you call a user mode function specifying the \\?\ prefix (note, only one ?), RtlDosPathNameToNtPathName_U is not called. Instead, the second back-slash is turned into a ? character and the path is used verbatim. This is what the docs mean when they talk about \\?\ turning off the "...automatic expansion of the path string."
However, when you call a user mode function with the \??\ prefix, which remember is an internal NT prefix, this expansion is still done.
The user mode functions specifically look for \\?\ to disable the automatic expansion process, and since you're not providing it, your path is treated as a non-prefixed path and fed to RtlDosPathNameToNtPathName_U. This function is smart enough not to add an extra \??\ prefix to the start of the path, however the fixed size static buffer is still used.
This is the key difference. When you pass \??\ as a prefix your paths are still subject to the MAX_PATH length limit.
The following example program demonstrates this. The TestNestedDir function simply attempts to create (and then delete) a path greater than MAX_PATH characters in length, one level at a time. The results you'll see if you run this code are:
CreateDir, no prefix = 0
CreateDir, prefix \\?\ = 1
CreateDir, prefix \??\ = 0
Only the creation done with the \\?\ prefix is successful.
#include <stdio.h>
#include <tchar.h>
#include <string>
#include <assert.h>
#include <Windows.h>
const wchar_t* pszLongPath =
L"C:\\"
L"12345678901234567890123456789012345678901234567890\\"
L"12345678901234567890123456789012345678901234567890\\"
L"12345678901234567890123456789012345678901234567890\\"
L"12345678901234567890123456789012345678901234567890\\"
L"12345678901234567890123456789012345678901234567890\\"
L"12345678901234567890123456789012345678901234567890";
bool TestCreateNestedDir(LPCWSTR pszPath)
{
std::wstring strPath = pszPath;
std::wstring::size_type pos = 0, first = std::wstring::npos;
bool fDirs = false, fResult = false;
// step through each level in the path, but only try to start creating directories
// after seeing a : character
while ((pos = strPath.find_first_of(L'\\', pos)) != std::wstring::npos)
{
if (fDirs)
{
// get a substring for this level of the path
std::wstring strSub = strPath.substr(0, pos);
// check if the level already exists for some reason
DWORD dwAttr = ::GetFileAttributesW(strSub.c_str());
if (dwAttr != -1 && (dwAttr & FILE_ATTRIBUTE_DIRECTORY))
{
++pos;
continue;
}
// try to make the dir. if it exists, remember the first one we successfully made for later cleanup
if (!::CreateDirectoryW(strSub.c_str(), nullptr))
break;
if (first == std::wstring::npos) first = pos;
}
else
if (pos > 0 && strPath[pos - 1] == L':')
fDirs = true;
++pos;
}
if (pos == std::wstring::npos)
{
// try to create the last level of the path (we assume this one doesn't exist)
if (::CreateDirectoryW(pszPath, nullptr))
{
fResult = true;
::RemoveDirectoryW(pszPath);
}
}
else
--pos;
// now delete any dirs we successfully made
while ((pos = strPath.find_last_of(L'\\', pos)) != std::wstring::npos)
{
::RemoveDirectoryW(strPath.substr(0, pos).c_str());
if (pos == first) break;
--pos;
}
return fResult;
}
int _tmain(int argc, _TCHAR* argv[])
{
assert(wcslen(pszLongPath) > MAX_PATH);
printf("CreateDir, no prefix = %ld\n", TestCreateNestedDir(pszLongPath));
std::wstring strPrefix = L"\\\\?\\" + std::wstring(pszLongPath);
printf("CreateDir, prefix \\\\?\\ = %ld\n", TestCreateNestedDir(strPrefix.c_str()));
strPrefix[1] = L'?';
printf("CreateDir, prefix \\??\\ = %ld\n", TestCreateNestedDir(strPrefix.c_str()));
return 0;
}
My program is combined with some additional data at the end of the original exe. The program would extract the additional data to disk when running the program.
However my program can't get the right offset of the appended data after signing the combined executable program.
I compared the signed exe and the original exe, the signing information is appended at the end of the exe. So I'm looking for a Win32 API to get the length of signing segment from the signed program. After that, my program could find the right offset of combined data, then extract them correctly.
Could anyone give me a hint?
I find a tool named PEDump(written by Matt Pietrek for his book) with source code to demonstrate how to get the size of signing information.
Below is the code extracted from PEDump for my purpose,
// MakePtr is a macro that allows you to easily add to values (including
// pointers) together without dealing with C's pointer arithmetic. It
// essentially treats the last two parameters as DWORDs. The first
// parameter is used to typecast the result to the appropriate pointer type.
#define MakePtr( cast, ptr, addValue ) (cast)( (DWORD)(ptr) + (DWORD)(addValue))
// Names of the data directory elements that are defined
const char *ImageDirectoryNames[] = {
"EXPORT", "IMPORT", "RESOURCE", "EXCEPTION", "SECURITY", "BASERELOC",
"DEBUG", "COPYRIGHT", "GLOBALPTR", "TLS", "LOAD_CONFIG",
"BOUND_IMPORT", "IAT", // These two entries added for NT 3.51
"DELAY_IMPORT" }; // This entry added in NT 5
#define NUMBER_IMAGE_DIRECTORY_ENTRYS \
(sizeof(ImageDirectoryNames)/sizeof(char *))
HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(getProgramFile()));
HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if ( hFileMapping == 0 )
{
printf("%s", "Couldn't open file mapping with CreateFileMapping()\n");
} else {
LPVOID lpFileBase = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);
if ( lpFileBase == 0 )
{
printf("%s", "Couldn't map view of file with MapViewOfFile()\n");
} else {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)lpFileBase;
PIMAGE_FILE_HEADER pImgFileHdr = (PIMAGE_FILE_HEADER)lpFileBase;
// it's EXE file
if ( dosHeader->e_magic == IMAGE_DOS_SIGNATURE )
{
PIMAGE_NT_HEADERS pNTHeader;
DWORD base = (DWORD)dosHeader;
pNTHeader = MakePtr( PIMAGE_NT_HEADERS, dosHeader, dosHeader->e_lfanew );
PIMAGE_OPTIONAL_HEADER optionalHeader = (PIMAGE_OPTIONAL_HEADER)&pNTHeader->OptionalHeader;
for ( int i=0; i < optionalHeader->NumberOfRvaAndSizes; i++)
{
// DataDirectory[4] represents security directory
if ( 4 == i ) {
signingLength = optionalHeader->DataDirectory[i].Size;
break;
}
}
}
UnmapViewOfFile(lpFileBase);
}
CloseHandle(hFileMapping);
}
Put a long signature line before and after your data, and then just search for those lines at the expected offsets.