Использование WinAPI для получения указателя класса?

Прежде чем кто-либо спросит, здесь нет злого умысла. Этот проект предназначен исключительно для образовательных и личных целей и, в лучшем случае, предназначен для того, чтобы стать "чит-движком" или возможным будущим античит-механизмом. Нет никакого намерения использовать это каким-либо злонамеренным способом.

У меня есть решение со следующими 3 проектами:

  • 32-битное приложение MFC, которое позволяет пользователю выбрать процесс для внедрения
  • 32-битная библиотека Win32 DLL для внедрения в целевой процесс с помощью метода VirtualAlloc + WriteProcessMemory + CreateRemoteThread + LoadLibrary
  • 32-битное консольное Win32-приложение для тестирования при локальном внедрении

В DLL я создал следующий набор функций:

////////////////
// Deceiver.h //
////////////////
#ifdef DECEIVED_EXPORTS
#   define DECEIVED_API __declspec(dllexport)
#else
#   define DECEIVED_API __declspec(dllimport)
#endif

volatile class DECEIVED_API CDeceived
{
public:
    CDeceived(void);
    virtual HANDLE WINAPI GetRunningProcess();
    virtual DWORD WINAPI GetRunningProcessId();
    virtual HANDLE WINAPI GetRunningThread();
    virtual DWORD WINAPI GetRunningThreadId();
    virtual LPVOID WINAPI Allocate(DWORD size);
    virtual BOOL WINAPI Deallocate(LPVOID address, DWORD size);
    virtual BOOL WINAPI Read(LPVOID address, LPVOID buffer, DWORD size);
    virtual BOOL WINAPI Write(LPVOID address, LPVOID buffer, DWORD size);
    virtual BOOL WINAPI ReadEx(HANDLE hProcess, LPVOID address, LPVOID buffer, DWORD size);
    virtual BOOL WINAPI WriteEx(HANDLE hProcess, LPVOID address, LPVOID buffer, DWORD size);

    WCHAR m_signature[10];
};

extern DECEIVED_API CDeceived* deceiver;
LPVOID DECEIVED_API WINAPI RemoteInitialize();


//////////////////
// Deceiver.cpp //
//////////////////
#include "stdafx.h"
#include "Deceived.h"

DECEIVED_API CDeceived* deceiver = NULL;

CDeceived::CDeceived()
{
    memcpy(&m_signature[0], L"Deceived?\0", 10);
}

HANDLE WINAPI CDeceived::GetRunningProcess()
{
    return GetCurrentProcess();
}

DWORD WINAPI CDeceived::GetRunningProcessId()
{
    return GetCurrentProcessId();
}

HANDLE WINAPI CDeceived::GetRunningThread()
{
    return GetCurrentThread();
}

DWORD WINAPI CDeceived::GetRunningThreadId()
{
    return GetCurrentThreadId();
}

LPVOID WINAPI CDeceived::Allocate(DWORD size)
{
    return VirtualAlloc(NULL, size, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE);
}

BOOL WINAPI CDeceived::Deallocate(LPVOID address, DWORD size)
{
    return VirtualFree(address, size, MEM_RELEASE);
}

BOOL WINAPI CDeceived::Read(LPVOID address, LPVOID buffer, DWORD size)
{
    DWORD dwBytesRead = 0;
    BOOL bRet = ReadProcessMemory(GetCurrentProcess(), address, buffer, size, &dwBytesRead);
    return bRet && (dwBytesRead > 0);
}

BOOL WINAPI CDeceived::Write(LPVOID address, LPVOID buffer, DWORD size)
{
    DWORD dwBytesWritten = 0;
    BOOL bRet = WriteProcessMemory(GetCurrentProcess(), address, buffer, size, &dwBytesWritten);
    return bRet && (dwBytesWritten > 0);
}

BOOL WINAPI CDeceived::ReadEx(HANDLE hProcess, LPVOID address, LPVOID buffer, DWORD size)
{
    DWORD dwBytesRead = 0;
    BOOL bRet = ReadProcessMemory(hProcess, address, buffer, size, &dwBytesRead);
    return bRet && (dwBytesRead > 0);
}

