Copy Link
Add to Bookmark
Report

Complete guide to process infection

Number 0x01: 03/23/2006

eZine's profile picture
Published in 
the bug magazine
 · 2 years ago

[ --- The Bug! Magazine 

_____ _ ___ _
/__ \ |__ ___ / __\_ _ __ _ / \
/ /\/ '_ \ / _ \ /__\// | | |/ _` |/ /
/ / | | | | __/ / \/ \ |_| | (_| /\_/
\/ |_| |_|\___| \_____/\__,_|\__, \/
|___/

[ M . A . G . A . Z . I . N . E ]


[ Numero 0x01 <---> Edicao 0x01 <---> Artigo 0x06 ]



.> 23 de Marco de 2006,
.> The Bug! Magazine < staff [at] thebugmagazine [dot] org >

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Complete guide to process infection
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

.> 24 de Fevereiro de 2006,
.> Carlos Barros a.k.a barros < barros [at] barrossecurity [dot] com >

Table of Contents

  1. Introduction
  2. Symbol resolution
    • 2.1. The PLT and GOT section

  3. Injection steps
    • 3.1. Determine what functions to hook
    • 3.2. Write a shellcode for each function
      • 3.2.1. Calling the original function
        • 3.2.1.1. If you hooked the PLT entry
        • 3.2.1.2. If you hooked the GOT entry

      • 3.2.2. Storing temporary data
      • 3.2.3. Using shared library instead of assembly code

    • 3.3. Locating functions' GOT and/or PLT entry
      • 3.3.1. Using the program executable to locate the address
        • 3.3.1.1. Locating a section within an ELF file
        • 3.3.1.2. Locating the GOT address
        • 3.3.1.3. Locating the PLT entry

      • 3.3.2. Using the process memory to locate the address
        • 3.3.2.1. Locating necessary sections
        • 3.3.2.2. Locating the GOT entry
        • 3.3.2.3. Locating the PLT entry

    • 3.4. Inject your shellcode somewhere in the process memory
    • 3.5. Patch functions' GOT and/or PLT entry
      • 3.5.1. Patching the GOT entry
      • 3.5.2. Patching the PLT entry

  4. Executing code with ptrace
  5. Putting all together
    • 5.1. ptrace module
    • 5.2. elf_file module
    • 5.3. elf_ptrace module

  6. References

1. Introduction

In the past few weeks I've decided to code something about process infection and started googling on this topic. I've found some good papers and started my work. During my coding sessions I found out that there isn't any paper that suffices to code some real stuffs. Each paper lacks some informations and some times this informations is not covered anywhere. I got jammed in this situations many times and had to figure out somethings by myself. This is way I've decided to write this article, to try to put together everything that is need to write some real stuffs in process infection, including all stuffs I developed by myself. In this paper I'll try to explain in details all the steps need to infect a process, and at the end you will find a library and a test case to illustrate all the theory of this paper.

2. Symbol resolution

The better way of injecting code into a process in order to make something useful is hooking external functions. To do this you will need to known how the binary locates and calls these functions. Here I'll give just a little introduction about this process, if you want more information on this, take a look at [1].

We will use this code as our example for the rest of this section:

--- snip --- 

int main()
{
printf("Hello world\n");
}

--- snip ---

Simple huh? Well, at first look yes, but, what happens from behind the scene? The printf function is located in the libc, so, when you compile this simple code, with gcc code.c -o code, this function is not located within the executable itself. You may be wondering, how can the compiler know the exactly address to direct the call to printf if it is not within the executable? The answer is simple: it doesn't know. The "magic" that makes this code to work is handled by two structures: Procedure Linkage Table (PLT) and Global Offset Table (GOT).

2.1. The PLT and GOT section

Remember that I said that the compiler does not know the address of printf function? So, gcc needs to direct that call to some place, and this place is the PLT section. Each function located outside the binary (in a .so for example) have an entry in this section. Lets take a look at the disassemble of this call:

0x0804839c <main+24>:   call   0x80482b0 <_init+56>

This is our call to printf. As you can see, it doesn't call printf directly, instead, it call some address in the _init section (actually, it is the PLT section). Here is the disassemble of this address:

0x80482b0 <_init+56>:   jmp    *0x80495c8 
0x80482b6 <_init+62>: push $0x8
0x80482bb <_init+67>: jmp 0x8048290 <_init+24>

This is our good friend PLT Entry. That call to printf actually calls the first jmp instruction. As you can see, it gets the address to jump from the address 0x80495c8. This address is actually an entry in our other friend: the GOT section.

The GOT section is a table of absolute addresses that maps external symbols into the process memory in order to keep the position-independence of the code. Each external symbol has you own GOT entry that is filled by the symbol resolution routine. On Linux systems (and others systems) the symbol resolution is called as "Lazy Resolution". It means that the symbol is resolved only when it is called for the very first time.

So, in the first call to our printf function, its GOT entry will be pointing to the next instruction following the first jmp (see above disassemble). Here, the program pushes the offset that identifies the symbol it is trying to access and jumps to the PLT0 entry, that is showed bellow.

0x8048290 <_init+24>:   pushl  0x80495bc 
0x8048296 <_init+30>: jmp *0x80495c0

The PLT0 entry is a special entry that is responsible for calling the symbol resolution routine. This routines gets the symbol offset from the stack, determines what symbol to look for, locates it, store its real address in the respective GOT entry (retrieved from the offset too) and finally calls the original function. After that, the GOT entry will point to the real printf address, this way, on subsequents call, it is called directly, with no need of the PLT0 or the symbol resolution routine. Lets make a generic diagram of the entire process of calling an external function, to illustrate all this steps for a generic case;

Calling a function for the first time:

                   ... 
+-------------- call function
| ... <-------------------------------------------+
| |
| +--> PLT0: push &(GOT+4) +-------------------+ +----------+
| | call *(GOT+8) -> | Symbol resolution | -> | function |
| | +-------------------+ +----------+
| | +-----------+
+--+--> PLT1: jmp *(GOT+12) ------------> | GOT Entry |
| push offset_1 <------+ +-----------+
+----------- jmp PLT0 | |
... +-----------+

Calling a function for the second time:

                   ... 
+-------------- call function
| ... <--------------------------------------+
| |
| PLT0: push &(GOT+4) |
| call *(GOT+8) |
| |
| +-----------+ +----------+
+-----> PLT1: jmp *(GOT+12) --> | GOT Entry | -> | function |
push offset_1 +-----------+ +----------+
jmp PLT0
...

The PLT section described in this section is called Absolute Procedure Linkage Table, but there's another type: Position-Independent Procedure Linkage Table, that I'll not enter in details cause it does not change the way we use it to hook functions, as the only change is in the PLT0 entry.

3. Injection steps

Now that you know how an external function is called, lets examine the basic steps to infect a process:

  1. Determine what functions to hook;
  2. Write a shellcode for each function;
  3. Locate functions' GOT and/or PLT entry;
  4. Inject your shellcode somewhere in the process memory;
  5. Patch functions' GOT and/or PLT entry to point to your shellcode;
  6. Have fun!

I'll use the code bellow for the rest of this article to use as example. The testcase attached to this paper infect this code to log everything that is typed by the user.

--- snip --- 

#include <stdio.h>

int main()
{
char buf[256];

while(fgets(buf,sizeof(buf),stdin))
printf("%s",buf);
}

--- snip ---


3.1. Determine what functions to hook

This is the first step in the entire process of infection and as you can think, it is very simple. Wrong!!! The simplicity of this step is highly dependent of what program you are trying to infect. In our example it is very easy to determine the symbols, but imagine if you ar trying to infect a SSHd or something like this. At first you need to determine what you wanna do, then understand the source code, the flow of execution of the program and finally identify the functions.

In our example we want to log everything the user typed, so, the best place to do this is in the fgets function. We will try to redirect it to our own code that will log to a file the text typed by the user.

3.2. Write a shellcode for each function

Ok, you have the list of function you will hook, now you need to write a shellcode for these functions. In general you'll need one shellcode for each function cause they are different in some aspects such as parameters and values returned. In our case we need just one cause we are hooking just one function, but it is not always like this, keep it in mind.

Before starting to write assembly code, read the "Function calling sequence" section in [4]. It describes some rules you have to follow when writing assembly functions such as registers you have to preserve. The most important thing you have to know is: never changes %ebx value inside a function (trust me, I had a bad experience with it).

In our target code, our shellcode will do the following:

  1. Call the original fgets function;
  2. Create some file in the system;
  3. Write in this file the value stored in buf;
  4. Return to the caller;

This is not a shellcode paper and I'll not give details in how to write a shellcode, you can find good papers on this on the Internet, but there are some points that you must be aware of when writing hooks:

  • Call the original hooke'd function;
  • Find some place to store temporary data (if you need);

3.2.1. Calling the original function

When you write a hook to some function, generally you need to call the original function to get returned values or just to not disturb the normal behavior of a program. As you will see later in this paper, you can hook functions using two techniques and each way of hooking has you own way of calling the original function that will be described in the section.

3.2.1.1. If you hooked the PLT entry

When you hook the PLT entry of a function, the best way of calling the original function is backing up the original jmp instruction of the PLT before patching it and then inserting it in the appropriated place within your shellcode. One thing must be considered when doing this: do you need to get the control after you called the original function? If you don't need, you can just put the backup'd jmp instruction where you should place a ret instruction, this way you jmp the to original function and it do the work of returning to the caller.

But what if you need to get the control again, after the original function gets called? It is not a problem, you can write a wrapper around the jmp instruction to force it returning to you control again. Take a look at the following example:

		call	jmp_to_function 
our_control_again:
...

jmp_to_function:
jmp *GOT_ENTRY

The call jmp_to_function will push into the stack the address of the next instruction and then our backup'd jmp will take place. The the original function executes the ret instruction, the flow will be transferred to out_control_again and you can do all you need before retuning to the caller.

If you use the second technique you must re-push all the parameters into the stack before jumping to the original function. It is necessary because you have another return address in the stack and it will shift the parameters.

3.2.1.2. If you hooked the GOT entry

When you hook the GOT entry, the best way of calling the original function is backing up the original address stored in the GOT entry before patching it and then inserting it in the appropriated place within your shellcode. Again you have to decide if you need to get the control of the execution after the original function returns. If you don't need, you just need to make a jmp to the original address:

		mov	$ORIGINAL_ADDR,%eax 
jmp *%eax

If you need to get the control after the original function returns, you just need to change the jmp *%eax to a call *%eax and again, re-push all the parameter into the stack:

		mov	$ORIGINAL_ADDR,%eax 
call *%eax

It seems simple but there's one more thing you must be aware of. Remember when I talked about the "Lazy Resolution"? So, if you hook one function before it was called for the first time, the original function address you will have will be the address of the second instruction of the function's PLT entry. The problem with this is that when you call the original function, you will be actually calling the symbol resolution routine, that will locate the symbol for you, call it and the, store the right address in the GOT entry, and here you will lose the game. As you can see, the address of your hook will be replaced and it will not be called anymore.

Here you have two options. The first one is, before returning the control to the caller (after calling the original function) you re-patch the GOT entry with the hook again. It is very simple, you just need to:

		mov	$YOUR_HOOK_ADDRESS,%ecx 
mov %ecx,GOT_ENTRY
ret

Do not use the %eax or %ebx register here if you need to return a value, you will just get extra work.

Another option is to force the symbol to be resolved before you patch the GOT entry. This can be done by force the program to make a dummy call to the function you want to hook. In our example we need to make a dummy call to fgets function (we actually don't need cause fgets will be for sure resolved when we hook it, but just to illustrate the process). A dummy call to fgets can be something like this:

		fgets(NULL,0,NULL);

or in assembly:

		xor	%eax,%eax 
push %eax
push %eax
push %eax
mov FGETS_GOT_ENTRY,%ebx
call *%ebx

When you force the program to execute this instruction, you guarantee that fgets symbols is resolved before you hook it, thus, you don't need to care about it anymore.

3.2.2. Storing temporary data

Depending on what you are doing in your hooking routine, you will need to store temporary that you will use some time later in the shellcode. A good example of this is when you hook a function that returns a function pointer and you need to hook that pointer too. You will need to store this pointer somewhere in the memory and return a fake one to the caller. When this new hook is called, you need to call the original function, so you need to get it from the memory (a bit confusing huh?).

One way of doing this is forcing the process to allocate some memory for you and then hardcode this address in you shellcode (patching the shellcode). This is a good way but there's another method that I find out.

As you will see when I talk about injecting the shellcode, you code will be stored in a writable section of the process memory. Taking advantage of this, you can leave some extra space anywhere in you shellcode to store temporary data. This way you don't need to care about what address your data will be placed, you can determine it at run-time. Following is a example of this technique:

shellcode: 
...
call temp_data
pop %ecx ; %ecx contains the base
... ; address of the storage
... ; space
ret
temp_data:
pop %edx
call *%edx
.dword 0x00000000 ; 8 bytes of
.dword 0x00000000 ; temporary storage

Every time you need to use your temporary storage space, you call temp_data and then pop the address in the stack to some register. This register will contain the base address of you storage space. Simple huh?

3.2.3. Using shared library instead of assembly code

In [2], an Anonymous author demonstrated one way of injecting a shared library in some process. The major advantage of this technique is that you can write you hooks in pure C code. The disadvantage of this is that you load a new library in the process, thus leaving traces in the process memory map. If you want details about this technique you can read [2].

3.3. Locating functions' GOT and/or PLT entry

Up to here we know what functions we will hook and how to write the shellcode, but we still need one more information: where are the GOT/PLT entry of our functions located? This is the most important step in the infection process and probably the most complex too. Here we have to choose what will be the approach used to hook our functions: GOT entry hook or PLT entry hook.

You have two ways of searching for these address, using the executable file itself to navigate through the sections or attaching to the process and navigating through the process memory.

3.3.1. Using the program executable to locate the address

To search for the GOT/PLT entry in a executable file, we will need to locate some sections within it. So, before starting our search, I need to explain how to locate a section in and ELF file. It is not an elf tutorial, so if you want to know more about this, read [5].

3.3.1.1. Locating a section within an ELF file

Before everything lets take a look in the elf's main header that is located in the offset 0 of the file:

typedef struct 
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;

There are three fields in this structure that is necessary to us: e_shoff, e_shnum and e_shstrndx. e_shoff give us the offset of the first section header in the sections header table. e_shnum gives the number of entries in the sections header table, and finally, the e_shstrndx gives the index in the sections header table of the section header string table, that contains the names of all sections. Each section has the following structure:

typedef struct 
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;

The important fields for us in this structure is sh_name and sh_offset. sh_name is an index for the name of the section in the Section header string table, and sh_offset is the offset of the section within the executable file.

Know that we know the necessary structures, the following pseudo-code illustrate the steps to locate any section in the ELF file:

sh_header <- e_shoff; 
str_table <- sh_header[e_shstrndx].sh_offset
for I = 0 to Elf32_Ehdr.e_shnum do
begin
if str_table[sh_header[I].sh_name] == "desired_section" then
found_section <- &sh_header[I];
end;

This will return a pointer to the desired_section's header.

3.3.1.2. Locating the GOT address

Using the executable itself to locate the GOT entry of a symbol is the easier and most reliable way of doing that. In the ELF file, there's a special section called .rel.plt section. Following is the struct that represents each entry in this section, from elf.h.

typedef struct 
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;

The r_offset field points to the GOT entry of the related symbol and r_info contains some informations about the symbol. Among other infos, r_info contains the symbol index in the .dynsym section. This index can be retrieved using the following macro, defined in elf.h:

#define ELF32_R_SYM(val)                ((val) >> 8)

The .dynsym section contains some more informations about each relocatable symbol in the file. Each entry is represented by this struct:

typedef struct 
{
Elf32_Word st_name; /* Symbol name (string tbl index) */
Elf32_Addr st_value; /* Symbol value */
Elf32_Word st_size; /* Symbol size */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;

The important field for us here is the st_name. This field is the index for the symbol's name within the .dynstr section. The .dynstr section is a string table, just like the section header string table, where all symbols name is stored. .dynstr[st_name] points to the symbol's name string.

Ok, now we have all the informations we need to locate the GOT address for any symbol inside the executable, so, the following pseudo-code illustrate how to locate one symbol's GOT entry:

rel_plt_header <- locate_section(".rel.plt"); 
rel_plt <- rel_plt_header.sh_offset;
symcount <- rel_plt_header.sh_size / rel_plt_header.sh_entsize;
dynstr <- locate_section(".dynstr").sh_offset;
dynsym <- locate_section(".dynsym").sh_offset;

for I = 0 to symcount do
begin
if dynstr[sym[ELF32_R_SYM(rel_plt[I].r_info)].st_name] == \
"desired_symbol" then
found_got <- rel_plt[I].r_offset;
end;


3.3.1.3. Locating the PLT entry

Locating the PLT entry is similar to locating the GOT entry. We travel through the .rel_plt section and locate the entry index related to our symbol. Using this index we can calculate the offset of the PLT entry in the .plt section.

Each entry in the .plt section has 16 bytes long and as I showed before, the first entry (PLT0) is not related to any symbol. So, to calculate the the offset in the .plt section we do:

address = .plt_address + (symindex + 1) * 16;

The following pseudo-code illustrate the entire process:

        rel_plt_header <- locate_section(".rel.plt"); 
rel_plt <- rel_plt_header.sh_offset;
symcount <- rel_plt_header.sh_size / rel_plt_header.sh_entsize;
dynstr <- locate_section(".dynstr").sh_offset;
dynsym <- locate_section(".dynsym").sh_offset;

for I = 0 to symcount do
begin
if dynstr[sym[ELF32_R_SYM(rel_plt[I].r_info)].st_name] == \
"desired_symbol" then
symindex <- I;
end;

found_plt <- locate_section(".plt").sh_addr + (symindex + 1) * 16;


3.3.2. Using the process memory to locate the address

Locating the necessary address navigating through the process memory is not that easy as using the executable file, but it is possible. This technique is based on the technique presented in [2], with some major modifications.

3.3.2.1. Locating necessary sections

Again, before we start we will need to locate some sections in the process. Here, we cannot navigate through the sections headers of the process cause it is not mapped in the process memory, but we have another way: we can use the program headers. We can locate the program header table using the the elf's main header (by the e_phoff field) that can be located at address 0x08048000 in the process memory (at least on Linux boxes). Lets take a look on what a program header looks like:

typedef struct 
{
Elf32_Word p_type; /* Segment type */
Elf32_Off p_offset; /* Segment file offset */
Elf32_Addr p_vaddr; /* Segment virtual address */
Elf32_Addr p_paddr; /* Segment physical address */
Elf32_Word p_filesz; /* Segment size in file */
Elf32_Word p_memsz; /* Segment size in memory */
Elf32_Word p_flags; /* Segment flags */
Elf32_Word p_align; /* Segment alignment */
} Elf32_Phdr;

We have to traverse the program header table until we find a program header with e_type = PT_DYNAMIC. The PT_DYNAMIC gives us the address of the .dynamic sections, that contains the address of all dynamic section in the process memory. Each entry in the .dynamic section has the following structure:

typedef struct 
{
Elf32_Sword d_tag; /* Dynamic entry type */
union
{
Elf32_Word d_val; /* Integer value */
Elf32_Addr d_ptr; /* Address value */
} d_un;
} Elf32_Dyn;

If we traverse through the .dynamic sections we will found six interesting entries, identified by the d_tag field:

* DT_PLTGOT	- d_un.d_ptr will point to the .got.plt section; 
* DT_JMPREL - d_un.d_ptr will point to the .rel.plt section;
* DT_PLTRELSZ - d_un.d_val will give the size in bytes of .rel.plt section;
* DT_INIT - d_un.d_ptr will point to the .init section;
* DT_SYMTAB - d_un.d_ptr will point to the .dynsym section;
* DT_STRTAB - d_un.d_ptr will point to the .dynstr section;

At this point we have all section address we need to start scanning for the GOT/PLT entries.

3.3.2.2. Locating the GOT entry

Locating the GOT entry in the process memory is the same as locating in the elf file itself. You traverse the .got.plt section, get the .dynsym symbols' index, compare the name in the .dynstr and if it matches, you get the GOT entry in the r_offset field of the .got.plt entry. The only difference here is that you are not reading an file, but a process memory via ptrace.

3.3.2.3. Locating the PLT entry

Well, here things is a bit different, but don't be afraid, it won't hurt. As you could notice, we don't know here the address of the .plt section as we know when playing with the binary, so we have to find another way to locate the PLT entry. If you look in the beginning of this article, you can see that for each PLT entry (except the PLT0) you have a jmp *GOT_ENTRY, and this is our chance. Lets disassemble the PLT entry of our target program's printf:

80482b0:       ff 25 c0 95 04 08       jmp    *0x80495c0

Hmm, looks interesting... We have the address of the GOT entry in the opcodes. As shown in the above section, it is easy to determine the GOT entry of a symbol, so what we have to do is locate the GOT entry of our symbol, then, starting from the .init section, search for this GOT entry pattern. When we found it, this_addr - 2 will point exactly in the PLT entry of the symbol. The following pseudo-code illustrates this process:

got_entry <- locate_got("our_symbol"); 
search_addr <- init_addr;
while true
begin
pattern <- (int)*search_addr;
if pattern == got_entry then
break;
pattern = pattern + 1;
end;
found_plt <- search_addr - 2;


3.4. Inject your shellcode somewhere in the process memory

Know that you have all the address you need and the shellcode you should be asking: where in the process memory will I put the shellcode? This question has more than one answer. You can find some safe place in the stack and put the shellcode there, but there's a drawback that if the stack is not executable, you loose the game. In [3], Ares showed that you can overwrite the program headers, located in the process image and put your code there, but the size is limited in a few hundred bytes, and you cannot use the technique I explained to store temporary data. Again in [3], Ares demonstrated that is possible to force the process mmap some anonymous memory space in its address space so we can use it, and this is the technique I'll talk about in this section. At first, lets look how we do mmap an anonymous memory:

mmap(NULL,length,PROT_READ|PROT_WRITE|PROT_EXEC, \ 
MAP_PRIVATE|MAP_ANONYMOUS,-1,0)

As you can see we set this new memory region as "executable", this way we skip any stack/heap protection scheme. To force a process to execute this mmap call we need to convert it to a shellcode and then inject the shellcode in the process. To convert it to a shellcode we need to disassemble this function call:

	0x08048213 <main+31>:   push   $0x0		// offset 
0x08048215 <main+33>: push $0xffffffff // fd
0x08048217 <main+35>: push $0x22 // flags
0x08048219 <main+37>: push $0x7 // prot
0x0804821b <main+39>: push $0x41414141 // length
0x08048220 <main+44>: push $0x0 // start
0x08048222 <main+46>: call 0x804ed50 <mmap>
...
...
0x0804ed50 <mmap+0>: mov %ebx,%edx
0x0804ed52 <mmap+2>: mov $0x5a,%eax
0x0804ed57 <mmap+7>: lea 0x4(%esp),%ebx
0x0804ed5b <mmap+11>: int $0x80
0x0804ed5d <mmap+13>: mov %edx,%ebx
0x0804ed5f <mmap+15>: cmp $0xfffff000,%eax
0x0804ed64 <mmap+20>: ja 0x8050a90 <__syscall_error>
0x0804ed6a <mmap+26>: ret

The mmap system call is identified by the code 0x5a of the int $0x80 and expect a single param, via %ebx register, that is a pointer to all of its parameters. In our shellcode, the parameter, except length, will be fixed, so we can hardcode all the param in the shellcode itself, and pass this address via %ebx to the mmap syscall. About the length, we can hardcode some placeholder value and before injecting the shellcode in the process memory, patch it with the real value. Rewriting the assembly code we get this:

	mmap:		mov	%0x5a,%eax 
jmp mmap_params
mmap1: pop %ebx
int $0x80

mmap_params: call mmap1
.dword 0x00000000 // start
.dword 0x41414141 // length
.dword 0x07000000 // prot
.dword 0x22000000 // flags
.dword 0xffffffff // fd
.dword 0x00000000 // offset

Quite simple huh? Now you just need to inject this shellcode in the process memory, force it to execute and then get the mmapd address via %eax register and inject you shellcode into this address. Later we will see how to force a process to execute your code.

3.5. Patch functions' GOT and/or PLT entry

Finally the last step to infect a process. Up to here you've gotten all addresses and injected you shellcode into the process memory, now the last step is patch the GOT/PLT entry to redirect the original function call to your shellcode.

3.5.1. Patching the GOT entry

Patching the GOT entry is a very easy task. The GOT entry is just a pointer to the real function, so, what have you to do? Just overwrite this pointer with a pointer to you shellcode. But don't forget to backup the original pointer before you patch it, as I said in the section about writing the shellcode.

3.5.2. Patching the PLT entry

PLT is not that simple, but it is not that hard too. The PLT entry contains a jmp *GOT_ENTRY instruction so we can overwrite this instruction with any other instruction (or instructions) to jump to your shellcode. Again, take a look in this jump disassemble:

        80482b0:       ff 25 c0 95 04 08       jmp    *0x80495c0

You have six bytes to play with. As you should know, the better way to make a jmp code is using the push and ret combination, so you can do a absolute jump anywhere you want.

jmpcode:	push	$SHELLCODE_ADDR 
ret

Lets look the opcodes generated by this instructions:

	0:   68 41 41 41 41          push   $0x41414141 
5: c3 ret

Woow. It is exactly six bytes long. So, know you just need to patch this jmpcode to change the 0x41414141 with the shellcode's address and overwrite the original jmp to you jmpcode, in the PLT entry. Again, remember to backup the original jmp, you may need it in you shellcode.

4. Executing code with ptrace

In this entire article, you could notice that I mentioned a few time something about force a process to execute some code for you, but don't explained how we are supposed to do that. Ok, here we go!

When you attach to a process, it stops and keep waiting for command that you can send via ptrace system call (read the ptrace man pages for more informations). At this point we have full control over the process. To force the process to execute some code for us, we need to inject the code in the process, set the %eip to that location and then send a command to the process to it continue the execution. Again we need to find a place to put our code, but this time we don't need to care with this. What we have to do here is:

  1. Get the %eip address via the PTRACE_GETREGS;
  2. Calculate the size of our shellcode;
  3. Backup size bytes from the %eip address and on, in the process memory, using the PTRACE_PEEKTEXT;
  4. Write our shellcode in the %eip address, using the PTRACE_POKETEXT;
  5. Send a command the the process to continue execution;
  6. Wait till the process stops;
  7. Restore the memory overwritten;
  8. Restore the registers via PTRACE_SETREGS;

In order to utilize this technique, the shellcode must be written in a such way that the process will stop in the end of it. One way of doing it is putting an int3 instruction, that generates one break-point and stop the process again.

Sometimes when you issue the continue command, you get jammed. This happens when the target application is in the blocked state waiting for some IO operations such as some keyboard input or data from a socket. In this case, you can simply interact with the target program (hitting ENTER, connecting to the application network port, something like this) and everything will work just fine. In our test case we need to hit enter in the target program to get it working, you will see it by yourself.

5. Putting all together

Ok ok, that's enough of theory. In this paper my focus was not in showing source codes that DO the work, but explaining HOW these codes should work. But of course, I've coded something to demonstrate all the theory of this article. I called it a libinfector and is attached to this article. This library is composed of three main modules: ptrace module, elf_file module and elf_ptrace module. With the library, there's a test case composed of one target application (the same used in the article) and two infectors, one utilizing the elf file and other the process memory to locate symbols.

5.1. ptrace module

The ptrace module contains functions responsible to attaching to a process, reading data, writing data, allocating memory via mmap technique and execute your supplied code. The interface is composed by 8 functions:

  • void ptrace_attach(int pid): this function attach to the process identified by pid;
  • void ptrace_cont(int pid): send the command to the process (pid) to continue the execution;
  • void ptrace_detach(int): detach from the process identified by pid;
  • void ptrace_read_data(int pid, int addr, void *vptr, int len): read len bytes from addr, in the process identified by pid, and store the data in vptr;
  • void ptrace_write_data(int pid,int addr, void *vptr, int len): writes len bytes of vptr in addr of the process identified by pid;
  • char *ptrace_read_str(int pid, int addr, int len): read a string of maximum len bytes from addr in the process identified by pid;
  • int ptrace_mmap(int pid, int len): allocate len bytes in the process identified by pid using the mmap technique;
  • int ptrace_execute_code(int pid, void *code, int len): force the process identified by pid to execute code, using the technique described in this article;

5.2. elf_file module

The elf_file module contains functions that is used to open and elf file and locate PLT and GOT entries of symbols. The API if composed by 5 functions:

  • elf_file_struct *elf_file_new(char *filename): open filename, create and elf_file_struct filled with the necessary values and return it;
  • Elf32_Shdr *elf_file_locate_section(elf_file_struct *elf, \ char *section_name): locates the section_name within the binary described in the elf structure;
  • int elf_file_find_symbol_index(elf_file_struct *elf, \ char *symbol_name): get the index of the symbol_name in the .got.plt section of elf;
  • int elf_file_find_symbol_got(elf_file_struct *elf, char *symbol_name): locate the GOT entry of symbol_name in elf;
  • int elf_file_find_symbol_plt(elf_file_struct *elf, char *symbol_name): locate the PLT entry of symbol_name in elf;

The elf_file_struct is composed by the following fields:

typedef struct 
{
char *elf_file_start;
Elf32_Shdr *rel_plt_section;
Elf32_Shdr *plt_section;
Elf32_Shdr *dynsym_section;
Elf32_Shdr *dynstr_section;
}elf_file_struct;

  • elf_file_start : this field points to the beginning of the file mmap into the memory;
  • rel_plt_section : points to the .rel.plt section header in the file;
  • plt_section : points to the .plt section header in the file;
  • dynsym_section : points to the .dynsym section header in the file;
  • dynstr_section : points to the .synsym section header in the file;

5.3. elf_ptrace module

This modules does the same job of the elf_file module, with the difference that it does not utilizes the elf file, but attaches to the process and use its memory. The API is composed by 4 functions:

  • elf_ptrace_struct *elf_ptrace_new(int pid): attaches to the process identified by pid, fill the elf_ptrace_struct and returns it;
  • int elf_ptrace_find_symbol(elf_ptrace_struct *elf, char *sym_name, \ int plt): locate the PLT/GOT entry of sym_name in the process identified by the elf structure. plt indicates if you are searching for the PLT entry (plt = 1) or the GOT entry (plt = 0);
  • int elf_ptrace_find_symbol_got(elf_ptrace_struct *elf, \ char *sym_name): wrapper to elf_ptrace_find_symbol(elf,sym_name,0);
  • int elf_ptrace_find_symbol_plt(elf_ptrace_struct *elf, \ char *sym_name): wrapper to elf_ptrace_find_symbol(elf,sym_name,1);

The elf_ptrace_struct is composed by the following fields:

typedef struct 
{
int pid;
int strtab;
int symtab;
int init;
int got;
int rel_plt;
int rel_plt_size;
}elf_ptrace_struct;

  • pid : process identification;
  • strtab : address of the .dynstr section in the process;
  • symtab : address of the .dynsym section in the process;
  • init : address of the .init section in the process;
  • got : address of the .got.plt section in the process;
  • rel_plt : address of the .rel.plt section in the process;
  • rel_plt_size : number of entries in the .rel.plt section;

6. References

  1. Shared Library Call Redirection Using ELF PLT Infection - Silvio Cesare - http://www.phiral.net/lib-redirection.txt
  2. Runtime Process Infection - Anonymous - http://www.phrack.org/phrack/59/p59-0x08.txt
  3. Runtime Process Infection 2 - Ares - http://ares.x25zine.org/ES/txt/0x4553-\Runtime_Process_Infecting.htm
  4. Intel386 Architecture Processor Supplement Fourth Edition - http://www.caldera.com/developers/devspecs/abi386-4.pdf
  5. Elf specification - http://x86.ddj.com/ftp/manuals/tools/elf.pdf

begin 644 libinfector-1.0.tar.gz 
M'XL(`,(9_D,``^Q;?VP;UWU_E"B11\JV9,EVFCCI6?(/2J8E4J)D1_X1R])9
MT2*)FDA5SF+C<B*/$FV*Q_".LN/4;0*G6Q4U6YMD:X9YP)`$ZX"U:98%79<%

...

MX"28`!=5"-VHEH#UP'4#U==",SQ0`SSS`W6`,S\TK\,#!&]^Q]6YQ9+G-16J
M%1#E"(ZR`YXV0%-("K5D](PIM]D0;//H^H=1,`I&P2@8!:-@%(R"43"L``"6
'\MK"`/``````
`
end

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT