fix(nid): rebuild ghu hash for patched nid

This commit is contained in:
boykopovar 2026-08-22 11:47:41 +03:00
parent 8672a5cc3d
commit e838a2d9df
1 changed files with 194 additions and 0 deletions

View File

@ -51,10 +51,185 @@ struct Elf64_Sym {
std::uint64_t st_size;
};
struct Elf64_Rela {
std::uint64_t r_offset;
std::uint64_t r_info;
std::int64_t r_addend;
};
constexpr std::uint32_t kShtDynsym = 11u;
constexpr std::uint32_t kShtGnuHash = 0x6ffffff6u;
constexpr std::uint32_t kShtRela = 4u;
constexpr std::uint8_t kStbLocal = 0u;
constexpr std::uint16_t kShnUndef = 0u;
std::uint32_t GnuHash(const std::string& name) {
std::uint32_t h = 5381u;
for (const unsigned char c : name)
h = h * 33u + c;
return h;
}
struct GnuHashLayout {
std::uint32_t NBuckets;
std::uint32_t SymOffset;
std::uint32_t BloomSize;
std::uint32_t BloomShift;
std::size_t BloomOffset;
std::size_t BucketsOffset;
std::size_t ChainOffset;
};
GnuHashLayout ReadGnuHashLayout(const std::vector<std::uint8_t>& elf, std::size_t sectionOffset) {
using namespace Internal;
GnuHashLayout layout{};
layout.NBuckets = Read<std::uint32_t>(elf, sectionOffset + 0u);
layout.SymOffset = Read<std::uint32_t>(elf, sectionOffset + 4u);
layout.BloomSize = Read<std::uint32_t>(elf, sectionOffset + 8u);
layout.BloomShift = Read<std::uint32_t>(elf, sectionOffset + 12u);
layout.BloomOffset = sectionOffset + 16u;
layout.BucketsOffset = layout.BloomOffset + static_cast<std::size_t>(layout.BloomSize) * 8u;
layout.ChainOffset = layout.BucketsOffset + static_cast<std::size_t>(layout.NBuckets) * 4u;
return layout;
}
void ReorderDynSymForGnuHash(
std::vector<std::uint8_t>& elf,
std::size_t dynSymOffset,
std::size_t symCount,
std::uint32_t symOffset,
std::uint32_t bucketCount,
std::vector<std::string>& symbolNames,
std::vector<std::uint32_t>& oldToNewIndex
) {
using namespace Internal;
std::vector<std::uint32_t> movableIndices;
for (std::uint32_t i = symOffset; i < symCount; ++i)
movableIndices.push_back(i);
std::stable_sort(
movableIndices.begin(), movableIndices.end(),
[&](const std::uint32_t lhs, const std::uint32_t rhs) {
return GnuHash(symbolNames[lhs]) % bucketCount < GnuHash(symbolNames[rhs]) % bucketCount;
}
);
std::vector<Elf64_Sym> reorderedSyms(symCount);
std::vector<std::string> reorderedNames(symCount);
for (std::uint32_t i = 0u; i < symOffset; ++i) {
reorderedSyms[i] = Read<Elf64_Sym>(elf, dynSymOffset + i * sizeof(Elf64_Sym));
reorderedNames[i] = symbolNames[i];
oldToNewIndex[i] = i;
}
for (std::size_t position = 0u; position < movableIndices.size(); ++position) {
const std::uint32_t oldIndex = movableIndices[position];
const std::uint32_t newIndex = symOffset + static_cast<std::uint32_t>(position);
reorderedSyms[newIndex] = Read<Elf64_Sym>(elf, dynSymOffset + oldIndex * sizeof(Elf64_Sym));
reorderedNames[newIndex] = symbolNames[oldIndex];
oldToNewIndex[oldIndex] = newIndex;
}
for (std::size_t i = 0u; i < symCount; ++i)
Write(elf, dynSymOffset + i * sizeof(Elf64_Sym), reorderedSyms[i]);
symbolNames = std::move(reorderedNames);
}
void FixupRelocationSymbolIndices(
std::vector<std::uint8_t>& elf,
const Elf64_Ehdr& ehdr,
std::uint32_t dynSymSectionIndex,
const std::vector<std::uint32_t>& oldToNewIndex
) {
using namespace Internal;
for (std::uint16_t i = 0u; i < ehdr.e_shnum; ++i) {
const std::size_t shOffset = static_cast<std::size_t>(ehdr.e_shoff) + i * sizeof(Elf64_Shdr);
const auto shdr = Read<Elf64_Shdr>(elf, shOffset);
if (shdr.sh_type != kShtRela) continue;
if (shdr.sh_link != dynSymSectionIndex) continue;
const std::size_t relaOffset = static_cast<std::size_t>(shdr.sh_offset);
const std::size_t relaCount = static_cast<std::size_t>(shdr.sh_size) / sizeof(Elf64_Rela);
for (std::size_t r = 0u; r < relaCount; ++r) {
const std::size_t entryOffset = relaOffset + r * sizeof(Elf64_Rela);
const auto rela = Read<Elf64_Rela>(elf, entryOffset);
const auto oldSymIndex = static_cast<std::uint32_t>(rela.r_info >> 32u);
if (oldSymIndex >= oldToNewIndex.size()) continue;
const std::uint32_t newSymIndex = oldToNewIndex[oldSymIndex];
const auto relType = static_cast<std::uint32_t>(rela.r_info & 0xffffffffu);
const std::uint64_t newInfo = (static_cast<std::uint64_t>(newSymIndex) << 32u) | relType;
Write(elf, entryOffset + offsetof(Elf64_Rela, r_info), newInfo);
}
}
}
void RebuildGnuHash(
std::vector<std::uint8_t>& elf,
std::size_t sectionOffset,
std::size_t sectionSize,
std::size_t symCount,
const std::vector<std::string>& symbolNames
) {
using namespace Internal;
const GnuHashLayout layout = ReadGnuHashLayout(elf, sectionOffset);
const std::size_t chainCount = symCount - layout.SymOffset;
const std::size_t requiredSize = layout.ChainOffset - sectionOffset + chainCount * 4u;
if (requiredSize > sectionSize)
throw std::runtime_error("gnu.hash section too small to rebuild in place");
std::vector<std::uint32_t> hashes(symCount, 0u);
for (std::size_t i = layout.SymOffset; i < symCount; ++i)
hashes[i] = GnuHash(symbolNames[i]);
std::vector<std::uint64_t> bloom(layout.BloomSize, 0u);
for (std::size_t i = layout.SymOffset; i < symCount; ++i) {
const std::uint32_t h = hashes[i];
const std::uint32_t wordIndex = (h / 64u) % layout.BloomSize;
const std::uint32_t bit1 = h % 64u;
const std::uint32_t bit2 = (h >> layout.BloomShift) % 64u;
bloom[wordIndex] |= (std::uint64_t{1} << bit1);
bloom[wordIndex] |= (std::uint64_t{1} << bit2);
}
std::vector<std::uint32_t> buckets(layout.NBuckets, 0u);
std::vector<std::uint32_t> chain(chainCount, 0u);
for (std::size_t i = layout.SymOffset; i < symCount; ++i) {
const std::uint32_t bucketIndex = hashes[i] % layout.NBuckets;
if (buckets[bucketIndex] == 0u)
buckets[bucketIndex] = static_cast<std::uint32_t>(i);
}
for (std::size_t i = layout.SymOffset; i < symCount; ++i) {
const std::uint32_t bucketIndex = hashes[i] % layout.NBuckets;
const bool isLastInBucket = (i + 1u == symCount) || (hashes[i + 1u] % layout.NBuckets != bucketIndex);
chain[i - layout.SymOffset] = isLastInBucket ? (hashes[i] | 1u) : (hashes[i] & ~std::uint32_t{1});
}
for (std::uint32_t bloomIndex = 0u; bloomIndex < layout.BloomSize; ++bloomIndex)
Write(elf, layout.BloomOffset + bloomIndex * 8u, bloom[bloomIndex]);
for (std::uint32_t bucketIndex = 0u; bucketIndex < layout.NBuckets; ++bucketIndex)
Write(elf, layout.BucketsOffset + bucketIndex * 4u, buckets[bucketIndex]);
for (std::size_t i = 0u; i < chainCount; ++i)
Write(elf, layout.ChainOffset + i * 4u, chain[i]);
if (requiredSize < sectionSize)
std::memset(elf.data() + sectionOffset + requiredSize, 0, sectionSize - requiredSize);
}
}
void ElfNidPatcher::PatchNids(std::vector<std::uint8_t>& elf, const std::string& libraryName) const {
@ -66,12 +241,21 @@ void ElfNidPatcher::PatchNids(std::vector<std::uint8_t>& elf, const std::string&
const auto ehdr = Read<Elf64_Ehdr>(elf, 0u);
std::size_t dynSymOffset = 0u, dynSymSize = 0u;
std::size_t gnuHashOffset = 0u, gnuHashSize = 0u;
std::uint32_t dynSymSectionIndex = 0u;
std::uint32_t gnuHashSymOffset = 0u;
for (std::uint16_t i = 0u; i < ehdr.e_shnum; ++i) {
const std::size_t shOffset = static_cast<std::size_t>(ehdr.e_shoff) + i * sizeof(Elf64_Shdr);
const auto shdr = Read<Elf64_Shdr>(elf, shOffset);
if (shdr.sh_type == kShtDynsym) {
dynSymOffset = static_cast<std::size_t>(shdr.sh_offset);
dynSymSize = static_cast<std::size_t>(shdr.sh_size);
dynSymSectionIndex = i;
}
if (shdr.sh_type == kShtGnuHash) {
gnuHashOffset = static_cast<std::size_t>(shdr.sh_offset);
gnuHashSize = static_cast<std::size_t>(shdr.sh_size);
gnuHashSymOffset = Read<std::uint32_t>(elf, gnuHashOffset + 4u);
}
}
@ -154,6 +338,7 @@ void ElfNidPatcher::PatchNids(std::vector<std::uint8_t>& elf, const std::string&
}
newDynStr.resize(dynStrSize, 0u);
std::vector<std::string> newSymbolNames(symCount);
for (std::size_t i = 1u; i < symCount; ++i) {
const std::size_t symOffset = dynSymOffset + i * sizeof(Elf64_Sym);
const auto sym = Read<Elf64_Sym>(elf, symOffset);
@ -165,10 +350,19 @@ void ElfNidPatcher::PatchNids(std::vector<std::uint8_t>& elf, const std::string&
);
if (mapped == oldToNewOffset.end()) continue;
newSymbolNames[i] = ReadCStr(newDynStr, mapped->second);
Write(elf, symOffset + offsetof(Elf64_Sym, st_name), mapped->second);
}
std::memcpy(elf.data() + dynStrOffset, newDynStr.data(), newDynStr.size());
if (gnuHashOffset != 0u) {
const auto bucketCount = Read<std::uint32_t>(elf, gnuHashOffset);
std::vector<std::uint32_t> oldToNewIndex(symCount);
ReorderDynSymForGnuHash(elf, dynSymOffset, symCount, gnuHashSymOffset, bucketCount, newSymbolNames, oldToNewIndex);
FixupRelocationSymbolIndices(elf, ehdr, dynSymSectionIndex, oldToNewIndex);
RebuildGnuHash(elf, gnuHashOffset, gnuHashSize, symCount, newSymbolNames);
}
}
}