miruos的文章

  • 数据压缩WIN32 API

    背景对于Windows上的数据压缩和解压缩的实现,最方便的就是直接调用Win32 API函数。Widnows系统的ntdll.dll专门提供了RtlCompressBuffer函数和RtlDecompressBuffer函数来负责对数据压缩和解压缩操作,这两个函数并未公开,需要通过在ntdll.dll中动态调用。
    正是由于这两个函数简单易用,所以常被病毒木马用来对数据进行压缩和解压缩。接下来,本文将介绍利用RtlCompressBuffer函数和RtlDecompressBuffer函数实现数据压缩和解压缩操作。
    函数介绍RtlGetCompressionWorkSpaceSize函数
    用于确定RtlCompressBuffer和RtlDecompressFragment函数的WorkSpace缓冲区的正确大小。
    NTSTATUS RtlGetCompressionWorkSpaceSize( _In_ USHORT CompressionFormatAndEngine, _Out_ PULONG CompressBufferWorkSpaceSize, _Out_ PULONG CompressFragmentWorkSpaceSize);
    参数
    CompressionFormatAndEngine[in]位掩码指定压缩格式和引擎类型。 该参数必须设置为以下按位或组合之一:COMPRESSION_FORMAT_LZNT1 |COMPRESSION_ENGINE_STANDARDCOMPRESSION_FORMAT_LZNT1 |COMPRESSION_ENGINE_MAXIMUM
    CompressBufferWorkSpaceSize[out]指向调用方分配缓冲区的指针,用于接收压缩缓冲区所需的大小(以字节为单位)。此值用于确定RtlCompressBuffer的WorkSpace缓冲区的正确大小。
    CompressFragmentWorkSpaceSize[out]一个指向调用者分配缓冲区的指针,用于接收将压缩缓冲区解压缩为片段所需的大小(以字节为单位)。此值用于确定RtlDecompressFragment的WorkSpace缓冲区的正确大小。请注意,RtlCompressFragment函数当前不存在。
    返回值
    返回STATUS_SUCCESS,则表示成功;否则,失败。

    RtlCompressBuffer函数
    压缩一个缓冲区,并且可以被文件系统驱动程序使用,以促进文件压缩的实现。
    NTSTATUS RtlCompressBuffer( _In_ USHORT CompressionFormatAndEngine, _In_ PUCHAR UncompressedBuffer, _In_ ULONG UncompressedBufferSize, _Out_ PUCHAR CompressedBuffer, _In_ ULONG CompressedBufferSize, _In_ ULONG UncompressedChunkSize, _Out_ PULONG FinalCompressedSize, _In_ PVOID WorkSpace);
    参数
    CompressionFormatAndEngine[in]指定压缩格式和引擎类型的位掩码。此参数必须设置为一种格式类型和一种引擎类型的有效按位或组合,相关值的含义如下。



    VALUE
    MEANING




    COMPRESSION_FORMAT_LZNT1
    LZ压缩算法


    COMPRESSION_FORMAT_XPRESS
    Xpress压缩算法


    COMPRESSION_FORMAT_XPRESS_HUFF
    Huffman压缩算法


    COMPRESSION_ENGINE_STANDARD
    标准压缩算法


    COMPRESSION_ENGINE_MAXIMUM
    最大程度压缩



    UncompressedBuffer[in]指向要压缩的数据缓冲区的指针。该参数是必需的,不能为NULL。UncompressedBufferSize[in]UncompressedBuffer缓冲区的大小(以字节为单位)。CompressedBuffer[out]指向压缩之后的数据缓存的缓冲区的指针,用于接收压缩数据。该参数是必需的,不能为NULL。CompressedBufferSize[in]CompressedBuffer缓冲区的大小(以字节为单位)。UncompressedChunkSize[in]压缩UncompressedBuffer缓冲区时使用的块大小。该参数必须是以下值之一:512、1024、2048或4096。操作系统使用4096,此参数的推荐值也是4096。FinalCompressedSize[out]指向调用方分配变量的指针,该变量接收存储在CompressedBuffer中的压缩数据的大小(以字节为单位)。该参数是必需的,不能为NULL。WorkSpace[in]指向压缩期间RtlCompressBuffer函数使用的调用方分配的工作空间缓冲区的指针。使用RtlGetCompressionWorkSpaceSize函数来确定正确的工作空间缓冲区大小。
    返回值
    返回STATUS_SUCCESS,则表示成功;否则,失败。

    RtlDecompressBuffer函数
    解压缩整个压缩缓冲区。
    NTSTATUS RtlDecompressBuffer( _In_ USHORT CompressionFormat, _Out_ PUCHAR UncompressedBuffer, _In_ ULONG UncompressedBufferSize, _In_ PUCHAR CompressedBuffer, _In_ ULONG CompressedBufferSize, _Out_ PULONG FinalUncompressedSize);
    CompressionFormat[in]指定压缩缓冲区压缩格式的位掩码。该参数必须设置为COMPRESSION_FORMAT_LZNT1。这个和其他相关压缩格式值的含义如下。



    VALUE
    MEANING




    COMPRESSION_FORMAT_LZNT1
    LZ解压缩算法


    COMPRESSION_FORMAT_XPRESS
    Xpress解压缩算法



    UncompressedBuffer[out]指向存储解压缩数据的缓冲区的指针,该缓冲区从CompressedBuffer接收解压缩的数据。该参数是必需的,不能为NULL。UncompressedBufferSize[in]UncompressedBuffer缓冲区的大小(以字节为单位)。CompressedBuffer[in]指向包含要解压缩的数据的缓冲区的指针。该参数是必需的,不能为NULL。CompressedBufferSize[in]CompressedBuffer缓冲区的大小(以字节为单位)。FinalUncompressedSize[out]指向解压之后得到的数据大小的指针,该变量接收UncompressedBuffer中存储的解压缩数据的大小(以字节为单位)。该参数是必需的,不能为NULL。
    返回值
    返回STATUS_SUCCESS,则表示成功;否则,失败。

    实现过程数据压缩主要是通过调用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”字符串进行压缩得到压缩数据,再对压缩数据进行解压缩得到解压数据。得到的压缩数据和解压数据如下图8-1所示。

    小结不能直接调用数据压缩函数RtlCompressBuffer和数据解压缩函数RtlDecompressBuffer,而是需要提前从ntdll.dll中通过GetProcAddress函数来动态获取。
    在调用RtlCompressBuffer函数进行压缩数据之前,一定要先调用RtlGetCompressionWorkSpaceSize函数来获取压缩数据的工作空间大小,以此来申请工作空间缓冲区。压缩数据的时候,可以先设置一个初始的压缩数据缓冲区来存储压缩数据;在压缩完毕之后,一定要判断该初始缓冲区的大小是否满足实际的压缩数据,若不满足,则重新申请缓冲区,重新压缩数据。
    在调用RtlDecompressBuffer函数解压缩数据的时候,压缩格式,必须为COMPRESSION_FORMAT_LZNT1;而且先设置一个初始的解压数据缓冲区来存储解压数据,在解压完毕后,再判断该初始缓冲区大小是否满足实际的解压数据,若不满足,则重新申请缓冲区,重新解压缩。
    参考参考自《Windows黑客编程技术详解》一书
    1  留言 2019-01-16 10:47:41
  • 使用VS2013实现创建快捷方式

    背景我们经常会遇到,程序安装完成后,会在桌面生成快捷方式图标。或者,当我们想在桌面就可以打开某个程序时,只要给程序创建相应的快捷方式图标发送到桌面就好。所以,我们对创建快捷方式这个功能并不陌生,反而还很熟悉。
    那么,这篇文档就讲解下使用VS2013去实现为指定程序创建快捷方式的功能。主要是使用Shell拓展编程的方式进行开发,把开发思路和实现过程写成文档的形式,分享给大家。
    函数介绍CoInitialize 函数
    用来告诉 Windows以单线程的方式创建com对象。应用程序调用com库函数(除CoGetMalloc和内存分配函数)之前必须初始化com库。
    函数声明
    HRESULT CoInitialize( _In_opt_ LPVOID pvReserved);
    参数

    参数被保留,且必须为NULL。
    返回值

    S_OK : 该线程中COM库初始化成功S_FALSE: 该线程中COM库已经被初始化 CoInitialize () 标明以单线程方式创建。
    注意

    CoInitialize和CoUninitialize必须成对使用。

    CoCreateInstance 函数
    用指定的类标识符创建一个Com对象,用指定的类标识符创建一个未初始化的对象。
    函数声明
    STDAPI CoCreateInstance( REFCLSID rclsid, //创建的Com对象的类标识符(CLSID) LPUNKNOWN pUnkOuter, //指向接口IUnknown的指针 DWORD dwClsContext, //运行可执行代码的上下文 REFIID riid, //创建的Com对象的接口标识符 LPVOID * ppv //用来接收指向Com对象接口地址的指针变量);
    参数

    rclsid[in] 用来唯一标识一个对象的CLSID(128位),需要用它来创建指定对象。pUnkOuter[in] 如果为NULL, 表明此对象不是聚合式对象一部分。如果不是NULL, 则指针指向一个聚合式对象的IUnknown接口。dwClsContext[in] 组件类别. 可使用CLSCTX枚举器中预定义的值.riid[in] 引用接口标识符,用来与对象通信。ppv[out] 用来接收指向接口地址的指针变量。如果函数调用成功,*ppv包括请求的接口指针。
    返回值

    S_OK指定的Com对象实例被成功创建。REGDB_E_CLASSNOTREG指定的类没有在注册表中注册. 也可能是指定的dwClsContext没有注册或注册表中的服务器类型损坏。CLASS_E_NOAGGREGATION这个类不能创建为聚合型。E_NOINTERFACE指定的类没有实现请求的接口, 或者是IUnknown接口没有暴露请求的接口。

    IShellLink类主要成员

    GetArguments:获得参数信息GetDescription:获得描述信息(备注行)GetHotkey:获得快捷键GetIconLocation:获得图标 GetIDList:获得快捷方式的目标对象的item identifier list (Windows外壳中的每个对象如文件,目录和打印机等都有唯一的item identifiler list)GetPath: 获得快捷方式的目标文件或目录的全路径GetShowCmd:获得快捷方式的运行方式,比如常规窗口,最大化GetWorkingDirectory:获得工作目录Resolve:按照一定的搜索规则试图获得目标对象,即使目标对象已经被删除或移动,重命名
    下面是对应信息的设置方法
    SetArguments
    SetDescriptionSetHotkeySetIconLocationSetIDListSetPathSetRelativePatSetShowCmdSetWorkingDirectory

    IPersistFile类主要成员

    Save:保存内容到文件中去Load:读取
    Load的函数原型

    HRESULT Load( LPCOLSTR pszFileName, //快捷方式的文件名,应该是ANSI字符 DWORD dwMode //读取方式 );
    参数

    dwMode可取如下值:
    STGM_READ:只读
    STGM_WRITE:只写
    STGM_READWRITE:读写


    实现原理本文介绍的创建快捷功能的实现,是使用Windows Shell编程,会涉及到COM组件调用的内容。如果你没有接触过COM,那也没有关系。你只需要了解清楚程序的逻辑就好,然后,知道每个操作大概是干什么就可以了。
    那么,创建快捷方式的过程原理就是:

    首先,先初始化COM接口环境,因为接下来会调用到COM组件。
    然后,创建一个IShellLink对象。
    接着,设置IShellLink对象的目标程序路径以及目标程序所在的目录。
    从IShellLink对象中获取IPersistFile接口,用于保存快捷方式的数据文件(*.lnk)。
    将保存的快捷方式路径窄字符串转换为宽字符串,然后调用IPersistFile接口保存生成快捷方式文件。
    释放对象接口、释放对象以及释放COM接口环境。

    这样,创建快捷方式的流程就走完了,那么,指定程序的快捷方式.lnk文件也已经生成。
    编程实现BOOL CreateLink(char *lpszExeFileName, char *lpszLnkFileName){ HRESULT hRet = 0; IShellLink *lpShellLink = NULL; IPersistFile *lpPersistFile = NULL; char szDirectory[MAX_PATH] = { 0 }; WCHAR szwLink[MAX_PATH] = { 0 }; // 初始化COM库 ::CoInitialize(NULL); // 创建一个IShellLink实例 hRet = ::CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&lpShellLink); if (FAILED(hRet)) { ShowError("CoCreateInstance"); return FALSE; } // 设置目标应用程序 lpShellLink->SetPath(lpszExeFileName); // 目标文件的工作目录 ::lstrcpy(szDirectory, lpszExeFileName); char *p = ::strrchr(szDirectory, '\\'); p[0] = '\0'; lpShellLink->SetWorkingDirectory(szDirectory); // 从ISHellLink获取IPersistFile接口,用于保存快捷方式的数据文件(*.lnk) hRet = lpShellLink->QueryInterface(IID_IPersistFile, (LPVOID *)&lpPersistFile); if (FAILED(hRet)) { ShowError("lpShellLink->QueryInterface"); return FALSE; } // 保存快捷方式的数据文件(*.lnk) C2W(lpszLnkFileName, szwLink, 2*MAX_PATH); hRet = lpPersistFile->Save(szwLink, STGM_READWRITE); if (FAILED(hRet)) { ShowError("lpPersistFile->Save"); return FALSE; } // 释放接口 lpPersistFile->Release(); lpShellLink->Release(); ::CoUninitialize(); return TRUE;}
    程序测试在 main 函数中上述封装好的函数,进行测试。
    main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ if (FALSE == CreateLink("C:\\Users\\DemonGan\\Desktop\\520.exe", "C:\\Users\\DemonGan\\Desktop\\520.lnk")) { printf("Create Link Error!\n"); } printf("Create Link OK!\n"); system("pause"); return 0;}
    测试结果:
    运行程序,提示执行成功。

    查看目录,“520.lnk”快捷方式文件生成。

    查看“520.lnk”文件的属性。

    并双击快捷方式,成功运行程序。所以,测试成功。
    总结这个程序,使用到COM组件相关的知识,理解起来有些复杂。但是,具体原理可以不进行深究,只需熟练使用提供的函数接口就可以了。
    参考参考自《Windows黑客编程技术详解》一书
    1  留言 2018-11-12 12:33:37
eject