On Sat, 1 Mar 2025, Johannes Schindelin wrote:
> Note: In the long run, we may very well want to follow the insightful
> suggestion by a helpful Windows kernel engineer who pointed out that it
> may be less fragile to implement kind of a disassembler that has a
> better chance to adapt to the ever-changing code of
> `ntdll!RtlpReferenceCurrentDirectory` by skipping uninteresting
> instructions such as `mov %rsp,%rax`, `mov %rbx,0x20(%rax)`, `push %rsi`
> `sub $0x70,%rsp`, etc, and focuses on finding the `lea`, `call
> ntdll!RtlEnterCriticalSection` and `mov ..., rbx` instructions, much
> like it was prototyped out for ARM64 at
> https://gist.github.com/jeremyd2019/aa167df0a0ae422fa6ebaea5b60c80c9
Since you kind of asked, here's a proof-of-concept that uses udis86 (I
left a whole bunch of pointer<->integer warnings since this is a PoC).
Tested on windows 11 and 8:
LPVOID find_fast_cwd_pointer_on_x64 ()
{
ud_t ud_obj;
ud_init (&ud_obj);
ud_set_mode (&ud_obj, 64);
LPCVOID proc = GetProcAddress(GetModuleHandle("ntdll"),
"RtlGetCurrentDirectory_U");
LPCVOID start = proc;
printf("%p\n", proc);
/* no idea for size */
ud_set_input_buffer (&ud_obj, proc, 80);
ud_set_pc (&ud_obj, proc);
/* find the call to RtlpReferenceCurrentDirectory, and get its address */
while (ud_disassemble (&ud_obj))
{
if (ud_insn_mnemonic (&ud_obj) == UD_Icall)
{
const ud_operand_t * operand = ud_insn_opr (&ud_obj, 0);
if (operand->type == UD_OP_JIMM && operand->size == 32)
{
proc = ud_insn_off (&ud_obj) + ud_insn_len (&ud_obj) +
operand->lval.sdword;
break;
}
}
}
printf("%p\n", proc);
if (proc == start)
return NULL;
start = proc;
/* no idea for size */
ud_set_input_buffer (&ud_obj, proc, 160);
ud_set_pc (&ud_obj, proc);
LPVOID critsec = NULL;
while (ud_disassemble (&ud_obj))
{
if (ud_insn_mnemonic (&ud_obj) == UD_Ilea)
{
/* this seems to follow intel syntax, in that operand 0 is the
register and 1 is the memory refernece */
const ud_operand_t * operand = ud_insn_opr (&ud_obj, 1);
if (operand->type == UD_OP_MEM && operand->base == UD_R_RIP &&
operand->index == UD_NONE && operand->scale == 0 &&
operand->offset == 32)
{
critsec = ud_insn_off (&ud_obj) + ud_insn_len (&ud_obj) +
operand->lval.sdword;
break;
}
}
}
if (critsec != NtCurrentTeb ()->Peb->FastPebLock)
return NULL;
/* find the call to RtlEnterCriticalSection */
proc = GetProcAddress(GetModuleHandle("ntdll"), "RtlEnterCriticalSection");
while (ud_disassemble (&ud_obj))
{
enum ud_mnemonic_code insn = ud_insn_mnemonic (&ud_obj);
if (insn == UD_Icall)
{
const ud_operand_t * operand = ud_insn_opr (&ud_obj, 0);
if (operand->type == UD_OP_JIMM && operand->size == 32)
{
if (proc != ud_insn_off (&ud_obj) + ud_insn_len (&ud_obj) +
operand->lval.sdword)
return NULL;
break;
}
}
else if (insn == UD_Ibtr && ud_obj.pfx_lock)
{
/* for Windows 8 */
const ud_operand_t * operand = ud_insn_opr (&ud_obj, 0);
if (operand->type == UD_OP_MEM && operand->base == UD_R_RIP &&
operand->index == UD_NONE && operand->scale == 0 &&
operand->offset == 32 && critsec == ud_insn_off (&ud_obj) +
ud_insn_len (&ud_obj) + operand->lval.sdword -
offsetof (RTL_CRITICAL_SECTION, LockCount))
break;
}
}
LPVOID RtlpCurDirRef = NULL;
/* probably the next instruction is the mov qword ptr */
while (ud_disassemble (&ud_obj))
{
if (ud_insn_mnemonic (&ud_obj) == UD_Imov)
{
const ud_operand_t * operand = ud_insn_opr (&ud_obj, 1);
if (operand->type == UD_OP_MEM && operand->base == UD_R_RIP &&
operand->index == UD_NONE && operand->scale == 0 &&
operand->offset == 32 && operand->size == 64)
{
RtlpCurDirRef = ud_insn_off (&ud_obj) + ud_insn_len (&ud_obj) +
operand->lval.sdword;
break;
}
}
}
printf("%p -> %p\n", ud_insn_off (&ud_obj), RtlpCurDirRef);
return RtlpCurDirRef;
}