Code Monkey home page Code Monkey logo

patcherex2's People

Contributors

antoniobianchi333 avatar bilbin avatar burhanr13 avatar dennydai avatar ninja3047 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

patcherex2's Issues

Linker doesn't resolve relocations when relocatable flag is used

I've been working on aarch64 support for the copy and micropatch fork and I've come across an issue with the use of the -relocatable flag during the linking process. This flag is used in compiler.py and llvm_recomp.py.

It seems that relative relocations are not resolved when the -relocatable flag is used. See this Stackoverflow post for more details: https://stackoverflow.com/questions/35324636/resolve-relative-relocations-in-partial-link

Merely removing the -relocatable flag causes a bunch of other errors. When the base=0, we get an error trace like this:

ERROR    | 2024-05-02 11:25:19,643 | patcherex2.components.compilers.compiler | ld.lld-15: error: /tmp/tmp_uu_rkga/linker.ld:1: unable to move location counter backward for: .patcherex2
ld.lld-15: error: /tmp/tmp_uu_rkga/linker.ld:1: unable to move location counter backward for: .patcherex2
ld.lld-15: error: section .patcherex2 at 0x34 of size 0xFFFFFFFFFFFFFFF0 exceeds available address space
ld.lld-15: error: section .eh_frame file range overlaps with .comment
>>> .eh_frame range is [0x10000, 0x10033]
>>> .comment range is [0x10024, 0x1005A]

ld.lld-15: error: section .comment file range overlaps with .patcherex2
>>> .comment range is [0x10024, 0x1005A]
>>> .patcherex2 range is [0x10034, 0x10023]

