Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- AUTOMATED REVERSE ENGINEERING: MISTFALL ENGINE
- ----------------------------------------------
- (x) 2000 Z0MBiE
- xlated from russian in 2001
- Our efforts are directed to develop such method of executable program
- modification, that finding changes will require maximal amount of time.
- Modification means addition of the viral code to some specified program,
- given in the PE format. It is obvious, that main viral body should be
- encrypted, and metamorphic (generated) virus decryptor should be
- integrated with program's code.
- Hence, our task is divided into 3 parts, or 3 global questions: WHAT?,
- WHERE? and HOW?
- WHAT? - is a question about instructions, those will be inserted into
- modifying program. It is explained in the article about metamorphism and
- shown in the CODEGEN metamorphic engine.
- WHERE? - is a question of finding places in the modifying program, our
- instructions should be inserted into. This is a simple task; engine's
- caller will resolve it in the different ways.
- This article shows HOW to insert our own instruction between two
- arbitrary instructions of the modifying program. In other words, how to
- decompile, modify and compile the whole program.
- THEORY
- So, task is to insert own instructions between PE file's ones. But,
- because of instructions are linked between each other, changing
- instructions involves change of links. But change of links involves change
- of other instructions, and so on, until significant part of code will be
- changed.
- For example: when inserting some instruction into block of code,
- offsets of all the instructions coming after insertion are increased.
- This means that mostly all offsets - both relative and absolute, should be
- fixed. While fixing relative offsets, some jxx can grow, that means that
- all this shit should be repeated from start.
- As you can see, there are two kinds of offsets: absolute (offsets
- itself) and relative (jmp/call arguments). And we should find all these
- offsets. Absolute offsets may be found by means of analyzing PE structure,
- including fixup table. Finding relative offsets requires partial
- disassembly.
- Hence, we must disassemble file into some easy-modifiable substance,
- change this substance, and assemble file back.
- Algorithm will be formalized as an universal engine, working with PE
- files.
- So, task is divided into 5 general steps:
- 1. Load PE file into virtual addresses; allocate flag table (DWORD for
- each byte), and initialize it with special PE structure - related bits;
- in other words, perform initial PE structure analysis.
- 2. Disassemble file (instruction by instruction), at the same time
- filling flag table with new information about instruction offsets. At this
- step we can not make a mistake, i.e. to recognize code as data or vise
- versa, because such fault is fatal; so, dual situations should be
- excluded, and some files may not be processed at all.
- 3. Convert all known information into list of: instructions,
- datablocks, labels and pointers. I.e. to make our information a bit more
- high-level, a bit nearer to source. Such list is an easy-modifiable
- substance we was speaking about; it is generated because of easy
- manipulation with its elements.
- 4. Call <user's mutator> (external relative to engine), which will
- modify instruction list; for example insert additional instructions or
- remove existing ones.
- 5. Assemble file back: recalculate all offsets; fix PE structure;
- rebuild fixup table and so on.
- DISASSEMBLING PROBLEMS
- Main problem, of course, is disassembling. We have no such resources,
- as IDA, for example, - neither memory, neither time, nor signatures;
- moreover, our virus is limited to some kilobytes.
- And main disassembling problem is duality of some labels: they can
- precede both code and data. I.e. if we have some data fixup that points to
- some label, and there is no code instruction pointing to the same label,
- then we can not correctly decide if this label precedes code or data.
- Such misrecognizing causes the following: code, interpreted as data,
- will not be fixed, and will fail program when executed, commonly on such
- instruction as jmp, jxx or call. Vise versa, incorrectly fixed data (i.e.
- data interpreted as code) will with high probability cause fault too.
- This means that we must always correctly disitinguish code and data.
- Some library functions may be found by singatures, but we can not insert
- signature base into virus; also, jmptable-analysis can help, but only
- partially.
- We can work with only special easy-decompileable files, containing only
- code in the CODE section, but such files are real rarity.
- So, we will a bit reduce set of processing files (exclude packed ones
- and etc.), improve our disassembler as possible, and hope for luck.
- IMPLEMENTATION
- Really, there are much more little steps we should perform; and each
- step have an influence to the result. Here is a complete list of steps
- required for PE file reversing:
- - check if given PE file is valid; if fixup table is present & etc.
- - allocate memory for virtual program image and for flag table
- - load into virtual addresses:
- 1. dos stub & pe header
- 2. sections
- - analyze PE header; process pointers; mark phsyical/virtual start/end
- of each section & etc.
- - process imports
- - process exports
- - process fixups
- - process resources
- - search for signatures (such as push ebp/mov ebp,esp) and mark 'em
- as for-next-analysis
- - mark entry point
- - disassemble file, instruction by instruction.
- algorithm is described in the article about permutation, but
- there are some differences:
- when there are no more bytes marked as for-next-analysis, we must
- search for labels-pointed-by-data-fixups, and check if they precedes
- code instructions. (see below)
- - build list of opcodes, labels, pointers & etc.
- note, that such list will contain zero-length pointers (labels), i.e.
- transfer is performed to higher level of abstraction, nearer to source.
- After converting linear arrays into list, linear arrays
- (memory & flag table) may be deallocated.
- - modify list; while debugging, i was inserting NOPs between instructions.
- - recalculate new virtual/physical addresses for each list entry
- - recalculate fixup table
- - recalculate pointers (i.e. rva's, fixups & relative aguments
- of the conditional jmps); if some jmps are grown,
- repeat from recalculating virtual addresses.
- Note, that here possible some iterations, it is normal.
- - assemble list (collect all stuff into single file)
- - write file to disk
- - copy overlay, if present
- - recalculate checksum, if non-zero
- Phrases, such as "analyzing PE header" or "process imports" means that
- we analyzing these structures and marking labels, pointers and other
- special objects in the flag table with special bits. For example byte at
- the pe_header+28h (28h=EntryPointRva) will be marked as FLAG_DWORD |
- FLAG_RVA, and byte it points to will be marked as FLAG_LABEL | FLAG_CREF.
- Which special objects will be present in the instruction list?
- 1. Label, i.e. zero-length substance pointed by RVAs and FIXUPs.
- 2. RVA, or DWORD pointing to some label.
- 3. FIXUP, or DWORD, same as RVA, but increased by IMAGEBASE,
- requires to be inserted into fixup table on the final steps.
- 4. so-called DELTA, or difference between virtual addresses of two labels.
- 5. instruction
- 6. datablock
- Now, about distinguishing code and data when only existing reference
- is data-reference. Algorithm: take instruction by instruction; check if it
- is not 00 00, FF FF, F4 (hlt), CD (int), and so on -- i.e. fail if such
- instruction is never present in the standard PE file. Also fail, if one of
- the middle bytes of the instruction is marked as label, data, or other
- special object. Also, if instruction is jmp,call,jxx,jecxz and so on, then
- it should not point into the middle of other instruction or to data.
- Analysis is repeated, until marked-as-code instruction, RET or JMP is
- found.
- But, there are another kind of dual situation. You can think that such
- object as label can not exists in the middle of some instruction,
- generated by the HLL compiler. But it can. Lets examine typical situation:
- AVPBASE.DLL:
- 100050D3 83E904 sub ecx, 4
- 100050D6 720C jb 100050E4
- 100050D8 83E003 and eax, 3
- 100050DB 03C8 add ecx,eax
- 100050DD FF2485F0500010 jmp dword ptr [100050F0+eax*4] (1)
- 100050E4 FF248DE8510010 jmp dword ptr [100051E8+ecx*4]
- 100050EB 90 nop
- 100050EC FF248D6C510010 jmp dword ptr [1000516C+ecx*4] (2)
- 100050F3 90 nop
- 100050F4 00510010 dd 10005100
- 100050F8 2C510010 dd 1000512C
- As you can see, 100050F0-address, that is XREF-ed by (1), really is
- part of the instruction (2). Its not so hard to understand why it is so.
- As a result, we can not decide, if (2) is code or data. I.e. it is
- impossible to determine if (10050F0 == 100050F4 - 4) or (10050F0 ==
- 100050EC + 4). So, pointer at (1) can not be fixed and file can not be
- processed.
- Or, another one example:
- FAR.EXE:
- 004474D8 B8E1C24200 mov eax,0042C2E1 ; ==42C350-6Fh ; (1)
- 004474DD 6A00 push 00
- 004474DF 6800000100 push 00010000
- 004474E4 83C06F add eax, 6F ; ==111
- 004474E7 50 push eax
- 004474E8 E8AF180100 call 00458D9C
- ...
- 0042C2DB E8B0450200 call 00450890
- 0042C2E0 83C408 add esp, 08 ; (2)
- 0042C2E3 8D9500FFFFFF lea edx,[ebp][0FFFFFF00]
- ...
- 0042C350 55 push ebp
- 0042C351 8BEC mov ebp, esp
- 0042C353 833D5054460003 cmp d,[000465450], 03
- And, in addition, another bad thing: parts of 16-bit code, inserted
- into 32-bit applications, such as antiviruses, formatters and so on.
- The ENGINE
- Though, it works. Engine is called MISTFALL, there is G.Martin's story
- named "With morning comes Mistfall".
- Engine is written in Borland C++, but without classes or other shit.
- Main engine() subroutine with fucking lots of parameters comes first,
- followed by some internal additional subroutines. All this stuff is called
- kernel, and it is located in the engine.cpp & .hpp; code and constants
- correspondingly. Kernel calls so-called user's mutator (mutate.cpp), which
- must be written by engine's user. Mutator works only with instruction
- list. List entries describes labels, opcodes, data blocks and so on.
- Moreover, all the engine interface technology is taken from RPME engine.
- The only difference is that RPME works with blocks of viral code, and
- MISTFALL works with PE files.
- As a result, simplest file infection is the following:
- 1. Find any two non-jmp instructions; insert JMP after first instruction
- to the second one; insert encrypted viral body after JMP.
- 2. The same for decryptor.
- 3. The same for commands calling the decryptor.
- This was only one of lots of possible variants; the more such methods
- we will use, the harder it will be to analyze the virus.
- USAGE
- Engine uses lots of memory. It can fail on incorrect file; and on
- normal one too. Memory allocation, same as in RPME, is provided by
- engine's caller. Before calling engine, you must allocate about 32 MB of
- memory, and write own malloc() subroutine which will provide to engine
- pointers to little parts of this big block. It is just kind of own heap.
- Engine's allocation strategy is specific: it will only allocate memory and
- never deallocate. But, its ok, 'coz the same big memory block will be used
- for each file. Really, engine uses (17*SizeOfImage) bytes of memory for
- linear arrays and 40 bytes of memory for each list entry. Engine's code is
- permutable, i.e. it satisfies most of demands described in the "Demands to
- engines" article. Engine can work in ring-0, but, because of lots of
- required memory & time, dont call it there. Time, used by engine is
- unpredictable; minimal time for processing one file is some minutes in
- realtime priority.
- Obvious, that such engine cant be invoked on system events, such as
- openfile. You should work with file queue, passing file by file to engine
- in the individual thread or process.
- SPECIAL FEATURES
- Engine works only with standard files. I.e. all the section names must
- be known: such as .text, .data and so on. Otherwise file is packed or
- whatever, and will not be processed.
- For all the standard files it is accepted that code exists only in the
- first section; this allows us to increase disassembly quality.
- SIGNATURE LIBRARY
- This is another external subroutine, performing 2 tasks:
- 1. on start: search for known codeblocks, and
- 2. add existing codeblocks into library
- I.e. before disassembling signature manager will mark known codeblocks
- as CODE, for-next-analysis, and, on exit, it will update signature library
- with new information.
- When applied to viruses, signature library will be created per each
- local machine, performing kind of self-education, with each new file.
- Signature here means some constant code bytes, not containing fixups
- and relative offsets.
- ENGINE INTERFACE
- // return: 0==success, non-zero error codes listed in ENGINE.HPP
- int __cdecl engine(
- DWORD user_arg, // user's parameter (whatever)
- BYTE* buf, // PE file
- DWORD inbufsize, // initial size of file
- DWORD* outbufsize, // ptr to resulting size of file
- DWORD maxbufsize, // maximal allocated buf size
- int __cdecl user_disasm(DWORD,BYTE*), // length-disassembler
- void* __cdecl user_malloc(DWORD,DWORD), // memory allocator
- DWORD __cdecl user_random(DWORD,DWORD), // randomer
- int __cdecl user_mutate( // mutator
- DWORD user_arg, // (described below)
- PE_HEADER* pe,
- PE_OBJENTRY* oe,
- hooy* root,
- int __cdecl (*)(DWORD user_arg,BYTE*),
- void* __cdecl (*)(DWORD user_arg,DWORD),
- DWORD __cdecl (*)(DWORD user_arg,DWORD)
- ),
- int __cdecl user_sigman( // signature manager or NULL
- DWORD user_arg,
- PE_HEADER* pe,
- PE_OBJENTRY* oe,
- BYTE* memb,
- DWORD* flag,
- DWORD action) // 0/1
- ); //engine
- int __cdecl mutate( //mutator
- DWORD user_arg, // user's parameter
- PE_HEADER* pe, // pe header
- PE_OBJENTRY* oe, // pe objtable
- hooy* root, // list root
- int __cdecl user_disasm(DWORD user_arg,BYTE*),
- void* __cdecl user_malloc(DWORD user_arg,DWORD),
- DWORD __cdecl user_random(DWORD user_arg,DWORD)
- ); //mutate
- int __cdecl sigman( //signature manager
- DWORD user_arg, // user parameter
- PE_HEADER* pe, // pe header
- PE_OBJENTRY* oe, // pe objtable
- BYTE* memb, // virtual memory block to work with
- DWORD* flag, // flag table
- DWORD action // 0==before(mark), 1==after(update)
- ); //sigman
- This shit shown above... of course it doesnt means, that you should
- program in C++ to use engine. Engine may be called both from asm & cpp.
- EXAMPLES
- Mistfall engine was used in the Z-10 virus, all sources are published
- in the TZ #1 e-zine.
- * * *
Advertisement
Add Comment
Please, Sign In to add comment