一个IPPack封包截获工具,书上讲的太简单,干脆心一横自己慢慢分析,在我看来这个程序还是有点复杂的,今天总算是了解了它的大致流程。。。
怕自己以后给忘了,所以写下来分析的过程同时也可以加深自己对这个程序的理解,肯定有很多错误的地方,大神勿喷。。。
此工具分两部分,一部分为exe,一部分为dll
第一部分动作有初始化全局变量、界面及其按钮的动作,注入以及卸载dll还有接收数据等工作
首先,要解决有全局dll发送的消息的接收问题,可以自定义消息映射,当dll发送某类型消息时触发什么事件
这是自定义的消息处理函数
// 处理DLL发送过来的HM_RECEIVE和HM_SEND消息
afx_msg long OnReceive(WPARAM wParam, LPARAM lParam);
afx_msg long OnSend(WPARAM wParam, LPARAM lParam);
他们添加在
//{{AFX_MSG(CSDlg)
//}}AFX_MSG
这中间,这是ms定义的宏注释,MFC中有一种特殊的注释,叫注释宏。注释宏一般由VC自动加入到你的代码中。它是为class wizard服务的,class wizard通过它来定位各种系统自动添加代码的添加位置。若要使用类向导添加成员变量和成员函数,则要保留注释宏;否则,必须手动添加。如果你把它删了,classwizad就不能自动生成代码了 。 你添加消息响应的时候是不是发现源代码里多了些代码??那些代码为什么会在那里出现?为什么不在别的文件里出现?就是因为那里有注释宏它要将代码生成在相应注释宏之间 。 (这个注释是让ClassWizard能够分辨出哪些代码是它生成的,哪些是你自己写的。你自己写的代码要在这个注释之外,这样ClassWizard再修改消息映射的时候就不会管你的代码了。新版本vc(vs)已经没有注释宏了。)
然后在消息映射表中添加dll发送消息过来时有什么函数进行处理
映射表象添加在
BEGIN_MESSAGE_MAP(CMainDialog, CDialog)
END_MESSAGE_MAP()之间
// 两个自定义消息
ON_MESSAGE(HM_RECEIVE, OnReceive)
ON_MESSAGE(HM_SEND, OnSend)
这样,当有dll消息过来时就会触发自定义的消息了,这样,之定义消息处理就解决了
下面就是这个程序真正的动作了。。。。
首先设置状态栏、初始化列表框以及注册热键
// 创建状态栏,设置它的属性(CStatusBarCtrl类封装了对状态栏控件的操作)
m_bar.Create(WS_CHILD|WS_VISIBLE|SBS_SIZEGRIP, CRect(0, 0, 0, 0), this, 101);
m_bar.SetBkColor(RGB(0xa6, 0xca, 0xf0)); // 背景色
int arWidth[] = { 250, -1 };
m_bar.SetParts(2, arWidth); // 分栏
m_bar.SetText(" Windows程序设计进阶之路!", 1, 0); // 第二个栏的文本
// 取得列表视图窗口的控制权,设置它的分栏
m_listData.SubclassWindow(::GetDlgItem(m_hWnd, IDC_LISTDATA));
m_listData.SetExtendedStyle(LVS_EX_FULLROWSELECT);
m_listData.InsertColumn(0, "编号", LVCFMT_LEFT, 38);
m_listData.InsertColumn(2, "类型", LVCFMT_LEFT, 80);
m_listData.InsertColumn(3, "数据", LVCFMT_LEFT, 180);
// 注册热键CTRL+F9。当用户按此热键时,主窗口显示或者隐藏
if(!::RegisterHotKey(m_hWnd, 11, MOD_CONTROL, VK_F9))
{
MessageBox("注册热键CTRL+F9失败!");
}
然后就开始初始化状态数据及更新界面
// 初始化状态数据
InitData();
// 更新用户界面
UIControl();
整个app的初始化就到此结束了。下面为各个按钮的动作函数
1.打开进程列表并选择一个进程进行远程dll注入
首先判断m_bOpen是否为真,如果是,则已经安装了钩子,要先卸载SetHook(FALSE);,然后初始化其状态InitData();
然后进行注入SetHook(TRUE, dlg.m_dwThreadId, m_hWnd),传入【用户选择的进程】的线程以及进程句柄
2.dll注入阶段
此函数为dll的导出函数,这里采用的动态加载方式
GetModuleHandle(szDll)--->>LoadLibrary(szDll)---->>PFNSETHOOK mSetHook = GetProcAddress(hModule, "SetHook")
使用sethook函数注入mSetHook(bInstall, dwThreadId, hWndCaller);
这时程序已经跳到dll里面去执行了,现在我们不分析dll,且看app里面各个控件的功能
3.接收dll发送的数据
接收目标程序接收或发送的封包并显示
dll只是发送触发消息让app去读取共享数据内存
OnReceive(WPARAM wParam, LPARAM lParam)
OnSend(WPARAM wParam, LPARAM lParam)
着这里检查用户是否暂停,然后显示获取的数据IsDlgButtonChecked()
这里只显示一部分,将完整的数据保存到共享数据段,当用户单击此项再读取并显示完整消息
显示发送的封包是同上
4.当用户单击列表框某项是触发OnClickListData()
显示完整封包信息
5.OnClear()清除列表框
6.OnCancel() OnClose()
7.OnPause() OnTopMost()
8.打开进程列表框时先遍历系统内进程并建立索引,用户点击某一项是返回此进程索引,并每隔一段时间刷新此列表
9.另外两个文件是共享数据段类的定义
第二部分为dll过程解析,上面分析到app执行到dll中的sethook函数,那么,就从这里下手吧
这个函数到底干了什么能让注入dll到目标进程呢?
首先函数获取目标进程的进程以及主线程句柄,并将主进程句柄保存到共享数据段中
然后函数在目标线程内安装一个钩子SetWindowsHookEx,回调函数为GetMsgProc,如果此时bInstall为false则卸载钩子UnhookWindowsHookEx
注意,此时程序任然是在app的进程地址空间,并没有进入目标进程
安装钩子后当目标进程调用WH_GETMESSAGE消息时就会触发钩子,windows此时将dll注入到目标进程并在目标进程中调用回调函数GetMsgProc
此时,目标进程就真正进入了我们的dll的代码上去了,也就是说dll获得了此进程的执行权限
但是,在回调函数中通常我们什么都不去做,仅仅将消息传递到钩子链中的下一个节点
问题是此时dll真的什么都没做吗?答案当然是否
回到dll被windows注入到目标进程空间的时候。。。
当windows检测到目标进程的特定消息被调用时,windows会将在这个消息上的所有钩子函数遍历一次,如果不调用CallNextHookEx这个消息就不会传递到真正响应这个消息的函数上去了
再回到这里,当windows遍历到我们的钩子上时,dll就会被windows强行加载到目标进程空间,在此时,dll完成初始化
这个时候
CULHook g_send("Ws2_32.dll", "send", (PROC)hook_send);
CULHook g_sendto("Ws2_32.dll", "sendto", (PROC)hook_sendto);
CULHook g_recv("Ws2_32.dll", "recv", (PROC)hook_recv);
CULHook g_recvfrom("Ws2_32.dll", "recvfrom", (PROC)hook_recvfrom);
这些类的实例被同时初始化,类初始化调用的是类的构造函数,这是我们自定义的
CULHook(LPSTR pszModName, LPSTR pszFuncName, PROC pfnHook)
// jmp eax == 0xFF, 0xE0
// 生成新的执行代码
BYTE btNewBytes[8] = { 0xB8, 0x00, 0x00, 0x40, 0x00, 0xFF, 0xE0, 0x00 };
memcpy(m_btNewBytes, btNewBytes, 8);
*(DWORD *)(m_btNewBytes + 1) = (DWORD)pfnHook;
// 加载指定模块,取得API函数地址
m_hModule = ::LoadLibrary(pszModName);
if(m_hModule == NULL)
{
m_pfnOrig = NULL;
return;
}
m_pfnOrig = ::GetProcAddress(m_hModule, pszFuncName); // 修改原API函数执行代码的前8个字节,使它跳向我们的函数
if(m_pfnOrig != NULL)
{
DWORD dwOldProtect;
MEMORY_BASIC_INFORMATION mbi;
::VirtualQuery( m_pfnOrig, &mbi, sizeof(mbi) );
::VirtualProtect(m_pfnOrig, 8, PAGE_READWRITE, &dwOldProtect);
// 保存原来的执行代码
memcpy(m_btOldBytes, m_pfnOrig, 8);
// 写入新的执行代码
::WriteProcessMemory(::GetCurrentProcess(), (void *)m_pfnOrig,
m_btNewBytes, sizeof(DWORD)*2, NULL);
::VirtualProtect(m_pfnOrig, 8, mbi.Protect, 0);
}
。。。。函数是通过字节码覆盖API首地址为内联汇编代码,直接跳到我们自己的函数上去了,这是我们在不同的函数里面就可以做不同的事了。。。。
函数传入要进行覆盖的函数以及我们自定义的函数地址
下面是目标进程调用这6个函数时跳转到的函数的定义
int WINAPI hook_send(SOCKET s, const char FAR *buf, int len, int flags)
int WINAPI hook_sendto(SOCKET s, const char* buf,int len, int flags, const struct sockaddr* to, int tolen)
int WINAPI hook_recv(SOCKET s, char FAR *buf, int len, int flags)
int WINAPI hook_recvfrom(SOCKET s, char* buf, int len, int flags, struct sockaddr* from, int* fromlen)
CULHook g_WSARecv("Ws2_32.dll", "WSARecv", (PROC)hook_WSARecv);
CULHook g_WSARecvFrom("Ws2_32.dll", "WSARecvFrom", (PROC)hook_WSARecvFrom);
int WINAPI hook_WSARecv(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd,
LPDWORD lpFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
)
int WINAPI hook_WSARecvFrom(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd,
LPDWORD lpFlags,
struct sockaddr FAR *lpFrom,
LPINT lpFromlen,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
)
。。。
似乎完了。。。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
上传的附件: