Monday, September 30, 2024

Injecting Code on the Fly: Overcoming Challenges to produce data self stuffed binary blobs.

Sometimes you run a long-lasting process on a remote machine… For example, to compress a large file… When suddenly you have an emergency: your wife is itching to shop. At that point, you typically have a couple of options:

  1. Stop the job and restart it at a more convenient time.
  2. Keep your computer connected and go out shopping.

Surely, if you had known beforehand, you could have started the job in a screen or tmux session, but usually things didn’t go that way, and now you have to decide what to do.

If you have enough time, you can use your trusty gdb to sort this problem out. You can attach to the program, close stderr and stdout, and then create new files to replace them. You can use sigaction to disable SIGHUP, but that isn’t something you can manage when shopping is calling… You simply don’t have the time.

To address this problem, I was trying to code a simple tool to automate the gdb process.

While I was able to produce a PoC using C and Assembly, trying to have the same using pure C, I ran into a challenge that I’d like to discuss briefly and gather suggestions on, if any.

The issue is that the tool needs to inject code into the running program to replace file descriptors and disable signals. However, the code that needs to be injected might require some data.

The obvious solution would be to write a small binary in assembly that can be executed in that context, but I wanted to write it in C. The problem is: can I embed data into a function in the .text section? The assembly equivalent would be something trivial like:

jmp code data: .byte [...] code: body of the function

Doing something similar in C that is both functional and portable is far from trivial. Here’s my current solution, which still has a few issues, and I’d like to collect suggestions:


void injected_function() { volatile int a = 0; if (a) { str: asm volatile ( ".byte 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20," "0x69, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x66, 0x75, 0x6e," "0x63, 0x21, 0x0a, 0x00, 0x00" ); } str_end: write(1, (void *) &&str, (&&str_end - &&str)); }

This function is supposed to be injected into the address space of a running program and write: “Hello from injected func!” However, there are a few quirks: (maybe more, but I haven’t stomped into them yet)

  • In x86_64, when -fcf-protection=full is enabled, the asm volatile statements are considered valid jump targets, resulting in an endbr64 being inserted. Solutions include skipping 2 bytes to avoid printing the opcodes of the endbr64 or disabling CFI using -fcf-protection=none. I don’t like either solution, but I couldn’t find another workaround.
  • When compiling this for aarch64, if the message length is not a multiple of four, the resulting label of the code after the string becomes misaligned. My solution was to add a few \x00 bytes to ensure proper alignment, but I’m not satisfied with this approach.

I’m looking for a solution that is architecture-independent, but I haven’t been able to find one. Does anyone have any suggestions?

No comments:

Post a Comment