利用 API 实现数据压缩和解压缩

背景

对于Windows上的数据压缩和解压缩的实现,最方便的就是直接调用Win32 API函数。Widnows系统的ntdll.dll专门提供了RtlCompressBuffer函数和RtlDecompressBuffer函数来负责对数据压缩和解压缩操作,这两个函数并未公开,需要通过在ntdll.dll中动态调用。

函数介绍

RtlGetCompressionWorkSpaceSize

// 确定缓冲区大小
// 返回STATUS_SUCCESS,则表示成功;否则,失败
    NTSTATUS RtlGetCompressionWorkSpaceSize(
      _In_  USHORT CompressionFormatAndEngine,		// 位掩码指定压缩格式和引擎类型
      _Out_ PULONG CompressBufferWorkSpaceSize,		// 接收压缩缓冲区所需的大小
      _Out_ PULONG CompressFragmentWorkSpaceSize	// 接收将压缩缓冲区解压缩为片段所需的大小
    );

RtlCompressBuffer

// 压缩一个缓冲区
// 返回STATUS_SUCCESS,则表示成功;否则,失败。
    NTSTATUS RtlCompressBuffer(
      _In_  USHORT CompressionFormatAndEngine,		// 指定压缩格式和引擎类型的位掩码
      _In_  PUCHAR UncompressedBuffer,				// 指向要压缩的数据缓冲区的指针
      _In_  ULONG  UncompressedBufferSize,			// UncompressedBuffer 缓冲区的大小
      _Out_ PUCHAR CompressedBuffer,				// 指向压缩之后的数据缓存的缓冲区的指针,用于接收压缩数据
      _In_  ULONG  CompressedBufferSize,			// CompressedBuffer缓冲区的大小
      _In_  ULONG  UncompressedChunkSize,			// 压缩UncompressedBuffer缓冲区时使用的块大小(512、1024、2048或4096)
      _Out_ PULONG FinalCompressedSize,				// 指向调用方分配变量的指针,该变量接收存储在CompressedBuffer中的压缩数据的大小
      _In_  PVOID  WorkSpace						// 指向压缩期间RtlCompressBuffer函数使用的调用方分配的工作空间缓冲区的指针
    );

RtlDecompressBuffer

// 解压缩整个压缩缓冲区。
// 返回STATUS_SUCCESS,则表示成功;否则,失败。
    NTSTATUS RtlDecompressBuffer(
      _In_  USHORT CompressionFormat,			// 指定压缩缓冲区压缩格式的位掩码
      _Out_ PUCHAR UncompressedBuffer,			// 指向存储解压缩数据的缓冲区的指针,该缓冲区从CompressedBuffer接收解压缩的数据
      _In_  ULONG  UncompressedBufferSize,		// UncompressedBuffer缓冲区的大小
      _In_  PUCHAR CompressedBuffer,			// 指向包含要解压缩的数据的缓冲区的指针
      _In_  ULONG  CompressedBufferSize,		// CompressedBuffer缓冲区的大小
      _Out_ PULONG FinalUncompressedSize		// 指向解压之后得到的数据大小的指针,该变量接收UncompressedBuffer中存储的解压缩数据的大小
    );

实现过程

数据压缩主要是通过调用RtlCompressBuffer函数来实现的,那么,具体的数据压缩实现流程如下所示。

首先,先调用LoadLibrary函数加载ntdll.dll,并获取ntdll.dll加载模块的句柄。再调用GetProcAddress函数来获取RtlGetCompressionWorkSpaceSize函数以及RtlCompressBuffer函数。

然后,直接调用RtlGetCompressionWorkSpaceSize函数来获取RtlCompressBuffer函数工作空间缓冲区的大小。其中,压缩格式和引擎类型设置为COMPRESSION_FORMAT_LZNT1和COMPRESSION_ENGINE_STANDARD。然后,根据工作空间缓冲区大小申请一个工作空间缓冲区给压缩数据使用。

