Skip to content

Instantly share code, notes, and snippets.

@kyle-go
Last active June 17, 2019 01:44
Show Gist options
  • Save kyle-go/8bed1ad0ae2b408a5f572ae544f4ff08 to your computer and use it in GitHub Desktop.
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.
#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