Last active
June 17, 2019 01:44
-
-
Save kyle-go/8bed1ad0ae2b408a5f572ae544f4ff08 to your computer and use it in GitHub Desktop.
Compress and Decompress with Windows API. BUT Decompress BUG on windows xp.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <windows.h> | |
#include <ctime> | |
#include <string> | |
/* | |
使用Windows api实现数据压缩与解压缩 | |
重点说下RtlDecompressBuffer这个api,经过反复测试发现有2个问题 | |
1. 某些情况下虽然给的解压缓冲区很小,但此API并不会返回STATUS_BAD_COMPRESSION_BUFFER,而是返回了STATUS_SUCCESS。 | |
只是某些情况下会这样,并非100%复现,规律暂时没有找到,但我找到了一些可以复现的情况。 | |
比如使用rand()随机生成3000个字节原始数据,然后压缩后大概率比3000还大一点点,解压缩这份数据,但是我们故意把解压缩缓冲区设置的很小,比如8字节, | |
此时函数会返回STATUS_SUCCESS,而最后一个参数返回的长度也是8. | |
2. 在XP系统(x86 & x64)下, 最后一个参数可能是错的(比原始数据长度要大)。 | |
大概率复现此问题的方法(仅发现在xp下能复现, win7, win10都不能复现,其他系统没有测试): | |
是用rand()构造3640个字节原始数据,压缩后大概率比3640大一点点,解压缩这份数据,给一个8192字节足够大的解压缓冲区,最后一个参数得到的值却是4096(应该是3640才对)。 | |
3. 在XP系统(x86 & x64)下,多线程环境下使用RtlCompressBuffer,会Crash,需要加锁。 | |
总结与实际使用的技巧: | |
我的感觉是微软设计RtlDecompressBuffer这个API能正确工作的前提是必须道原始数据的长度。 | |
基于这个思路,我们把压缩后的数据前4个字节存储原始数据的长度(不考虑大于4GB大文件4字节存不下的情况),解压缩前前先拿到原始数据长度,然后就可以绕开上述的两个问题了。 | |
ps.当然zlib不存在上面所说的两个问题,但毕竟不是系统api啊,各有各的好~~~ | |
参考: | |
https://stackoverflow.com/questions/6061482/how-to-use-rtldecompressbuffer-without-knowing-the-size-of-the-uncompressed-data/20518744 | |
*/ | |
#ifndef STATUS_SUCCESS | |
#define STATUS_SUCCESS 0x0 | |
#endif | |
#ifndef STATUS_BUFFER_TOO_SMALL | |
#define STATUS_BUFFER_TOO_SMALL 0xC0000023 | |
#endif | |
#ifndef STATUS_BUFFER_ALL_ZEROS | |
#define STATUS_BUFFER_ALL_ZEROS 0x00000117 | |
#endif | |
bool CompressBuffer(const std::string& in, std::string& out) { | |
DWORD(WINAPI *fnRtlGetCompressionWorkSpaceSize)(USHORT, PULONG, PULONG) | |
= (DWORD(WINAPI *)(USHORT, PULONG, PULONG)) | |
(GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlGetCompressionWorkSpaceSize")); | |
DWORD(WINAPI *fnRtlCompressBuffer)(USHORT, PUCHAR, ULONG, PUCHAR, ULONG, ULONG, PULONG, PVOID) | |
= (DWORD(WINAPI *)(USHORT, PUCHAR, ULONG, PUCHAR, ULONG, ULONG, PULONG, PVOID)) | |
(GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlCompressBuffer")); | |
if (fnRtlGetCompressionWorkSpaceSize == NULL) { | |
printf("cannot find RtlGetCompressionWorkSpaceSize.\n"); | |
return false; | |
} | |
if (fnRtlCompressBuffer == NULL) { | |
printf("cannot find RtlCompressBuffer.\n"); | |
return false; | |
} | |
ULONG uCompressBufferWorkSpaceSize, uCompressFragmentWorkSpaceSize; | |
if (fnRtlGetCompressionWorkSpaceSize( | |
COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_MAXIMUM, | |
&uCompressBufferWorkSpaceSize, | |
&uCompressFragmentWorkSpaceSize)) { | |
return false; | |
} | |
PVOID pWorkSpace; | |
unsigned char sz16[16] = { 0 }; | |
if (uCompressBufferWorkSpaceSize == 16) { | |
pWorkSpace = sz16; | |
} | |
else { | |
pWorkSpace = new BYTE[uCompressBufferWorkSpaceSize]; | |
} | |
std::string outData; | |
outData.resize(1024); // init size | |
ULONG finalCompressedSize = 0; | |
DWORD status = 0; | |
while (STATUS_BUFFER_TOO_SMALL == (status = fnRtlCompressBuffer( | |
COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_MAXIMUM, | |
(PUCHAR)in.data(), | |
in.size(), | |
(PUCHAR)outData.data(), | |
outData.size(), | |
4096, | |
&finalCompressedSize, | |
pWorkSpace))) | |
{ | |
outData.resize(outData.size() * 2); | |
continue; | |
} | |
if (uCompressBufferWorkSpaceSize != 16) { | |
delete[] pWorkSpace; | |
} | |
if (status == STATUS_SUCCESS || status == STATUS_BUFFER_ALL_ZEROS) { | |
ULONG inSize = in.size(); | |
out = std::string((char*)&inSize, 4) + std::string(outData.data(), finalCompressedSize); | |
return true; | |
} | |
return false; | |
} | |
bool DecompressBuffer(const std::string& in, std::string& out) { | |
DWORD(WINAPI *fnRtlDecompressBuffer)(USHORT, PUCHAR, ULONG, PUCHAR, ULONG, PULONG) | |
= (DWORD(WINAPI *)(USHORT, PUCHAR, ULONG, PUCHAR, ULONG, PULONG)) | |
(GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlDecompressBuffer")); | |
if (fnRtlDecompressBuffer == NULL) { | |
printf("cannot find RtlDecompressBuffer.\n"); | |
return false; | |
} | |
ULONG finalCompressedSize = 0; | |
std::string outData; | |
ULONG outSize = *(ULONG*)in.data(); | |
outData.resize(outSize); // init size | |
DWORD status = 0; | |
if (STATUS_SUCCESS != fnRtlDecompressBuffer( | |
COMPRESSION_FORMAT_LZNT1, | |
(PUCHAR)outData.data(), | |
outData.size(), | |
(PUCHAR)in.data() + 4, | |
in.size() - 4, | |
&finalCompressedSize)) | |
{ | |
return false; | |
} | |
out = std::string((char*)outData.data(), outSize); | |
return true; | |
} | |
DWORD __stdcall test(LPVOID param) { | |
srand((unsigned int)time(0) + (unsigned int)param); | |
int count = 0; | |
while (true) { | |
std::string buf; | |
int buf_length = rand()%8192 + 1; //随机字符串最大长度 | |
int theMagic = rand() % 256 + 1; // 这个数越小,重复度就越高,压缩率就越高 | |
for (int i = 0; i < buf_length; i++) { | |
std::string cstr = "0"; | |
cstr[0] = rand() % theMagic;; | |
buf += cstr; | |
} | |
std::string out, out2; | |
bool b1 = CompressBuffer(buf, out); | |
bool b2 = DecompressBuffer(out, out2); | |
printf("len=%d, b1=%d len1=%d, b2=%d len2=%d\n", buf.length(), b1 ? 1 : 0, out.length(), b2 ? 1 : 0, out2.length()); | |
if ((buf.length() == out2.length()) && (0 == memcmp(buf.c_str(), out2.c_str(), buf.length()))) { | |
printf("OK Count=%d, len=%d, press=%.2f\n", count++, buf.length(), out.length()*100.0 / buf.length()); | |
} | |
else { | |
printf("Failed\n"); | |
break; | |
} | |
} | |
printf("WTF!!"); | |
ExitProcess(0); | |
return 0; | |
} | |
int main() { | |
srand((unsigned int)time(0)); | |
// 开8个线程测试 | |
for (int i = 0; i < 8; i++) { | |
HANDLE h = CreateThread(NULL, 0, test, (LPVOID)rand(), 0, 0); | |
if (h) { | |
CloseHandle(h); | |
} | |
} | |
while (1) { | |
Sleep(1000); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment