从零开始做一个远控

已完结,项目地址: 0xcc12138/Ez_Spy: 从零开始做一个远控工具,持续更新

想做病毒分析,但是无奈好像连远控开发都不太会,所以开一个专题,记录下远控开发的过程

主要参考文章: 从零开始做远控 簡介篇 做一个属于你自己的远控_zeronet qt-CSDN博客

这是16年的项目,有一些是无法正常直接编译的,我只参考这位大佬文章的内容,具体他发布的不在文章的源码我并未参照

第一节:用QT搭一个初始界面

有点拙劣,先随便看看

1733803323926

用UI搓的

1733803553940

右键可以弹出菜单:

1733803617171

第二节:搭建服务端网络通讯

要先添加一个Network的模块才可以是用网络库

1733818347175

因为想要移植UI,所以特地研究了一下如何移植别人的项目的UI文件到自己

移植Qt Ui文件

我在写QT项目的时候,想拿之前一个项目的UI文件直接使用,但是当我直接把其他项目的UI文件导入我的项目的时候,发现并不能直接使用

求助ChatGPT,发现QT已经为我们想到了这一切

在QT的bin目录下,有一个叫uic.exe的应用程序,这个负责将ui文件转为头文件.h,直接导入这个头文件,我们就可以使用原来的ui界面了

1733835008955

头文件长这样:

其实就是以代码的形式展现

1733835146569

其中:

1
2
3
namespace Ui {
class ServerClass: public Ui_ServerClass {};
} // namespace Ui

解释一下就是:

Ui::ServerClass代替Ui_ServerClass

然后可以自己新建一个头文件:

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
#include "ZeroServer_ui.h"
#include <QMainWindow>
#include <QMenu>
#include <QMessageBox>
#pragma execution_character_set("utf-8")
class ServerWindow : public QMainWindow {
Q_OBJECT

public:
explicit ServerWindow(QWidget* parent = nullptr)
: QMainWindow(parent) {
ui.setupUi(this); // 初始化界面
Initialize_Interface();
}

private:
Ui::ServerClass ui; // 包含由 .ui 文件生成的 UI 类


private:
void Initialize_Interface(); //初始化界面
void Set_Right_click_options();//给表单结构的行设置右键选项

private slots:
void showContextMenu(const QPoint& pos);
};

例如这样,加了我之前项目的一些函数,具体实现直接复制过来就行

完美运行

1733835593767

配合PacketSender的效果展示:

1733842224392

代码分析

ZeroServer含有两个主要成员:

1
2
TcpServer* mServer;         // Tcp服务端
QHash<int, ZeroClient*> mClients; // 用ID来索引相应的客户

先说说mServer服务端:

这里初始化了一个TcpServer实例

1733842770613

而这个TcpServer实例其实是来自QT的类QTcpServer,然后TcpServer这个类会去对 newConnection这个信号进行转发,这样ZeroServer就可以对新连接进行处理

1733842832113

同理,在客户端mClients这里

调用 login进行添加

1733890189288

溯源发现是当有新连接的时候,ZeroClient会发一个login的信号

1733890243500

总之,QT的信号机制就是要一直溯源回去查看,这存在于类包含类的情况

第三节:受控程序的网络搭建:

还是像上一节服务端一样,建一个TcpSocket类和ZeroClient类

TcpSocket的接口主要有:连接,断开,发送,接受这几个功能

ZeroClient类

不断死循环,接收从服务端传过来的命令,比如:屏幕监控,键盘监控等等

和原版的代码相比,我自己优化了对于系统的判断,毕竟微软官方文档说了GetVersionExA已经废弃

改成了这样:

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
std::string ZeroClient::getSystemModel()
{
std::string version = "Unknown";

// 获取 RtlGetVersion 函数指针
HMODULE hNtdll = GetModuleHandle(L"ntdll.dll");
RtlGetVersionPtr RtlGetVersion = (RtlGetVersionPtr)GetProcAddress(hNtdll, "RtlGetVersion");

if (RtlGetVersion) {
// 获取操作系统版本信息
RTL_OSVERSIONINFOEXW osvi;
ZeroMemory(&osvi, sizeof(osvi));
osvi.dwOSVersionInfoSize = sizeof(osvi);

NTSTATUS status = RtlGetVersion(&osvi);
if (status == 0) { // 状态成功
// 检查 Windows 11
if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= 22000) {
version = "Windows11OrGreater";

}
// 检查 Windows 10
else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0) {
version = "Windows10OrGreater";

}
// 检查 Windows 8.1
else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 3) {
version = "Windows8Point1OrGreater";

}
// 检查 Windows 8
else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 2) {
version = "Windows8OrGreater";

}
// 检查 Windows 7
else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1) {
version = "Windows7OrGreater";

}
// 检查 Windows Vista
else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0) {
version = "VistaOrGreater";

}
// 检查 Windows XP
else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) {
version = "XPOrGreater";

}
}
}

