On Fri, 29 Nov 2024, Corinna Vinschen wrote:
> On Nov 27 14:04, Jeremy Drake via Cygwin-patches wrote:
> > https://gist.github.com/jeremyd2019/aa167df0a0ae422fa6ebaea5b60c80c9
>
> Nice! If you feel confident to merge something like this into
> Cygwin, feel free to send patches.
I've found a little time to add in the additional checking that I noticed
in the path.cc code for x64 (make sure the load of the RtlpCurDirRef is
followed immediately by a NULL check, and that it is preceded by a call to
RtlEnterCriticalSection with the FastPebLock as its parameter). I tested
this on Windows builds 16299 (the oldest version I could easily figure out
how to pull from uupdump), 19045, 22631, and 26100 for arm64, x32 and x64
(where possible).
I don't know when or if I'll have time to try to integrate this into
Cygwin, but I've updated the gist (and you can see revision history
there) and also attached the latest version for the mailing list archives
if somebody else wants to.
#include <windows.h>
/* hacks on top of hacks ... */
#define _PEB _NOT__PEB
#define PEB _NOT_PEB
#define PPEB _NOT_PPEB
#define _TEB _NOT__TEB
#define TEB _NOT_TEB
#define PTEB _NOT_PTEB
#include <winternl.h>
#undef _PEB
#undef PEB
#undef PPEB
#undef _TEB
#undef TEB
#undef PTEB
#include <stdint.h>
#include <stdio.h>
#include <assert.h>
/* hacks from cygwin for testing */
typedef struct _PEB
{
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PVOID Ldr;
PVOID ProcessParameters;
PVOID Reserved4;
PVOID ProcessHeap;
PRTL_CRITICAL_SECTION FastPebLock;
/* A lot more follows... */
} PEB, *PPEB;
typedef struct _TEB
{
NT_TIB Tib;
PVOID EnvironmentPointer;
CLIENT_ID ClientId;
PVOID ActiveRpcHandle;
PVOID ThreadLocalStoragePointer;
PPEB Peb;
/* A lot more follows... */
} TEB, *PTEB;
/* This is the layout used in Windows 8 and later. */
struct FAST_CWD_8 {
LONG ReferenceCount; /* Only release when this is 0. */
HANDLE DirectoryHandle;
ULONG OldDismountCount; /* Reflects the system DismountCount
at the time the CWD has been set. */
UNICODE_STRING Path; /* Path's Buffer member always refers
to the following Buffer array. */
LONG FSCharacteristics; /* Taken from FileFsDeviceInformation */
WCHAR Buffer[MAX_PATH] __attribute ((aligned (8)));
};
/* end hacks from cygwin for testing */
LPCVOID GetArm64ProcAddress(HMODULE hModule, LPCSTR procname)
{
const BYTE * proc = (const BYTE *) GetProcAddress (hModule, procname);
#if defined (__aarch64__)
return proc;
#else
#if defined(__i386__)
static const BYTE thunk[] = "\x8b\xff\x55\x8b\xec\x5d\x90\xe9";
#elif defined(__x86_64__)
/* see
https://learn.microsoft.com/en-us/windows/arm/arm64ec-abi#fast-forward-sequences */
static const BYTE thunk[] = "\x48\x8b\xc4\x48\x89\x58\x20\x55\x5d\xe9";
#else
#error "Unhandled architecture for thunk detection"
#endif
if (memcmp(proc, thunk, sizeof(thunk) - 1) == 0)
{
proc += sizeof(thunk) - 1;
proc += 4 + *(const int32_t *)proc;
}
return proc;
#endif
}
#define IS_INSN(pc, name) ((*(pc) & name##_mask) == name##_id)
static const uint32_t add_id = 0x11000000;
static const uint32_t add_mask = 0x7fc00000;
static const uint32_t adrp_id = 0x90000000;
static const uint32_t adrp_mask = 0x9f000000;
static const uint32_t bl_id = 0x94000000;
static const uint32_t bl_mask = 0xfc000000;
/* matches both cbz and cbnz */
static const uint32_t cbz_id = 0x34000000;
static const uint32_t cbz_mask = 0x7e000000;
static const uint32_t ldr_id = 0xb9400000;
static const uint32_t ldr_mask = 0xbfc00000;
static inline LPCVOID extract_bl_target(const uint32_t * pc)
{
assert (IS_INSN(pc, bl));
int32_t offset = *pc & ~bl_mask;
/* sign extend */
if (offset & (1 << 25))
offset |= bl_mask;
/* Note uint32_t * artithmatic will implicitly multiply the offset by 4 */
return pc + offset;
}
static inline uint64_t extract_adrp_address(const uint32_t * pc)
{
assert (IS_INSN(pc, adrp));
uint64_t adrp_base = (uint64_t)pc & ~0xFFF;
int64_t adrp_imm = (*pc >> (5+19+5)) & 0x3;
adrp_imm |= ((*pc >> 5) & 0x7FFFF) << 2;
/* sign extend */
if (adrp_imm & (1 << 20))
adrp_imm |= ~((1 << 21) - 1);
adrp_imm <<= 12;
return adrp_base + adrp_imm;
}
LPVOID find_fast_cwd_pointer_on_arm64 ()
{
LPCVOID proc = GetArm64ProcAddress(GetModuleHandle("ntdll"), "RtlGetCurrentDirectory_U");
printf("%p\n", proc);
const uint32_t * start = (uint32_t *)proc;
const uint32_t * pc = start;
/* find the call to RtlpReferenceCurrentDirectory, and get its address */
for (; pc < start + 20; pc++)
{
if (IS_INSN(pc, bl))
{
proc = extract_bl_target (pc);
break;
}
}
printf("%p\n", proc);
if (proc == start)
return NULL;
start = pc = (uint32_t *)proc;
const uint32_t * ldrpc = NULL;
uint32_t ldroffset, ldrsz;
uint32_t ldrrn, ldrrd;
/* find the ldr (immediate unsigned offset) for RtlpCurDirRef */
for (; pc < start + 20; pc++)
{
if (IS_INSN(pc, ldr))
{
ldrpc = pc;
ldrsz = (*pc & 0x40000000);
ldroffset = (*pc >> (5+5)) & 0xFFF;
ldroffset <<= ldrsz ? 3 : 2;
ldrrn = (*pc >> 5) & 0x1F;
ldrrd = *pc & 0x1F;
break;
}
}
printf("%p -> %x\n", pc, ldroffset);
if (ldrpc == NULL)
return NULL;
/* the next instruction after the ldr should be checking if it was NULL:
either a compare and branch if zero or not zero (hence why cbz_mask is 7e
instead of 7f) */
if (!IS_INSN(pc + 1, cbz) || (*(pc + 1) & 0x1F) != ldrrd
|| (*(pc + 1) & 0x80000000) != (ldrsz << 1))
return NULL;
/* work backwards, find a bl to RtlEnterCriticalSection whose argument
is the fast peb lock */
proc = GetArm64ProcAddress(GetModuleHandle("ntdll"), "RtlEnterCriticalSection");
for (pc = ldrpc; pc >= start; pc--)
{
if (IS_INSN(pc, bl) && extract_bl_target(pc) == proc)
break;
}
uint32_t addoffset;
uint32_t addrn;
for (; pc >= start; pc--)
{
if (IS_INSN(pc, add) && (*pc & 0x1F) == 0)
{
addoffset = (*pc >> (5+5)) & 0xFFF;
addrn = (*pc >> 5) & 0x1F;
break;
}
}
LPVOID critsec = NULL;
for (; pc >= start; pc--)
{
if (IS_INSN(pc, adrp) && (*pc & 0x1F) == addrn)
{
critsec = (LPVOID)(extract_adrp_address(pc) + addoffset);
break;
}
}
if (critsec != NtCurrentTeb ()->Peb->FastPebLock)
return NULL;
/* work backwards from the ldr to find the corresponding adrp */
LPVOID RtlpCurDirRef = NULL;
for (pc = ldrpc; pc >= start; pc--)
{
if (IS_INSN(pc, adrp) && (*pc & 0x1F) == ldrrn)
{
RtlpCurDirRef = (LPVOID)(extract_adrp_address(pc) + ldroffset);
break;
}
}
printf("%p -> %p\n", pc, RtlpCurDirRef);
return RtlpCurDirRef;
}
int main (void)
{
typedef ULONG (WINAPI * RtlGetCurrentDirectory_U_t) (ULONG, LPWSTR);
RtlGetCurrentDirectory_U_t pRtlGetCurrentDirectory_U = (RtlGetCurrentDirectory_U_t) GetProcAddress(GetModuleHandle("ntdll"), "RtlGetCurrentDirectory_U");
printf("%p\n", pRtlGetCurrentDirectory_U);
struct FAST_CWD_8 ** RtlpCurDirRef = find_fast_cwd_pointer_on_arm64 ();
printf("%p\n", RtlpCurDirRef);
getchar();
printf("%S\n", (*RtlpCurDirRef)->Path.Buffer);
WCHAR buf[MAX_PATH];
pRtlGetCurrentDirectory_U (sizeof(buf), buf);
printf("%S\n", buf);
return 0;
}