If you don't have the GetProcAddress()
function, you have to iterate entire export table, here a example how i made it in the past...
template<typename INTH_T>
NTSTATUS GetRemoteProcAddressArch(HANDLE hProcess, HMODULE ModuleHandle, LPCSTR RoutineName, PULONG_PTR hRoutine)
{
HMODULE hModule = GetModuleHandleA("Ntdll.dll");
if(!hModule)
hModule = LoadLibraryA("Ntdll.dll");
NTSTATUS Result = STATUS_UNSUCCESSFUL;
if(hModule)
{
NTSTATUS(WINAPI*ZwReadVirtualMemory)
(
HANDLE ProcessHandle,
PVOID BaseAddress,
PVOID Buffer,
SIZE_T NumberOfBytesToRead,
PSIZE_T NumberOfBytesReaded
) = reinterpret_cast<NTSTATUS(WINAPI*)(HANDLE, PVOID, PVOID, SIZE_T, PSIZE_T)>(GetProcAddress(hModule, "ZwReadVirtualMemory"));
if(ZwReadVirtualMemory)
{
const size_t rLength = strlen(RoutineName);
char* rNameSz = new char[rLength + 1] { 0 };
if(ModuleHandle)
{
PIMAGE_DOS_HEADER iDos = reinterpret_cast<PIMAGE_DOS_HEADER>(ModuleHandle);
SIZE_T BytesReaded = NULL;
LONG PEHeaderVirtualAddress = NULL; // same both x86 and x64, shuldn't be problem...
NTSTATUS rResult = ZwReadVirtualMemory(hProcess, reinterpret_cast<PVOID>(&iDos->e_lfanew), &PEHeaderVirtualAddress, sizeof(LONG), &BytesReaded);
if(rResult == STATUS_SUCCESS)
{
INTH_T* PEHeader = reinterpret_cast<INTH_T*>(PEHeaderVirtualAddress + reinterpret_cast<ULONG_PTR>(ModuleHandle));
ULONG ImageExportDirectoryVirtualAddress = NULL; // same both x86 and x64
BytesReaded = NULL;
rResult = ZwReadVirtualMemory(hProcess, reinterpret_cast<PVOID>(&PEHeader->OptionalHeader.DataDirectory[0]), &ImageExportDirectoryVirtualAddress, sizeof(ULONG), &BytesReaded);
if(rResult == STATUS_SUCCESS)
{
PIMAGE_EXPORT_DIRECTORY nED = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(reinterpret_cast<ULONG_PTR>(ModuleHandle) + ImageExportDirectoryVirtualAddress);
IMAGE_EXPORT_DIRECTORY ed = { 0 };
BytesReaded = NULL;
if(ZwReadVirtualMemory(hProcess, reinterpret_cast<PVOID>(nED), &ed, sizeof(IMAGE_EXPORT_DIRECTORY), &BytesReaded) == STATUS_SUCCESS)
{
PDWORD rvaNames = reinterpret_cast<PDWORD>(reinterpret_cast<ULONG_PTR>(ModuleHandle) + static_cast<ULONG_PTR>(ed.AddressOfNames));
PDWORD rvaFunctions = reinterpret_cast<PDWORD>(reinterpret_cast<ULONG_PTR>(ModuleHandle) + static_cast<ULONG_PTR>(ed.AddressOfFunctions));
PWORD rvaOrdinals = reinterpret_cast<PWORD>(reinterpret_cast<ULONG_PTR>(ModuleHandle) + static_cast<ULONG_PTR>(ed.AddressOfNameOrdinals));
for(DWORD i = 0; i < ed.NumberOfFunctions; i++)
{
BytesReaded = NULL;
DWORD rva = NULL;
if(ZwReadVirtualMemory(hProcess, reinterpret_cast<PVOID>(&rvaNames[i]), &rva, sizeof(DWORD), &BytesReaded) == STATUS_SUCCESS)
{
BytesReaded = NULL;
if(ZwReadVirtualMemory(hProcess, reinterpret_cast<PVOID>(reinterpret_cast<ULONG_PTR>(ModuleHandle) + static_cast<ULONG_PTR>(rva)), reinterpret_cast<PVOID>(const_cast<LPSTR>(rNameSz)), rLength, &BytesReaded) == STATUS_SUCCESS)
{
if(!ncstrncmp(rNameSz, RoutineName, rLength))
{
BytesReaded = NULL;
WORD rvaOrdinal = NULL;
if(ZwReadVirtualMemory(hProcess, reinterpret_cast<PVOID>(&rvaOrdinals[i]), &rvaOrdinal, sizeof(WORD), &BytesReaded) == STATUS_SUCCESS)
{
BytesReaded = NULL;
rva = NULL;
if(ZwReadVirtualMemory(hProcess, reinterpret_cast<PVOID>(&rvaFunctions[rvaOrdinal]), &rva, sizeof(DWORD), &BytesReaded) == STATUS_SUCCESS)
{
*hRoutine = (static_cast<ULONG_PTR>(rva) + reinterpret_cast<ULONG_PTR>(ModuleHandle));
Result = STATUS_SUCCESS;
}
}
break;
}
}
}
}
}
}
}
}
delete[] rNameSz;
}
}
return Result;
}
usage example:
auto hProcess = OpenProcess( ... );
auto possibleHandle = RemoteHandleFrom(hProcess, "ntdll.dll"); // i owe you, but you can find it in the msdn, there's a lot examples...
ULONG_PTR addr = NULL;
GetRemoteProcAddressArch<IMAGE_NT_HEADERS64>(hProccess, possibleHandle, "ZwOpenProcess", &addr );