ELFun File Injector

Why not use #ifndef stuff?

Because it would determin on runtime, it’s not about the injector, it’s about the target file.
@0x00pf Would it be possible to infect 64 bit files, if I’d compile this injector as a 32 bit ELF binary?
I maybe found some bugs:

  elf_seg = (Elf64_Phdr *) ((unsigned char*) elf_hdr 
			    + (unsigned int) elf_hdr->e_phoff);

Here (unsigned int) is a 32 bit value, could it be possible that e_phoff exceeds 0xffffffff ?

also, shouldnt:

if (elf_seg->p_type == PT_LOAD && elf_seg->p_flags & 0x011)

be:

if (elf_seg->p_type == PT_LOAD && ~(elf_seg->p_flags ^ 0x5)

Considering:
PF_X = 0x1 PF_W = 0x2 PF_R = 0x4
& 0x11, would only hold true for PF_X. Or is .text always the first executable segment it will find?

And I have a question about the following:

  for (i = 0; i < n_seg; i++)
    {
      if (elf_seg->p_type == PT_LOAD && elf_seg->p_flags & 0x011)
	{
	  printf ("+ Found .text segment (#%d)\n", i);
	  text_seg = elf_seg;
	  text_end = elf_seg->p_offset + elf_seg->p_filesz;
	}
      else
	{
	  if (elf_seg->p_type == PT_LOAD && 
	      (elf_seg->p_offset - text_end) < gap) 
	    {
	      printf ("   * Found LOAD segment (#%d) close to .text (offset: 0x%x)\n",
		      i, (unsigned int)elf_seg->p_offset);
	      gap = elf_seg->p_offset - text_end;
	    }
	}
      elf_seg = (Elf64_Phdr *) ((unsigned char*) elf_seg 
			    + (unsigned int) elf_hdr->e_phentsize);
    }

At the end of each iteration, the elf_seg pointer gets incremented.
that means it points to the struct Elf64_Phdr, + some additional.
This seems counterintuitive to me because if:

| member0 | ... | memberx | ... | member n | ... | member n+x | ...
   └─strct strct->a──┘               │                   │
 after m iterations: strct───────────┘ strct->a──────────┘

So if you’d increment the pointer, wouldn’t that cause it to no longer point to the first member of the struct. And wouldn’t that cause that adressing members goes wrong?

Nvm I’m being stupid, shouldve checked the manpage properly ^^
e_phentsize This member holds the size in bytes of one entry in the file’s program header table; all entries are the same size.
So it does indeed increment the pointer of the struct to such extends that it actually does point to a new struct (entry) in the file
Awesome ^^

Another question (I just keep 'em coming):
Why are we looking for the smallest gap, instead of the biggest?

1 Like

That is indeed possible. I just chose 64bits in the post to keep it simple, but indeed, a proper program should work with both architectures… I’m looking forward to your version of the injector!!!

Sure. We are just changing bytes in a file.

If you check the structure in the elf.h it is actuall 16bits.

I think you are right with this. have you tried the modification?. I will take a look later but looks like you are right.

Well, that code is not the best in the world. This injector always adds the code at the end of the .text segment, so the gap we are looking for in that function is the one from the end of the .text section to the beginning, of whatever other sections comes after. As I didn’t know if the .data segment is always the next section, I just calculate the gap for every single section in the file, and I keep the smaller as that is the one between .text segment and .XXX next segment in memory.

Sorry for the poor wording of this explanation… Let me know if I manage to explain it :slight_smile:

Yes I get it :slight_smile:

About the modification, I’ll try it right now, see if it makes a difference ^^

I’ll also try to implement the 32 and 64 bit stuff.

So far I have this:
http://pastebin.com/DywNNiw2

Though there are some issues ^_^'
Think I there are some pointer issues.

3 Likes

Make another post and link this post!

At first glance it looks good!

I got bored so I used your code to create a Python module out of this. The code is on Pastebin The instructions are included in the source code in the comment at the top along with an example.

3 Likes

Looks nice @0x00_Jinx. There a couple more techniques to inject payloads that may be added and make the module grow.

Nice. It would be awesome if we could make the module grow, and also have a way to obfuscate payloads so they are harder to find. If you have any experience in these subjects, let me know! I’m going to be researching ways to encrypt the payload then unencrypt when the program is ran, making the AV signature useless. Also, I added some error checking to the module, like a function to make sure it is a .elf file.

1 Like

You would be referring to a Crypter, pico has actually done a tutorial on Linux Crypters here

You just need to watch out that they don’t fingerprint the stub, once they’ve done that encrypting the payload is useless.

1 Like

@pry0cc is right the main problem is the stub… encoding the payload is easy, but the stub cannot be encoded and you need to make it… change to avoid detection. Sounds like a nice topic for a post :stuck_out_tongue:

2 Likes

Brilliant. Great job. Have I reached 20 characters yet?

2 Likes

Wow, after an insane amount of research and coming back to this post I can finally make sense out of it. Brilliant post @0x00pf!

2 Likes

I might be a little late on that but I was reading through it again and I noticed that sentence. I think you mean string table instead of symbol table(?) Thus the “&shdr[elf_hdr->e_shstrndx];” and “shdr[i].sh_name” parts of your code which point to the string table section and the index into the section header string table section respectively.

2 Likes

Good catch @_py . I would like to say that it was in purpose to see if people was following the paper… but it was just a mistake :sweat_smile:

2 Likes

Sorry for necroposting, but as the current code didn’t work for me I thought I would add how I fixed it, in case anybody else comes here after me.

The payload, although running and passing flow over to _main, made the kernel segfault. The reason being that the CPU state had been corrupted by the syscall code. Saving the registers before and then restoring them after fixed it.

Also, I had to change the pattern being replaced by the original entry point, to be 8 bytes. When replacing the 0x11111111 pattern with ep, the value is zero extended when casting to long:

elfi_mem_subst (d+p, p_text_sec->sh_size, 0x11111111, (long)ep);

My final payload:

section .text
  global _start

_start:
  ;; save cpu state
  push rax
  push rdi
  push rsi
  push rdx

  ;; write msg to stdout
  mov rax,1                     ; [1] - sys_write
  mov rdi,1                     ; 0 = stdin / 1 = stdout / 2 = stderr
  lea rsi,[rel msg]             ; pointer(mem address) to msg (*char[])
  mov rdx, msg_end - msg        ; msg size
  syscall                       ; calls the function stored in rax

  ;; restore cpu state
  pop rdx
  pop rsi
  pop rdi
  pop rax

  ;; jump to _main
  mov rax, 0x1111111111111111   ; address changed during injection
  jmp rax

align 8
  msg     db 0x1b,'[31msuch infected, much wow!',0x1b,'[0m',0x0a,0
  msg_end db 0x0

Sorry about the notification guys.

Really awesome tut, pico. Have my first contribution to 0x00sec :heart:

6 Likes

very interesting article, thank you ! :smiley:

1 Like

This topic was automatically closed after 30 days. New replies are no longer allowed.