Merge pull request #340 from QDarkRex/fix/relinker-tls-fs-disp32
fix(relinker): load Windows guest TLS values at signed displacements
This commit is contained in:
commit
9f9a8f5a53
|
|
@ -8,6 +8,7 @@
|
|||
#include <bit>
|
||||
#include <limits>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace Elfpatcher::Windows {
|
||||
|
||||
|
|
@ -15,10 +16,12 @@ namespace {
|
|||
|
||||
struct TlsAccess {
|
||||
std::uint32_t Rva;
|
||||
Domain::FileByteOffset FileOffset;
|
||||
std::size_t Length;
|
||||
bool StoreImmediate;
|
||||
std::uint32_t Immediate;
|
||||
std::uint8_t Register;
|
||||
std::uint32_t Displacement;
|
||||
};
|
||||
|
||||
void patchAccess(std::vector<PeSection>& sections, const TlsAccess& access, const std::uint32_t target) {
|
||||
|
|
@ -78,11 +81,11 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
|
|||
else if (value != 0x64 && !(prefix + 1 == position && value >= 0x40 && value <= 0x4f)) supportedPrefixes = false;
|
||||
}
|
||||
const auto loadRegister = info.Length - position == 7 ? static_cast<std::uint8_t>(((bytes[position + 1] >> 3) & 7) | ((info.RexPrefix & 4) << 1)) : std::uint8_t{4};
|
||||
const bool loadPointer = supportedPrefixes && (info.RexPrefix == 0x48 || info.RexPrefix == 0x4c) && loadRegister != 4 && bytes[position] == 0x8b && (bytes[position + 1] & 0xc7) == 0x04 && bytes[position + 2] == 0x25 && Io::ReadU32(source, header.Offset + offset + position + 3) == 0;
|
||||
const bool loadValue = supportedPrefixes && (info.RexPrefix == 0x48 || info.RexPrefix == 0x4c) && loadRegister != 4 && bytes[position] == 0x8b && (bytes[position + 1] & 0xc7) == 0x04 && bytes[position + 2] == 0x25;
|
||||
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;
|
||||
if (!loadPointer && !storeImmediate)
|
||||
if (!loadValue && !storeImmediate)
|
||||
throw Domain::RelinkerException("Unsupported Windows guest TLS instruction", header.Offset + offset);
|
||||
accesses.push_back({rva, info.Length, storeImmediate, storeImmediate ? Io::ReadU32(source, header.Offset + offset + position + 7) : 0, storeImmediate ? std::uint8_t{0} : loadRegister});
|
||||
accesses.push_back({rva, header.Offset + offset, info.Length, storeImmediate, storeImmediate ? Io::ReadU32(source, header.Offset + offset + position + 7) : 0, storeImmediate ? std::uint8_t{0} : loadRegister, storeImmediate ? 0 : Io::ReadU32(source, header.Offset + offset + position + 3)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -106,6 +109,13 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
|
|||
const auto blockSize = CheckedRva((tls->MemorySize + alignment - 1) & ~(alignment - 1));
|
||||
const auto templateOffset = CheckedRva((64 + alignment - 1) & ~(alignment - 1));
|
||||
constexpr std::uint32_t threadControlBlockSize = 0x30;
|
||||
constexpr std::int64_t stackGuardDisplacement = 0x28;
|
||||
for (const auto& access : accesses) {
|
||||
if (access.StoreImmediate || access.Displacement == 0) continue;
|
||||
const auto displacement = static_cast<std::int64_t>(std::bit_cast<std::int32_t>(access.Displacement));
|
||||
if (displacement != stackGuardDisplacement && (displacement > 0 || displacement < -static_cast<std::int64_t>(blockSize)))
|
||||
throw Domain::RelinkerException("Windows guest TLS load displacement " + std::to_string(displacement) + " is not in the thread TLS block, 0 or the stack guard at 0x28", access.FileOffset);
|
||||
}
|
||||
PeSection data{".gtls", nextRva, SectionRead | SectionWrite | 0x40u, std::vector<std::uint8_t>(templateOffset + blockSize + threadControlBlockSize)};
|
||||
|
||||
const auto indexRva = nextRva + 40;
|
||||
|
|
@ -143,6 +153,10 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
|
|||
if (preserveCounter) code.Emit({0x51});
|
||||
if (preserveAccumulator) code.Emit({0x50});
|
||||
loadPointer();
|
||||
if (!access.StoreImmediate && access.Displacement != 0) {
|
||||
code.Emit({0x48, 0x8b, 0x80});
|
||||
code.U32(access.Displacement);
|
||||
}
|
||||
if (access.StoreImmediate) {
|
||||
code.Emit({0xc7, 0x40, 0x28});
|
||||
code.U32(access.Immediate);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,118 @@ def register_load(register):
|
|||
return len(prefix), prefix + load + rest + restore + bytes.fromhex("31 c0 c3")
|
||||
|
||||
|
||||
def fs_load(register, displacement):
|
||||
return bytes([0x64, 0x48 | (register >> 3) << 2, 0x8b, 0x04 | (register & 7) << 3, 0x25]) + struct.pack("<i", displacement)
|
||||
|
||||
|
||||
def displacement_load(register, displacement, flags, round_trip=False):
|
||||
saved = (3, 5, 6, 7, 12, 13, 14, 15)
|
||||
code = bytearray()
|
||||
failures = []
|
||||
|
||||
def emit(data):
|
||||
code.extend(data)
|
||||
|
||||
def check():
|
||||
emit(bytes.fromhex("0f 85 00 00 00 00"))
|
||||
failures.append(len(code) - 4)
|
||||
|
||||
def immediate(target, value):
|
||||
emit(bytes([0x48 | (target >> 3), 0xb8 | (target & 7)]) + struct.pack("<Q", value))
|
||||
|
||||
def stack_store(target, offset):
|
||||
emit(bytes([0x48 | (target >> 3) << 2, 0x89, 0x84 | (target & 7) << 3, 0x24]) + struct.pack("<i", offset))
|
||||
|
||||
for target in saved:
|
||||
emit((b"\x41" if target >= 8 else b"") + bytes([0x50 | (target & 7)]))
|
||||
emit(bytes.fromhex("48 8d a4 24 00 ff ff ff"))
|
||||
emit(fs_load(0, 0))
|
||||
stack_store(0, 136)
|
||||
positive = {8: 0x123456789abcdef0, 16: 0xfedcba9876543210, 40: 0xabcdef1278563412}
|
||||
for offset, value in positive.items():
|
||||
immediate(1, value)
|
||||
emit(bytes([0x48, 0x89, 0x48, offset]))
|
||||
immediate(1, 0x2468ace013579bdf)
|
||||
emit(bytes.fromhex("48 89 08"))
|
||||
if displacement == 0:
|
||||
emit(fs_load(2, 40))
|
||||
immediate(1, positive[40])
|
||||
emit(bytes.fromhex("48 39 ca"))
|
||||
check()
|
||||
else:
|
||||
value = 0xabcdef1289abcdef if round_trip else {
|
||||
**positive, -64: 0x8877665544332211, -40: 0x1122334455667788, -8: 0,
|
||||
}[displacement]
|
||||
immediate(1, value)
|
||||
stack_store(1, 136)
|
||||
markers = [0x1020304050607000 + target * 0x101 for target in range(16)]
|
||||
for target in range(16):
|
||||
if target != 4:
|
||||
immediate(target, markers[target])
|
||||
emit(b"\x68" + struct.pack("<I", flags) + b"\x9d")
|
||||
emit(bytes.fromhex("48 8d a4 24 70 ff ff ff 9c 8f 84 24 20 01 00 00 48 8d a4 24 90 00 00 00"))
|
||||
for offset in range(-128, 0, 8):
|
||||
emit(bytes([0x48, 0xc7, 0x44, 0x24, offset & 255]) + struct.pack("<I", 0x34560000 - offset))
|
||||
if round_trip:
|
||||
emit(bytes.fromhex("64 c7 04 25 28 00 00 00 ef cd ab 89"))
|
||||
load_offset = len(code)
|
||||
emit(fs_load(register, displacement))
|
||||
for target in range(16):
|
||||
stack_store(target, target * 8)
|
||||
emit(bytes.fromhex("48 8d a4 24 70 ff ff ff 9c 8f 84 24 10 01 00 00 48 8d a4 24 90 00 00 00"))
|
||||
emit(bytes.fromhex("48 8b 84 24 90 00 00 00 48 39 84 24 80 00 00 00"))
|
||||
check()
|
||||
for target in range(16):
|
||||
if target == 4:
|
||||
emit(bytes.fromhex("48 8d 04 24"))
|
||||
elif target == register:
|
||||
emit(bytes.fromhex("48 8b 84 24 88 00 00 00"))
|
||||
else:
|
||||
immediate(0, markers[target])
|
||||
emit(bytes.fromhex("48 39 84 24") + struct.pack("<i", target * 8))
|
||||
check()
|
||||
for offset in range(-128, 0, 8):
|
||||
emit(bytes([0x48, 0x81, 0x7c, 0x24, offset & 255]) + struct.pack("<I", 0x34560000 - offset))
|
||||
check()
|
||||
emit(bytes.fromhex("b8 2a 00 00 00 eb 05"))
|
||||
failure = len(code)
|
||||
emit(bytes.fromhex("b8 01 00 00 00 48 8d a4 24 00 01 00 00"))
|
||||
for target in reversed(saved):
|
||||
emit((b"\x41" if target >= 8 else b"") + bytes([0x58 | (target & 7)]))
|
||||
emit(b"\xc3")
|
||||
for offset in failures:
|
||||
struct.pack_into("<i", code, offset, failure - offset - 4)
|
||||
return load_offset, code
|
||||
|
||||
|
||||
def displacement_cases():
|
||||
for register in range(16):
|
||||
if register == 4:
|
||||
continue
|
||||
for flags in (0x202, 0xad7):
|
||||
for displacement, round_trip in ((0, False), (40, False), (-64, False), (-40, False),
|
||||
(-8, False), (40, True)):
|
||||
offset, body = displacement_load(register, displacement, flags, round_trip)
|
||||
image = make_image("register", "unwind", body=body)
|
||||
struct.pack_into("<IIQQQQQQ", image, 176, 7, 4, 0x800, 0x800, 0x800, 48, 48, 32)
|
||||
image[0x800:0x830] = bytes(48)
|
||||
struct.pack_into("<Q", image, 0x800, 0x8877665544332211)
|
||||
struct.pack_into("<Q", image, 0x818, 0x1122334455667788)
|
||||
name = f"displacement-{register}-{flags:x}-{displacement}-{'round-trip' if round_trip else 'load'}"
|
||||
yield name, image, 0x1240 + offset
|
||||
|
||||
|
||||
def displacement_bounds_cases():
|
||||
for register in (0, 12):
|
||||
for displacement in (1, 8, 0x10, 0x20, 0x27, 0x29, 0x2c, 0x30, -65, -0x80000000, 0x7fffffff):
|
||||
image = make_image("register", "unwind", body=fs_load(register, displacement) + b"\xc3")
|
||||
struct.pack_into("<IIQQQQQQ", image, 176, 7, 4, 0x800, 0x800, 0x800, 48, 48, 32)
|
||||
if register == 12:
|
||||
image[176:232], image[288:344] = image[288:344], image[176:232]
|
||||
error = f"Windows guest TLS load displacement {displacement} is not in the thread TLS block, 0 or the stack guard at 0x28"
|
||||
yield f"displacement-bounds-{register}-{displacement}", image, error
|
||||
|
||||
|
||||
def make_image(transfer, metadata, extent=None, body=None):
|
||||
image = fixture()
|
||||
image.extend(b"\x90" * 0x1000)
|
||||
|
|
@ -90,7 +202,7 @@ def main():
|
|||
with tempfile.TemporaryDirectory(prefix="anyps5-tls-coverage-") as directory:
|
||||
work = Path(directory)
|
||||
|
||||
def convert(name, image, error=None, tls_address=0x1240):
|
||||
def convert(name, image, error=None, tls_address=0x1240, displacement=0, error_offset=None):
|
||||
source = work / (name + ".elf")
|
||||
output = source.with_suffix(".exe")
|
||||
source.write_bytes(image)
|
||||
|
|
@ -98,10 +210,18 @@ def main():
|
|||
capture_output=True, text=True, timeout=30)
|
||||
if error is not None:
|
||||
assert result.returncode == 2 and error in result.stderr and not output.exists(), result
|
||||
if error_offset is not None:
|
||||
assert f"(offset {error_offset:#x})" in result.stderr, (name, result.stderr)
|
||||
return
|
||||
assert result.returncode == 0, (name, result.stdout, result.stderr)
|
||||
pe = output.read_bytes()
|
||||
assert pe_bytes_at(pe, 0x10000 + tls_address, 1) == b"\xe9", name
|
||||
patched_address = 0x10000 + tls_address
|
||||
patched = pe_bytes_at(pe, patched_address, 5)
|
||||
assert patched[0] == 0xe9, name
|
||||
if displacement != 0:
|
||||
stub_address = patched_address + 5 + struct.unpack_from("<i", patched, 1)[0]
|
||||
load = bytes.fromhex("48 8b 80") + struct.pack("<i", displacement)
|
||||
assert load in pe_bytes_at(pe, stub_address, 64), name
|
||||
assert pe_bytes_at(pe, 0x11850, len(TLS_LOAD)) == TLS_LOAD, name
|
||||
if os.name == "nt":
|
||||
executed = subprocess.run([str(output)], capture_output=True, timeout=30)
|
||||
|
|
@ -126,6 +246,26 @@ def main():
|
|||
convert("load-register-4", image, "Unsupported Windows guest TLS instruction")
|
||||
else:
|
||||
convert("load-register-" + str(register), image, tls_address=0x1240 + offset)
|
||||
for name, image, address in displacement_cases():
|
||||
displacement = struct.unpack_from("<i", image, address + 5)[0]
|
||||
convert(name, image, tls_address=address, displacement=displacement)
|
||||
for name, image, error in displacement_bounds_cases():
|
||||
convert(name, image, error, error_offset=0x1240)
|
||||
rejected = {
|
||||
"rsp-displacement": fs_load(4, 40),
|
||||
"dword-load": bytes.fromhex("64 8b 04 25 28 00 00 00"),
|
||||
"register-address": bytes.fromhex("64 48 8b 00"),
|
||||
"gs-load": bytes.fromhex("65 48 8b 04 25 28 00 00 00"),
|
||||
"rex-b-load": bytes.fromhex("64 49 8b 04 25 28 00 00 00"),
|
||||
"rex-x-load": bytes.fromhex("64 4a 8b 04 25 28 00 00 00"),
|
||||
"compare": bytes.fromhex("64 48 3b 04 25 28 00 00 00"),
|
||||
"subtract": bytes.fromhex("64 48 2b 04 25 28 00 00 00"),
|
||||
"add": bytes.fromhex("64 48 03 04 25 28 00 00 00"),
|
||||
"xor": bytes.fromhex("64 48 33 04 25 28 00 00 00"),
|
||||
}
|
||||
for name, instruction in rejected.items():
|
||||
convert(name, make_image("register", "unwind", body=instruction + b"\xc3"),
|
||||
"Unsupported Windows guest TLS instruction")
|
||||
conflicting = make_image("register", "symbol")
|
||||
unwind = make_image("register", "unwind", 0x51)
|
||||
conflicting[0x900:0x9a0] = unwind[0x900:0x9a0]
|
||||
|
|
|
|||
Loading…
Reference in New Issue