最后,调用RtlCompressBuffer函数来压缩数据。数据压缩缓冲区的大小为4096字节,在成功压缩数据之后,便获取实际的压缩数据大小。此时需要将实际压缩数据大小和数据压缩缓冲区大小进行比较,如果数据压缩缓冲区太小,则需要释放原来的缓冲区,重新按照实际压缩数据的大小来申请一个新的数据压缩缓冲区,并且重新压缩数据。这样,才能获取所有的压缩数据。

那么,使用RtlCompressBuffer函数压缩数据的具体实现代码如下所示。

    // 数据压缩
    BOOL CompressData(BYTE *pUncompressData, DWORD dwUncompressDataLength, BYTE **ppCompressData, DWORD *pdwCompressDataLength)
    {
     
        BOOL bRet = FALSE;
        NTSTATUS status = 0;
        HMODULE hModule = NULL;
        typedef_RtlGetCompressionWorkSpaceSize RtlGetCompressionWorkSpaceSize  = NULL;
        typedef_RtlCompressBuffer RtlCompressBuffer = NULL;
        DWORD dwWorkSpaceSize = 0, dwFragmentWorkSpaceSize = 0;
        BYTE *pWorkSpace = NULL;
        BYTE *pCompressData = NULL;
        DWORD dwCompressDataLength = 4096;
        DWORD dwFinalCompressSize = 0;
        do
        {
     
            // 加载 ntdll.dll 
            hModule = ::LoadLibrary("ntdll.dll");
            if (NULL == hModule)
            {
     
                ShowError("LoadLibrary");
                break;
            }
            // 获取 RtlGetCompressionWorkSpaceSize 函数地址
            RtlGetCompressionWorkSpaceSize = (typedef_RtlGetCompressionWorkSpaceSize)::GetProcAddress(hModule, "RtlGetCompressionWorkSpaceSize");
            if (NULL == RtlGetCompressionWorkSpaceSize)
            {
     
                ShowError("GetProcAddress");
                break;
            }
            // 获取 RtlCompressBuffer 函数地址
            RtlCompressBuffer = (typedef_RtlCompressBuffer)::GetProcAddress(hModule, "RtlCompressBuffer");
            if (NULL == RtlCompressBuffer)
            {
     
                ShowError("GetProcAddress");
                break;
            }
            // 获取WorkSpqce大小
            status = RtlGetCompressionWorkSpaceSize(COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_STANDARD, &dwWorkSpaceSize, &dwFragmentWorkSpaceSize);
            if (0 != status)
            {
     
                ShowError("RtlGetCompressionWorkSpaceSize");
                break;
            }
            // 申请动态内存
            pWorkSpace = new BYTE[dwWorkSpaceSize];
            if (NULL == pWorkSpace)
            {
     
                ShowError("new");
                break;
            }
            ::RtlZeroMemory(pWorkSpace, dwWorkSpaceSize);
            while (TRUE)
            {
     
                // 申请动态内存
                pCompressData = new BYTE[dwCompressDataLength];
                if (NULL == pCompressData)
                {
     
                    ShowError("new");
                    break;
                }
                ::RtlZeroMemory(pCompressData, dwCompressDataLength);
                // 调用RtlCompressBuffer压缩数据
                RtlCompressBuffer(COMPRESSION_FORMAT_LZNT1, pUncompressData, dwUncompressDataLength, pCompressData, dwCompressDataLength, 4096, &dwFinalCompressSize, (PVOID)pWorkSpace);
                if (dwCompressDataLength < dwFinalCompressSize)
                {
     
                    // 释放内存
                    if (pCompressData)
                    {
     
                        delete[]pCompressData;
                        pCompressData = NULL;
                    }
                    dwCompressDataLength = dwFinalCompressSize;
                }
                else
                {
     
                    break;
                }
            }
            // 返回
            *ppCompressData = pCompressData;
            *pdwCompressDataLength = dwFinalCompressSize;
            bRet = TRUE;
        } while(FALSE);
        // 释放
        if (pWorkSpace)
        {
     
            delete[]pWorkSpace;
            pWorkSpace = NULL;
        }
        if (hModule)
        {
     
            ::FreeLibrary(hModule);
        }
        return bRet;
    }