return version;

}

另外,如果受控主机是GBK编码,用户名可能会出错,所以要进行一个GBK转UTF-8的转换

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
std::string ZeroClient::getSystemModel()
{
std::string version = "Unknown";

// 获取 RtlGetVersion 函数指针
HMODULE hNtdll = GetModuleHandle(L"ntdll.dll");
RtlGetVersionPtr RtlGetVersion = (RtlGetVersionPtr)GetProcAddress(hNtdll, "RtlGetVersion");

if (RtlGetVersion) {
// 获取操作系统版本信息
RTL_OSVERSIONINFOEXW osvi;
ZeroMemory(&osvi, sizeof(osvi));
osvi.dwOSVersionInfoSize = sizeof(osvi);

NTSTATUS status = RtlGetVersion(&osvi);
if (status == 0) { // 状态成功
// 检查 Windows 11
if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= 22000) {
version = "Windows11OrGreater";

}
// 检查 Windows 10
else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0) {
version = "Windows10OrGreater";

}
// 检查 Windows 8.1
else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 3) {
version = "Windows8Point1OrGreater";

}
// 检查 Windows 8
else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 2) {
version = "Windows8OrGreater";

}
// 检查 Windows 7
else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1) {
version = "Windows7OrGreater";

}
// 检查 Windows Vista
else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0) {
version = "VistaOrGreater";

}
// 检查 Windows XP
else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) {
version = "XPOrGreater";

}
}
}

return version;

}

效果展示:

1733969372452

第四节:服务端对客户端的基本控制

这一节主要实现了弹窗,重启,下线的操作

具体博客有的东西不说了,主要讲讲lambda表达式

我发现lambda表达式在qt这种信号connect的方式非常好用

例如发送信号的时候,要传递参数,但是有一些信号,例如 QPushButton::clicked 信号的参数是固定的,但是又想要传进去参数,就可以用lambda表达式

例如这个信号 QTableWidget::customContextMenuRequested,对应的函数参数只能是const QPoint& pos

但是我又想使用一个非全局变量变量,就可以这样写

1
2
3
connect(ui.tableWidget, &QTableWidget::customContextMenuRequested,this, [this,contextMenu](const QPoint& pos) {
showContextMenu(pos, contextMenu); //传递QPoint和捕获的contextMenu
});

然后showContextMenu是我们的逻辑,但是lambda表达式的参数是符合这个信号的参数要求的

Ollvm配置

另外就是今天配置了一下visual studio的ollvm代码混淆,我是参考这篇文章的 构建含有ollvm功能的LLVM(clang-cl)供Microsoft Visual Studio 2022使用 - 哔哩哔哩

VS项目属性要这样设置

1734016495313

1734016528967

1734016548034

总之自己的项目实测的话,没有加ollvm混淆,微信传文件会被杀,但是如果加了ollvm混淆,可以成功让被控主机弹窗,重启,当然前提是人家双击运行…..

1734016927935

1734016974920

OLLVM遇到的坑:

换一个新项目,死活报错,但是从错误列表中又看不出哪里错了

1735976716312

这时候我们需要设置一下错误详细信息

在 Visual Studio 中,启用详细的编译输出:工具 > 选项 > 项目和解决方案 > 生成并运行 > 详细输出

1735976782523

现在终于知道原来是因为有一个 预编译头的原因……

1735976811065

然后把预编译头的选项关闭

1735976841885

这下成功了

1735976865548

第五节:屏幕监控

这一节,很淦,因为我按照文章上复现,发现我的图片是斜的??

后来才发现,原来bmp这里的iWidth,和iHeight必须要4的倍数,否则图片大小会有问题

BMP24位图因为字节不对齐而显示倾斜的问题_bmp显示在显示器上是斜体的有斜杠是什么原因-CSDN博客

靠,原因找半天

所以代码要这样改

1734355479931

还有就是Vector的问题,文章源代码是有点问题的

我本以为vector是动态容器,所以不需要担心越界的问题,死活没看出来源代码有问题,但是一直报错实在崩溃,最后原因找到了,就是死在这,这里的bmpData是一个vector

data()方法会返回存储的缓冲区,但是这个可不是动态扩容的,这就是一个固定大小的缓冲区,写超了在Debug模式下是要报错的,vector在push的时候,会检查是否越界,此时才会决定是否动态扩容

1734355736691

效果演试:

1734356315370

第六节:文件监控

效果展示

1734439690297

第七节:键盘监控和CMD命令行监控

效果展示

1734533406316

更新

v1.1修复服务端已知问题

原来的服务端在监控文件的时候有bug,源码中被监控的用户和本机用户的目录要完全一致,才能正常监控….这实在太那啥了,我也不知道为什么源码会出现这个低级错误……

总之这里已经修复了

修改在这两个函数

1735305115790

1735305103604

经过测试已经没问题了

1735305176437