Skip to content

Instantly share code, notes, and snippets.

@EvanMcBroom
Last active September 1, 2024 05:17
Show Gist options
  • Save EvanMcBroom/9af396eb0624814365df96ed8c66b8b6 to your computer and use it in GitHub Desktop.
Save EvanMcBroom/9af396eb0624814365df96ed8c66b8b6 to your computer and use it in GitHub Desktop.
Example code that may be used in DllMain to unlock the loader lock.
// Copyright (C) 2023 Evan McBroom
// Originally authored October 19th, 2023.
//
// Geoff Chappell first documented the format of the loader lock cookie on November 26th, 2008.
// His work is applied here to unlock the loader lock without knowing the original cookie that
// LdrLockLoaderLock returned. This same example code may be safely used in DllMain to unlock
// the loader lock and execute code that would otherwise deadlock the loader.
// Sources:
// - https://www.geoffchappell.com/studies/windows/win32/ntdll/api/ldrapi/lockloaderlock.htm
// - https://www.geoffchappell.com/studies/windows/win32/ntdll/api/ldrapi/unlockloaderlock.htm
#include <Windows.h>
#include <iostream>
#include <winternl.h>
#include <winnt.h>
#include <string>
#include <utility>
template<typename Function>
inline auto LazyLoad(HMODULE library, const std::string& procName) {
return (library) ? reinterpret_cast<Function*>(GetProcAddress(library, procName.data())) : nullptr;
}
template<typename Function>
inline std::pair<HMODULE, Function*> LazyLoad(const std::wstring& libraryName, const std::string& procName) {
auto library{ LoadLibraryW(libraryName.data()) };
return { library, (library) ? reinterpret_cast<Function*>(GetProcAddress(library, procName.data())) : nullptr };
}
template<typename ReturnType, typename... ArgTypes>
inline auto LazyLoadWithType(HMODULE library, const std::string& procName) {
return LazyLoad<ReturnType(*)(ArgTypes...)>(library, procName);
};
template<typename ReturnType, typename... ArgTypes>
inline auto LazyLoadWithType(const std::wstring& libraryName, const std::string& procName) {
return LazyLoad<ReturnType(*)(ArgTypes...)>(libraryName, procName);
};
#define LAZY_LOAD_WSTRING(_) L#_
#define LAZY_LOAD_LIBRARY_AND_PROC(LIBRARY, PROC) \
HMODULE Lazy##LIBRARY; \
decltype(PROC)* Lazy##PROC; \
std::tie(Lazy##LIBRARY, Lazy##PROC) = LazyLoad<decltype(PROC)>(LAZY_LOAD_WSTRING(LIBRARY##.dll), #PROC);
#define LAZY_LOAD_NATIVE_PROC(PROC) \
auto Lazy##PROC{ LazyLoad<decltype(PROC)>(GetModuleHandleW(L"ntdll.dll"), #PROC) };
#define LAZY_LOAD_PROC(LIBRARY, PROC) \
auto Lazy##PROC{ LazyLoad<decltype(PROC)>(LIBRARY, #PROC) };
// Loader lock reference:
// https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices
NTSTATUS NTAPI LdrLockLoaderLock(IN ULONG Flags, OUT ULONG* Disposition OPTIONAL, OUT PVOID* Cookie);
NTSTATUS NTAPI LdrUnlockLoaderLock(IN ULONG Flags, IN OUT PVOID Cookie);
struct NT_TEB {
NT_TIB NtTib;
PVOID EnvironmentPointer;
CLIENT_ID ClientId;
};
namespace LoaderLock {
enum class Disposition : size_t {
Invalid,
LockAcquired,
LockNotAcquired
};
enum class Flags : size_t {
Default,
RaiseOnErrors,
TryOnly
};
union Cookie {
void* value;
struct {
size_t code : (sizeof(size_t) * 8) - 16;
size_t tid : 12;
size_t type : 4;
};
constexpr Cookie(size_t code, size_t tid) : code(code), tid(tid), type(TypeNormal()) {
};
constexpr Cookie(size_t tid) : Cookie(0, tid) {
};
constexpr Cookie() : Cookie(0, (size_t)(reinterpret_cast<NT_TEB*>(NtCurrentTeb())->ClientId.UniqueThread)) {
};
constexpr size_t TypeNormal() {
return 0;
}
};
// LdrUnlockLoaderLock completely ignores Cookie.code and only checks Cookie.tid
// That allows us to unlock the loader lock without knowing what Cookie.code is
bool Unlock(size_t tid) {
LAZY_LOAD_NATIVE_PROC(LdrUnlockLoaderLock);
return NT_SUCCESS(LazyLdrUnlockLoaderLock(static_cast<ULONG>(Flags::Default), Cookie(tid).value));
}
}
int main() {
LAZY_LOAD_NATIVE_PROC(LdrLockLoaderLock);
ULONG lockState{ 0 };
PVOID lockCookie;
if (NT_SUCCESS(LazyLdrLockLoaderLock(0, &lockState, &lockCookie))) {
std::cout << "The loader has been locked and we received the cookie: 0x" << std::hex << lockCookie << std::endl;
// Although the value of lockCookie is partially unknown,
// the unknown parts of it are fully ignored by LdrUnlockLoaderLock
// The only part that is checked is the part we know, the value of our current thread ID
// LoaderLock::Cookie constructs a value with only that data, which is fully usable
LoaderLock::Cookie cookie;
std::cout << "The cookie we're going to use to unlock the loader is: 0x" << std::hex << cookie.value << std::endl;
LAZY_LOAD_NATIVE_PROC(LdrUnlockLoaderLock);
if (NT_SUCCESS(LazyLdrUnlockLoaderLock(0, cookie.value))) {
std::cout << "No more loader lock!" << std::endl;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment