From b4cc6d26b9642599c7a6e52725901368e48bf2bf Mon Sep 17 00:00:00 2001 From: emirplus Date: Thu, 8 Oct 2026 18:39:24 +0300 Subject: [PATCH 1/2] feat(relinker): support Windows guest TLS reads by ALU register-address instructions Recognize read-direction ALU forms (add, or, adc, sbb, and, sub, xor, cmp) that read from fs:[mem] where mem has a base or index register (such as 64 48 33 03, xor rax, fs:[rbx], in #217). The stub loads the TLS value through the computed address, performs the ALU operation, and preserves scratch registers and flags. Extend test_tls_function_coverage with execution and stub coverage cases for register-addressed ALU operations, including rax, rcx, rbx and r13 destinations, wide and 32-bit forms, and verification of register and flag preservation. Refs #217 Co-Authored-By: Claude Opus 5.5 --- .../src/windows/WindowsTlsBuilder.cpp | 93 +++++++++++--- .../tests/test_tls_function_coverage.py | 121 +++++++++++++++++- 2 files changed, 196 insertions(+), 18 deletions(-) diff --git a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp index 3fb5379dc..3f824a9f8 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp @@ -135,7 +135,8 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector& source, co const bool storeImmediate = supportedPrefixes && !hasOperandSizePrefix && (info.RexPrefix == 0 || info.RexPrefix == 0x40) && info.Length - position == 11 && bytes[position] == 0xc7 && bytes[position + 1] == 0x04 && bytes[position + 2] == 0x25 && Io::ReadU32(source, header.Offset + offset + position + 3) == 0x28; const bool aluRead = supportedPrefixes && (info.RexPrefix == 0x48 || info.RexPrefix == 0x4c) && loadRegister != 4 && info.Length - position == 7 && isAluReadOpcode(bytes[position]) && (bytes[position + 1] & 0xc7) == 0x04 && bytes[position + 2] == 0x25; const bool wide = (info.RexPrefix & 8) != 0; - const auto address = supportedPrefixes && !loadValue && !aluRead && bytes[position] == 0x8b && (wide || !hasOperandSizePrefix) ? registerAddress(bytes, position, info.Length, info.RexPrefix) : std::nullopt; + const bool isAlu = isAluReadOpcode(bytes[position]); + const auto address = supportedPrefixes && !loadValue && !aluRead && (bytes[position] == 0x8b || isAlu) && (wide || !hasOperandSizePrefix) ? registerAddress(bytes, position, info.Length, info.RexPrefix) : std::nullopt; const auto addressRegister = static_cast(((bytes[position + 1] >> 3) & 7) | ((info.RexPrefix & 4) << 1)); if (address && addressRegister != 4) { std::vector moved; @@ -149,7 +150,7 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector& source, co moved.push_back({CheckedRva(rva + length), std::vector(bytes + length, bytes + length + next.Length), next.HasRipRelativeDisp ? std::optional(next.RipRelativeDispOffset) : std::nullopt}); length += next.Length; } - accesses.push_back({rva, header.Offset + offset, length, false, 0, addressRegister, 0, 0, *address, wide, std::move(moved)}); + accesses.push_back({rva, header.Offset + offset, length, false, 0, addressRegister, 0, isAlu ? bytes[position] : std::uint8_t{0}, *address, wide, std::move(moved)}); continue; } if (!loadValue && !storeImmediate && !aluRead) { @@ -224,26 +225,84 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector& source, co patchAccess(sections, access, code.GetRva()); code.Emit({0x48, 0x8d, 0x64, 0x24, 0x80}); if (!access.Address.empty()) { - code.Emit({0x51}); - code.Emit({0x50}); - for (const auto byte : access.Address) - code.Emit({byte}); - code.Emit({0x50}); - loadPointer(); - code.Emit({0x59}); - const auto rex = static_cast((access.Wide ? 0x48 : 0x40) | ((access.Register >> 3) << 2)); - if (rex != 0x40) - code.Emit({rex}); - code.Emit({0x8b, static_cast(0x04 | ((access.Register & 7) << 3)), 0x08}); - if (access.Register == 0) { - code.Emit({0x48, 0x8d, 0x64, 0x24, 0x08}); + if (access.AluOpcode != 0 && access.Register == 0) { + code.Emit({0x51}); + code.Emit({0x52}); + code.Emit({0x50}); + for (const auto byte : access.Address) + code.Emit({byte}); + code.Emit({0x50}); + loadPointer(); code.Emit({0x59}); - } else if (access.Register == 1) { + const auto rex = static_cast((access.Wide ? 0x48 : 0x40) | 0x00); + if (rex != 0x40) + code.Emit({rex}); + code.Emit({0x8b, 0x14, 0x08}); + code.Emit({0x58}); + if (access.Wide) + code.Emit({0x48, access.AluOpcode, 0xc2}); + else + code.Emit({access.AluOpcode, 0xc2}); + code.Emit({0x5a}); + code.Emit({0x59}); + } else if (access.AluOpcode != 0 && access.Register == 1) { + code.Emit({0x51}); + code.Emit({0x50}); + for (const auto byte : access.Address) + code.Emit({byte}); + code.Emit({0x50}); + loadPointer(); + code.Emit({0x59}); + const auto rex = static_cast((access.Wide ? 0x48 : 0x40) | 0x00); + if (rex != 0x40) + code.Emit({rex}); + code.Emit({0x8b, 0x04, 0x08}); + if (access.Wide) + code.Emit({0x48, 0x8b, 0x4c, 0x24, 0x08, 0x48, access.AluOpcode, 0xc8}); + else + code.Emit({0x8b, 0x4c, 0x24, 0x08, access.AluOpcode, 0xc8}); code.Emit({0x58}); code.Emit({0x48, 0x8d, 0x64, 0x24, 0x08}); + } else if (access.AluOpcode != 0) { + code.Emit({0x51}); + code.Emit({0x50}); + for (const auto byte : access.Address) + code.Emit({byte}); + code.Emit({0x50}); + loadPointer(); + code.Emit({0x59}); + const auto rexLoad = static_cast((access.Wide ? 0x48 : 0x40) | 0x00); + if (rexLoad != 0x40) + code.Emit({rexLoad}); + code.Emit({0x8b, 0x04, 0x08}); + const auto rexAlu = static_cast((access.Wide ? 0x48 : 0x40) | ((access.Register >> 3) << 2)); + if (rexAlu != 0x40) + code.Emit({rexAlu}); + code.Emit({access.AluOpcode, static_cast(0xc0 | ((access.Register & 7) << 3))}); + code.Emit({0x58}); + code.Emit({0x59}); } else { - code.Emit({0x58}); + code.Emit({0x51}); + code.Emit({0x50}); + for (const auto byte : access.Address) + code.Emit({byte}); + code.Emit({0x50}); + loadPointer(); code.Emit({0x59}); + const auto rex = static_cast((access.Wide ? 0x48 : 0x40) | ((access.Register >> 3) << 2)); + if (rex != 0x40) + code.Emit({rex}); + code.Emit({0x8b, static_cast(0x04 | ((access.Register & 7) << 3)), 0x08}); + if (access.Register == 0) { + code.Emit({0x48, 0x8d, 0x64, 0x24, 0x08}); + code.Emit({0x59}); + } else if (access.Register == 1) { + code.Emit({0x58}); + code.Emit({0x48, 0x8d, 0x64, 0x24, 0x08}); + } else { + code.Emit({0x58}); + code.Emit({0x59}); + } } } else if (access.AluOpcode != 0 && access.Register == 0) { code.Emit({0x51}); diff --git a/core/relinker/relinker/tests/test_tls_function_coverage.py b/core/relinker/relinker/tests/test_tls_function_coverage.py index 420fcc7fb..843fbcf0a 100644 --- a/core/relinker/relinker/tests/test_tls_function_coverage.py +++ b/core/relinker/relinker/tests/test_tls_function_coverage.py @@ -234,6 +234,15 @@ REGISTER_LOAD_FOLLOWERS = ( ("rax-rbx-rip-follower", bytes.fromhex("64 48 8b 03"), 0, True, {3: -8}, -8, b"", 0x0fedcba987654321), ("edx-rbx-nop-follower", bytes.fromhex("64 8b 13"), 2, False, {3: 0x10}, 0x10, b"\x90", None), ) +REGISTER_ALUS = ( + ("xor-rax-rbx", bytes.fromhex("64 48 33 03"), 0x33, 0, True, {3: -8}, -8), + ("xor-rcx-rax", bytes.fromhex("64 48 33 08"), 0x33, 1, True, {0: 0x10}, 0x10), + ("xor-rbx-rax", bytes.fromhex("64 48 33 18"), 0x33, 3, True, {0: 0x28}, 0x28), + ("add-rax-rbx", bytes.fromhex("64 48 03 03"), 0x03, 0, True, {3: -8}, -8), + ("cmp-rax-rbx", bytes.fromhex("64 48 3b 03"), 0x3b, 0, True, {3: -8}, -8), + ("xor-r13-rbx-rcx-disp8", bytes.fromhex("64 4c 33 6c 0b f0"), 0x33, 13, True, {3: 0x18, 1: -0x10}, -8), + ("xor-edx-rbx", bytes.fromhex("64 33 13"), 0x33, 2, False, {3: 0x10}, 0x10), +) def register_load_body(instruction, register, wide, address, offset, flags, follower=b"", rip_constant=None): @@ -323,6 +332,109 @@ def register_load_stub(instruction, register, wide): return bytes.fromhex("48 8d 64 24 80 51 50") + address + b"\x50", b"\x59" + move + restore + bytes.fromhex("48 8d a4 24 80 00 00 00") +def register_alu_body(instruction, opcode, register, wide, address, offset, flags, follower=b"\x90"): + saved = (3, 5, 6, 7, 12, 13, 14, 15) + mask = (1 << 64) - 1 + code = bytearray() + failures = [] + + def emit(data): + code.extend(data) + + def immediate(target, value): + emit(bytes([0x48 | (target >> 3), 0xb8 | (target & 7)]) + struct.pack("= 8 else b"") + bytes([0x50 | (target & 7)])) + emit(bytes.fromhex("48 8d a4 24 00 ff ff ff")) + emit(fs_load(0, 0)) + for displacement, value in REGISTER_VALUES.items(): + immediate(1, value) + emit(bytes([0x48, 0x89, 0x48, displacement & 255])) + markers = [0x1020304050607000 + target * 0x101 for target in range(16)] + expected = list(markers) + for target in range(16): + if target != 4: + immediate(target, markers[target]) + for target, value in address.items(): + immediate(target, value) + expected[target] = value & mask + value = REGISTER_VALUES[offset] + base_val = expected[register] if wide else expected[register] & 0xffffffff + operand_val = value if wide else value & 0xffffffff + if opcode == 0x33: + res = base_val ^ operand_val + elif opcode == 0x23: + res = base_val & operand_val + elif opcode == 0x0b: + res = base_val | operand_val + elif opcode == 0x03: + res = (base_val + operand_val) & (mask if wide else 0xffffffff) + elif opcode == 0x2b: + res = (base_val - operand_val) & (mask if wide else 0xffffffff) + elif opcode == 0x3b: + res = base_val + else: + raise ValueError(f"Unknown opcode: {opcode}") + expected[register] = res if wide else res & 0xffffffff + emit(b"\x68" + struct.pack("> 3) << 2, 0x89, 0x84 | (target & 7) << 3, 0x24]) + struct.pack("= 8 else b"") + bytes([0x58 | (target & 7)])) + emit(b"\xc3") + for position in failures: + struct.pack_into("> 3) << 2]) if wide or register >= 8 else b"") + alu = rex_alu + bytes([opcode, 0xc0 | (register & 7) << 3]) + return bytes.fromhex("48 8d 64 24 80 51 50") + address + b"\x50", b"\x59" + load + alu + bytes.fromhex("58 59 48 8d a4 24 80 00 00 00") + + + def make_image(transfer, metadata, extent=None, body=None): image = fixture() image.extend(b"\x90" * 0x1000) @@ -530,10 +642,18 @@ def main(): stub = pe_bytes_at(pe, 0x10000 + address + 5 + struct.unpack_from(" Date: Thu, 8 Oct 2026 21:36:42 +0300 Subject: [PATCH 2/2] fix(relinker): restore full rcx for 32-bit register alu Restore full 64-bit RCX before 32-bit ALU operation so CMP and other non-writing/carry instructions do not zero-extend and clear upper 32 bits. Add test coverage for 32-bit CMP, ADC, and SBB. --- .../elfpatcher/src/windows/WindowsTlsBuilder.cpp | 2 +- .../relinker/tests/test_tls_function_coverage.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp index 3f824a9f8..be1eb4a43 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp @@ -260,7 +260,7 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector& source, co if (access.Wide) code.Emit({0x48, 0x8b, 0x4c, 0x24, 0x08, 0x48, access.AluOpcode, 0xc8}); else - code.Emit({0x8b, 0x4c, 0x24, 0x08, access.AluOpcode, 0xc8}); + code.Emit({0x48, 0x8b, 0x4c, 0x24, 0x08, access.AluOpcode, 0xc8}); code.Emit({0x58}); code.Emit({0x48, 0x8d, 0x64, 0x24, 0x08}); } else if (access.AluOpcode != 0) { diff --git a/core/relinker/relinker/tests/test_tls_function_coverage.py b/core/relinker/relinker/tests/test_tls_function_coverage.py index 843fbcf0a..a73220c29 100644 --- a/core/relinker/relinker/tests/test_tls_function_coverage.py +++ b/core/relinker/relinker/tests/test_tls_function_coverage.py @@ -242,6 +242,9 @@ REGISTER_ALUS = ( ("cmp-rax-rbx", bytes.fromhex("64 48 3b 03"), 0x3b, 0, True, {3: -8}, -8), ("xor-r13-rbx-rcx-disp8", bytes.fromhex("64 4c 33 6c 0b f0"), 0x33, 13, True, {3: 0x18, 1: -0x10}, -8), ("xor-edx-rbx", bytes.fromhex("64 33 13"), 0x33, 2, False, {3: 0x10}, 0x10), + ("cmp-ecx-rax", bytes.fromhex("64 3b 08"), 0x3b, 1, False, {0: 0x10}, 0x10), + ("adc-rax-rbx", bytes.fromhex("64 48 13 03"), 0x13, 0, True, {3: -8}, -8), + ("sbb-rcx-rax", bytes.fromhex("64 48 1b 08"), 0x1b, 1, True, {0: 0x10}, 0x10), ) @@ -378,9 +381,14 @@ def register_alu_body(instruction, opcode, register, wide, address, offset, flag res = (base_val - operand_val) & (mask if wide else 0xffffffff) elif opcode == 0x3b: res = base_val + elif opcode == 0x13: + res = (base_val + operand_val + (flags & 1)) & (mask if wide else 0xffffffff) + elif opcode == 0x1b: + res = (base_val - operand_val - (flags & 1)) & (mask if wide else 0xffffffff) else: raise ValueError(f"Unknown opcode: {opcode}") - expected[register] = res if wide else res & 0xffffffff + if opcode != 0x3b: + expected[register] = res if wide else res & 0xffffffff emit(b"\x68" + struct.pack("