【虚表新年红包】MFCspy新年快乐!

没赚到钱,发个干货

新年,作为新客户,祝大家新春快乐:slightly_smiling_face:

虚表是多态的机制用到的,研究这个很有必要,不论是普通程序还是内核程序,破解软件,或者游戏逆向,都可以通过过滤虚表来做检测,看过《iOS应用逆向工程》里面有些工具就是利用这点

游戏里面可以实现一些寻路,使用技能,一系列的方法,破解软件更是一绝,软件被vm咯无从下口,老火吧,冒痘?

更可恶的是栈的破坏,调用回溯gg了

MFC程序的宏展开就不多说了,网上一大堆,实现不想看别人的,自己动手写一个,下个断,通过调用堆栈视图,可以找到AfxWndProc这样一个函数,判断了一个消息宏,这个宏的值是0x360,来判断是不是MFC程序,给程序发消息,值就是这个宏,就能直接判断是不是MFC程序

然后调用了一个函数,FromHandlePermanent,通过句柄可以获取到CWnd类,有这个类就什么都可以获取到,可以调用RuntimeClass得到类名,一些信息

反汇编的代码每个版本都有所不同动态编译的还是静态编译的,发布版本还是调试版本都有所不同,只能每个版本做一个case

然后找到GetMeassgeMap的偏移,这个可以每个编译的版本偏移不一样,消息映射表就获取到,这里参考了git上面的项目

这里可以对虚表做一个过滤,观察所有虚表里的函数参数,这里参考《游戏外挂开发艺术》的代码,有兴趣可以看下这本书

虚表就利用完了,下面是没有写完的代码,可以拿去研究,对逆向非常有帮助,祝在逆向的道路上越来越强,成为为数不多的技术牛人。

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"

#include <windows.h>
#include <stdio.h>

HHOOK g_hHook = NULL;
HINSTANCE g_hInstance = NULL;

//////////////////////////过滤虚表的代码/////////
#define JMP_TOTAL_NUM 40000
typedef struct _JMP_TABLE_ITEM
{
  UCHAR uPushEbx;     //push ebx 0x53 存真正的虚表地址
  UCHAR uPuhsad;      //保存堆栈
  UCHAR uPushfd;      //保存堆栈
  UCHAR uMovEax[5];   //mov eax, 0xffffffff(B8ffffffff)将偏移存放在eax
  UCHAR uPushEax;     //push eax eax虚表的编号   将eax保存在堆栈中
  UCHAR uJmp[5];      //jmp 0xXXXXXXXX(E9 XXXXXXXX) 统一跳转到我们指定的地址中
  UCHAR uSave;        //保留以后在用
}JMP_TABLE_ITEM, *PJMP_TABLE_ITEM;


void Monitor() {


}

