看到了我的之一,我想大家会很清楚,因为这个方法的最大的BUG就是在回调函数之后来处理,如果前面已经处理了,
那么我们就无法处理了。而且又要修改系统DLL,比较不完美。我最近又发现了两种方法:
一、修改RegisterClassA:
相对前面的方法来说,这个方法很好用,因为,我们可以很容易把回掉函数改掉。类似如下代码:
//---------------------------------------------------------------------------
int CMuGame::InitRegClass()
{
//获取模块句柄:
HINSTANCE hModule=::GetModuleHandle("User32.dll");
//获取原始函数地址:
fpsRegisterClassA=(REGWINCLASS)::GetProcAddress(hModule,"RegisterClassA");
//获取当前内存块的保护类型:
MEMORY_BASIC_INFORMATION _base;
::VirtualQuery((LPVOID)((DWORD)hModule+0x1000),&_base,sizeof(MEMORY_BASIC_INFORMATION));
//处理为读写类型:
::VirtualProtect(_base.BaseAddress,_base.RegionSize,PAGE_READWRITE,&_base.Protect);
//获得导出表的地址和大小:
DWORD dwExportSize=NULL;
PDWORD pdwExportAddr=NULL;
appfun.Load(hModule);
appfun.GetExportTable(&dwExportSize,&pdwExportAddr);
//查找RegisterClassA函数地址:
for(DWORD i=0;i<dwExportSize;i++,pdwExportAddr++)
{
if((*pdwExportAddr+(DWORD)hModule)==(DWORD)fpsRegisterClassA)
{
//计算跳转地址:
DWORD dword=(DWORD)fpnRegisterClassA-(DWORD)fpsRegisterClassA;
//声明结构对象:
_ASM_JMP _jmp;
_jmp._01cmd =0xE9;
memcpy((PDWORD)_jmp._02dat,&dword,4);
memcpy((LPVOID)((DWORD)fpsRegisterClassA-5),&_jmp,5);
//修改导出表地址:
DWORD dwAddr=*pdwExportAddr-5;
memcpy(pdwExportAddr,&dwAddr,4);
break;
}
}
//处理完成后,恢复保护类型:
DWORD dwProctect=NULL;
::VirtualProtect(_base.BaseAddress,_base.RegionSize,_base.Protect,&dwProctect);
return NULL;
}
//----------------------------------------------------------------------------
当然,我们可以修改IAT表来实现,但是我们大家都明白,IAT表很不确定,有的程序还没有IAT表,我们必须找个通用
的方法,那就是直接把User32.dll的导出表修改掉,因此,我们让程序正常调用RegisterClassA这个之后,千万没想到的是,现
在的导处API函数的地址已经被修改,而在中途转到我们的DLL中,然后再回到真正的函数地址入口:
//-----------------------------------------------------------------------------
ATOM WINAPI fpnRegisterClassA(WNDCLASS *lpWndClass)
{
//替换"XX"窗口的回调函数:
if(lstrcmp(lpWndClass->lpszClassName,"XX")==0) //如果是我们要替换的窗口,就替换,不是就不替换
{
fpsWndProc=lpWndClass->lpfnWndProc;
lpWndClass->lpfnWndProc=fpnWndProc;
}
ATOM atom=fpsRegisterClassA(lpWndClass);
return atom;
}
LRESULT CALLBACK fpnWndProc(HWND hDlg,UINT message,WPARAM wParam,LPARAM lParam)
{
switch(message)
{
case WM_INITDIALOG:
{
//保存窗口句柄:
theApp.game.hWnd=hDlg;
//修改标题,方便我们调用:
::SetWindowText(hDlg,_T("[PhantomNet] XX")); //我们把标题修改掉,方便通信,可以不修改的。
break;
}
case WM_CMD: //自定义命令:
{
if((DWORD)wParam==0x01){PostQuitMessage(0);} //收到命令,我们直接用API让程序结束。
if((DWORD)wParam==0x02){} //……什么操作都可以
break;
}
default:{break;}
}
//转到游戏的回调函数:
return fpsWndProc(hDlg,message,wParam,lParam);
}
//----------------------------------------------------------------------------------------
上面的代码就是实现修改RegisterClassA注册类,来转到我们的回调函数的。我们的回调函数,是在真正的回调函数
之前就被执行的,我们可以截获部分消息,让真正的回调函数收不到,因为,我们事先已经替它处理了事件。这样就避免了部
分消息收不到,但是这样的方法,是在程序没有启动,而随程序启动而加载……,我们就没办法对现有的进程进行操作。
二、直接替换回调函数:
怎么能直接替换呢?很多朋友都会想改这个改那个,实际KS给我们4个函数来让我们做这个工作。但是局限在同一进程,
//----------------------------------------------
GetClassLong(HWND hWnd,int Index);
GetWindowLong(HWND hWnd,int Index);
SetClassLong(HWND hWnd,int nIndex,LONG dwNewLong);
SetWindowLong(HWND hWnd,int nIndex,LONG dwNewLong);
//----------------------------------------------
有必要对这两类函数做说明,GetClassLong和SetClassLong在RegisterClassA这个函数运行时才起作用,而GetWindowLong
和SetWindowLong在CreateWindowA后才有作用。我们完全可以不用CreateRemoteThread这个来触发,我们的程序。我们在替换回调
函数时可以在其他进程对目标进程处理,我们只需要把程序写入到目标进程,我们没必要专门去创建一条线程来加载DLL,我们直接
把它写进去后,再让某个消息来触发加载。那样就会隐蔽点,这里简要说一下,为了安全期间不贴代码了。
//--------------------------------------------------
int CMgMuAssistantApp::LoadMain()
{
//初始化SOCKET:
if (!AfxSocketInit())
{
AfxMessageBox("Error InitSocket!");
}
//查找窗口是否存在:
pWnd=CWnd::FindWindow(NULL,"XX");
if(pWnd!=NULL)
{
//由于窗口有可能在创建前被找到,所以我们完善期间不用GetWindowLong,
//保存原始窗口回调函数:
fpsWndProc=(WNDPROC)::GetClassLong(pWnd->m_hWnd,GCL_WNDPROC);
//由于SetClassLong只在注册类时起作用,我们必须用SetWindowLong;
//修改回调函数:
::SetWindowLong(pWnd->m_hWnd,GWL_WNDPROC,(LONG)fpnWndProc);
//修改Socket函数:
hooksocket.InitHookApi(::GetModuleHandle(NULL),0x00376000,0x0568);
//装载OpenGL:
PDWORD pdwAddr=(PDWORD)((DWORD)::GetModuleHandle(NULL)+0x00375A00);
*pdwAddr=(DWORD)fpnRender;
pdwAddr =(PDWORD)((DWORD)::GetModuleHandle(NULL)+0x002B5E69);
*pdwAddr=(DWORD)((DWORD)::GetModuleHandle(NULL)+0x00375A00);
}
return 0;
}
//---------------------------------------------------
上面的代码是我针对某个游戏做的,由于游戏是基于OpenGL的,所以我就把OpenGL的Render修改下来,来处理我的代码
比如,我们要用到程序内部函数:
//-----------------------------------------------------------
//原程序是如下的调用方法:
00664B75 |. 50 push eax
00664B76 |. 8D55 8C lea edx, dword ptr [ebp-74]
00664B79 |. 52 push edx
00664B7A |. A1 181E9E07 mov eax, dword ptr [79E1E18]
00664B7F |. 83C0 14 add eax, 14
00664B82 |. 50 push eax
00664B83 |. 8B0D 141E9E07 mov ecx, dword ptr [79E1E14]
00664B89 |. 83C1 23 add ecx, 23
00664B8C |. 51 push ecx
00664B8D |. E8 8E7CF2FF call 0058C820
//我把它做成C函数调用,这样我们就可以直接用这个函数来调用程序内部函数了:
int CMgMuAssistantApp::SetMainText(int x,int y,LPSTR str,int unk1,int unk2,int unk3)
{
ShowText=(MAINTEXT)0x0058C820;
ShowText(x,y,str,unk1,unk2,unk3);
return 0;
}
//---------------------------------------------------------------------
下面是我的回调函数,我们发现我们再发送命令给目标程序时,目标程序就乖乖的按我们说的做,让它放那,它就放那,让
它返回什么,它就乖乖返回什么,我们可以设置更多的命令来对程序操作:
//--------------------------------------------------------------------
LRESULT CALLBACK fpnWndProc(HWND hDlg,UINT message,WPARAM wParam,LPARAM lParam)
{
//保存窗口句柄:
theApp.game.hWnd=hDlg;
switch(message)
{
case WM_INITDIALOG:{break;}
case WM_PAINT:{break;}
case WM_CMD:
{
if((DWORD)wParam==0x01){PostQuitMessage(0);}
//返回的是主程序句柄:
if((DWORD)wParam==0x02){
//保存主程序句柄:
theApp.m_hWndMainApp=(HWND)lParam;
//返回游戏句柄:
::SendMessage(theApp.m_hWndMainApp,WM_CMD,0x02,(LPARAM)hDlg);
}
if((DWORD)wParam==0x08){
//连接主程序,这里我创建了SOCKET来网络传输数据:
theApp.m_uPortMainApp=(UINT)lParam;
pClient=new CClient;
pClient->Close();
pClient->Create();
if(lstrcmp(theApp.m_strAddrMainApp,"")==0)
{
//获取主机地址:
char hostname[MAX_PATH]={0};
::gethostname(hostname,MAX_PATH);
hostent *addr=::gethostbyname(hostname);
theApp.m_strAddrMainApp=inet_ntoa(*(struct in_addr *)addr->h_addr_list[0]);
}
if(pClient->Connect(theApp.m_strAddrMainApp,theApp.m_uPortMainApp)==FALSE)
{
::SendMessage(theApp.m_hWndMainApp,WM_CMD,0x08,(LPARAM)-1);
}else{::SendMessage(theApp.m_hWndMainApp,WM_CMD,0x08,(LPARAM)1);}
}
if((DWORD)wParam==0x10){
//修改标题,方便我们调用:
::SetWindowText(hDlg,_T("[MG] MU"));
}
if((DWORD)wParam==0x11){
CRect rect;
//获取窗口矩形:
::GetWindowRect(hDlg,&rect);
//移动窗口:
::MoveWindow(hDlg,LOWORD(lParam),HIWORD(lParam),rect.Width(),rect.Height(),TRUE);
}
return TRUE; //直接返回,不让游戏回调处理,这个是我们的命令机密不能让程序知道:
}
case WM_COPYDATA: //我们替我们的目标程序处理进程间通信消息,为了方便点:)
{
//wParam:窗口句柄
//lParam:数据
theApp.OnCopyData(CWnd::FromHandle((HWND)wParam),(COPYDATASTRUCT*)lParam); return TRUE;
}
default:{break;}
}
//转到游戏的回调函数:
return fpsWndProc(hDlg,message,wParam,lParam);
}
//-----------------------------------------------------------------------------------------------
实际这回是在上回的基础而来的,我们总结一下就知道,当我们完全操作了回调函数时,一个应用程序就几乎成了你
手中的蛋糕,想怎么样吃就怎么样吃:).KS在进程的通信和相互性上没做多少,我们很多时候都要用CreateRemoteThread这个
函数来做DLL的加载,实际不用它的方法还是有很多的,只是没有被人发现而已,同过前面的说明,我想大家都会明白了,怎么
样把一个程序完全操控,但是回过头来一想,如果我们对一个进程做了完全操作,我们可以再用这个进程操作其它的进程,那么
会是烦琐点,但是是件很让人恐怖的事。前面的这个回调函数处理的事情很少,因为,我没把它做的有多强,我不想把它做多强
实际,当一个进程的回调函数交给你来处理,那么我们就可以做很多我们想做的事。当我们将DLL放入某个进程时,我们就可以
调用它的任意一个函数,来为我们工作,我们只需要做影射,把我们要调用的函数和地址影射到我们的DLL中,然后再加工~~~~
:)^^^^
-By EasyStudy For PhantomNet
[课程]FART 脱壳王!加量不加价!FART作者讲授!