From 8746807b659213894366263359c733e39bdb79e2 Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Fri, 18 Sep 2026 10:20:38 -0400 Subject: [PATCH] [PPC] Lift string operations, recognize copy pairs, and fix blrl returns Lift lswi and stswi with word-sized memory accesses and exact partial-word handling. Support register wrapping, byte ordering, and PPC64 zero-extension. Combine matching lswi/stswi pairs into a memory-copy intrinsic while preserving loaded registers and respecting basic-block boundaries. Preserve the original LR target and link-register update for blrl. Recognize GOT helpers as contextual returns without decoding trailing data as code, while retaining ordinary indirect calls and fallthrough. Add lifting regression tests for 32/64-bit PowerPC in both endiannesses. --- arch/powerpc/arch_ppc.cpp | 40 +++++++++- arch/powerpc/il.cpp | 107 +++++++++++++++++++++++++ arch/powerpc/il.h | 2 + arch/powerpc/test_lift.py | 163 ++++++++++++++++++++++++++++++++++++++ defaultarch.cpp | 5 +- 5 files changed, 312 insertions(+), 5 deletions(-) create mode 100644 arch/powerpc/test_lift.py diff --git a/arch/powerpc/arch_ppc.cpp b/arch/powerpc/arch_ppc.cpp index ef6f7dc417..365ad20a4e 100644 --- a/arch/powerpc/arch_ppc.cpp +++ b/arch/powerpc/arch_ppc.cpp @@ -440,6 +440,12 @@ class PowerpcArchitecture: public Architecture } case PPC_ID_BCLRx: + // Keep linked branches as calls in LLIL so dataflow can distinguish + // a GOT-address helper returning through incoming LR from an indirect call. + if ((bo & 0x14) == 0x14) + result.AddBranch(FunctionReturn); + break; + case PPC_ID_VLE_SE_BLRx: if (!instruction.flags.lk && (bo & 0x14) == 0x14) result.AddBranch(FunctionReturn); @@ -661,6 +667,11 @@ class PowerpcArchitecture: public Architecture return ""; } + virtual BNIntrinsicClass GetIntrinsicClass(uint32_t intrinsic) override + { + return intrinsic == PPC_INTRIN_COPY_STRING_WORDS ? MemoryIntrinsicClass : GeneralIntrinsicClass; + } + virtual string GetIntrinsicName(uint32_t intrinsic) override { switch (intrinsic) @@ -669,6 +680,8 @@ class PowerpcArchitecture: public Architecture return "__builtin_clz"; case PPC_INTRIN_FRSP: return "float_round"; + case PPC_INTRIN_COPY_STRING_WORDS: + return "copy_string_words"; default: if ((decodeFlags & DECODE_FLAGS_PS)) { @@ -682,9 +695,7 @@ class PowerpcArchitecture: public Architecture virtual std::vector GetAllIntrinsics() override { - // Highest intrinsic number currently is PPC_PS_INTRIN_END. - // If new extensions are added please update this code. - std::vector result{PPC_PS_INTRIN_END}; + std::vector result{PPC_INTRIN_COPY_STRING_WORDS}; // Double check someone didn't insert a new intrinsic at the beginning of our enum since we rely // on it to fill the next array. @@ -728,6 +739,10 @@ class PowerpcArchitecture: public Architecture return {NameAndType(Type::IntegerType(4, false))}; case PPC_INTRIN_FRSP: return {NameAndType(Type::FloatType(4))}; + case PPC_INTRIN_COPY_STRING_WORDS: + return {NameAndType("dest", Type::PointerType(GetAddressSize(), Type::IntegerType(1, false))), + NameAndType("source", Type::PointerType(GetAddressSize(), Type::IntegerType(1, false))), + NameAndType("count", Type::IntegerType(4, false))}; // for now, quantize is operating on the float in, and the gqr that holds the scale default: if ((decodeFlags & DECODE_FLAGS_PS)) @@ -762,6 +777,9 @@ class PowerpcArchitecture: public Architecture return {Type::IntegerType(4, false)}; case PPC_INTRIN_FRSP: return {Type::FloatType(4)}; + case PPC_INTRIN_COPY_STRING_WORDS: + // Up to eight words loaded by lswi, preserved as native-width GPR values. + return vector>>(8, Type::IntegerType(GetAddressSize(), false)); default: if ((decodeFlags & DECODE_FLAGS_PS)) { @@ -775,6 +793,7 @@ class PowerpcArchitecture: public Architecture virtual bool GetInstructionLowLevelIL(const uint8_t* data, uint64_t addr, size_t& len, LowLevelILFunction& il) override { + const size_t available = len; size_t instructionLength = GetInstructionLength(data, len, decodeFlags); if (instructionLength == 0) { @@ -783,7 +802,6 @@ class PowerpcArchitecture: public Architecture } len = instructionLength; - Instruction instruction; if (!FillInstruction(&instruction, data, instructionLength, addr, DECODE_FLAGS_VLE_TRANSLATE)) { @@ -792,6 +810,20 @@ class PowerpcArchitecture: public Architecture return false; } + // The default lifter supplies the remaining bytes of the current basic block. + // Keep instruction decoding at four bytes; only lifting consumes the pair. + if (!(decodeFlags & DECODE_FLAGS_VLE) && instruction.id == PPC_ID_LSWI && available >= 8 + && !il.GetLabelForAddress(this, addr + 4)) + { + Instruction store; + if (FillInstruction(&store, data + 4, 4, addr + 4) + && GetLowLevelILForPPCStringCopy(this, il, &instruction, &store)) + { + len = 8; + return true; + } + } + return GetLowLevelILForPPCInstruction(this, il, &instruction, addr); } diff --git a/arch/powerpc/il.cpp b/arch/powerpc/il.cpp index 043acf08ac..dac9c97663 100644 --- a/arch/powerpc/il.cpp +++ b/arch/powerpc/il.cpp @@ -359,6 +359,15 @@ static bool LiftBranches(Architecture* arch, LowLevelILFunction &il, const Instr if (instruction->flags.lk) { + if (blr && !wasConditionalBranch) + { + // BLRL branches through the old LR while setting LR to the next + // instruction. Preserve that write if analysis turns the call into a return. + il.AddInstruction(il.SetRegister(addressSize_l, LLIL_TEMP(0), expr)); + il.AddInstruction(il.SetRegister(addressSize_l, PPC_REG_LR, + il.ConstPointer(addressSize_l, addr + instruction->numBytes))); + expr = il.Register(addressSize_l, LLIL_TEMP(0)); + } il.AddInstruction(il.Call(expr)); if (wasConditionalBranch) il.AddInstruction(il.Goto(*falseLabel)); @@ -500,6 +509,94 @@ static void load_float(LowLevelILFunction& il, } } +bool GetLowLevelILForPPCStringCopy(Architecture* arch, LowLevelILFunction& il, Instruction* load, Instruction* store) +{ + if (load->id != PPC_ID_LSWI || store->id != PPC_ID_STSWI + || load->numOperands != 3 || store->numOperands != 3 + || load->operands[0].reg != store->operands[0].reg + || load->operands[2].uimm != store->operands[2].uimm) + return false; + + const uint32_t firstReg = load->operands[0].reg; + const uint32_t sourceReg = load->operands[1].reg; + const uint32_t destReg = store->operands[1].reg; + const uint32_t count = load->operands[2].uimm ? load->operands[2].uimm : 32; + if (firstReg == PPC_REG_GPR0 && sourceReg == PPC_REG_GPR0) + return false; // Invalid lswi form. + + vector outputs; + for (uint32_t offset = 0; offset < count; offset += 4) + { + const uint32_t reg = PPC_REG_GPR0 + ((firstReg - PPC_REG_GPR0 + offset / 4) % 32); + // Reject invalid loads and stores whose destination address is changed by the load. + if ((sourceReg != PPC_REG_GPR0 && reg == sourceReg) || (destReg != PPC_REG_GPR0 && reg == destReg)) + return false; + outputs.push_back(RegisterOrFlag::Register(reg)); + } + + // Snapshot all source bytes before writing the destination, including when the + // ranges overlap. Outputs retain the loaded words (zero-padded and zero-extended) + // for later register uses, exactly as with the separate lswi and stswi. + const size_t addressSize = arch->GetAddressSize(); + il.AddInstruction(il.Intrinsic(outputs, PPC_INTRIN_COPY_STRING_WORDS, { + operToIL(il, &store->operands[1], OTI_GPR0_ZERO, 0, addressSize), + operToIL(il, &load->operands[1], OTI_GPR0_ZERO, 0, addressSize), il.Const(4, count)})); + return true; +} + +static void LiftStringWord(Architecture* arch, LowLevelILFunction& il, Instruction* instruction) +{ + const size_t addressSize = arch->GetAddressSize(); + const bool littleEndian = arch->GetEndianness() == LittleEndian; + const bool load = instruction->id == PPC_ID_LSWI; + const uint32_t count = instruction->operands[2].uimm ? instruction->operands[2].uimm : 32; + const uint32_t firstReg = instruction->operands[0].reg; + const ExprId base = operToIL(il, &instruction->operands[1], OTI_GPR0_ZERO, 0, addressSize); + auto address = [&](uint32_t offset) { + return offset ? il.Add(addressSize, base, il.Const(addressSize, offset)) : base; + }; + // String instructions place bytes left to right in the low word of each GPR. + auto loadBytes = [&](size_t size, uint32_t offset) { + ExprId value = il.Load(size, address(offset)); + return littleEndian && size > 1 ? il.ByteSwap(size, value) : value; + }; + auto storeBytes = [&](size_t size, uint32_t offset, ExprId value) { + if (littleEndian && size > 1) + value = il.ByteSwap(size, value); + il.AddInstruction(il.Store(size, address(offset), value)); + }; + + for (uint32_t offset = 0; offset < count; offset += 4) + { + const uint32_t reg = PPC_REG_GPR0 + ((firstReg - PPC_REG_GPR0 + offset / 4) % 32); + const uint32_t remaining = count - offset; + const size_t size = remaining >= 4 ? 4 : remaining >= 2 ? 2 : 1; + if (load) + { + ExprId value = loadBytes(size, offset); + if (size < 4) + value = il.ShiftLeft(4, il.ZeroExtend(4, value), il.Const(1, (4 - size) * 8)); + // A three-byte tail uses a halfword and a byte, without reading past the string. + if (remaining == 3) + value = il.Or(4, value, il.ShiftLeft(4, il.ZeroExtend(4, loadBytes(1, offset + 2)), + il.Const(1, 8))); + if (addressSize == 8) + value = il.ZeroExtend(8, value); + il.AddInstruction(il.SetRegister(addressSize, reg, value)); + } + else + { + ExprId value = il.Register(4, reg); + if (size < 4) + value = il.LowPart(size, il.LogicalShiftRight(4, value, il.Const(1, (4 - size) * 8))); + storeBytes(size, offset, value); + if (remaining == 3) + storeBytes(1, offset + 2, + il.LowPart(1, il.LogicalShiftRight(4, il.Register(4, reg), il.Const(1, 8)))); + } + } +} + /* returns TRUE - if this IL continues FALSE - if this IL terminates a block */ bool GetLowLevelILForPPCInstruction(Architecture *arch, LowLevelILFunction &il, @@ -937,6 +1034,11 @@ bool GetLowLevelILForPPCInstruction(Architecture *arch, LowLevelILFunction &il, } break; + case PPC_ID_LSWI: + REQUIRE3OPS + LiftStringWord(arch, il, instruction); + break; + case PPC_ID_LMW: REQUIRE2OPS for (i = oper0->reg; i <= PPC_REG_GPR31; ++i) @@ -1318,6 +1420,11 @@ bool GetLowLevelILForPPCInstruction(Architecture *arch, LowLevelILFunction &il, il.AddInstruction(ei0); break; + case PPC_ID_STSWI: + REQUIRE3OPS + LiftStringWord(arch, il, instruction); + break; + case PPC_ID_STMW: REQUIRE2OPS for (i = oper0->reg; i <= PPC_REG_GPR31; ++i) diff --git a/arch/powerpc/il.h b/arch/powerpc/il.h index 1e8f67c21d..8402859af6 100644 --- a/arch/powerpc/il.h +++ b/arch/powerpc/il.h @@ -217,8 +217,10 @@ enum PPCIntrinsic : uint32_t PPC_PS_INTRIN_QUANTIZE, PPC_PS_INTRIN_DEQUANTIZE, PPC_PS_INTRIN_END, + PPC_INTRIN_COPY_STRING_WORDS, PPC_INTRIN_INVALID = 0xFFFFFFFF, }; bool GetLowLevelILForPPCInstruction(Architecture *arch, LowLevelILFunction& il, Instruction* instruction, uint64_t addr); +bool GetLowLevelILForPPCStringCopy(Architecture* arch, LowLevelILFunction& il, Instruction* load, Instruction* store); diff --git a/arch/powerpc/test_lift.py b/arch/powerpc/test_lift.py new file mode 100644 index 0000000000..0cd8ebc8fb --- /dev/null +++ b/arch/powerpc/test_lift.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python + +test_cases = \ +[ + # lswi: full words with one-byte and three-byte tails. + ('ppc', b'\x7c\xd0\x2c\xaa', 'LLIL_SET_REG.d(r6,LLIL_LOAD.d(LLIL_REG.d(r16))); LLIL_SET_REG.d(r7,LLIL_LSL.d(LLIL_ZX.d(LLIL_LOAD.b(LLIL_ADD.d(LLIL_REG.d(r16),LLIL_CONST.d(0x4)))),LLIL_CONST.b(0x18)))'), + ('ppc', b'\x7c\xd0\x3c\xaa', 'LLIL_SET_REG.d(r6,LLIL_LOAD.d(LLIL_REG.d(r16))); LLIL_SET_REG.d(r7,LLIL_OR.d(LLIL_LSL.d(LLIL_ZX.d(LLIL_LOAD.w(LLIL_ADD.d(LLIL_REG.d(r16),LLIL_CONST.d(0x4)))),LLIL_CONST.b(0x10)),LLIL_LSL.d(LLIL_ZX.d(LLIL_LOAD.b(LLIL_ADD.d(LLIL_REG.d(r16),LLIL_CONST.d(0x6)))),LLIL_CONST.b(0x8))))'), + ('ppc_le', b'\xaa\x2c\xd0\x7c', 'LLIL_SET_REG.d(r6,LLIL_BSWAP.d(LLIL_LOAD.d(LLIL_REG.d(r16)))); LLIL_SET_REG.d(r7,LLIL_LSL.d(LLIL_ZX.d(LLIL_LOAD.b(LLIL_ADD.d(LLIL_REG.d(r16),LLIL_CONST.d(0x4)))),LLIL_CONST.b(0x18)))'), + ('ppc_le', b'\xaa\x3c\xd0\x7c', 'LLIL_SET_REG.d(r6,LLIL_BSWAP.d(LLIL_LOAD.d(LLIL_REG.d(r16)))); LLIL_SET_REG.d(r7,LLIL_OR.d(LLIL_LSL.d(LLIL_ZX.d(LLIL_BSWAP.w(LLIL_LOAD.w(LLIL_ADD.d(LLIL_REG.d(r16),LLIL_CONST.d(0x4))))),LLIL_CONST.b(0x10)),LLIL_LSL.d(LLIL_ZX.d(LLIL_LOAD.b(LLIL_ADD.d(LLIL_REG.d(r16),LLIL_CONST.d(0x6)))),LLIL_CONST.b(0x8))))'), + ('ppc64', b'\x7c\xd0\x2c\xaa', 'LLIL_SET_REG.q(r6,LLIL_ZX.q(LLIL_LOAD.d(LLIL_REG.q(r16)))); LLIL_SET_REG.q(r7,LLIL_ZX.q(LLIL_LSL.d(LLIL_ZX.d(LLIL_LOAD.b(LLIL_ADD.q(LLIL_REG.q(r16),LLIL_CONST.q(0x4)))),LLIL_CONST.b(0x18))))'), + ('ppc64', b'\x7c\xd0\x3c\xaa', 'LLIL_SET_REG.q(r6,LLIL_ZX.q(LLIL_LOAD.d(LLIL_REG.q(r16)))); LLIL_SET_REG.q(r7,LLIL_ZX.q(LLIL_OR.d(LLIL_LSL.d(LLIL_ZX.d(LLIL_LOAD.w(LLIL_ADD.q(LLIL_REG.q(r16),LLIL_CONST.q(0x4)))),LLIL_CONST.b(0x10)),LLIL_LSL.d(LLIL_ZX.d(LLIL_LOAD.b(LLIL_ADD.q(LLIL_REG.q(r16),LLIL_CONST.q(0x6)))),LLIL_CONST.b(0x8)))))'), + ('ppc64_le', b'\xaa\x2c\xd0\x7c', 'LLIL_SET_REG.q(r6,LLIL_ZX.q(LLIL_BSWAP.d(LLIL_LOAD.d(LLIL_REG.q(r16))))); LLIL_SET_REG.q(r7,LLIL_ZX.q(LLIL_LSL.d(LLIL_ZX.d(LLIL_LOAD.b(LLIL_ADD.q(LLIL_REG.q(r16),LLIL_CONST.q(0x4)))),LLIL_CONST.b(0x18))))'), + ('ppc64_le', b'\xaa\x3c\xd0\x7c', 'LLIL_SET_REG.q(r6,LLIL_ZX.q(LLIL_BSWAP.d(LLIL_LOAD.d(LLIL_REG.q(r16))))); LLIL_SET_REG.q(r7,LLIL_ZX.q(LLIL_OR.d(LLIL_LSL.d(LLIL_ZX.d(LLIL_BSWAP.w(LLIL_LOAD.w(LLIL_ADD.q(LLIL_REG.q(r16),LLIL_CONST.q(0x4))))),LLIL_CONST.b(0x10)),LLIL_LSL.d(LLIL_ZX.d(LLIL_LOAD.b(LLIL_ADD.q(LLIL_REG.q(r16),LLIL_CONST.q(0x6)))),LLIL_CONST.b(0x8)))))'), + + # stswi: full words with one-byte and three-byte tails. + ('ppc', b'\x7c\xd0\x2d\xaa', 'LLIL_STORE.d(LLIL_REG.d(r16),LLIL_REG.d(r6)); LLIL_STORE.b(LLIL_ADD.d(LLIL_REG.d(r16),LLIL_CONST.d(0x4)),LLIL_LOW_PART.b(LLIL_LSR.d(LLIL_REG.d(r7),LLIL_CONST.b(0x18))))'), + ('ppc', b'\x7c\xd0\x3d\xaa', 'LLIL_STORE.d(LLIL_REG.d(r16),LLIL_REG.d(r6)); LLIL_STORE.w(LLIL_ADD.d(LLIL_REG.d(r16),LLIL_CONST.d(0x4)),LLIL_LOW_PART.w(LLIL_LSR.d(LLIL_REG.d(r7),LLIL_CONST.b(0x10)))); LLIL_STORE.b(LLIL_ADD.d(LLIL_REG.d(r16),LLIL_CONST.d(0x6)),LLIL_LOW_PART.b(LLIL_LSR.d(LLIL_REG.d(r7),LLIL_CONST.b(0x8))))'), + ('ppc_le', b'\xaa\x2d\xd0\x7c', 'LLIL_STORE.d(LLIL_REG.d(r16),LLIL_BSWAP.d(LLIL_REG.d(r6))); LLIL_STORE.b(LLIL_ADD.d(LLIL_REG.d(r16),LLIL_CONST.d(0x4)),LLIL_LOW_PART.b(LLIL_LSR.d(LLIL_REG.d(r7),LLIL_CONST.b(0x18))))'), + ('ppc_le', b'\xaa\x3d\xd0\x7c', 'LLIL_STORE.d(LLIL_REG.d(r16),LLIL_BSWAP.d(LLIL_REG.d(r6))); LLIL_STORE.w(LLIL_ADD.d(LLIL_REG.d(r16),LLIL_CONST.d(0x4)),LLIL_BSWAP.w(LLIL_LOW_PART.w(LLIL_LSR.d(LLIL_REG.d(r7),LLIL_CONST.b(0x10))))); LLIL_STORE.b(LLIL_ADD.d(LLIL_REG.d(r16),LLIL_CONST.d(0x6)),LLIL_LOW_PART.b(LLIL_LSR.d(LLIL_REG.d(r7),LLIL_CONST.b(0x8))))'), + ('ppc64', b'\x7c\xd0\x2d\xaa', 'LLIL_STORE.d(LLIL_REG.q(r16),LLIL_REG.d(r6)); LLIL_STORE.b(LLIL_ADD.q(LLIL_REG.q(r16),LLIL_CONST.q(0x4)),LLIL_LOW_PART.b(LLIL_LSR.d(LLIL_REG.d(r7),LLIL_CONST.b(0x18))))'), + ('ppc64', b'\x7c\xd0\x3d\xaa', 'LLIL_STORE.d(LLIL_REG.q(r16),LLIL_REG.d(r6)); LLIL_STORE.w(LLIL_ADD.q(LLIL_REG.q(r16),LLIL_CONST.q(0x4)),LLIL_LOW_PART.w(LLIL_LSR.d(LLIL_REG.d(r7),LLIL_CONST.b(0x10)))); LLIL_STORE.b(LLIL_ADD.q(LLIL_REG.q(r16),LLIL_CONST.q(0x6)),LLIL_LOW_PART.b(LLIL_LSR.d(LLIL_REG.d(r7),LLIL_CONST.b(0x8))))'), + ('ppc64_le', b'\xaa\x2d\xd0\x7c', 'LLIL_STORE.d(LLIL_REG.q(r16),LLIL_BSWAP.d(LLIL_REG.d(r6))); LLIL_STORE.b(LLIL_ADD.q(LLIL_REG.q(r16),LLIL_CONST.q(0x4)),LLIL_LOW_PART.b(LLIL_LSR.d(LLIL_REG.d(r7),LLIL_CONST.b(0x18))))'), + ('ppc64_le', b'\xaa\x3d\xd0\x7c', 'LLIL_STORE.d(LLIL_REG.q(r16),LLIL_BSWAP.d(LLIL_REG.d(r6))); LLIL_STORE.w(LLIL_ADD.q(LLIL_REG.q(r16),LLIL_CONST.q(0x4)),LLIL_BSWAP.w(LLIL_LOW_PART.w(LLIL_LSR.d(LLIL_REG.d(r7),LLIL_CONST.b(0x10))))); LLIL_STORE.b(LLIL_ADD.q(LLIL_REG.q(r16),LLIL_CONST.q(0x6)),LLIL_LOW_PART.b(LLIL_LSR.d(LLIL_REG.d(r7),LLIL_CONST.b(0x8))))'), + + # Matching lswi/stswi pairs copying 8 and 16 bytes. + ('ppc', b'\x7d\x69\x44\xaa\x7d\x63\x45\xaa', 'LLIL_INTRINSIC([r11,r12],copy_string_words,[LLIL_REG.d(r3),LLIL_REG.d(r9),LLIL_CONST.d(0x8)])'), + ('ppc', b'\x7c\xa9\x84\xaa\x7c\xa3\x85\xaa', 'LLIL_INTRINSIC([r5,r6,r7,r8],copy_string_words,[LLIL_REG.d(r3),LLIL_REG.d(r9),LLIL_CONST.d(0x10)])'), + ('ppc_le', b'\xaa\x44\x69\x7d\xaa\x45\x63\x7d', 'LLIL_INTRINSIC([r11,r12],copy_string_words,[LLIL_REG.d(r3),LLIL_REG.d(r9),LLIL_CONST.d(0x8)])'), + ('ppc_le', b'\xaa\x84\xa9\x7c\xaa\x85\xa3\x7c', 'LLIL_INTRINSIC([r5,r6,r7,r8],copy_string_words,[LLIL_REG.d(r3),LLIL_REG.d(r9),LLIL_CONST.d(0x10)])'), + ('ppc64', b'\x7d\x69\x44\xaa\x7d\x63\x45\xaa', 'LLIL_INTRINSIC([r11,r12],copy_string_words,[LLIL_REG.q(r3),LLIL_REG.q(r9),LLIL_CONST.d(0x8)])'), + ('ppc64', b'\x7c\xa9\x84\xaa\x7c\xa3\x85\xaa', 'LLIL_INTRINSIC([r5,r6,r7,r8],copy_string_words,[LLIL_REG.q(r3),LLIL_REG.q(r9),LLIL_CONST.d(0x10)])'), + ('ppc64_le', b'\xaa\x44\x69\x7d\xaa\x45\x63\x7d', 'LLIL_INTRINSIC([r11,r12],copy_string_words,[LLIL_REG.q(r3),LLIL_REG.q(r9),LLIL_CONST.d(0x8)])'), + ('ppc64_le', b'\xaa\x84\xa9\x7c\xaa\x85\xa3\x7c', 'LLIL_INTRINSIC([r5,r6,r7,r8],copy_string_words,[LLIL_REG.q(r3),LLIL_REG.q(r9),LLIL_CONST.d(0x10)])'), + + # blrl: a GOT helper followed by data, and an ordinary indirect call. + ('ppc', b'\x4e\x80\x00\x21\x10\x09\x60\x20\x00\x00\x00\x00\x00\x00\x00\x00', 'LLIL_SET_REG.d(temp0,LLIL_REG.d(lr)); LLIL_SET_REG.d(lr,LLIL_CONST.d(0x4)); LLIL_RET(LLIL_REG.d(temp0))'), + ('ppc', b'\x7f\xe8\x02\xa6\x7c\x68\x03\xa6\x4e\x80\x00\x21\x38\x60\x00\x07\x7f\xe8\x03\xa6\x4e\x80\x00\x20', 'LLIL_SET_REG.d(r31,LLIL_REG.d(lr)); LLIL_SET_REG.d(lr,LLIL_REG.d(r3)); LLIL_SET_REG.d(temp0,LLIL_REG.d(lr)); LLIL_SET_REG.d(lr,LLIL_CONST.d(0xC)); LLIL_CALL(LLIL_REG.d(temp0)); LLIL_SET_REG.d(r3,LLIL_CONST.d(0x7)); LLIL_SET_REG.d(lr,LLIL_REG.d(r31)); LLIL_RET(LLIL_REG.d(lr))'), + ('ppc_le', b'\x21\x00\x80\x4e\x20\x60\x09\x10\x00\x00\x00\x00\x00\x00\x00\x00', 'LLIL_SET_REG.d(temp0,LLIL_REG.d(lr)); LLIL_SET_REG.d(lr,LLIL_CONST.d(0x4)); LLIL_RET(LLIL_REG.d(temp0))'), + ('ppc_le', b'\xa6\x02\xe8\x7f\xa6\x03\x68\x7c\x21\x00\x80\x4e\x07\x00\x60\x38\xa6\x03\xe8\x7f\x20\x00\x80\x4e', 'LLIL_SET_REG.d(r31,LLIL_REG.d(lr)); LLIL_SET_REG.d(lr,LLIL_REG.d(r3)); LLIL_SET_REG.d(temp0,LLIL_REG.d(lr)); LLIL_SET_REG.d(lr,LLIL_CONST.d(0xC)); LLIL_CALL(LLIL_REG.d(temp0)); LLIL_SET_REG.d(r3,LLIL_CONST.d(0x7)); LLIL_SET_REG.d(lr,LLIL_REG.d(r31)); LLIL_RET(LLIL_REG.d(lr))'), + ('ppc64', b'\x4e\x80\x00\x21\x10\x09\x60\x20\x00\x00\x00\x00\x00\x00\x00\x00', 'LLIL_SET_REG.q(temp0,LLIL_REG.q(lr)); LLIL_SET_REG.q(lr,LLIL_CONST.q(0x4)); LLIL_RET(LLIL_REG.q(temp0))'), + ('ppc64', b'\x7f\xe8\x02\xa6\x7c\x68\x03\xa6\x4e\x80\x00\x21\x38\x60\x00\x07\x7f\xe8\x03\xa6\x4e\x80\x00\x20', 'LLIL_SET_REG.q(r31,LLIL_REG.q(lr)); LLIL_SET_REG.q(lr,LLIL_REG.q(r3)); LLIL_SET_REG.q(temp0,LLIL_REG.q(lr)); LLIL_SET_REG.q(lr,LLIL_CONST.q(0xC)); LLIL_CALL(LLIL_REG.q(temp0)); LLIL_SET_REG.q(r3,LLIL_CONST.q(0x7)); LLIL_SET_REG.q(lr,LLIL_REG.q(r31)); LLIL_RET(LLIL_REG.q(lr))'), + ('ppc64_le', b'\x21\x00\x80\x4e\x20\x60\x09\x10\x00\x00\x00\x00\x00\x00\x00\x00', 'LLIL_SET_REG.q(temp0,LLIL_REG.q(lr)); LLIL_SET_REG.q(lr,LLIL_CONST.q(0x4)); LLIL_RET(LLIL_REG.q(temp0))'), + ('ppc64_le', b'\xa6\x02\xe8\x7f\xa6\x03\x68\x7c\x21\x00\x80\x4e\x07\x00\x60\x38\xa6\x03\xe8\x7f\x20\x00\x80\x4e', 'LLIL_SET_REG.q(r31,LLIL_REG.q(lr)); LLIL_SET_REG.q(lr,LLIL_REG.q(r3)); LLIL_SET_REG.q(temp0,LLIL_REG.q(lr)); LLIL_SET_REG.q(lr,LLIL_CONST.q(0xC)); LLIL_CALL(LLIL_REG.q(temp0)); LLIL_SET_REG.q(r3,LLIL_CONST.q(0x7)); LLIL_SET_REG.q(lr,LLIL_REG.q(r31)); LLIL_RET(LLIL_REG.q(lr))'), +] + +import sys +import binaryninja +from binaryninja import binaryview +from binaryninja import lowlevelil +from binaryninja.enums import LowLevelILOperation + + +def il2str(il): + sz_lookup = {1: '.b', 2: '.w', 4: '.d', 8: '.q', 16: '.o'} + if isinstance(il, lowlevelil.LowLevelILInstruction): + size_code = sz_lookup.get(il.size, '?') if il.size else '' + # PowerPC names the absence of flag writes "none". + flags = getattr(il, 'flags', None) + flags_code = '' if not flags or flags == 'none' else '{%s}' % flags + + # print size-specified IL constants in hex + if il.operation in [ + LowLevelILOperation.LLIL_CONST, + LowLevelILOperation.LLIL_CONST_PTR + ] and il.size: + tmp = il.operands[0] + if tmp < 0: + tmp = (1 << (il.size * 8)) + tmp + tmp = '0x%X' % tmp if il.size else '%d' % il.size + return 'LLIL_CONST%s(%s)' % (size_code, tmp) + else: + return '%s%s%s(%s)' % (il.operation.name, size_code, flags_code, + ','.join([il2str(o) for o in il.operands])) + elif isinstance(il, list): + return '[' + ','.join([il2str(x) for x in il]) + ']' + elif type(il) == lowlevelil.LowLevelILFlagCondition: + return f'LowLevelILFlagCondition.{il.name}' + else: + return str(il) + + +def instr_to_il(data, arch_name): + # blr return fence, in the architecture's byte order + RETURN = b'\x20\x00\x80\x4e' if arch_name.endswith( + '_le') else b'\x4e\x80\x00\x20' + platform = binaryninja.Architecture[arch_name].standalone_platform + with binaryview.BinaryView.new(data + RETURN) as bv: + bv.add_function(0, plat=platform) + bv.update_analysis_and_wait() + assert len(bv.functions) == 1 + + # Exclude only the appended fence, preserving returns in the input itself. + return '; '.join( + il2str(il) + for block in bv.functions[0].lifted_il + for il in block + if il.address < len(data)) + + +def il_str_to_tree(ilstr): + result = '' + depth = 0 + for c in ilstr: + if c == '(': + result += '\n' + depth += 1 + result += ' ' * depth + elif c == ')': + depth -= 1 + elif c == ',': + result += '\n' + result += ' ' * depth + elif c == ';': + result += '\n' + depth = 0 + elif c == ' ': + pass + else: + result += c + return result + + +def fail_test(message): + raise AssertionError(message) + + +def check_il(test_i, arch_name, data, expected, actual): + if '?' in expected: + fail_test( + 'INVALID EXPECTED LLIL AT TEST %s (%s)!\n\t input: %s\n\texpected: %s' + % (test_i, arch_name, data.hex(), expected)) + + if '?' in actual: + fail_test( + 'INVALID ACTUAL LLIL AT TEST %s (%s)!\n\t input: %s\n\t actual: %s\n\t tree:\n%s' + % (test_i, arch_name, data.hex(), actual, il_str_to_tree(actual))) + + if actual != expected: + fail_test( + 'MISMATCH AT TEST %s (%s)!\n\t input: %s\n\texpected: %s\n\t actual: %s\n\t tree:\n%s' + % (test_i, arch_name, data.hex(), expected, actual, + il_str_to_tree(actual))) + + +def run_all_tests(): + for (test_i, (arch_name, data, + expected)) in enumerate(test_cases): + actual = instr_to_il(data, arch_name) + check_il(test_i, arch_name, data, expected, actual) + + +def test_all(): + run_all_tests() + + +if __name__ == '__main__': + run_all_tests() + print('success!') + sys.exit(0) + +if __name__ == 'test_lift': + test_all() + print('success!') diff --git a/defaultarch.cpp b/defaultarch.cpp index d4a1b837b5..edced29e9e 100644 --- a/defaultarch.cpp +++ b/defaultarch.cpp @@ -394,7 +394,10 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly { Ref ilFunc = new LowLevelILFunction(location.arch, nullptr); location.arch->GetInstructionLowLevelIL(opcode, location.address, maxLen, *ilFunc); - if (ilFunc->GetInstructionCount() && ((*ilFunc)[0].operation == LLIL_CALL)) + // A linked return may save its target and update the link register before the call. + // Match the final call, which is the instruction translated to LLIL_RET during lifting. + if (ilFunc->GetInstructionCount() + && ((*ilFunc)[ilFunc->GetInstructionCount() - 1].operation == LLIL_CALL)) contextualFunctionReturns[location] = true; } }