/// 
/// 对象基地址,指向的就是一个虚表,所以把这个虚表改成伪造的
/// 过滤函数的地址
///
void fakeFun(DWORD dwObjectBaseAddr, DWORD *pdwMonitorFn)
{
  // 
  JMP_TABLE_ITEM *pJmpTable = NULL;
  DWORD *pdwFakeVTblItem = NULL;
 
  pdwFakeVTblItem = (DWORD*)VirtualAlloc(NULL, JMP_TOTAL_NUM *sizeof(DWORD), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  if (!pdwFakeVTblItem)
  {
    OutputDebugString("pdwFakeVTblItem is NULL");
    return;
  }
  ZeroMemory(pdwFakeVTblItem, JMP_TOTAL_NUM * sizeof(DWORD));

  pJmpTable = (JMP_TABLE_ITEM*)VirtualAlloc(NULL, JMP_TOTAL_NUM * sizeof(JMP_TABLE_ITEM),MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  if (!pJmpTable)
  {
    OutputDebugString("pJmpTable is NULL");
    return;
  }

  ZeroMemory(pJmpTable, JMP_TOTAL_NUM *sizeof(JMP_TABLE_ITEM));

  for (DWORD dwLoop = 0; dwLoop < JMP_TOTAL_NUM; dwLoop++)
  {
    
    pJmpTable->uPushEbx = 0x53;
    pJmpTable->uPuhsad = 0x60;
    pJmpTable->uPushfd = 0x9C;
    pJmpTable->uMovEax[0] = 0xB8;

    *((DWORD*)&(pJmpTable->uMovEax[1])) = dwLoop;  

    pJmpTable->uPushEax = 0x50;
    pJmpTable->uJmp[0] = 0xE9;


    *( (DWORD*) & (pJmpTable->uJmp[1]) ) = (DWORD)(pdwMonitorFn) - ( (DWORD)(pJmpTable) + FIELD_OFFSET(JMP_TABLE_ITEM, uSave) );


    *pdwFakeVTblItem = (DWORD)pJmpTable;
    pdwFakeVTblItem++;
    pJmpTable++;

  }


  *(PDWORD)dwObjectBaseAddr = (DWORD)pdwFakeVTblItem;

}
////////////////////////////////////////////////////下面是hook代码////////////
class CObject{};

struct CRuntimeClass 
{
    LPCSTR m_lpszClassName;
    int m_nObjectSize;
    UINT m_wSchema;
    CObject* (*m_pfnCreateObject)();
    CRuntimeClass *m_pBaseClass;
};


class CCmdTarget {
public:
  virtual AFX_MSGMAP* GetMessageMap() { return NULL; };
};//可以用这个来get网上的代码这样写的

typedef void (CCmdTarget::* AFX_PMSG)(void);
struct AFX_MSGMAP_ENTRY
{
  UINT nMessage;   // windows message
  UINT nCode;      // control code or WM_NOTIFY code
  UINT nID;        // control ID (or 0 for windows messages)
  UINT nLastID;    // used for entries specifying a range of control id's
  UINT_PTR nSig;       // signature type (action) or pointer to message #
  AFX_PMSG pfn;    // routine to call (or special value)
};
struct AFX_MSGMAP
{
  const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
  const AFX_MSGMAP_ENTRY* lpEntries;
};

class CWnd{
public:
    virtual CRuntimeClass* GetRuntimeClass() = 0;       
};       


typedef CWnd* (__stdcall *FromHandlePermanent)(HWND);  

/*
LRESULT CALLBACK
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
	// special message which identifies the window as using AfxWndProc
	if (nMsg == WM_QUERYAFXWNDPROC)
		return 1;

	// all other messages route through message map
	CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
	ASSERT(pWnd != NULL);
	ASSERT(pWnd==NULL || pWnd->m_hWnd == hWnd);
	if (pWnd == NULL || pWnd->m_hWnd != hWnd)
		return ::DefWindowProc(hWnd, nMsg, wParam, lParam);
	return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}
*/
LRESULT CALLBACK CallWndProc(int nCode,      // hook code
    WPARAM wParam,  // current-process flag  
    LPARAM lParam   // message data
){
    static WNDPROC s_WndProc = NULL;

    PCWPSTRUCT pInfo = (PCWPSTRUCT)lParam;

    if (pInfo->message == WM_USER+1)
    {
 
        HWND hWnd = (HWND)wParam;
        if (IsWindowUnicode(pInfo->hwnd))
        {
            //32位 64位都可以拿
            s_WndProc = (WNDPROC)GetWindowLongPtrW(pInfo->hwnd, GWLP_WNDPROC);
        }
        else
        {
            s_WndProc = (WNDPROC)GetWindowLongPtrA(pInfo->hwnd, GWLP_WNDPROC);
        }

        unsigned char* pCode = (unsigned char*)s_WndProc;

        if (*pCode == 0x55)
        {
            //找到call        第一个call是不是找到E8就可以了
            while (*pCode != 0xE8)  //位置找错的话有风险
            {
                pCode++;
            }
 
            FromHandlePermanent pfnFromHandlePermanent = (FromHandlePermanent)(pCode + *(DWORD*)(pCode + 1) + 5);

            CWnd *pWnd = pfnFromHandlePermanent(pInfo->hwnd);  
            

            CRuntimeClass *pRuntimeClass = pWnd->GetRuntimeClass();

            char bufff[255] = {};


            while (pRuntimeClass != NULL)
            {
              sprintf(bufff, "%s  0x%p  \n", pRuntimeClass->m_lpszClassName, pRuntimeClass->m_pfnCreateObject);
              OutputDebugString(bufff);
              if (!strcmp(pRuntimeClass->m_lpszClassName,"CWnd"))
              {
                ::MessageBox(NULL, pRuntimeClass->m_lpszClassName, NULL, NULL);
              }
              pRuntimeClass = pRuntimeClass->m_pBaseClass;
            }

            /*
	DWORD* pVFunc = *(DWORD**)pCWnd;
	while (*pVFunc)
	{
		g_SpyInfo.vTable[g_SpyInfo.n_numOfVFunc] = *pVFunc;
		g_SpyInfo.n_numOfVFunc++;
		pVFunc++;
	}


	// 5.获取消息映射表,先得到GetMessageMap在虚表中的位置(与版本,编译选项相关)
	CCmdTarget obj;
	CCmdTarget* pCmdTarget = &obj;
	//修改虚表位置,让GetMessageMap成为第一项
	*(int*)pCmdTarget = *(int*)pCWnd + 12 * sizeof(uintptr_t);
	AFX_MSGMAP* pMsgMap = pCmdTarget->GetMessageMap();
	AFX_MSGMAP_ENTRY* pEntry = pMsgMap->lpEntries;
	while (pEntry != NULL && pEntry->nSig != 0)
	{
		g_SpyInfo.g_MsgMap[g_SpyInfo.g_nMsgEntry] = *pEntry;

		pEntry++;
		g_SpyInfo.g_nMsgEntry++;
	}
            */

           
        }

        
    }



    return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

void SetHook(HWND hWnd)
{

    if (IsWindowUnicode(hWnd))
    {
        g_hHook = SetWindowsHookExW(WH_CALLWNDPROC, &CallWndProc, g_hInstance, 0);
    }
    else
    {
        g_hHook = SetWindowsHookExA(WH_CALLWNDPROC, &CallWndProc, g_hInstance, 0);
    }
    
}
void UnSetHook()
{
    UnhookWindowsHookEx(g_hHook);
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
    g_hInstance = hModule;
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}