数据解压缩主要是通过调用RtlDecompressBuffer函数来实现的,相比于数据压缩,数据解压缩实现起来更为简单。那么,具体的数据解压缩实现流程如下所示。

首先,同样是先调用LoadLibrary函数加载ntdll.dll,并获取ntdll.dll加载模块的句柄。再调用GetProcAddress函数RtlDecompressBuffer函数。不需要获取RtlGetCompressionWorkSpaceSize函数的地址,因为数据解压缩操作不需要确定压缩工作空间缓冲区大小

然后,开始调用RtlDecompressBuffer函数来解压缩数据。其中,压缩格式和引擎类型必须设置为COMPRESSION_FORMAT_LZNT1。数据解压缩缓冲区的初始大小为4096字节,在成功解压数据之后,便获取实际的解压数据大小。此时需要将实际解压数据大小和数据解压缓冲区大小进行比较,如果数据解压缓冲区太小,则需要释放原来的缓冲区,重新按照实际解压数据的大小来申请一个新的数据解压缓冲区,并且重新解压缩数据。这样,才能获取所有的解压数据。

那么,使用RtlDecompressBuffer函数压缩数据的具体实现代码如下所示。

    // 数据解压缩
    BOOL UncompressData(BYTE *pCompressData, DWORD dwCompressDataLength, BYTE **ppUncompressData, DWORD *pdwUncompressDataLength)
    {
     
        BOOL bRet = FALSE;
        HMODULE hModule = NULL;
        typedef_RtlDecompressBuffer RtlDecompressBuffer = NULL;
        BYTE *pUncompressData = NULL;
        DWORD dwUncompressDataLength = 4096;
        DWORD dwFinalUncompressSize = 0;
        do
        {
     
            // 加载 ntdll.dll 
            hModule = ::LoadLibrary("ntdll.dll");
            if (NULL == hModule)
            {
     
                ShowError("LoadLibrary");
                break;
            }
            // 获取 RtlDecompressBuffer 函数地址
            RtlDecompressBuffer = (typedef_RtlDecompressBuffer)::GetProcAddress(hModule, "RtlDecompressBuffer");
            if (NULL == RtlDecompressBuffer)
            {
     
                ShowError("GetProcAddress");
                break;
            }
            while (TRUE)
            {
     
                // 申请动态内存
                pUncompressData = new BYTE[dwUncompressDataLength];
                if (NULL == pUncompressData)
                {
     
                    ShowError("new");
                    break;
                }
                ::RtlZeroMemory(pUncompressData, dwUncompressDataLength);
                // 调用RtlCompressBuffer压缩数据
                RtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, pUncompressData, dwUncompressDataLength, pCompressData, dwCompressDataLength, &dwFinalUncompressSize);
                if (dwUncompressDataLength < dwFinalUncompressSize)
                {
     
                    // 释放内存
                    if (pUncompressData)
                    {
     
                        delete[]pUncompressData;
                        pUncompressData = NULL;
                    }
                    dwUncompressDataLength = dwFinalUncompressSize;
                }
                else
                {
     
                    break;
                }
            }
            // 返回
            *ppUncompressData = pUncompressData;
            *pdwUncompressDataLength = dwFinalUncompressSize;
            bRet = TRUE;
        } while (FALSE);
        // 释放
        if (hModule)
        {
     
            ::FreeLibrary(hModule);
        }
        return bRet;
    }

测试

直接运行上述程序,对“DDDDDDDDDDGGGGGGGGGGGG”字符串进行压缩得到压缩数据,再对压缩数据进行解压缩得到解压数据。得到的压缩数据和解压数据如下图:
利用 API 实现数据压缩和解压缩_第1张图片

你可能感兴趣的:(加密解密)