银狐远程控制软件一共提供了4种远程屏幕模式,分别是差异屏幕、高速屏幕、娱乐屏幕和后台屏幕,如下图所示:
其中前三种屏幕都是获取被控端桌面的内容展示出来,只不过屏幕传输算法不一样,例如差异屏幕是传输远程屏幕图像中发生变化的部分,高速屏幕把屏幕数据压缩成有损JPEG,压缩比例可以调整,压缩后的数据相比较原始数据小很多,所以传输起来,速度非常快。
最后一种后台屏幕的逻辑,来源于坊间流传出来的HVNC项目,这个在看雪论坛有详细的帖子讨论,其思路是Windows系统默认有三个桌面:登录前的开机桌面、登录成功后的桌面、屏保桌面,程序在这三个桌面之外又使用Windows API CreateDesktop新建一个桌面,然后使用SwitchDesktop切换到新建的桌面,需要运行在这个桌面的程序使用CreateProcess这个API创建,这个API的最后倒数第二个参数LPSTARTUPINFOW中指定新创建的进程为新建的桌面:
typedef struct _STARTUPINFOW {
DWORD cb;
LPWSTR lpReserved;
// 用这个参数指定新进程的桌面
LPWSTR lpDesktop;
LPWSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFOW, *LPSTARTUPINFOW;
所以,后台桌面的效果时,在用户电脑上启动程序,操作一些文件,用户是无任何感知的,任务管理里面也看不到,所以这个思路被很多人利用,用于一些非法活动。
特别声明,本文仅技术方面纯粹的讨论,请勿利用本文介绍的技术,从事任何非法活动,否则后果自负。
但是,由于新建的桌面缺少正常一些必要的元素,并不是每个程序都能在该桌面启动,所以一般只用来启动一些浏览器或者执行一些自定义cmd命令,但是影响程度也不容小觑。
不过话又说回来,自从看雪论坛讨论过这个技术之后,很多杀毒软件可以检测用户电脑,看看是否有新的桌面新建,以防止这类攻击。
我的银狐代码每次打开差异屏幕功能时会就会崩溃,崩溃截图如下:
崩溃的堆栈被截断了,没有父堆栈信息,莫名其妙,一直想解决这个问题,但是没有思路。我试着启用Google Address Sanitizer(关于该工具如何在Visual Studio中排查问题可以参考这一篇),但是该工具也抓不到任何内存问题;也自己检查过代码,也没有线程安全问题和缓冲区溢出问题。
有一天,在路上行走的时候,突然想到,会不会是这里的汇编代码在新的平台上不兼容?于是立马回家,打开电脑,把汇编代码改写成如下C++代码,聪明的读者,看一下,这段汇编代码翻译成C++代码是什么呢?
void CDifScreenSpyDlg::DrawNextScreenDiff(PBYTE pDeCompressionData, unsigned long destLen)
{
// 根据鼠标是否移动和屏幕是否变化判断是否重绘鼠标,防止鼠标闪烁
bool bIsReDraw = false;
int nHeadLength = sizeof(POINT) + sizeof(BYTE); // 标识 + 光标位置 + 光标类型索引
LPVOID lpFirstScreen = m_lpScreenDIB;
LPVOID lpNextScreen = pDeCompressionData + nHeadLength;
DWORD dwBytes = destLen - nHeadLength;
POINT oldPoint;
memcpy(&oldPoint, &m_RemoteCursorPos, sizeof(POINT));
memcpy(&m_RemoteCursorPos, pDeCompressionData, sizeof(POINT));
// 鼠标移动了
if (memcmp(&oldPoint, &m_RemoteCursorPos, sizeof(POINT)) != 0)
bIsReDraw = true;
// 光标类型发生变化
int nOldCursorIndex = m_bCursorIndex;
LPBYTE lpNextCursorIndex = (LPBYTE)(pDeCompressionData + 8);
if (*lpNextCursorIndex != m_bCursorIndex)
{
m_bCursorIndex = *lpNextCursorIndex;
if (m_bIsTraceCursor)
bIsReDraw = true;
if (m_bIsCtrl && !m_bIsTraceCursor)
SetClassLong(m_hWnd, GCL_HCURSOR, (LONG)m_CursorInfo.getCursorHandle(m_bCursorIndex == (BYTE)-1 ? 1 : m_bCursorIndex));
}
// 屏幕是否变化
if (dwBytes > 0)
bIsReDraw = true;
//EnterCriticalSection(&m_cs);
///m_clcs.lock();
__asm
{
mov ebx, [dwBytes]
mov esi, [lpNextScreen]
jmp CopyEnd
CopyNextBlock :
mov edi, [lpFirstScreen]
lodsd // 把lpNextScreen的第一个双字节,放到eax中,就是DIB中改变区域的偏移
add edi, eax // lpFirstScreen偏移eax
lodsd // 把lpNextScreen的下一个双字节,放到eax中, 就是改变区域的大小
mov ecx, eax
sub ebx, 8 // ebx 减去 两个dword
sub ebx, ecx // ebx 减去DIB数据的大小
rep movsb
CopyEnd :
cmp ebx, 0 // 是否写入完毕
jnz CopyNextBlock
}
//LeaveCriticalSection(&m_cs);
//m_clcs.unlock();
if (bIsReDraw)
{
#if _DEBUG
PostMessage(WM_PAINT);
//DoPaint();
#else
//PostMessage(WM_PAINT);
DoPaint();
#endif
}
}
我改成如下C++代码:
void CDifScreenSpyDlg::DrawNextScreenDiff(PBYTE pDeCompressionData, unsigned long destLen)
{
// ...重复代码省略...
// 保存原始指针用于后续计算
BYTE* pNext = (BYTE*)lpNextScreen;
BYTE* pFirst = (BYTE*)lpFirstScreen;
DWORD remainingBytes = dwBytes;
// 循环处理所有数据块
while (remainingBytes > 0)
{
// 读取偏移值(4字节)
DWORD offset = *reinterpret_cast<DWORD*>(pNext);
pNext += 4;
// 读取数据块大小(4字节)
DWORD blockSize = *reinterpret_cast<DWORD*>(pNext);
pNext += 4;
// 计算剩余字节数:减去已读取的8字节(偏移+大小)和当前数据块大小
remainingBytes -= 8;
remainingBytes -= blockSize;
// 计算目标地址:基础地址 + 偏移
BYTE* pDest = pFirst + offset;
// 复制数据块
memcpy(pDest, pNext, blockSize);
// 移动源指针到下一个数据块
pNext += blockSize;
}
// ...重复代码省略...
}
重新编译程序,启动测试,差异屏幕可以正常运行了。
我猜想原作者最初想使用汇编代码是出于提高速度考虑,但是时过境迁,新的系统和位数,很多在32位系统运行的寄存器等信息和结构已经改变,所以,如果想要使用汇编,得重新适配。
至此,困扰了我的这个bug被解决了。
源码获取
如果对银狐(winos)有兴趣,可以通过下面的方式获取全套源码:
关注后回复【winos】即可获取源码
特别申明
本套源码仅用于个人学习使用,不得用于其他用途,请遵守国家相关法律,使用该源码产生的问题与本号无关。

