herm1t
December 2009
Andrew Tanenbaum "Modern operating systems"
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.
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):
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?
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:
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:
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:
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:
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 ... RELOCATION RECORDS FOR [.text]: 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:
| Bits | Name | Description |
|---|---|---|
| 31 | SRC_FRAG | reloc resides in (0 code section, 1 - data) |
| 30..29 | DST_FRAG | the value points to (0 - code, 1 - data, 2 - library function, 3 - special symbol) |
| 28 | SRC_TYPE | Type of address (0 - sbsolute, 1 - relative) |
| 27-14 | SRC_OFF | Offset within fragment (depending of SRC_FRAG) of code/data which should be patched |
| 13-0 | DST_OFF | Offset 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:
The addresses of library functions:
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. :-)
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:
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.*)
...
to
__text_start =.;
_text : {
stub.o(.text)
virus.o(.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):
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. :-)
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.
$ 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)]