#include /* 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 #undef _PEB #undef PEB #undef PPEB #undef _TEB #undef TEB #undef PTEB #include #include #include /* 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; }