mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-08 11:04:43 +02:00
147 lines
5.5 KiB
Python
147 lines
5.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Patch ELF .so/.node files that require GLIBCXX_3.4.30 to use GLIBCXX_3.4.29
|
|
instead. Required for AlmaLinux 9 / RHEL 9 which ships GCC 11 (max GLIBCXX_3.4.29).
|
|
|
|
Both the version string AND the vna_hash in .gnu.version_r are updated so the
|
|
dynamic linker finds the version correctly at runtime.
|
|
"""
|
|
import sys
|
|
import struct
|
|
|
|
|
|
def elf_hash(name: str) -> int:
|
|
"""ELF version symbol hash (not GNU hash — used in .gnu.version_r)."""
|
|
h = 0
|
|
for c in name:
|
|
h = ((h << 4) + ord(c)) & 0xFFFFFFFF
|
|
g = h & 0xF0000000
|
|
if g:
|
|
h ^= g >> 24
|
|
h &= (~g) & 0xFFFFFFFF
|
|
return h
|
|
|
|
|
|
def patch_file(path: str) -> bool:
|
|
with open(path, 'rb') as f:
|
|
data = bytearray(f.read())
|
|
|
|
old_ver = b'GLIBCXX_3.4.30'
|
|
new_ver = b'GLIBCXX_3.4.11'
|
|
|
|
if old_ver not in data:
|
|
print(f' skip (no GLIBCXX_3.4.30): {path}')
|
|
return False
|
|
|
|
# ----- ELF header -----
|
|
if data[:4] != b'\x7fELF':
|
|
print(f' skip (not ELF): {path}')
|
|
return False
|
|
|
|
ei_class = data[4] # 1=32-bit, 2=64-bit
|
|
ei_data = data[5] # 1=LE, 2=BE
|
|
endian = '<' if ei_data == 1 else '>'
|
|
|
|
if ei_class == 2:
|
|
# 64-bit ELF
|
|
e_shoff_fmt = f'{endian}Q'
|
|
ehdr_size = 64
|
|
shdr_size = 64
|
|
shdr_fmt = f'{endian}IIQQQQIIQQ' # 64-byte shdr
|
|
(e_shoff,) = struct.unpack_from(f'{endian}Q', data, 40)
|
|
(e_shentsize, e_shnum, e_shstrndx) = struct.unpack_from(f'{endian}HHH', data, 58)
|
|
shdr_type_off = 4
|
|
shdr_off_off = 24 # sh_offset within shdr
|
|
shdr_link_off = 48 # sh_link within shdr
|
|
else:
|
|
# 32-bit ELF
|
|
(e_shoff,) = struct.unpack_from(f'{endian}I', data, 32)
|
|
(e_shentsize, e_shnum, e_shstrndx) = struct.unpack_from(f'{endian}HHH', data, 46)
|
|
shdr_type_off = 4
|
|
shdr_off_off = 16
|
|
shdr_link_off = 28
|
|
|
|
SHT_GNU_verneed = 0x6FFFFFFE
|
|
|
|
patched = False
|
|
# Target GLIBCXX_3.4.11 — that's where condition_variable::wait actually lives
|
|
# in AlmaLinux 9's libstdc++.so.6 (same byte length: 14 chars each)
|
|
old_hash = elf_hash('GLIBCXX_3.4.30')
|
|
new_hash = elf_hash('GLIBCXX_3.4.11')
|
|
old_hash_bytes = struct.pack(f'{endian}I', old_hash)
|
|
new_hash_bytes = struct.pack(f'{endian}I', new_hash)
|
|
|
|
for i in range(e_shnum):
|
|
shdr_base = e_shoff + i * e_shentsize
|
|
sh_type = struct.unpack_from(f'{endian}I', data, shdr_base + shdr_type_off)[0]
|
|
|
|
if sh_type != SHT_GNU_verneed:
|
|
continue
|
|
|
|
# sh_offset and sh_link (index of associated string table section)
|
|
if ei_class == 2:
|
|
sh_offset = struct.unpack_from(f'{endian}Q', data, shdr_base + 24)[0]
|
|
sh_size = struct.unpack_from(f'{endian}Q', data, shdr_base + 32)[0]
|
|
sh_link = struct.unpack_from(f'{endian}I', data, shdr_base + 40)[0]
|
|
sh_info = struct.unpack_from(f'{endian}I', data, shdr_base + 44)[0]
|
|
else:
|
|
sh_offset = struct.unpack_from(f'{endian}I', data, shdr_base + 16)[0]
|
|
sh_size = struct.unpack_from(f'{endian}I', data, shdr_base + 20)[0]
|
|
sh_link = struct.unpack_from(f'{endian}I', data, shdr_base + 24)[0]
|
|
sh_info = struct.unpack_from(f'{endian}I', data, shdr_base + 28)[0]
|
|
|
|
# String table section referenced by sh_link
|
|
strtab_shdr = e_shoff + sh_link * e_shentsize
|
|
if ei_class == 2:
|
|
strtab_off = struct.unpack_from(f'{endian}Q', data, strtab_shdr + 24)[0]
|
|
else:
|
|
strtab_off = struct.unpack_from(f'{endian}I', data, strtab_shdr + 16)[0]
|
|
|
|
# Walk Elf64_Verneed / Elf64_Vernaux entries
|
|
# Elf64_Verneed: vn_version(2)+vn_cnt(2)+vn_file(4)+vn_aux(4)+vn_next(4) = 16 bytes
|
|
# Elf64_Vernaux: vna_hash(4)+vna_flags(2)+vna_other(2)+vna_name(4)+vna_next(4) = 16 bytes
|
|
vneed_pos = sh_offset
|
|
for _ in range(sh_info): # sh_info = number of Verneed entries
|
|
vn_cnt = struct.unpack_from(f'{endian}H', data, vneed_pos + 2)[0]
|
|
vn_aux = struct.unpack_from(f'{endian}I', data, vneed_pos + 8)[0] # +8 (after vn_file)
|
|
vn_next = struct.unpack_from(f'{endian}I', data, vneed_pos + 12)[0]
|
|
|
|
vaux_pos = vneed_pos + vn_aux
|
|
for _ in range(vn_cnt):
|
|
vna_hash = struct.unpack_from(f'{endian}I', data, vaux_pos)[0]
|
|
vna_name = struct.unpack_from(f'{endian}I', data, vaux_pos + 8)[0]
|
|
vna_next = struct.unpack_from(f'{endian}I', data, vaux_pos + 12)[0]
|
|
|
|
# Read the version name from the string table
|
|
name_off = strtab_off + vna_name
|
|
name_end = data.index(b'\x00', name_off)
|
|
ver_name = bytes(data[name_off:name_end])
|
|
|
|
if ver_name == old_ver:
|
|
# Patch hash
|
|
struct.pack_into(f'{endian}I', data, vaux_pos, new_hash)
|
|
# Patch string (same byte length — safe in-place)
|
|
data[name_off:name_off + len(old_ver)] = new_ver
|
|
patched = True
|
|
|
|
if vna_next == 0:
|
|
break
|
|
vaux_pos += vna_next
|
|
|
|
if vn_next == 0:
|
|
break
|
|
vneed_pos += vn_next
|
|
|
|
if patched:
|
|
with open(path, 'wb') as f:
|
|
f.write(data)
|
|
print(f' patched (hash+string): {path}')
|
|
return True
|
|
else:
|
|
print(f' skip (GLIBCXX_3.4.30 not in verneed): {path}')
|
|
return False
|
|
|
|
|
|
if __name__ == '__main__':
|
|
for path in sys.argv[1:]:
|
|
patch_file(path)
|