Traceback (most recent call last):
  File "/home/caleb/Documents/vibesproject/Patcherex2/tests/test_binaries/aarch64/iip_c.py", line 26, in <module>
    p.apply_patches()
  File "/home/caleb/Documents/vibesproject/Patcherex2/src/patcherex2/patcherex.py", line 100, in apply_patches
    patch.apply(self)
  File "/home/caleb/Documents/vibesproject/Patcherex2/src/patcherex2/patches/instruction_patches.py", line 224, in apply
    self._apply_c(p)
  File "/home/caleb/Documents/vibesproject/Patcherex2/src/patcherex2/patches/instruction_patches.py", line 340, in _apply_c
    p.utils.insert_trampoline_code(
  File "/home/caleb/Documents/vibesproject/Patcherex2/src/patcherex2/components/utils/utils.py", line 70, in insert_trampoline_code
    self.p.compiler.compile(
  File "/home/caleb/Documents/vibesproject/Patcherex2/src/patcherex2/components/compilers/compiler.py", line 96, in compile
    raise e
  File "/home/caleb/Documents/vibesproject/Patcherex2/src/patcherex2/components/compilers/compiler.py", line 93, in compile
    print(subprocess.run(args, check=True, capture_output=True))
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/subprocess.py", line 571, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['ld.lld-15', '/tmp/tmp_uu_rkga/obj.o', '-T', '/tmp/tmp_uu_rkga/linker.ld', '-o', '/tmp/tmp_uu_rkga/obj_linked.o']' returned non-zero exit status 1.

For the nonzero base, the linking process completes successfully but results in a different error:

Traceback (most recent call last):
  File "/home/caleb/Documents/vibesproject/Patcherex2/tests/test_binaries/aarch64/iip_c.py", line 26, in <module>
    p.apply_patches()
  File "/home/caleb/Documents/vibesproject/Patcherex2/src/patcherex2/patcherex.py", line 100, in apply_patches
    patch.apply(self)
  File "/home/caleb/Documents/vibesproject/Patcherex2/src/patcherex2/patches/instruction_patches.py", line 224, in apply
    self._apply_c(p)
  File "/home/caleb/Documents/vibesproject/Patcherex2/src/patcherex2/patches/instruction_patches.py", line 340, in _apply_c
    p.utils.insert_trampoline_code(
  File "/home/caleb/Documents/vibesproject/Patcherex2/src/patcherex2/components/utils/utils.py", line 124, in insert_trampoline_code
    compiled_code = self.p.compiler.compile(
                    ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/caleb/Documents/vibesproject/Patcherex2/src/patcherex2/components/compilers/compiler.py", line 108, in compile
    compiled = ld.memory.load(
               ^^^^^^^^^^^^^^^
  File "/home/caleb/Documents/vibesproject/Patcherex2/venv/lib/python3.11/site-packages/cle/memory.py", line 391, in load
    raise KeyError(addr)
KeyError: 203024

This issue crops up when we attempt to compile a copy-and-micropatch C file which has an extern _CALLBACK function. The _CALLBACK symbol ends up being a 1 instruction relative jump. On aarch64 this will cause the program to jump to the jump instruction, which causes an infinite loop. I have an example to reproduce this issue, however it uses the Draper branch. It is based off of the iip_c test case found in the tests/test_binaries/aarch64 folder. I have attached the relevant compiled iip_c test case and the InsertInstructionPatch C script: iip_c.zip

If you look in iip_c.patched you will see the infinite loop (highlighted in green) in Ghidra:
image

The correct assembly code should be 01 00 00 14. If you don't use the -relocatable flag with nonzero base and look at obj_linked.o as created in the /tmp directory, you will see that the linker does correctly resolve this branch instruction. Even though the linking succeeds, the program then fails with the KeyError that I posted previously.

Some other possibly helpful links:

Proposal: Copy and Micropatch based target

My coworker, Phil Zucker, came up with a new clever method of creating micropatches using an ordinary C compiler. The strategy is called "copy and micropatch" and operates similarly to copy and patch JITs. The strategy is based around abuse of the calling convention to force values into certain registers.

The easiest way to illustrate the concept is with an example (taken from Phil's blog):

#include <stdint.h>
uint64_t CALLBACK(uint64_t rdi, uint64_t rsi, uint64_t rdx, uint64_t rcx, uint64_t r8, uint64_t r9);
uint64_t PATCHCODE(uint64_t rdi, uint64_t rsi, uint64_t rdx, uint64_t rcx, uint64_t r8, uint64_t r9){
    // Some random patch code here
    if(rcx >= r8){
        rdi = rsi * rdx; 
    }
    // End patchcode
    return CALLBACK(rdi, rsi, rdx, rcx, r8, r9);
}

The calling convention for this snippet ensures that the PATCHCODE receives certain registers as inputs, and the CALLBACK at the end ensures that the variables are placed into the correct registers once the function terminates.

The code is passed through an ordinary C compiler, and the body of PATCHCODE is extracted and inserted somewhere where there is space. This process requires tail-call optimization turned on, which turns the call to CALLBACK into a jump. Through the use of a linker script we could set the CALLBACK symbol to be placed at the detour return point.

With the __attribute__((preserve_none)) tag built into the latest version of Clang, we can get control over many registers (at least on x64). Note that the preserve_none is brand new, I don't think it has landed into any release versions of Clang yet. As an alternative to preserve_none, we could add shims to push/pop registers to ensure the data gets to the right place.

For more info, see Phil's blog here: https://www.philipzucker.com/permutation_compile/

I'm willing to put the time into developing this target for integration into patcherex2. Is there anything that we need to know before forking and getting started? Using the version of Clang with support for preserve_none would be highly desirable.

See also:

Instructions that use the PC are considered movable

    def is_movable_instruction(self, addr: int) -> bool:
        is_thumb = self.p.binary_analyzer.is_thumb(addr)
        insn = self.p.binary_analyzer.get_instr_bytes_at(addr)
        asm = self.p.disassembler.disassemble(insn, addr, is_thumb=is_thumb)[0]
        asm = self.p.disassembler.to_asm_string(asm)
        for addr in [0x0, 0x7F00000, 0xFE000000]:
            if self.p.assembler.assemble(asm, addr, is_thumb=is_thumb) != insn:
                return False
        return True

i don't believe the above check is sufficient. Some examples found in the arm printf pie binary are

0000050e  064b       ldr     r3, [pc, #0x18]  {data_528}
00000510  7b44       add     r3, pc  {data_5cc, "Hi"}

which would pass the above check since the disassembly does no change if the base address changes, but these aren't safe to move since they depend on the value of the PC.

.eh_frame (exception frame) section getting tacked onto compiled C code for some bases

When working on the InsertInstructionPatch lang="C" code, I ran across some scenarios where the .eh_frame section was getting tacked onto the output from Compiler.compile. Here is what was causing the error for me:

  • When base=0 was passed to Compiler.compile, the .eh_frame section was not directly adjacent to the .text section, so Compiler.comple did not return that section.
  • When base=0x5edb was passed to Compiler.compile, the .eh_frame was placed directly adjacent to the .text section, and was returned by Compiler.compile.
  • This caused a size discrepancy error, where there wasn't enough allocated space for the version compiled with the correct offset. However since the content of .eh_frame is completely useless for us, it can be safely omitted.
  • I solved the issue by adding an extra compiler flag -fno-asynchronous-unwind-tables to stop the section from being outputted from the compiler in the first place.
  • I'm not sure if this is the ideal fix, maybe it would be possible for other sections to be accidentally returned by Compiler.compile.

I fixed this issue by passing the compiler flag -fno-asynchronous-unwind-tables here:

  1. https://github.com/draperlaboratory/Patcherex2/blob/main/src/patcherex2/components/utils/utils.py#L79
  2. https://github.com/draperlaboratory/Patcherex2/blob/main/src/patcherex2/components/utils/utils.py#L137

I have attached the files in the compiler /tmp directory for both the version where -fno-asynchronous-unwind-tables was passed, and the version where it wasn't.

eh_frame_bug.zip

We may want to add -fno-asynchronous-unwind-tables as a default flag to pass to the compiler, or fix the section extraction code.

Here are two screenshots from Ghidra showing the adjacency of the text section:

At base 0:
image

At base 0x5edb (notice that .eh_frame is now directly adjacent):
image

Linking of ARM ELF fails in presence of .ARM.exidx section

I have an ELF file for one of the upcoming AMP Hackathon problems that I am attempting to patch. The ELF file in question is ARM, and it contains a section .ARM.exidx, which from my understanding is related to stack unwinding. Attempting either an InsertFunctionPatch or a InsertInstructionPatch results in failure due to some sort of relative addressing related to this section.

Here is the stacktrace and error message that I obtained:

ERROR    | 2024-03-01 14:52:42,847 | patcherex2.components.compilers.compiler | ld.lld-15: error: /tmp/tmpnykktgjr/obj.o:(.ARM.exidx+0x0): relocation R_ARM_PREL31 out of range: 1610719160 is not in [-1073741824, 1073741823]
>>> referenced by code.c

Traceback (most recent call last):
  File "/home/caleb/Documents/vibesproject/cozy/test_programs/GridIDPS/build/micropatch.py", line 40, in <module>
    apply_badpatch()
  File "/home/caleb/Documents/vibesproject/cozy/test_programs/GridIDPS/build/micropatch.py", line 37, in apply_badpatch
    proj.apply_patches()
  File "/home/caleb/Documents/vibesproject/VIBES-internal/experiments/cozy/venv/lib/python3.11/site-packages/patcherex2/patcherex.py", line 57, in apply_patches
    patch.apply(self)
  File "/home/caleb/Documents/vibesproject/VIBES-internal/experiments/cozy/venv/lib/python3.11/site-packages/patcherex2/patches/function_patches.py", line 134, in apply
    p.compiler.compile(
  File "/home/caleb/Documents/vibesproject/VIBES-internal/experiments/cozy/venv/lib/python3.11/site-packages/patcherex2/components/compilers/clang_arm.py", line 37, in compile
    compiled = super().compile(
               ^^^^^^^^^^^^^^^^
  File "/home/caleb/Documents/vibesproject/VIBES-internal/experiments/cozy/venv/lib/python3.11/site-packages/patcherex2/components/compilers/compiler.py", line 83, in compile
    raise e
  File "/home/caleb/Documents/vibesproject/VIBES-internal/experiments/cozy/venv/lib/python3.11/site-packages/patcherex2/components/compilers/compiler.py", line 80, in compile
    subprocess.run(args, check=True, capture_output=True)
  File "/usr/lib/python3.11/subprocess.py", line 571, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['ld.lld-15', '-relocatable', '/tmp/tmpnykktgjr/obj.o', '-T', '/tmp/tmpnykktgjr/linker.ld', '-o', '/tmp/tmpnykktgjr/obj_linked.o']' returned non-zero exit status 1.

Here is my Python script:

import patcherex2

proj = patcherex2.Patcherex("amp_challenge_arm.ino_unstripped.elf")

# Make sure InsertFunctionPatch is working
# Applying the InsertFunctionPatch fails even if we comment out the body of flushSerial()
flushSerial_code = '''
void flushSerial() {
    while(usb_serial_available() > 0) {
        char t = usb_serial_getchar();
    }
}
'''
flushSerial_patch = patcherex2.InsertFunctionPatch("flushSerial", flushSerial_code)
proj.patches.append(flushSerial_patch)

# Make sure InsertInstructionPatch is working
instructionPatch_code = '''
nop
nop
'''
loop_addr = proj.binary_analyzer.get_function('loop')['addr']
instruction_patch = patcherex2.InsertInstructionPatch(loop_addr, instructionPatch_code)
proj.patches.append(instruction_patch)

proj.apply_patches()
proj.binfmt_tool.save_binary("amp_challenge_arm.ino_unstripped-draper-badpatch.elf")

Some information I have found on this exidx section:

The linker script that patcherex2 generates does not seem to have anything about the exidx. Unfortunately I am not well versed enough in the ELF format or linker scripts to make a determination on how to fix this issue.

I have attached the ELF file and the Python script to this issue.
arm_exidx_bug.zip

Alignment issues with InsertInstructionPatch

As I've been writing some test cases for InsertInstructionPatch when language="C", I've come across segmentation faults that occur when data sections are not aligned properly. As an example, take a look at the following program in Ghidra:

image

The highlighted instruction is accessing a chunk of readable data, and the argument needs to be 16 byte aligned. Running this program results in a segfault due to misalignment. Other instructions in this micropatch seem to have a similar problem.

Here is the error in gef:

image

There seems to be a chicken and egg problem with the patch, where you don't know the true size of the patch until you know its insertion address, but to get the address of the patch you need the size. Right now I believe we first get the size by assuming the compiled function starts at address 0. Could this be related to the alignment bug?

Although this problem cropped up with the InsertIntructionPatch, I assume this issue also crops up in InsertFunctionPatch.

I have attached the binaries (before and after patch) and the Python script needed to reproduce the issue (you need to run the Python script on the draperlaboratory fork). I have also attached the log of running the Python script, which prints out what the MICROPATCH function is:

iip_c_alignment_bug.zip

Trampoline does not jump back to proper location when force_insert=True

On line 32 of utils.py, moved_instrs_len is set to 0, which means that the trampoline will jump back to the instruction that jumped to the trampoline in the first place. This will either cause an infinite loop or a crash.

My workaround is to set moved_instrs_len = self.p.target.JMP_SIZE, but for variable length instruction sets such as x64 this may be incorrect. A better solution would be to somehow get the length of the first jump and use that to compute where to jump back.

I have attached the programs and patcherex Python script to this issue. The target3 binary was produced after changing line 32 to be moved_instrs_len = self.p.target.JMP_SIZE. In this case using JMP_SIZE is okay, because I've noped out the instructions in the trampoline start area.

Relevant source files: patcherex_bug.zip

Expected output of test should be:

patched failed

Expected output of test3 should be:

ched failed

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.