MSN 双开工具源代码

网上偶然拾得本人于 2006 年所写的 **MSN Messenger** 双/多开工具的源代码。此即时通讯软件早于 2013 年便已停止服务,而今再看昔日所写代码不甚感慨,遂记录于此,以兹纪念。
c++#pragma comment(linker,"/subsystem:windows")
#pragma comment(linker,"/align:4096")
#define MSN_APP "\\MSN Messenger\\msnmsgr.exe"
#define TF_BIT    0x100
#include <windows.h>
#include <map>

using namespace std;

map <DWORD, HANDLE>    ThreadList;
HANDLE            hTargetProcess;
DWORD            dwMainThreadId;

DWORD GetRetAddress( LPVOID lpBsp )
{
    DWORD dwBytesRead, dwRetAddress;
    if ( ReadProcessMemory( hTargetProcess, lpBsp, &dwRetAddress, sizeof(DWORD), &dwBytesRead ) )
    {
        return(dwRetAddress);
    }
    return(NULL);
}


LPVOID g_lpGetLastErrorRet;
int SetHook( bool bSet )
{
    static bool    bInitial;
    static LPVOID    lpGetLastErrorRet;
    BYTE        bytOriginalOpCode = 0xC3;
    if ( !bInitial )
    {
        BYTE    lpCode[32];
        LPVOID    lpGetLastError;
        lpGetLastError = GetProcAddress( GetModuleHandle( "kernel32.dll" ), "GetLastError" );
        DWORD dwBytesRead;
/* ReadProcessMemory(hTargetProcess,lpGetLastError,&bytOriginalOpCode,sizeof(BYTE),&dwBytesRead); */
        ReadProcessMemory( hTargetProcess, lpGetLastError, &lpCode, sizeof(lpCode), &dwBytesRead );
        for ( int i = 0; i < 32; i++ )
        {
            if ( 0xC3 == lpCode[i] )
            {
                g_lpGetLastErrorRet = lpGetLastErrorRet = (LPVOID) ( ( (DWORD) lpGetLastError) + i);
                break;
            }
        }
        bInitial = true;
    }
    BYTE                bytBreakOpCode = 0xCC;
    DWORD                dwBytesReadWrite;
    MEMORY_BASIC_INFORMATION    mbi;
    VirtualQueryEx( hTargetProcess, lpGetLastErrorRet, &mbi, sizeof(mbi) );
    VirtualProtectEx( hTargetProcess, mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect );
    if ( bSet )
    {
        WriteProcessMemory( hTargetProcess, lpGetLastErrorRet, &bytBreakOpCode, sizeof(BYTE), &dwBytesReadWrite );
    }else{
        WriteProcessMemory( hTargetProcess, lpGetLastErrorRet, &bytOriginalOpCode, sizeof(BYTE), &dwBytesReadWrite );
    }
    FlushInstructionCache( hTargetProcess, lpGetLastErrorRet, sizeof(BYTE) );
    return(TRUE);
}


int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
/* int main() */
{
    char            lpApplication[MAX_PATH];
    STARTUPINFO        si;
    PROCESS_INFORMATION    pi;
    ZeroMemory( &si, sizeof(si) );
    ZeroMemory( &pi, sizeof(pi) );
    si.cb = sizeof(si);
    GetEnvironmentVariable( "ProgramFiles", lpApplication, MAX_PATH );
    strcat( lpApplication, MSN_APP );
    if ( !CreateProcess( lpApplication, NULL, NULL, NULL, FALSE, DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &si, &pi ) )
    {
        MessageBox( GetDesktopWindow(), "Fail to launch \"msnmsgr.exe\"!", "Error", MB_OK | MB_ICONSTOP );
        return(FALSE);
    }
    DEBUG_EVENT    de;
    BOOL        bContinue = TRUE, bInitial = FALSE;
    DWORD        dwContinueStatus;
    CONTEXT        context;
    ZeroMemory( &context, sizeof(context) );
    UINT uHookCount = 0;
    while ( bContinue )
    {
        bContinue        = WaitForDebugEvent( &de, INFINITE );
        dwContinueStatus    = DBG_CONTINUE;
        switch ( de.dwDebugEventCode )
        {
        case CREATE_THREAD_DEBUG_EVENT:
            ThreadList[de.dwThreadId] = de.u.CreateThread.hThread;
            break;
        case CREATE_PROCESS_DEBUG_EVENT:
            hTargetProcess            = de.u.CreateProcessInfo.hProcess;
            ThreadList[de.dwThreadId]    = de.u.CreateProcessInfo.hThread;
            dwMainThreadId            = de.dwThreadId;
            break;
        case EXIT_PROCESS_DEBUG_EVENT:
            bContinue = FALSE;
            break;
        case EXCEPTION_DEBUG_EVENT:
            switch ( de.u.Exception.ExceptionRecord.ExceptionCode )
            {
            case EXCEPTION_SINGLE_STEP:
                if ( uHookCount < 3 )
                {
                    context.ContextFlags = CONTEXT_FULL;
                    if ( GetThreadContext( ThreadList[de.dwThreadId], &context ) )
                    {
                        SetHook( TRUE );
                    }
                }
                break;
            case EXCEPTION_BREAKPOINT:
                dwContinueStatus = de.u.Exception.dwFirstChance ? DBG_CONTINUE : DBG_EXCEPTION_NOT_HANDLED;
                if ( !bInitial )
                {
                    SetHook( TRUE );
                    bInitial = TRUE;
                }
                context.ContextFlags = CONTEXT_FULL;
                if ( GetThreadContext( ThreadList[de.dwThreadId], &context ) )
                {
                    if ( (context.Eip - 1) == (DWORD) g_lpGetLastErrorRet )
                    {
/* printf("Return Address: 0x%08X\n",GetRetAddress((LPVOID)context.Esp)); */
                        if ( 0x00700000 > GetRetAddress( (LPVOID) context.Esp ) && de.dwThreadId == dwMainThreadId )
                        {
                            if ( ERROR_ALREADY_EXISTS == context.Eax )
                            {
                                context.Eax = 0;
                                uHookCount++;
                            }
                        }
                        SetHook( FALSE );
                        context.Eip--;
                        context.EFlags |= TF_BIT;
                        SetThreadContext( ThreadList[de.dwThreadId], &context );
                        dwContinueStatus = DBG_CONTINUE;
                    }
                }
                break;
            default: dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
            }
            break;
/* default: */
        }
        ContinueDebugEvent( de.dwProcessId, de.dwThreadId, dwContinueStatus );
    }
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );
    return(TRUE);
}

MSN Messenger 通过创建全局互斥量来保证系统中同时只能运行一个进程。伪代码如下:

c++hMutex = CreateMutex(NULL, FALSE, "MSNMessengerIsRunning");

if (GetLastError() == ERROR_ALREADY_EXISTS) 
{
     // MSN Messenger 已启动,本进程即将退出
     ExitProcess();
}

工具的破解原理是:通过 Hook GetLastError,修改该 API 的返回值,从而欺骗 MSN 进程,以绕过限制。