Created
August 15, 2025 11:17
-
-
Save odzhan/be12d064ba56a0a777db35bd682a669b to your computer and use it in GitHub Desktop.
Dump Lua byte code from resource directory of PE file.
This file contains hidden or 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
| /** | |
| windiag.dll has support for executing Lua bytecode. At the time of writing, there's only one DLL that uses windiag.dll to execute Lua code and that's the Windows Hardware Error Service (whesvc.dll). | |
| The Lua byte code itself is located in the resource directory of whesvc_assets.dll | |
| You need to decompress the data before actually obtaining the byte code, but this is simply done via cabinet compression API. | |
| cl /EHsc /std:c++20 dump_lua.cpp | |
| */ | |
| #ifndef _WIN32_WINNT | |
| #define _WIN32_WINNT 0x0602 // Windows 8+ for compressapi.h | |
| #endif | |
| #define WIN32_LEAN_AND_MEAN | |
| #include <windows.h> | |
| #include <compressapi.h> | |
| #include <cstdio> | |
| #include <cstdint> | |
| #include <string> | |
| #include <vector> | |
| #include <set> | |
| #include <iostream> | |
| #include <filesystem> | |
| #include <algorithm> | |
| #pragma comment(lib, "Cabinet.lib") | |
| namespace fs = std::filesystem; | |
| static const wchar_t* kTypes[] = { L"LUAMOD", L"LUALIBM" }; | |
| // --------- small helpers --------- | |
| static std::wstring Sanitize(const std::wstring& s) { | |
| std::wstring out; | |
| out.reserve(s.size()); | |
| for (wchar_t ch : s) { | |
| if ((ch >= L'0' && ch <= L'9') || (ch >= L'A' && ch <= L'Z') || | |
| (ch >= L'a' && ch <= L'z') || ch == L'_' || ch == L'-' || ch == L'.') { | |
| out.push_back(ch); | |
| } else { | |
| out.push_back(L'_'); | |
| } | |
| } | |
| if (out.empty()) out = L"unnamed"; | |
| return out; | |
| } | |
| static std::wstring ResNameToString(LPCWSTR name) { | |
| if (IS_INTRESOURCE(name)) { | |
| std::wstring s = name; | |
| return L"ID" + s; | |
| } else { | |
| return Sanitize(name); | |
| } | |
| } | |
| static bool LooksLikeTextLua(const uint8_t* p, size_t n) { | |
| if (!p || n == 0) return false; | |
| // Disallow NULs; allow \t \r \n and printable ASCII. Require some newlines. | |
| size_t nl = 0; | |
| for (size_t i = 0; i < n; ++i) { | |
| uint8_t c = p[i]; | |
| if (c == 0) return false; | |
| if (c == '\n') ++nl; | |
| if (!(c == '\t' || c == '\r' || c == '\n' || (c >= 32 && c <= 126))) { | |
| return false; | |
| } | |
| } | |
| return nl >= 1; // crude, but good enough to choose ".lua" | |
| } | |
| static std::wstring GuessExtension(const uint8_t* p, size_t n) { | |
| if (n >= 4 && p[0] == 0x1B && p[1] == 'L' && (p[2] == 'u' /*Lua*/ || p[2] == 'J' /*LuaJIT*/)) { | |
| return L".luac"; | |
| } | |
| if (LooksLikeTextLua(p, n)) return L".lua"; | |
| return L".bin"; | |
| } | |
| // Result of a decompression attempt | |
| enum class DecompResult { Decompressed, NotCompressed, Error }; | |
| static DecompResult TryDecompressMsZip(const uint8_t* src, size_t srcSize, | |
| std::vector<uint8_t>& out, DWORD& errorOut) | |
| { | |
| errorOut = 0; | |
| if (!src || srcSize == 0) return DecompResult::Error; | |
| COMPRESSOR_HANDLE h = nullptr; | |
| if (!CreateDecompressor(COMPRESS_ALGORITHM_MSZIP, nullptr, &h)) { | |
| errorOut = GetLastError(); | |
| return DecompResult::Error; | |
| } | |
| SIZE_T required = 0; | |
| BOOL ok = Decompress(h, src, srcSize, nullptr, 0, &required); | |
| if (!ok) { | |
| DWORD e = GetLastError(); | |
| if (e == ERROR_INSUFFICIENT_BUFFER && required > 0) { | |
| out.assign(required, 0); | |
| SIZE_T actual = required; | |
| ok = Decompress(h, src, srcSize, out.data(), out.size(), &actual); | |
| if (!ok) { | |
| errorOut = GetLastError(); | |
| CloseDecompressor(h); | |
| out.clear(); | |
| return DecompResult::Error; | |
| } | |
| // Resize to actual decompressed length if different | |
| if (actual <= out.size()) out.resize(actual); | |
| CloseDecompressor(h); | |
| return DecompResult::Decompressed; | |
| } else if (e == ERROR_UNSUPPORTED_TYPE) { | |
| CloseDecompressor(h); | |
| return DecompResult::NotCompressed; | |
| } else { | |
| errorOut = e; | |
| CloseDecompressor(h); | |
| return DecompResult::Error; | |
| } | |
| } | |
| // We shouldn't get here with ok==TRUE for probe call, but handle defensively: | |
| CloseDecompressor(h); | |
| return DecompResult::Error; | |
| } | |
| static bool WriteAll(const fs::path& path, const uint8_t* data, size_t n) { | |
| try { | |
| std::error_code ec; | |
| fs::create_directories(path.parent_path(), ec); | |
| FILE* f = _wfopen(path.c_str(), L"wb"); | |
| if (!f) return false; | |
| size_t written = fwrite(data, 1, n, f); | |
| fclose(f); | |
| return written == n; | |
| } catch (...) { | |
| return false; | |
| } | |
| } | |
| // --------- resource dumping --------- | |
| struct DumpCtx { | |
| HMODULE h; | |
| std::wstring type; | |
| fs::path outdir; | |
| std::wstring baseName; | |
| int counter = 0; | |
| }; | |
| static void DumpOneResource(DumpCtx& ctx, LPCWSTR name) { | |
| HRSRC hrsrc = FindResourceW(ctx.h, name, ctx.type.c_str()); | |
| if (!hrsrc) { | |
| std::wcerr << L"[warn] " << ctx.baseName << L": FindResource failed for type=" | |
| << ctx.type << L" name=" << ResNameToString(name) | |
| << L" (err " << GetLastError() << L")\n"; | |
| return; | |
| } | |
| DWORD sz = SizeofResource(ctx.h, hrsrc); | |
| if (!sz) return; | |
| HGLOBAL hglob = LoadResource(ctx.h, hrsrc); | |
| if (!hglob) return; | |
| const void* p = LockResource(hglob); | |
| if (!p) return; | |
| const uint8_t* src = static_cast<const uint8_t*>(p); | |
| size_t srcSize = static_cast<size_t>(sz); | |
| // Try MSZIP decompression first | |
| std::vector<uint8_t> decoded; | |
| DWORD deErr = 0; | |
| DecompResult dr = TryDecompressMsZip(src, srcSize, decoded, deErr); | |
| const uint8_t* toWrite = nullptr; | |
| size_t toWriteSize = 0; | |
| bool decompressed = false; | |
| if (dr == DecompResult::Decompressed) { | |
| toWrite = decoded.data(); | |
| toWriteSize = decoded.size(); | |
| decompressed = true; | |
| } else if (dr == DecompResult::NotCompressed) { | |
| toWrite = src; | |
| toWriteSize = srcSize; | |
| } else { | |
| std::wcerr << L"[warn] " << ctx.baseName << L": Decompress error for " | |
| << ctx.type << L"/" << ResNameToString(name) | |
| << L" (err " << deErr << L"); writing raw.\n"; | |
| toWrite = src; | |
| toWriteSize = srcSize; | |
| } | |
| std::wstring cleanName = ResNameToString(name); | |
| std::wstring ext = GuessExtension(toWrite, toWriteSize); | |
| // Make filename: <type>_<name>[#N].<ext> (avoid collisions) | |
| fs::path out = ctx.outdir / (ctx.type + L"_" + cleanName + ext); | |
| if (!fs::exists(ctx.outdir)) { | |
| std::error_code ec; | |
| fs::create_directories(ctx.outdir, ec); | |
| } | |
| int uniquifier = 1; | |
| while (fs::exists(out)) { | |
| out = ctx.outdir / (ctx.type + L"_" + cleanName + L"_" + std::to_wstring(uniquifier++) + ext); | |
| } | |
| if (WriteAll(out, toWrite, toWriteSize)) { | |
| std::wcout << L"[ok] " << out.wstring() | |
| << L" (" << toWriteSize << L" bytes" | |
| << (decompressed ? L", decompressed" : L", raw") << L")\n"; | |
| } else { | |
| std::wcerr << L"[err] failed to write " << out.wstring() << L"\n"; | |
| } | |
| } | |
| static BOOL CALLBACK EnumNamesCb(HMODULE h, LPCWSTR type, LPWSTR name, LONG_PTR lparam) { | |
| (void)type; | |
| DumpCtx* ctx = reinterpret_cast<DumpCtx*>(lparam); | |
| DumpOneResource(*ctx, name); | |
| return TRUE; | |
| } | |
| static void DumpTypesForFile(const fs::path& file) { | |
| // Load resource-only | |
| HMODULE hmod = LoadLibraryExW(file.c_str(), nullptr, | |
| LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); | |
| if (!hmod) { | |
| std::wcerr << L"[err] cannot load " << file.wstring() | |
| << L" as resources (err " << GetLastError() << L")\n"; | |
| return; | |
| } | |
| fs::path outdir = file.filename().wstring() + std::wstring(L".luares"); | |
| DumpCtx ctx{}; | |
| ctx.h = hmod; | |
| ctx.outdir = outdir; | |
| ctx.baseName = file.filename().wstring(); | |
| for (const wchar_t* t : kTypes) { | |
| ctx.type = t; | |
| if (!EnumResourceNamesW(hmod, t, EnumNamesCb, reinterpret_cast<LONG_PTR>(&ctx))) { | |
| DWORD e = GetLastError(); | |
| if (e != ERROR_RESOURCE_TYPE_NOT_FOUND && e != ERROR_FILE_NOT_FOUND) { | |
| std::wcerr << L"[warn] " << ctx.baseName << L": EnumResourceNames failed for type=" | |
| << t << L" (err " << e << L")\n"; | |
| } | |
| } | |
| } | |
| FreeLibrary(hmod); | |
| } | |
| // --------- glob expansion --------- | |
| static std::vector<fs::path> ExpandPattern(const std::wstring& pattern) { | |
| std::vector<fs::path> out; | |
| WIN32_FIND_DATAW fd{}; | |
| HANDLE h = FindFirstFileW(pattern.c_str(), &fd); | |
| if (h == INVALID_HANDLE_VALUE) return out; | |
| // Determine directory prefix (everything before the last slash/backslash) | |
| std::wstring dir; | |
| size_t pos = pattern.find_last_of(L"\\/"); | |
| if (pos != std::wstring::npos) dir = pattern.substr(0, pos + 1); | |
| do { | |
| if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { | |
| out.emplace_back(dir + fd.cFileName); | |
| } | |
| } while (FindNextFileW(h, &fd)); | |
| FindClose(h); | |
| return out; | |
| } | |
| int wmain(int argc, wchar_t* argv[]) { | |
| if (argc < 2) { | |
| std::wcerr << L"Usage:\n dump_lua <pattern> [pattern ...]\n" | |
| L"Examples:\n dump_lua *.dll\n dump_lua *.exe *.dll\n"; | |
| return 2; | |
| } | |
| std::set<fs::path> files; // dedupe | |
| for (int i = 1; i < argc; ++i) { | |
| std::wstring pat = argv[i]; | |
| // Expand wildcards; if none found, treat as literal file if it exists. | |
| auto matches = ExpandPattern(pat); | |
| if (matches.empty()) { | |
| if (fs::exists(pat)) files.insert(fs::path(pat)); | |
| } else { | |
| files.insert(matches.begin(), matches.end()); | |
| } | |
| } | |
| if (files.empty()) { | |
| std::wcerr << L"[err] no matching files.\n"; | |
| return 1; | |
| } | |
| for (const auto& f : files) { | |
| std::wcout << L"==> " << f.wstring() << L"\n"; | |
| DumpTypesForFile(f); | |
| } | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment