chore(windows): loading prx info to stdout

This commit is contained in:
boykopovar 2026-09-11 07:08:01 +03:00
parent 999a723a5f
commit e075f99757
2 changed files with 89 additions and 18 deletions

View File

@ -34,8 +34,10 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
const bool absolutePath = path.starts_with("\\\\") || (path.size() >= 3 && path[1] == ':' && path[2] == '\\');
if (!absolutePath && (path.front() == '\\' || path.find(':') != std::string::npos))
throw Domain::RelinkerException("Ambiguous Windows run path: " + path);
WindowsEntryStub result{{".startup", dataRva, SectionRead | SectionWrite | 0x40u, {}}, {".entry", 0, SectionRead | SectionExecute | 0x20u, {}}, {}};
auto& data = result.Data.Data;
const auto reserve = [&](const std::size_t size) {
const auto rva = CheckedRva(dataRva + data.size());
data.resize(data.size() + size);
@ -46,12 +48,14 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
Io::AppendString(data, value);
return rva;
};
const auto programPath = reserve(PathCapacity);
const auto modulePath = reserve(PathCapacity);
const auto handles = reserve(libraries.size() * 8);
const auto functionTable = reserve(12);
const auto unwindRva = CheckedRva(dataRva + data.size());
data.insert(data.end(), {1, 10, 6, 0, 10, 0xb2, 6, 0xc0, 4, 0x70, 3, 0x60, 2, 0x50, 1, 0x30});
std::vector<std::uint32_t> libraryPaths;
std::vector<std::string> libraryNames;
for (const auto& library : libraries) {
@ -61,9 +65,11 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
libraryPaths.push_back(addString(name));
libraryNames.push_back(std::move(name));
}
std::vector<std::uint32_t> symbolNames;
for (const auto& import : imports)
symbolNames.push_back(addString(import.Name));
const auto lastError = reserve(4);
const auto errorDigits = reserve(11);
const auto errorMessage = reserve(ErrorMessageCapacity);
@ -76,9 +82,11 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
const auto searched = addString("Searched libraries:\n");
const auto indent = addString(" ");
const auto enteringElf = addString("Transferring control to ELF entry point\n");
std::vector<std::uint32_t> resolvedPaths;
for (std::size_t index = 0; index < libraries.size(); ++index)
resolvedPaths.push_back(reserve(PathCapacity));
const auto diagnosticsOffset = data.size();
std::vector<std::string> errors = {"FAIL: cannot obtain executable path\n", "FAIL: executable or library path is too long\n", "FAIL: executable path has no directory\n"};
for (const auto& import : imports)
@ -88,9 +96,12 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
errorRvas.push_back(addString(error));
if (data.size() <= diagnosticsOffset)
throw Domain::RelinkerException("Empty startup diagnostics");
result.Code.Rva = AlignRva(dataRva + data.size());
WindowsStubEmitter code(result.Code.Rva);
const auto call = [&](const std::string& name) { code.Rip({0xff, 0x15}, nativeImports.Functions.at(name)); };
const auto raise = [&](const std::uint32_t status) {
code.Emit({0xb9});
code.U32(status);
@ -98,17 +109,19 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
call("RaiseException");
code.Emit({0x0f, 0x0b});
};
const auto requireDiagnosticSuccess = [&] {
code.Emit({0x48, 0x85, 0xc0});
const auto success = code.Branch({0x0f, 0x85});
raise(0xc0000001u);
code.PatchBranch(success, code.GetRva());
};
const auto writeString = [&](const std::uint32_t stringRva) {
const auto writeString = [&](const std::uint32_t stringRva, const bool isError = false) {
code.Rip({0x48, 0x8d, 0x0d}, stringRva);
call("lstrlenA");
code.Emit({0x89, 0x44, 0x24, 0x3c, 0xb9});
code.U32(0xfffffff4u);
code.U32(isError ? 0xfffffff4u : 0xfffffff5u);
call("GetStdHandle");
requireDiagnosticSuccess();
code.Emit({0x48, 0x83, 0xf8, 0xff});
@ -125,8 +138,9 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
raise(0xc0000001u);
code.PatchBranch(complete, code.GetRva());
};
const auto writeLastError = [&] {
writeString(errorPrefix);
writeString(errorPrefix, true);
code.Rip({0x8b, 0x05}, lastError);
code.Rip({0x48, 0x8d, 0x3d}, errorDigits + 10);
code.Emit({0xc6, 0x07, 0, 0x41, 0xb8, 10, 0, 0, 0});
@ -138,8 +152,8 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
code.Emit({0x48, 0x29, 0xf9});
code.Rip({0x48, 0x8d, 0x3d}, errorDigits);
code.Emit({0xf3, 0xa4});
writeString(errorDigits);
writeString(messageSeparator);
writeString(errorDigits, true);
writeString(messageSeparator, true);
code.Emit({0xb9, 0, 0x12, 0, 0, 0x31, 0xd2});
code.Rip({0x44, 0x8b, 0x05}, lastError);
code.Emit({0x41, 0xb9, 9, 4, 0, 0});
@ -149,25 +163,29 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
code.Emit({0x48, 0xc7, 0x44, 0x24, 0x30, 0, 0, 0, 0});
call("FormatMessageA");
requireDiagnosticSuccess();
writeString(errorMessage);
writeString(errorMessage, true);
};
const auto captureLastError = [&] {
call("GetLastError");
code.Rip({0x89, 0x05}, lastError);
};
const auto fail = [&](const std::size_t error, const std::uint32_t status) {
writeString(errorRvas.at(error));
writeString(errorRvas.at(error), true);
raise(status);
};
const auto requireNonzero = [&](const std::size_t error, const std::uint32_t status) {
code.Emit({0x48, 0x85, 0xc0});
const auto success = code.Branch({0x0f, 0x85});
captureLastError();
writeString(errorRvas.at(error));
writeString(errorRvas.at(error), true);
writeLastError();
raise(status);
code.PatchBranch(success, code.GetRva());
};
code.Emit({0x53, 0x55, 0x56, 0x57, 0x41, 0x54, 0x48, 0x83, 0xec, 0x60});
code.Emit({0x31, 0xc9});
code.Rip({0x48, 0x8d, 0x15}, programPath);
@ -193,6 +211,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
code.Emit({0x49, 0xff, 0xcc, 0x41, 0x80, 0x3c, 0x24, 0x5c});
code.Rip({0x0f, 0x85}, findSeparator);
code.Emit({0x49, 0xff, 0xc4});
for (std::size_t index = 0; index < libraries.size(); ++index) {
if (absolutePath) {
code.Rip({0x48, 0x8d, 0x0d}, libraryPaths[index]);
@ -224,13 +243,14 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
code.Emit({0x48, 0x85, 0xc0});
const auto loadSucceeded = code.Branch({0x0f, 0x85});
captureLastError();
writeString(loadFailed);
writeString(loadFailed, true);
writeLastError();
raise(0xc0000135u);
code.PatchBranch(loadSucceeded, code.GetRva());
code.Rip({0x48, 0x89, 0x05}, CheckedRva(handles + index * 8));
writeString(loaded);
}
std::vector<std::size_t> unresolvedBranches;
std::vector<std::size_t> lazyUnresolvedImports;
for (std::size_t index = 0; index < imports.size(); ++index) {
@ -259,7 +279,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
continue;
}
captureLastError();
writeString(errorRvas.at(3 + index));
writeString(errorRvas.at(3 + index), true);
unresolvedBranches.push_back(code.Branch({0xe9}));
code.PatchBranch(resolved, code.GetRva());
if (imports[index].Addend != 0) {
@ -269,6 +289,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
}
code.Rip({0x48, 0x89, 0x05}, imports[index].TargetRva);
}
writeString(enteringElf);
code.Emit({0x48, 0xc7, 0x44, 0x24, 0x40, 1, 0, 0, 0});
code.Rip({0x48, 0x8d, 0x05}, programPath);
@ -278,27 +299,31 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
code.Emit({0x89, 0xc1});
call("ExitProcess");
code.Emit({0x0f, 0x0b});
if (!unresolvedBranches.empty()) {
for (const auto branch : unresolvedBranches)
code.PatchBranch(branch, code.GetRva());
writeString(searched);
writeString(searched, true);
for (const auto resolvedPath : resolvedPaths) {
writeString(indent);
writeString(resolvedPath);
writeString(newline);
writeString(indent, true);
writeString(resolvedPath, true);
writeString(newline, true);
}
writeLastError();
raise(0xc0000139u);
}
const auto functionEnd = code.GetRva();
code.PatchBranch(exitCallback, functionEnd);
code.Emit({0xc3});
for (const auto index : lazyUnresolvedImports) {
const auto stubRva = code.GetRva();
writeString(errorRvas.at(3 + index));
writeString(errorRvas.at(3 + index), true);
raise(0xc0000139u);
result.LazyStubs.push_back({imports[index].TargetRva, stubRva});
}
Io::WriteU32(data, functionTable - dataRva, result.Code.Rva);
Io::WriteU32(data, functionTable - dataRva + 4, functionEnd);
Io::WriteU32(data, functionTable - dataRva + 8, unwindRva);

View File

@ -21,6 +21,8 @@
#include <memory>
#include <string>
#include <optional>
#include <cstdlib>
#include <limits>
#include <elfpatcher/windows/WindowsElfPatcher.hpp>
int main(const int argc, char* argv[]) {
@ -31,6 +33,7 @@ int main(const int argc, char* argv[]) {
bool writeRegistry = false;
bool toWindows = false;
bool lazyBinding = false;
bool autorun = false;
std::string inputPath;
std::string outputPath;
@ -62,6 +65,8 @@ int main(const int argc, char* argv[]) {
toWindows = true;
} else if (arg == "--lazy-binding") {
lazyBinding = true;
} else if (arg == "--autorun") {
autorun = true;
} else if (arg.rfind("--", 0) == 0 || arg == "unused-filter") {
std::cerr << "FAIL: unknown option: " << arg << "\n";
return 1;
@ -76,7 +81,7 @@ int main(const int argc, char* argv[]) {
}
if (inputPath.empty() || outputPath.empty()) {
std::cerr << "Usage: relinker [--windows] [--skip-syscall-check] [--to-intel] [unused-filter=0|1|2] [--registry] [--rpath <path>] [--lazy-binding] <input.elf> <output.elf>\n"
std::cerr << "Usage: relinker [--windows] [--skip-syscall-check] [--to-intel] [unused-filter=0|1|2] [--registry] [--rpath <path>] [--lazy-binding] [--autorun] <input.elf> <output.elf>\n"
"Example: relinker input.elf output.elf\n";
return 1;
}
@ -94,6 +99,21 @@ int main(const int argc, char* argv[]) {
auto result = converter->Convert(std::move(sourceBytes), elfReader.ReadCodeSegments());
fileWriter.Write(outputPath, std::move(result.Bytes));
std::cout << "OK: " << result.ReplacedCount << " instructions replaced\n";
if (autorun) {
if (!toWindows) {
std::filesystem::permissions(outputPath,
std::filesystem::perms::owner_exec |
std::filesystem::perms::group_exec |
std::filesystem::perms::others_exec,
std::filesystem::perm_options::add);
}
const std::string absPath = std::filesystem::absolute(outputPath).string();
const std::string cmd = "\"" + absPath + "\"";
std::system(cmd.c_str());
std::cout << "\nPress Enter to exit...\n";
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cin.get();
}
return 0;
}
@ -129,6 +149,7 @@ int main(const int argc, char* argv[]) {
}
auto byteWriter = std::make_shared<Io::ByteWriter>();
const std::string absPath = std::filesystem::absolute(outputPath).string();
std::shared_ptr<Elfpatcher::IElfPatcher> patcher;
if (toWindows) {
@ -145,9 +166,34 @@ int main(const int argc, char* argv[]) {
);
}
auto patched = patcher->Patch(sourceBytes, result.OriginalHeaders, result.DynamicSection, result.OriginalPltGotVaddr, runPath, lazyBinding);
fileWriter.Write(outputPath, patched);
fileWriter.Write(absPath, patched);
std::cout << "External prx references: " << result.RegistryEntries.size()
<< "\nOutput file: " << outputPath << '\n';
<< "\nOutput file: " << absPath << '\n';
if (autorun) {
if (!toWindows) {
std::filesystem::permissions(outputPath,
std::filesystem::perms::owner_exec |
std::filesystem::perms::group_exec |
std::filesystem::perms::others_exec,
std::filesystem::perm_options::add);
}
const std::string cmd = "\"" + absPath + "\"";
const int rawCode = std::system(cmd.c_str());
if (toWindows) {
std::cout << "Exit code: " << rawCode << '\n';
}
else {
std::cout << "Raw exit code: " << rawCode
<< "; Unpacked: " << (rawCode >> 8) << '\n';
}
std::cout << "\nPress Enter to exit...\n";
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cin.get();
}
} catch (const Domain::RelinkerException& e) {
std::cerr << "FAIL: " << e.what();