BOOL WINAPI CDeceived::WriteEx(HANDLE hProcess, LPVOID address, LPVOID buffer, DWORD size)
{
    DWORD dwBytesWritten = 0;
    BOOL bRet = WriteProcessMemory(hProcess, address, buffer, size, &dwBytesWritten);
    return bRet && (dwBytesWritten > 0);
}

LPVOID DECEIVED_API WINAPI RemoteInitialize()
{
    #ifdef _DEBUG
        MessageBoxA(NULL, "Please attach a debugger", "Deceived::RemoteInitialize", MB_ICONINFORMATION);
    #endif

    if(deceiver != NULL) delete deceiver;
    deceiver = new CDeceived();
    LPVOID lpReturn = deceiver->Allocate(sizeof(deceiver));

    if(lpReturn) {
        deceiver->Write(lpReturn, &deceiver, sizeof(deceiver));
        return lpReturn;
    }

    return NULL;
}


После того, как приложение MFC внедряет DLL в консольный тестовый проект...

Он вызывает RemoteInitialize() для инициализации удаленного класса и возврата адреса в пространстве виртуальной памяти вызывающей стороне, где он затем должен быть локализован в общем экземпляре класса CDeceived. Вот как я справляюсь с этим:

BOOL CDeceiverHook::Validate(LPVOID lpDeceivedAddress)
{
    CDeceived *deceiver = new CDeceived();
    BOOL bRet = deceiver->ReadEx(hProcess, lpDeceivedAddress, &m_deceived, sizeof(m_deceived));
    int cmp = _wcsicmp(m_deceived->m_signature, L"Deceived?");
    return bRet && (cmp == 0);
}


... но локализованный указатель класса, похоже, не указывает на удаленный, а вместо этого содержит несколько указателей NULL в своей виртуальной таблице, которые вызывают нарушения прав доступа, если вы пытаетесь выполнить любой из них.

Я должен, вероятно, отметить, что я успешно предоставил приложению MFC надлежащие права на отладку через OpenThreadToken, ImpersonateSelf и SetPrivilege. Должен ли я также каким-то образом заблокировать адрес класса в памяти? Ключевое слово volatile недостаточно или неправильно используется здесь? Что мне нужно сделать, чтобы получить тот же указатель, выделенный DLL?

Заранее спасибо! Голосование будет дано за любой действительный совет.

1 ответ

Решение

Каждый процесс имеет свой экземпляр библиотеки DLL в адресном пространстве и, возможно, по другому адресу, вы не можете просто заставить процесс загрузить библиотеку DLL и ожидать использования ее адресного пространства от другого процесса.

Есть несколько способов связать вашу внедренную DLL с процессом инжектора:

  • Общая память: вы можете разделить память между двумя или более процессами, используя MapViewOfFile, обратите внимание, что вы должны позаботиться о том, какие указатели хранятся в экземпляре класса, и что виртуальные члены не работают, поскольку виртуальная таблица принадлежит другому адресному пространству.

  • RPC: Вы можете использовать Win32 RPC для выполнения вызовов между процессом и совместного использования данных, лично я считаю, что это слишком сложно.

  • Named Pipes / Winsock: Мои любимые, простые в использовании, и вы можете делать (почти) все, что захотите.

  • Microsoft Message Queue (MSMQ): Не знаю много об этом, я думаю, что это тоже можно использовать.

  • Сообщения Win32: RegisterWindowMessage можно использовать для обработки сообщений Windows по всей системе и обмена данными, что полезно только для обмена небольшими значениями (два DWORD)

Есть и другие способы выполнения IPC, которые вы можете увидеть в MSDN: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365574(v=vs.85).aspx

Я настоятельно рекомендую вам использовать Named Pipes/Winsock, если это так, вы можете использовать Google Protobuf для простого обмена структурами данных между процессами.

Другие вопросы по тегам