/* * Copyright (c) 2022 Trail of Bits, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gtest/gtest.h" namespace { const static std::unordered_map> reg_to_accessor = { {"r15", [](AArch32State &st) -> uint32_t & { return st.gpr.r15.dword; }}, {"sp", [](AArch32State &st) -> uint32_t & { return st.gpr.r13.dword; }}, {"r1", [](AArch32State &st) -> uint32_t & { return st.gpr.r1.dword; }}}; std::optional GetFlows(std::string_view bytes, uint64_t address, uint64_t tm_val) { llvm::LLVMContext context; auto arch = remill::Arch::Build(&context, remill::OSName::kOSLinux, remill::ArchName::kArchAArch32LittleEndian); auto sems = remill::LoadArchSemantics(arch.get()); remill::DecodingContext dec_context; dec_context.UpdateContextReg(std::string(remill::kThumbModeRegName), tm_val); CHECK(dec_context.HasValueForReg("TMReg")); remill::Instruction insn; if (!arch->DecodeInstruction(address, bytes, insn, dec_context)) { return std::nullopt; } else { return insn; } } } // namespace using MemoryModifier = std::function; struct RegisterPrecondition { std::string register_name; uint32_t enforced_value; }; class TestOutputSpec { public: uint64_t addr; std::string target_bytes; private: remill::Instruction::Category expected_category; std::vector register_preconditions; std::vector register_postconditions; std::vector initial_memory_conditions; void ApplyCondition(AArch32State &state, std::string reg, uint32_t value) const { auto accessor = reg_to_accessor.find(reg); if (accessor != reg_to_accessor.end()) { accessor->second(state) = value; } } void CheckCondition(AArch32State &state, std::string reg, uint32_t value) const { auto accessor = reg_to_accessor.find(reg); if (accessor != reg_to_accessor.end()) { CHECK_EQ(accessor->second(state), value); } } public: template void AddPrecWrite(uint64_t addr, T value) { this->initial_memory_conditions.push_back( [addr, value](test_runner::MemoryHandler &mem_hand) { mem_hand.WriteMemory(addr, value); }); } const std::vector &GetMemoryPrecs() const { return this->initial_memory_conditions; } TestOutputSpec(uint64_t disas_addr, std::string target_bytes, remill::Instruction::Category expected_category, std::vector register_preconditions, std::vector register_postconditions) : addr(disas_addr), target_bytes(target_bytes), expected_category(expected_category), register_preconditions(std::move(register_preconditions)), register_postconditions(std::move(register_postconditions)) {} void SetupTestPreconditions(AArch32State &state) const { for (auto prec : this->register_preconditions) { this->ApplyCondition(state, prec.register_name, prec.enforced_value); } } void CheckLiftedInstruction(const remill::Instruction &lifted) const { CHECK_EQ(lifted.category, this->expected_category); } void CheckResultingState(AArch32State &state) const { for (auto post : this->register_postconditions) { this->CheckCondition(state, post.register_name, post.enforced_value); } } }; class TestSpecRunner { private: test_runner::LiftingTester lifter; uint64_t tst_ctr; test_runner::random_bytes_engine rbe; llvm::support::endianness endian; public: TestSpecRunner(llvm::LLVMContext &context) : lifter(test_runner::LiftingTester( context, remill::OSName::kOSLinux, remill::ArchName::kArchThumb2LittleEndian)), tst_ctr(0), endian(lifter.GetArch()->MemoryAccessIsLittleEndian() ? llvm::support::endianness::little : llvm::support::endianness::big) {} void RunTestSpec(const TestOutputSpec &test) { std::stringstream ss; ss << "test_disas_func_" << this->tst_ctr++; auto maybe_func = lifter.LiftInstructionFunction(ss.str(), test.target_bytes, test.addr); CHECK(maybe_func.has_value()); auto lifted_func = maybe_func->first; auto new_mod = llvm::CloneModule(*lifted_func->getParent()); remill::OptimizeBareModule(new_mod.get()); auto just_func_mod = std::make_unique("", new_mod->getContext()); auto new_func = test_runner::CopyFunctionIntoNewModule( just_func_mod.get(), lifted_func, new_mod); AArch32State st = {}; test.CheckLiftedInstruction(maybe_func->second); test_runner::RandomizeState(st, this->rbe); st.sr.z = test_runner::random_boolean_flag(this->rbe); st.sr.c = test_runner::random_boolean_flag(this->rbe); st.sr.v = test_runner::random_boolean_flag(this->rbe); st.sr.z = test_runner::random_boolean_flag(this->rbe); st.sr.n = test_runner::random_boolean_flag(this->rbe); test.SetupTestPreconditions(st); auto mem_hand = std::make_unique(this->endian); for (const auto &prec : test.GetMemoryPrecs()) { prec(*mem_hand); } test_runner::ExecuteLiftedFunction( new_func, test.target_bytes.length(), &st, mem_hand.get(), [](AArch32State *st) { return st->gpr.r15.dword; }); LOG(INFO) << "Pc after execute " << st.gpr.r15.dword; test.CheckResultingState(st); } }; int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); google::ParseCommandLineFlags(&argc, &argv, true); google::InitGoogleLogging(argv[0]); return RUN_ALL_TESTS(); } TEST(ThumbRandomizedLifts, PopPC) { llvm::LLVMContext curr_context; std::string insn_data("\x00\xbd", 2); TestOutputSpec spec(0x12, insn_data, remill::Instruction::Category::kCategoryFunctionReturn, {{"r15", 12}, {"sp", 10}}, {{"r15", 16}}); spec.AddPrecWrite(10, 16); llvm::LLVMContext context; TestSpecRunner runner(context); runner.RunTestSpec(spec); } TEST(ThumbRandomizedLifts, RelPcTest) { llvm::LLVMContext curr_context; std::string insn_data("\x03\x49", 2); TestOutputSpec spec(0x12, insn_data, remill::Instruction::Category::kCategoryNormal, {{"r15", 0x12}}, {{"r1", 0xdeadc0de}}); // So ok instruction is at 18 which means pc is = 22 spec.AddPrecWrite(32, 0xdeadc0de); llvm::LLVMContext context; TestSpecRunner runner(context); runner.RunTestSpec(spec); } TEST(RegressionTests, AARCH64RegSize) { llvm::LLVMContext context; auto arch = remill::Arch::Build(&context, remill::OSName::kOSLinux, remill::ArchName::kArchAArch64LittleEndian); auto sems = remill::LoadArchSemantics(arch.get()); remill::IntrinsicTable instrinsics(sems.get()); auto op_lifter = arch->DefaultLifter(instrinsics); auto target_lift = arch->DefineLiftedFunction("test_lift", sems.get()); auto st_ptr = remill::LoadStatePointer(target_lift); CHECK_NOTNULL(st_ptr); auto lifted = op_lifter->LoadRegValue(&target_lift->getEntryBlock(), st_ptr, "W0"); CHECK_EQ(lifted->getType()->getIntegerBitWidth(), 32); op_lifter->ClearCache(); auto lifted2 = op_lifter->LoadRegValue(&target_lift->getEntryBlock(), st_ptr, "W0"); CHECK_EQ(lifted2->getType()->getIntegerBitWidth(), 32); } TEST(RegressionTests, Armv8FPSCR) { llvm::LLVMContext context; auto arch = remill::Arch::Build(&context, remill::OSName::kOSLinux, remill::ArchName::kArchAArch32LittleEndian); CHECK_NOTNULL(arch->RegisterByName("FPSCR")); } /* These tests are transcribed from the behaviors described in: A2.3.1 MOV(reg, thumb) ignores last bit, but does not mode-switch Thumb -> Thumb (1, mov pc, r1) -> {true: 1} B always remains in the same state: Arm -> Arm (0, b 4) -> {true: 0} Arm -> Arm (0, b 0) -> {true: 0} Thumb -> Thumb (1, b 4) -> {true: 1} Thumb -> Thumb (1, b 0) -> {true: 1} BLX immediate always changes, but is interprocedural so we keep the state the same Arm -> Arm (0, blx 1) -> {true: 0} Thumb -> Thumb (1, blx 1) -> {true: 1} Indirects (LDR, MOV(reg,ARM), BX): Arm -> (Thumb/Arm) (0, LDR PC, [r0]) -> {true: non_constant} Arm -> (Thumb/Arm) (0, BX r1) -> {true: non_constant} Thumb -> (Thumb/Arm) (1, LDR PC, [0]) -> {true: non_constant} Thumb -> (Thumb/Arm) (1, BX r1) -> {true: non_constant} Arm only Arm -> (Thumb/Arm) (0, mov pc, r1) -> {true: non_constant} Same but with conditionals: B always remains in the same state: Arm -> Arm (0, bne 4) -> {true: 0} Arm -> Arm (0, bne 0) -> {true: 0} \ BLX immediate always changes, but is interprocedural so we keep the state the same Arm -> Arm (0, blxne 1) -> {true: 0} Indirects (LDR, MOV(reg,ARM), BX): Arm -> (Thumb/Arm) (0, LDRNE PC, [r0]) -> {!=branch_not_taken_pc: non_constant, ==branch_not_taken_pc: 0} Arm -> (Thumb/Arm) (0, BXNE r1) -> {!=branch_not_taken_pc: non_constant, ==branch_not_taken_pc: 0} Arm only Arm -> (Thumb/Arm) (0, movne pc, r1) -> {!=branch_not_taken_pc: non_constant, ==branch_not_taken_pc: 0} */ /* TEST(ArmContextTests, ThumbMovIgnoresAnyStateChange) { //mov pc, r1 std::string insn_data("\x8f\x46", 2); auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 1); ASSERT_TRUE(maybe_map.has_value()); auto map = *maybe_map; EXPECT_TRUE(map(0xdeadbee2).GetContextValue(remill::kThumbModeRegName)); EXPECT_TRUE(map(0x100).GetContextValue(remill::kThumbModeRegName)); } TEST(ArmContextTests, ArmBStaysInArmAligned1) { // b 0 std::string insn_data("\xfe\xff\xff\xea", 4); auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0); ASSERT_TRUE(maybe_map.has_value()); auto map = *maybe_map; EXPECT_FALSE(map(0x0).GetContextValue(remill::kThumbModeRegName)); } TEST(ArmContextTests, ArmBStaysInArmAligned2) { // b 4 std::string insn_data("\xff\xff\xff\xea", 4); auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0); ASSERT_TRUE(maybe_map.has_value()); auto map = *maybe_map; EXPECT_FALSE(map(0x4).GetContextValue(remill::kThumbModeRegName)); } TEST(ArmContextTests, ThumbBStaysInThumbAligned1) { // b 0 std::string insn_data("\xfe\xe7", 2); auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 1); ASSERT_TRUE(maybe_map.has_value()); auto map = *maybe_map; EXPECT_TRUE(map(0x0).GetContextValue(remill::kThumbModeRegName)); } TEST(ArmContextTests, ThumbBStaysInThumbAligned2) { // b 4 std::string insn_data("\x00\xe0", 2); auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 1); ASSERT_TRUE(maybe_map.has_value()); auto map = *maybe_map; EXPECT_TRUE(map(0x4).GetContextValue(remill::kThumbModeRegName)); } TEST(ArmContextTests, ArmBLXInterProcStaysInSameMode) { // blx 4 std::string insn_data("\xff\xff\xff\xfa", 4); auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0); ASSERT_TRUE(maybe_map.has_value()); auto map = *maybe_map; EXPECT_FALSE(map(0xdeadbee4).GetContextValue(remill::kThumbModeRegName)); } TEST(ArmContextTests, ThumbBLXInterProcStaysInSameMode) { // blx 4 std::string insn_data("\x00\xf0\x00\xe8", 4); auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 1); ASSERT_TRUE(maybe_map.has_value()); auto map = *maybe_map; EXPECT_TRUE(map(0xdeadbee4).GetContextValue(remill::kThumbModeRegName)); } TEST(ArmContextTests, ArmLDRIndirect) { // ldr pc, [r0] std::string insn_data("\x00\xf0\x90\xe5", 4); auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0); ASSERT_TRUE(maybe_map.has_value()); auto map = *maybe_map; EXPECT_FALSE(map(0xdeadbee4).HasValueForReg(remill::kThumbModeRegName)); EXPECT_FALSE(map(0x1000).HasValueForReg(remill::kThumbModeRegName)); } TEST(ArmContextTests, ArmBXIndirect) { // bx r1 std::string insn_data("\x11\xff\x2f\xe1", 4); auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0); ASSERT_TRUE(maybe_map.has_value()); auto map = *maybe_map; EXPECT_FALSE(map(0xdeadbee4).HasValueForReg(remill::kThumbModeRegName)); EXPECT_FALSE(map(0x1000).HasValueForReg(remill::kThumbModeRegName)); } TEST(ArmContextTests, ThumbLDRIndirect) { // ldr pc, [r0] std::string insn_data("\xd0\xf8\x00\xf0", 4); auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 1); ASSERT_TRUE(maybe_map.has_value()); auto map = *maybe_map; EXPECT_FALSE(map(0xdeadbee4).HasValueForReg(remill::kThumbModeRegName)); EXPECT_FALSE(map(0x1000).HasValueForReg(remill::kThumbModeRegName)); } TEST(ArmContextTests, ThumbBXIndirect) { // bx r1 std::string insn_data("\x08\x47", 2); auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 1); ASSERT_TRUE(maybe_map.has_value()); auto map = *maybe_map; EXPECT_FALSE(map(0xdeadbee2).HasValueForReg(remill::kThumbModeRegName)); EXPECT_FALSE(map(0x1000).HasValueForReg(remill::kThumbModeRegName)); } TEST(ArmContextTests, ArmMovPCIndirectDoesAllowModeSwitch) { // mov pc, r1 std::string insn_data("\x01\xf0\xa0\xe1", 4); auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0); ASSERT_TRUE(maybe_map.has_value()); auto map = *maybe_map; EXPECT_FALSE(map(0xdeadbee4).HasValueForReg(remill::kThumbModeRegName)); EXPECT_FALSE(map(0x1000).HasValueForReg(remill::kThumbModeRegName)); } // Conditionals TEST(ArmContextTests, ArmBStaysInArmConditionalAligned1) { // bne 0 std::string insn_data("\xfe\xff\xff\x1a", 4); auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0); ASSERT_TRUE(maybe_map.has_value()); auto map = *maybe_map; EXPECT_FALSE(map(0x0).GetContextValue(remill::kThumbModeRegName)); EXPECT_FALSE(map(0xdeadbee4).GetContextValue(remill::kThumbModeRegName)); } TEST(ArmContextTests, ArmBStaysInArmConditionalAligned2) { // bne 4 std::string insn_data("\xff\xff\xff\x1a", 4); auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0); ASSERT_TRUE(maybe_map.has_value()); auto map = *maybe_map; EXPECT_FALSE(map(0x0).GetContextValue(remill::kThumbModeRegName)); EXPECT_FALSE(map(0xdeadbee4).GetContextValue(remill::kThumbModeRegName)); } TEST(ArmContextTests, ArmLDRIndirectConditional) { // ldrne pc, [r0] std::string insn_data("\x00\xf0\x90\x15", 4); auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0); ASSERT_TRUE(maybe_map.has_value()); auto map = *maybe_map; EXPECT_FALSE(map(0xdeadbee4).GetContextValue(remill::kThumbModeRegName)); EXPECT_FALSE(map(0x1000).HasValueForReg(remill::kThumbModeRegName)); } TEST(ArmContextTests, ArmBXIndirectConditional) { // bxne r1 std::string insn_data("\x11\xff\x2f\x11", 4); auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0); ASSERT_TRUE(maybe_map.has_value()); auto map = *maybe_map; EXPECT_FALSE(map(0xdeadbee4).GetContextValue(remill::kThumbModeRegName)); EXPECT_FALSE(map(0x1000).HasValueForReg(remill::kThumbModeRegName)); } TEST(ArmContextTests, ArmMovPCIndirectDoesAllowModeSwitchConditional) { // movne pc, r1 std::string insn_data("\x01\xf0\xa0\x11", 4); auto maybe_map = GetSuccessorContext(insn_data, 0xdeadbee0, 0); ASSERT_TRUE(maybe_map.has_value()); auto map = *maybe_map; EXPECT_FALSE(map(0xdeadbee4).GetContextValue(remill::kThumbModeRegName)); EXPECT_FALSE(map(0x1000).HasValueForReg(remill::kThumbModeRegName)); } */ // Two things we need to test: correct categories and when we lift, the branch taken var is set appropriately TEST(ArmContextTests, ThumbBXIndirect) { // bx r1 std::string insn_data("\x08\x47", 2); auto maybe_flow = GetFlows(insn_data, 0xdeadbee0, 1); ASSERT_TRUE(maybe_flow.has_value()); auto flow = *maybe_flow; remill::Instruction::InstructionFlowCategory jmp = remill::Instruction::IndirectJump( remill::Instruction::IndirectFlow(std::nullopt)); remill::Instruction::IndirectJump str = std::get(flow.flows); EXPECT_FALSE(str.taken_flow.maybe_context.has_value()); EXPECT_EQ(flow.flows, jmp); } TEST(ArmContextTests, ThumbMovIgnoresAnyStateChange) { //mov pc, r1 std::string insn_data("\x8f\x46", 2); auto maybe_flow = GetFlows(insn_data, 0xdeadbee0, 1); ASSERT_TRUE(maybe_flow.has_value()); auto flow = *maybe_flow; remill::Instruction::InstructionFlowCategory jmp = remill::Instruction::IndirectJump( remill::Instruction::IndirectFlow(remill::kThumbContext)); remill::Instruction::IndirectJump str = std::get(flow.flows); EXPECT_TRUE(str.taken_flow.maybe_context.has_value()); remill::Instruction::IndirectJump str2 = std::get(jmp); EXPECT_TRUE(str2.taken_flow.maybe_context.has_value()); EXPECT_EQ(flow.flows, jmp); } TEST(ArmContextTests, ThumbBLXInterProcStaysInSameMode) { // blx 4 std::string insn_data("\x00\xf0\x00\xe8", 4); auto maybe_flow = GetFlows(insn_data, 0xdeadbee0, 1); ASSERT_TRUE(maybe_flow.has_value()); auto flow = *maybe_flow; remill::Instruction::InstructionFlowCategory jmp = remill::Instruction::DirectFunctionCall( remill::Instruction::DirectFlow(0xdeadbee0, remill::kARMContext)); } TEST(ArmContextTests, ThumbBLStaysInSameContext) { // bl 1b528 std::string insn_data("\x05\xf0\x74\xfc", 4); auto maybe_flow = GetFlows(insn_data, 0x1596c, 1); ASSERT_TRUE(maybe_flow.has_value()); auto act_insn = *maybe_flow; remill::Instruction::InstructionFlowCategory jmp = remill::Instruction::DirectFunctionCall( remill::Instruction::DirectFlow(0x1b258, remill::kThumbContext)); auto dfcall = std::get(act_insn.flows); EXPECT_EQ(0x0001b258, dfcall.taken_flow.known_target); EXPECT_EQ(remill::kThumbContext, dfcall.taken_flow.static_context); EXPECT_EQ(jmp, act_insn.flows); EXPECT_EQ(0x00015970, act_insn.next_pc); } TEST(ArmContextTests, ThumbBPLRegressionTest) { // bpl #0x135e0 std::string insn_data("\x7f\xf5\x70\xae", 4); auto maybe_flow = GetFlows(insn_data, 0x000138fc, 1); ASSERT_TRUE(maybe_flow.has_value()); auto act_insn = *maybe_flow; remill::Instruction::InstructionFlowCategory expect_cond_flow = remill::Instruction::ConditionalInstruction( remill::Instruction::DirectJump( remill::Instruction::DirectFlow(0x135e0, remill::kThumbContext)), remill::Instruction::FallthroughFlow(remill::kThumbContext)); auto act_cond_insn_flow = std::get(act_insn.flows); EXPECT_EQ(expect_cond_flow, act_insn.flows); }