fenris - program execution path analysis tool Copyright (C) 2001, 2002 by Bindview Corporation Developed and maintained by Michal Zalewski ========================================================================== Sections in this document: [0x00] What is fenris? [0x01] How does it work? [0x02] How can I use it? [0x03] Project limitations [0x04] Security issues / forensics [0x05] Tracing mechanism [0x06] Bug reporting and other feedback [0x07] Known bugs / TODO [0x08] Thanks and credits [0x09] Further reading URL: http://razor.bindview.com/tools/fenris/ UI demo: http://razor.bindview.com/tools/fenris/fenris-ui.html Portions of this program use code derived from libi386 library, a part of project 'bastard'. See libdisasm/00-READ_FIRST for more details. Portions of 'dress' utility inspired by klog's code from Phrack 56. For alternative programs of this kind and other promising projects, be sure to visit http://lcamtuf.coredump.cx/fenris/other.txt . [0x00] What is fenris? ---------------------- Code analysis is not limited to debugging, quality assurance or security audits. Understanding and handling file formats or communication protocols used by proprietary solutions, a problem that many corporations face when they decide to change their base software platform from one, obsolete or insufficient solution to another, perhaps more suitable, is a task that can consume long months and millions of dollars, especially when any misjudgment or misinterpretation is made. Because of that, accurate and complete information about existing solutions has to be obtained and evaluated in a timely manner. This project is an attempt to fill the gap between currently used tools by providing a freely available program analysis utility, suitable for black-box code audits, algorithm analysis, rapid reconnaissance in open-source projects, tracking down bugs, evaluating security subsystems, performing computer forensics, etc. This program does not automate the process of auditing, and does not favor any particular use. Instead of that, it is intended to be a flexible and universal application that will be a valuable solution for many advances users. While functional, it is probably not tested sufficiently, there are many issues to fix, several known bugs, some portability issues. It is being released primarily to get user feedback, comments, and, most important, to request development support, as my resources are very limited, both in terms of available time and development platforms. This project is and will be distributed as a free software, regardless of projected use, accompanied by complete sources, under the terms and conditions of GPL. Why do you might need this code? Well, there are few reasons... Human beings are, so far, the best code analysts. Unlike computer programs, they have imagination, ability to build synthetic abstract models, and yet to observe and analyze smallest details at the same time. Functionality is often being described as "doing what the program is supposed to do", security as "doing what the program is supposed to do and nothing more". While it might sound funny, that is the most general and complete definition we have. In most real-life scenarios only humans really know what are their expectations. Building strict formal models of our expectations does not necessarily mean that models themselves are flawless, and is very time-consuming. Then, even with such models, validating the code is not always possible, due to its computational complexity. That is why real, live programs (not including some critical developments) do not have such models, do not follow any particular coding guidelines, and cannot be formally examined without human judgment. Unfortunately, humans are also highly inaccurate and very expensive. They work slowly, and better results can be achieved by hiring better specialists and performing more careful audit. And after all, even the best expert can overlook something in complex, hard to read code. It is almost impossible for human to perform an accurate audit of a large, complex, heterogeneous project written e.g. in C - like Sendmail, BIND, Apache - and provide results in reasonable time. Things get even worse when humans try to understand algorithms and protocols used by complex closed-source black box solutions. They are simply too slow, and not always able to make accurate guesses about dozens of complicated, conditional parameter passes and function calls before final action is taken. While it might sound surprising, human-driven code audit is very similar to playing chess - it is a general analysis of possible states, way too many to be implicitly projected by our conscience, a result of experience, knowledge, some unparalleled capabilities of human brain, and luck. It is also a subject to false moves and misjudgment. And there is just maybe few hundreds of excellent players. As for today, freely and commercially available audit tools both use two opposite approaches. First approach tends to minimize human role by automating the review of source code. Source code analysis methods are good in spotting known, repeatable static errors in the code - such as format string vulnerabilities. On the other hand, static tools are not able to trace and analyze all possible execution paths of complex application by simply looking at its source. The reason for inability to follow all execution paths lies deeply in the foundations of modern computation theory, and one of its aspects is known as "the halting problem". Speaking in more general terms, in many cases (such as complex software, or even underlying operating system), the amount of medium needed to store all possible states of a complex program exceeds significantly the number of particles in the universe; and the amount of time needed to generate and process them sequentially is greater than the lifetime of our universe, even having a machine that works with the speed of light. This might be changed by the development of new computation models, such as quantum computing, or by creating mathematical models that allow us to make such problems non-polynomial - but for now, we are far from this point, and static analysis is restrained in many very serious ways, even thought many software suppliers tend to market their products as the ultimate, 100% solutions. Subtle, complex, conditional dynamic errors, such as privilege dropping problems, input-dependent table overflows in C and many other issues usually cannot be detected without generating completely unacceptable number of false positives. This kind of software is highly dependent on coding style, and specific notation or development practices might render them less efficient - for example, automated audit utilities can usually detect problems like insecure call to strcpy() function, but will very likely not notice insecure manual copy in do-while loop. The truth is, for programs that do not have previously built formal models, static auditing utilities look for known, common problems in known, common types of code in a very limited scope. Another issue is the applicability of this approach to algorithm analysis tasks. In the domain of automated audit tools, this problem is "reduced" to building a formal model of program behavior, or, more appropriately, generating certain predictive statements about the code. While there are very interesting developments in this direction, such as the work of professor Patrick Cousot, it is very difficult to make any detailed, accurate and abstract enough run-time predictions for a complex source code that have any immediate value in the analysis of unknown algorithm. Last but not least, static analysis of sources can be deployed only when the source code is available, which does not have to be the case. This approach is a subject to many shortcomings, tricky assertions, and is a technique of strictly limited capabilities. This is, of course, not to dismiss this method - but to demonstrate that this much favored approach is not flawless and how much it needs to be accompanied with auxiliary methods. Second approach to be discussed here is based on a dynamic run-time program analysis. This method is usually used to provide the user with an information about actual program execution path, letting him make decisions on which path to follow and giving him free will to draw any conclusions and perform all the synthetic reasoning. This method is applied to live binary executed in real-time and bases on monitoring syscalls (strace), libcalls (ltrace) or functions (xtrace); in certain cases, breakpoint debuggers, such as gdb, can be used, however it is usually not feasible to use them to perform anything more than in-depth analysis of a very small portion of program functionality. Usually, such analysis provides a very useful information on what is happening, and this information is provided in uniform, reduced-output form. Careful auditor can analyze program behavior and find interesting or potentially dangerous run-time conditions. By monitoring how given application interacts with external world, he (or she) can determine whether some other conditions can be triggered and eventually explore them by examining sources or re-running the program. Advantages are enormous, as such software enables the auditor to spot very subtle errors in code that "looked good", to observe actual execution, not to try to figure it out, and to find or trace down not obvious or non-schematic vulnerabilities. Run-time trace tools are primarily used for fast reconnaissance tasks and for tracing down notorious errors that are not clearly visible in the source, significantly reducing the time of such operations. There are, however, serious drawbacks related to this method. First of all, known tracing tools do not provide the complete information. They will detect strcpy() call, but won't report if exactly the same functionality has been implemented from scratch by the author of given program. And, in some cases, the amount of produced data can be enormous, and because of its completely unstructured character, it makes the observation of overall execution vector almost impossible. Two most important problems are: correlating trace data with actual code, and determining what occurred in the "dark matter" between two lines of trace output. There are some attempts to combine both approaches - run-time evaluation and source code analysis - such as Purify or many other commercial development support products. Unfortunately, they all feature a limited set of capabilities that need development-side or compilation-time support and are not really suitable for comprehending black box solutions or performing a general analysis. Most of them are targeted for dynamic memory debugging and code / memory profiling. While not mentioned above, there is also another approach to black-box code - high-level decompiler. However, the complexity of modern compilers makes it very difficult to develop effective C decompiler or similar utility, and there are only a few project available to accomplish it, most of them not able to deal with too complex or optimized code. Finally, there is no guarantee that generated output code will be any help in comprehending the program. For now, this approach remains almost purely theoretical, and I am not aware of any auditors using it extensively. This project, Fenris, is named after the monstrous wolf, son of the Norse god Loki. It is not the ultimate answer to all questions, not a solution for all problems, and under no circumstances is intended to replace other tools and techniques. On the other hand, it makes one step forward compared to other tools, trying to support the auditor and to make his work much more effective. This is accomplished by combining a number of techniques, including partial run-time decompiler, stateful analysis, code fingerprinting, I/O analysis and high-level visualization layer, run-time code modification capabilities. The goal is to provide a very detailed trace information, and, at the same time, to provide a data suitable to build a model of program behavior more quickly and in more convenient way. Fenris is not supposed to find vulnerabilities or bugs, or to guess algorithms or describe protocols. It is supposed to report and analyze the execution path - detect and describe functional blocks, monitor data flow in the program, marking its lifetime, source, migration and destination, analyze how functions work and what conditions are evaluated. At the end, it is supposed to create execution model of traced program (or arbitrarily chosen portion of it, if complete trace results in too much noise or irrelevant information), and to suggest how this model can change in different conditions. Fenris does not need source codes of analyzed application, but obviously does not keep the auditor from using them. For many users, Fenris might be a new tool, for others - just a command-line replacement or addition to strace, ltrace or similar applications (there's a brief list of other nice tools in doc/other.txt). And that's the idea - to build a tool that is simple, reusable, but also precise and smart. It is supposed to have advantages over other tools, but not to be an ultimate replacement or the final solution. Some users can just use very specific features, such as automated function fingerprinting, and use companion tools instead of the main program. There are two layers on which Fenris work. First of all, it works on real-time level, tracing program structure and interaction with libc and OS, detecting and describing parameters, tracing buffer sizes and such. First layer processing produces text-format output stream, which can look like that: 8617:00 setuid (0) 8617:01 [L] SYS setuid (0) = 0 8617:00 ...return from libc = 0 8617:00 <0x80123456> cndt: conditional block +10 skipped 8617:00 local fnct_34 (bfffb01a "$something?" ) 8617:00 + bfffb01a is bfffb01a:1000 (created by create_buffer) 8617:00 last input: my_function:read from /etc/config.cfg 8617:01 __libc_open64 (l/bffff80d "onefile", 3) 8617:02 [L] SYS open (bffff80d "onefile", O_RDWR|O_CREAT) = 4 8617:02 + 0xbffff80d first seen in fnct_4:open, size 20 8617:02 last input: handle_net:recv 8617:01 ...return from libc = 4 8617:00 ...return from fnct = 8617:00 Function has written non-local memory: 8617:00 * local object irc_servers:123 (0x80654321) 8617:00 * unknown address 0x40123456 Fenris output is a descriptive trace of program activity. For larger project, this trace might have many megabytes, and, to avoid problems reading it, it should be either delimited to a revelant structural portion of code that is being analyzed right now, or should be transformed in a different model of execution path. One of such trivial transform tools is called 'ragnarok'. It is able to convert first layer output into graphical html model that summarizes program structure, marks data migration, I/O points, conditional points, and, on request, provides detailed information about single functions. Ragnarok is hardly an integral component of Fenris - it is merely a parser that transforms text output into html. Any other presentation schemes or destination formats can be easily achieved. Ragnarok output consists of five sections, or views: a) "program flow": a table, or, in the future, a browsable graph (think of IBM's OpenDX) with horizontal lines (rows) representing functions and vertical lines (columns) representing buffers and I/O sources (Y axis is time). When function uses one of buffers or interacts with I/O source, intersection of this object and function is marked in a way dependent on the nature of this operation. Conditional expressions evaluated during program execution are also marked. Data migration is marked. Additional table section outlines interaction with data sources (file descriptors). b) "function view": a description of a single function call: current parameters, modified memory, return code, inside-function conditions, function trace, etc - everything intended and providing some useful shortcuts to follow nested calls. c) "function summary": a summary of all calls of this particular function, useful for determining the purpose and range of accepted parameters of given function. d) "buffer view": a history of all modifications and I/O ops applied to a single buffer. e) "I/O view": a history of all I/O operations performed on a single file descriptor. [What is missing for now are files mapped to memory - fix it] The main idea is to provide five different views of program activity, each of them can be used separately for different purposes. For example, buffer view can be very useful for vulnerability research, and function summary view will be an invaluable source of reverse engineering information. Ragnarok basically consists of five different and losely connected parts. Ragnarok is text-browser friendly, but reading outputs for complex programs might be rather unpleasant and graphic browser is recommended. As HTML is not necessarily the most useful way of presenting huge amounts of formatted data, be prepared to get few megs of html for less than one meg of trace output (still, -R option is a neat workaround; thanks to negative, we also provide 'split' utility to convert Ragnarok output file into several separate html pieces). Fenris provides some other interesting features, as well. For example, it is able to manage a database of MD5 fingerprints of functions. With a companion utility provided with Fenris, you can even recover symbol tables for stripped static binaries and then use your favorite debugger: Before: 0x804811c: call 0x8054ad4 After: 0x804811c: call 0x8054ad4 By default, only libc functions are placed in the database, but it is fairly easy to archive user's own functions there. If any unknown function is found (e.g. in static binary with no symbols), quick matching is performed and all possible guesses are reported back. This makes work much easier, knowing that fnct_123 is actually fprintf, and fnct_124 - vfprintf: 25281:02 local fnct_12 (0, 0, 0) 25281:02 + fnct_12 = 0x804c868 25281:02 # Matches for signature 42BA3CA4: libc_init_secure I must say I'm pretty disappointed to find out that IDA disassembler for Windows features somewhat similar mechanism for function fingeprinting =) On the other hand, IDA is something completely different, plus I believe my implementation is simply more straight and accurate :) And they don't have symtab recovery!:P Other interesting features include text buffer auto-detection, buffer size and modification tracking and such. Fenris does also provide some limited code modification capabilities, so that you can, for example, NOP-out debugger detection code, checksum verification, or some fork() you don't want to happen. This is very advisable to elliminate fork()s and trace one execution branch at once, as Linux does not provide a reliable mechanism for attaching to the forked child immediately. There are some tricks to do it (such as manually inserted jmp-to-self loop that is restored later when the process is attached), but it is not implemented in Fenris at this moment. Right. The reasons why you might want this project are exactly the same as the reasons you might find it useless. It is very verbose, designed for a specific kind of work, and not all people will find it useful. But once again, I encourage you to research alternative you might have never heard of and support other promising developments - please read doc/other.txt for more information. This README is not intended to describe all features of this project or to provide complete documentation, at least not for now. I believe the project is rather intuitive, so I will focus on essentials now :) [0x01] How does it work? ------------------------ Fenris is far from being complex. The key to its simplicity is the fact it traces one execution path instead of trying to predict all possibilities. It is all about detecting, reporting and analyzing low-level assembly language constructions that are known to represent specific operations. Current version of fenris is developed to work fine with i386 executables on Linux, and to understand code generated by GNU C compiler, or other compilers that use GCC framework - GNU C++, GNU Ada Translator, possibly GNU Fortran. On the other hand, there is no reason why it cannot be ported to support other high-level languages, compiler frameworks or architectures - and one of main reasons for releasing the code is having this process of porting started. The most significant drawback of using universal real-time tracers such as ltrace or strace is a lack of abilities to observe internal program structure, function calls, conditional execution, etc - "black matter" mentioned earlier. Unfortunately, for many programs, this black matter, not I/O manifestations, is critical for understanding algorithms. I/O activity is just a shadow of something that cannot be seen. There are certain programs, such as xtrace, that are capable of providing this additional information, but their applications are very limited - they are not suitable for black-box code, and provide a very basic, unstructured information. Fenris tries to change this situation in many ways. Others than mentioned earlier (ragnarok, partial tracing): a) recognizing internal function calls and assigning unique names to them even if original name cannot be determined, b) automatically counting the number of passed parameters and recognizing them appropriately, so the purpose of given function and its domain can be determined easily; used mechanism can handle such a complex expressions as "myfunction(otherfunction(7*sin(1))*2,17*i,val?10:0)" and handle parameters properly, even in the code generated with high optimization settings; parameter detection includes automatic search for text entities and displaying them properly, c) providing appropriate information about function nesting (both using visual indentation and numbers), so the structure of program is clearly visible and certain layer of processing can be extracted and separated easily, d) detecting shared code by comparing MD5 fingerprints for functions in statically linked binaries; this way, in many black-box solutions, unique functions can be distinguished from common library functions in a quick way; it also enables the auditor to maintain a database of already identified functions and have their names displayed every time given code is called, e) stateful tracing; Fenris maintains an internal map of used memory, opened files, etc. Whenever a file descriptor or a buffer is involved in any operation, its current state, information about its origins, etc, is provided. f) buffer detection and tracing; buffer sizes and contents is being traced; both modifications performed by known library functions and syscalls, and manual access performed by unknown functions is reported (assembly-language level analysis is used); additional memory analysis is deployed to keep tracks of buffer lifetime and location (this includes local buffers passed to nested functions, too), g) certain known library and system calls are handled and used to keep track of abstract operations such as data migration, h) conditional expressions, grouped in several classes that represent certain high-level constructions, are detected and reported, i) providing run-time code modification capability to bypass checks, anti-debugging code, simulate different run-time conditions, etc. For recognized structures and calls, please refer chapter 0x05 (tracing mechanism). [0x02] How can I use it? ------------------------ Fenris is distributed under terms and conditions of the GNU public license. It means you are free to use, distribute and modify this program. You can use it in any way you want - to debug your own code, find bugs in third-party software, understand and reverse-engineer protocols and algorithms, and such. If you want to use this code in your own software, please let me know. On compilation time, you can choose what variant of fenris you want to build. Some of more useful options: make all - typical compilation; highly optimized, but build process is time and memory consuming make minimal - optimized version; should compile much faster, but might deliver lower performance. make debug - debugging version; this enables memory tracing, debugging symbols, disables all optimizations and such; not recommended for everyday use. make heavy - heavy debugging of calling conventions; produces tons of useless info; suitable for debugging, but works fine with dynamic binaries w/symbols only. Invoking fenris is pretty simple, and does not really require any documentation for anyone who ever used 'strace' or similar application. Called without any parameters, it will display short help. The main program is invoked the following way: fenris [ -E PAR=VAL ] [ -u user ] [ -o file ] [ -L dbase ] [ -R a:b ] [ -t nnn ] [ -P ip:off:val ] [ -sdyiCSfFmxpAe ] program [ params... ] Mandatory parameter is program name, eventually followed by program parameters. If, for some reason, program name has to start with '-', it should be preceded with '--' parameter. Before program name, you can place one or more optional parameters, such as: -o filename This options writes results to file instead of stderr. It is faster and recommended in all cases. -E PAR=VAL Puts PAR in the environment. This is especially useful if you want to trace a program with unusual LD_PRELOAD or other settings that would affect the functionality of 'fenris' itself if modified earlier. Multiple -E options are allowed. -u user Run as user. This option is available for root (see section 0x04, security issues), and will cause program to effectively run with uids, gids and supplementary groups of given user. -R a:b This option traces code from the moment when eip reaches point a to the moment when it reaches b. Incomplete range can be provided - for example, -R :0x12345678 will trace code from the beginning to eip 0x12345678, and -R 0x12345678: will start tracing at 0x12345678 and continue as long as possible. NOTE: think of it as trigger points, not a continous range. For example, if you use -R 0x12345678:, but eip 0x12345678 is never reached, even if 0x23456789 is being executed, trace will never start. This option is wonderful for starting trace at certain nest level and continuing it until this execution level is exited. -L dbase Load additional (supplementary) fingerprints database. Multiple -L options allowed. If filename does not contain slashes, fenris will look for it in directories described later in this section. -t nnn Main function is nnn rets from _do_global_ctors_aux. By default, this is set to 2, and does not have to be changed unless something is really wrong. You should use this option if you see that trace ends with '...return from main' almost immediately at the beginning (try increasing -t parameter) or somewhere in the middle or does not reach main at all (try decreasing). However, this should not happen, in general. The only case I'm aware of are HMM 3 binaries (patchlevel 1.3.1a, does not affect 1.3), they require -t 3 instead. -P ip:off:val This directive means: change a byte at address 'off' to 'val' when eip reaches 'ip'. If 'ip' is omitted or zero, this rule will be applied immediately to the freshly mapped binary (keep in mind that some memory regions mapped later may be not available at this moment). Read-only flag is generally overriden, and for files mapped into memory in read-only mode, a local copy of the modified page is spawned. All values passed to this parameter can be in decimal or in hex if preceeded with 0x, and multiple options are possible. Non-IP entries will be applied only once, at the beginning. All others will be applied every time a given IP is reached. There are some additional considerations to be aware of when used in conjunction with tracing across execve()s - see -e option description for details. -s This option disables automatic prolog detection. It is not recommended, as it makes ./fenris trace whole linking process and libc initialization. However, in rare cases when binary is compiled on odd, not supported system, this might be a solution. For long-term operations, however, it is recommended to contact the author providing his with this binary (or parts of it), so he'll be able to add support for this specific construction. -y Reports memory writes and reads immediately (without -y, memory access is reported per function on return). -C Inhibits tracing conditional expressions. This option is useful if output will be read by human, as it might decrease amount of reported information. -S Inhibits resolving library functions. This might effect in some speed improvement, but is generally not recommended without a good reason. -f Trace child processes after fork() or vfork(). Might be useful for tracing daemons and such (however it might cause some problems due to signal delivery semantics changes, see 0x07, known bugs) -d Do not describe function parameters. Reduces amount of generated output. -F Do not fingerprint functions. This option is effective for static binaries only, and will disable loading and displaying fingerprints. This is not really recommended - for stripped binaries, it makes your life more difficult, for binaries with symbols has almost no effect. However it might reduce memory usage and improve speed. -m Do not trace memory writes. This option reduces amount of generated input. -i This option disables indenting, reporting of pid and nesting level. It makes output non-structural, non-standard, but shorter. This will also break compatibility with ragnarok. -x This option causes fenris to ignore 'return from main' and to continue tracing, returning to nest level 0. Generally speaking, this is not recommended at any time. If you have problems with 'return from main' appearing too early in the trace, try re-adjusting -t parameter instead. If this do not help, apparently one or more of calling or return conventions used by traced application are not supported, and you shouldn't rely on results anyway. -p Prefix every message with eip. Some commands report eip, some not, this might be useful for debugging, and is a must if you want to modify the code later with -P option. This option is compatible with ragnarok. Note that information is not displayed in some uniform way. For example, syscalls are displayed after return, local functions are displayed before call - so it takes some time to get the idea. -A Assume that all functions return some value, regardless of all other conditions. This will trigger some meaningless return values reported, but is useful if the binary is very optimized. -e Trace new code loaded by execve(). This option might be convinient in some cases, but should be used with caution. Also, be warned that -P option will be global and apply to both old and new image in memory, except for no-IP entries that would be applied only once. For more information on computer forensics applications, you may want to visit http://lcamtuf.coredump.cx/fenris/reverse.txt, where I tried to give few hints on approaching May 2002 reverse engineering challenge from Project Honeynet. Managing fingerprints database is relatively simple. First of all, fenris looks for a database in the following places: ./fnprints.dat $HOME/.fenris/fnprints.dat /etc/fnprints.dat $HOME/fnprints.dat Additionally, custom fingerprints database can be specified by -L option (multiple databases allowed). Same search logic applies to -L parameters, unless they contain path components ('/'). This is reasonable to maintain separate fingerprint databases, as it allows you to be selective. For example, if you are about to trace 'sash', you can be pretty sure it won't use libX* libraries, so first, you can make lookups faster, and then, you minimize eventual false positives or confusion caused by identifying some functions incorrectly. As an example, I provide fingerprints for pretty old, but still used glibc 2.0.7 in support/fn-2.0.7.dat, and fingerprints for libc5 (support/fn-libc5.dat). Note that, as for today, fenris will probably not work on libc5 systems (I have to port it), but this can be used against statically linked binaries taken from such systems. The main database shipped with Fenris right now is a composite database for all major libraries for x86 libc 2.1.x and 2.2.x generated by gcc 2.9x to 3.1. It is pretty huge, but also versatile. If you believe it makes sense to maintain smaller libraries, feel free to do it and send me your selection! Fingerprints database is a plain text file in the following format: [debug info] function_name MD5_SIGN Where 'debug info' is used by 'fprints' utility to indicate the source (filename+offset) of given symbol, function_name is self-explainatory, and MD5_SIGN is 8-digit hexadecimal MD5 shortcut for given function (see section 0x05, tracing mechanism for more details on hashing algorithm). 'fprints' utility accepts any ELF file (executable, shared library or relocatable .o file / .a archive) as a parameter and generates signatures for all functions. It does not really make any sense to grab signatures from shared libraries, as they are not used to build static binaries, so you should target .o files instead. However, it is possible and sometimes reasonable to gather signatures from ELF executables. It allows you to fingerprint some frequently used functions (e.g. __non_dynamic_init or some custom common code used by others; let's say Loki uses some common engine for all their games, you can easily index functions in this engine once and benefit from automated recognition later). Typical output looks like that: [printf.o+52] printf CC6E587C [printf.o+52] _IO_printf CC6E587C --> printf.o: done (2 functions) As you see, one of entries is just an alias. Selected 'fprints' results can be appended to fnprints.dat file of your choice. It is important to mention that many libraries have multiple entries for the same function, so 'fprints' shouldn't be really used to gather fingerprints for large .a archives, like libc. This task can be accomplished by invoking 'getfprints' utility, which is a shell script wrapper around fprints. It can process whole .a archive or even multiple archives at once, elliminate dupes, and such. Please note that it is perfectly possible to copy .a files from a system that is not directly supported by Fenris, for example, libc5 box, and extract signatures on a different system. When invoked with no parameters, 'getfprints' will extract default set of symbols from: /usr/lib/libc.a /usr/lib/libm.a /usr/lib/libdl.a /usr/lib/libresolv.a /usr/lib/libreadline.a /usr/lib/libtermcap.a /usr/lib/libssl.a /usr/lib/libBrokenLocale.a /usr/lib/libcrypt.a (one static binary) This is the way it is invoked by ./build script, and can be used at any time to restore defaults or to update signatures (for new libc version, for example). If invoked with one parameter, 'getfprints' will go thru this .a file or set of .a files. An example would be: ./getfprints "/usr/lib/libcrypto.a /usr/lib/libmd5.a" It is important to quote the list so it effectively makes one parameter. Otherwise, only first file will be processed. Call it laziness on my end ;-) Default output file for 'getfprints' is NEW-fnprints.dat in current directory. When integrating it with existing fnprints.dat, please make sure you elliminate dupes by issuing the following command: cat NEW-fnprints.dat fnprints.dat | sort | uniq >clean-new.dat This utility requires ./fprints to be in current directory or in your path. The last tool, currently under development, is 'dress', roughly an opposite to 'strip'. It will accept a stripped static ELF binary as a parameter, and will try to detect library functions. Detected names will be placed in the symbol table and a new ELF file will be generated. Usage is rather simple: ./dress input_elf - this will dump symbols to stdout ./dress input_elf output_elf - this will create a new ELF with symbols Additional options: -F nnn - use this file for fingerprint database -S xxx - use this name as a code section (override .text) Note that symbols generated are not GDB debugging info. In other words, you can view them with nm, objdump, they will be shown in gdb disassembly, but you might have problems setting an explicit breakpoint such as "break printf". Blame GDB. As a workaround, you can run dress without a second parameter once again, and grab interesting addresses from the output. Enjoy. Note that 'dress' has nothing to do with 'unstrip', which is used to, quote, "replace the symbol table in dynamically linked executables". [0x03] Project limitations -------------------------- There are several limitations, yes. First of all, as discussed above, it is hard to talk about portability, but this will hopefully change soon. For now, fenris has been tested and confirmed to work fine on following platforms: * Linux 2.0.38/ow, glibc 2.0.7, gcc 2.8.1 * Linux 2.2.16, glibc 2.1.92, gcc 2.96 * Linux 2.2.19, glibc 2.2.2, gcc 2.96 * Linux 2.2.19, glibc 2.2, gcc 2.96 * Linux 2.2.20/ow SMP, glibc 2.1.3, gcc 2.95.3 It is supposed to support much more than that, but I had no occassion to perform extensive testing on other platforms. It is NOT supposed to work on libc5 and earlier systems, at least for now, and the build process requires decent versions of some basic utilities (file, grep, awk, gdb, etc). Other than that, fenris is dependent on assembly language code "readability". It will very likely produce not too useful nor reliable results if traced program uses large portions of inlined complex assembly code, and will have some problems understanding code generated with high optimization flags, like -O9. "Some problems" mean failure to predict whether function returns a value or not, and, from time to time, failure to detect the presence of function call at all (because some functions will be inlined and there will be no physical call in the code). As calling conventions are not always clean and fixed, in some rare cases, fenris might fail to predict number of parameters passed to unknown function (this problem can be fixed, but would require some heavy modifications and delayed reporting, turning it from realtime tracer into post-execution reporting tool, which isn't probably a good idea, especially in this stage of development). Generally speaking, parameter detection should be 75% to 100% successful in typical code, and this ratio is being reported at the end of each run. But "some problems" also mean it will be possible and still reasonable to use this tracer =) As a proof, here's an example of fenris tracing... itself, compiled with almost all possible optimization flags. Note that functions fprintf and getopt were renamed to avoid special handling (and because of that, they are handled like generic unknown functions): 5858:00 lame_fprintf (s/4024e9c0, g/8064000, 5556) 5858:00 + s/4024e9c0 = _IO_2_1_stderr_ 5858:01 [L] SYS write (2, bfffd320 "fenris 0.01b (1197, 5556) - pro"..., 125) = 125 5858:01 \ new buffer candidate: bfffd320:125 5858:00 ...return from libc = 5858:00 lame_getopt (2, l/bffffab4, g/806407c) 5858:00 ...return from libc = 5858:00 <805c63a> cndt: on-match block -440 exited 5858:00 <805c648> cndt: if-above block (signed) +34 executed 5858:00 <805c673> cndt: on-match block +16 executed As you see, the output is still readable and neat, both for fixed and variable parameters functions, however return codes are not reported, and minor glitches might pop-up from time to time: 5879:00 lame_geteuid (s/400375ac) Problems with -O9 mentioned above have not much to do with limitations of current implementation - simply, optimization will result in embedding some functions into code (implicit inlines), using non-constant constructions for returns and such. It is generally better to recompile programs without optimization flags, if possible. Fenris will try to detect and report "too optimized" code. Please be extra careful and use -A option on all codes of this kind. Fenris will be able to name local functions if the binary is not stripped - however, for stripped binaries, it will work fine assigning some unique names to functions (e.g. fnct_123). Worth mentioning, fenris works fine with programs compiled with -fomit-frame-pointer, -fPIC or similar flags designed to make life harder. [0x04] Security issues / forensics ---------------------------------- I hardly believe there are any security issues related to using 'fenris'. One thing you have to remember is that fenris, like all live code tracers or debuggers (gdb, strace, ltrace and such), is not supposed to protect you from malicious code. That's it, there is no way of ensuring that malicious code you are tracing won't try to interact with your system or to modify / fool tracing application to mislead you as to its purpose. It is supposed to trace legitimate applications coming from less or more trusted sources. If you have suspiciously looking code, don't even think of it (or run it in the lab or inside VMWare). It is impossible to ensure accuracy of real-time trace results and security of your system while tracing application written specifically to mislead or trick you, at least without having suitable VM or isolated environment that can be accurately probed. That said, it does not mean you can't use fenris for computer forensics, just like it does not mean you can't use strace or ltrace. Au contraire - fenris is probably much better for the analysis of rootkits and exploits than any other tool, and because of its run-time code modification capabilities, it can be used to simulate specific conditions - for example, the code being run as root, while, in fact, it isn't - or can be used to bypass anti-debugging checks, system configuration checks, and so on. But, as with every other tool, be careful. Perform forensics using many different tools, as the author might have added some code to fool the tool you are using primarily. Always do it on the machine with no direct Internet connectivity, using a test account with very limited write privileges on a reasonably secure system that does not serve any purpose other than a test environment. It is always good to look at the software carefully before trying to actually run it - but, of course, most of the time it is easier to simply test the program. It is not advisable to run any untrusted code with superuser privileges, as it can even cause irreversible damage to your hardware (for example, by wiping out fixed mount flashROM chips) and has excessive control over your environment. Try to run all dangerous programs with no extra privileges. If they demand them, try to fool them by modifying geteuid() call. If the code needs root privileges to bind to, say, RAW socket, make this an UDP socket instead. It should be enough, and you'd have better control over what the program receives and sends. Also, keep in mind that "su" is by no means a secure way to drop privileges. A malicious program can kill your su session but keep access to your terminal and use TIOCSTI ioctl to inject malicious commands as root. Log in directly to your test account, and if you use local console, make sure to get rid of enhancements such as pam_console. Other than that, as I said, I wouldn't expect security issues related to using fenris. It runs unprivileged and does not interact with untrusted environment (like /tmp) in most cases. I didn't really pay attention to buffer size checking, truncating names and such - current code should not have problems handling normal code, but will probably crash sooner or later is you stress-test it. Feel free to report any bugs like that, or, better, just fix them. Perhaps the only thing you should be aware is that last entry on the search path for fingerprints database is $HOME/fnprints.dat. It will be used ONLY if you do not have fnprints.dat in any other standard location, but - of course - if you, for example, run IRC client with DCC auto-get and with working directory set to your home, and at the same time you do not have fnprints.dat in any other location, you might want to comment out this line. But you have a security problem anyway ;) [0x05] Tracing mechanism ------------------------ Function fingerprinting: Function fingerprinting takes first 24 bytes of each function, replace relocs with zeros, eventually trims it and pads with zeros after three subsequent NOPs. This buffer is then passed to MD5 algorithm to generate 128-bit shortcut. Finally, subsequent dwords of generated shortcut are XORed with each other to generate 32-bit signature. Properties of MD5 transformation ensure us that this 32-bit space is uniformly used and that all properties of initial 192-bit buffer are used to construct the signature, giving 2^32 possibilities. In other words, we do not have to worry that many functions have almost similar beginnings and thus will generate identical shortcuts. The number of fingerprints shipped with Fenris is not supposed to exceed 100,000 any time soon, which gives somethind 0.025% coverage of the possible signature space, and should not trigger too many false positives. But, of course, when we get dangerously close, it'd be reasonable to extend the fingerprint size and to maintain a separate signature database for every platform. Signatures are matched for all not resolved local symbols in static binaries. Function tracing Local functions, library calls and system calls are traced by fenris. Fenris is capable of performing numerous operations on non-specific functions, such as parameter enumeration, parameter describing, memory access reporting, conditional conditions reporting, etc, but it also tries to handle most of system calls and large number of library calls in a specific way. The criteria for choosing traced library calls is relatively simple: it has to be relatively popular and preferably documented, and at least one of the following: * perform an operation not immediately corelated with a syscall or not self-explainatory (example: printf, getpwent), * manipulate special meta-objects, such as memory buffers (example: malloc, free, strdup), * automate memory manipulations (e.g. strcpy). Recognized syscalls: exit, fork, execve, clone, read, write, waitpid, open, mknod, oldstat, stat, fstat, oldfstat, chmod, creat, link, unlink, chdir, fchdir, lseek, lchown, fchown, chown, time, close, getpid, getgid, getuid, geteuid, getegid, mmap, munmap, mount, setuid, stime, ptrace, alarm, pause, utime, access, nice, sync, rename, mkdir, rmdir, dup, dup2, pipe, signal, brk, times, acct, umount2, ioctl, setpgid, umask, chroot, getppid, setsid, fcntl, getpgrp, setreuid, setregid, symlink, clone, sigaction, rt_sigaction, sgetmask, ssetmask, chown sigsuspend, sigpending, rt_sigsuspend, rt_sigpending, sethostname, gethostname [*], setrlimit, getrlimit, getrusage, gettimeofday, settimeofday, lstat, oldlstat, uselib, swapon, swapoff, truncate, ftruncate, fchmod, stat, lstat, readlink, reboot, readdir, getpriority, setpriority, statfs, fstatfs, socket, bind, connect, listen, accept, recv, send, shutdown, socketpair, syslog, iopl, ioperm, idle, vhangup, vm86, vm86old, fsync, getpgid, sigreturn, personality, setfsuid, setfsgid, flock, msync, getsid, fdatasync, mlock, munlock, mlockall, munlockall, rt_sigreturn Unimplemented syscalls: break, stty, gtty, kill, prof, ftime, lock, mpx, oldolduname, ulimit, ustat, profil To do: setitimer, getitimer, olduname, wait4, sysinfo, ipc, setdomainname, uname, modify_ldt, adjtimex, mprotect, sigprocmask, create_module, init_module, delete_module, get_kernel_syms, quotactl, bdflush, sysfs, afs_syscall, _llseek, getdents, _newselect, readv, writev, _sysctl, sched_setparam, sched_getparam, sched_setscheduler, sched_getscheduler, sched_yield, sched_get_priority_max, sched_get_priority_min, sched_rr_get_interval, nanosleep, mremap, setresuid, getresuid, query_module, poll, nfsservctl, setresgid, getresgid, prctl, rt_sigprocmask, rt_sigtimedwait, rt_sigqueueinfo, pread, pwrite, getcwd, capget, capset, sigaltstack, sendfile, getpmsg, putpmsg, ugetrlimit, mmap2, truncate64, ftruncate64, stat64, lstat64, fstat64, lchown32, getuid32, getgid32, geteuid32, getegid32, setreuid32, setregid32, getgroups32, setgroups32, fchown32, setresuid32, getresuid32, setresgid32, getresgid32, chown32, setuid32, setgid32, setfsuid32, setfsgid32, pivot_root, mincore, madvise, madvise1, getdents64, fcntl64, getgroups, setgroups, sendto, recvfrom, setsockopt, getsockopt, sendmsg, recvmsg Recognized library calls: strlen, malloc, strdup, calloc, realloc, free, getenv, atexit, strcpy, memcpy, memset, bzero, bcopy, memcmp, getc, strcmp, strncmp, strncpy Libcalls to do: fopen, fclose, perror, printf, vprintf, sprintf, snprintf, vsnprintf, fprintf, vfprintf, vfnprintf, fnprintf, vsprintf, *put*, exec*, fclose, *scanf*, fread, fwrite, fflush, alloca... Recognized assembly language constructions (all handled cases): .----------------+--------------------------------------------. | E8 | library call / function call entry | | E8 00 00 00 00 | PIC trampoline (ignored) | | FF 25 | PLT libc entry | | E9 r | alternative libc entry | | FF D0 | code-from-libc invocation | | FF D1 | code-from-libc invocation | | FF D2 | code-from-libc invocation | | FF D3 | code-from-libc invocation | | FF D6 | code-from-libc invocation | | FF D7 | code-from-libc invocation | | C3 | libc and function return | | C2 | libc return-to-function | | FF A2 | call to function | | FF 15 | function absolute ptr call | | 89 C0 | return value indicator (without -O9) | | B8 | imm. return value indicator (without -O9) | | CD 80 | syscall entry point | | 83 EC | function params: level up | | 81 EC | function params: level up | | 83 E4 | function params: adjust count | | 81 E4 | function params: adjust count | | 89 E5 | function params: reset | | 8D 45 | function params: adjust count | | 83 C4 | function params: reset | | 50 | parameter push | | 51 | parameter push | | 52 | parameter push | | 53 | parameter push | | 54 | parameter push | | 55 | parameter push | | 56 | parameter push | | 57 | parameter push | | 68 | parameter push | | 6A | parameter push | | FF 30 | parameter push | | FF 33 | parameter push | | FF 35 | parameter push | | FF 70 | parameter push | | FF 74 | parameter push | | FF 75 | parameter push | | FF B4 | parameter push | | FF B5 | parameter push | | 89 04 24 | parameter push | | C7 04 24 | parameter push | | 89 1C 24 | parameter push | | 58 | parameter counter reset | | 59 | parameter counter reset | | 5A | parameter counter reset | | 5B | parameter counter reset | | 5D | parameter counter reset | | 5E | parameter counter reset | | 5F | parameter counter reset | | 89 E5 53 82 | ctors signature | | EC 04 83 F8 | | | 8B 5D F8 89 | ctors signature | | EC 5D C3 89 | | | 74 0C 8B 38 | ctors signature | | FF D0 83 C3 | | | 83 E8 04 89 | ctors signature | | 45 FC 8D 76 | | | 75 | conditional | | 74 | conditional | | 76 | conditional | | 7E | conditional | | 7F | conditional | | 77 | conditional | | 0F 85 | conditional | | 0F 84 | conditional | | 0F 8E | conditional | | 0F 86 | conditional | | 0F 8F | conditional | | 0F 87 | conditional | | 5A 59 87 04 | resolv signature | | 24 C2 08 00 | | | CC | int3: report to base | `----------------+--------------------------------------------' Additionally, all memory write constructions (few hundred separate opcodes) are recognized and handled by libdisasm code. Specific situations (e.g. cross-segment eip change) are detected even if no known instruction triggered them. Output format: If you wish to develop or already developed an alternative to ragnarok, please let me know. If you are just planning, the following information might be useful: Output file header: <<-- fenris [TYPE] VERSION -->>, where TYPE is STD (STD stands for standard settings, CSTM for suboptimal command-line options). Normal line: starts with 'pid:ind' where ind is nesting level ('--' for events on top level or before / after main function). This sequence is followed by one or more spaces. If -i option is used, this pid:ind+spaces prefix is skipped. Then, optional '[L]' modifier is added if even occours inside library segment. Errors / exit messages: lines starting with '>>' Task status messages: lines starting with '+++' Warnings and information: lines starting with '*' Normal line format (after pid:ind): Lines starting with '#' specify signature database matches. Lines starting with '-' specify odd conditions in memory tracing module or other modules. Lines with '+' describe parameters. Some of them might be followed with additional info line starting with 'last input:' string. Lines starting with @ mean operations on file descriptors, such as open, dup, close. Lines starting with '\' describe buffer discards, modification, buffer creation, data flow, merges and similar usual conditions. "UNEXPECTED" modifier denotes unexpected resizes, e.g. strcpy into too small malloced buffer, or such. Lines starting with '//' mean information provided for readability and can be safely ignored. Lines starting with ' cndt:' are conditional expressions. Lines starting with '*' list modified memory. Lines starting with '...' are function returns. Lines starting with 'SIGNAL' report signal delivery. Lines starting with modifier 'local' are local functions. Lines starting with 'signal handler' modifier are signal handlers. Lines starting with U are non-specific library calls, lines starting with L are separately handled library calls. Parameters are presented in function-dependent format. Note: buffer detection and such is performed for assembly-level statements as well as for function calls. However, buffers first spotted on assembly level are not reported immediately to keep things clean (e.g. a function that walks thru 100 characters in a table effectively causes detection of one 100-byte buffer, but it is pointless to report every time single opcode changes buffer size: 2, 3, 4, 5, ..., 100). Thus, some buffers might be mentioned post-factum, with no implilcit 'new buffer' line (but with appropriate size description and such). [0x06] Bug reporting and other feedback --------------------------------------- If fenris refuses to compile or crashes, please use ./fenris-bug reporting utility. To suggest some enhancements or new features, e-mail me at . If you want to report problems directly to me, use , but follow suggestions given by 'fenris-bug' to make things easier :) If you have already fixed any bugs or interoperability issues, or added any features, please send diffs to me. Please, before sending patches, test them carefully. It does not hurt to run modified code against some code you've compiled, few standard tools in C (ps, id, ls) and C++ (wget) plus something from other system or provided as a binary (netscape?) - and saves you, me and other time and efforts... Not much basic troubleshooting can be performed by an average user, maybe except poking with -t option and eventually using -s mode. [0x07] Known bugs / TODO ------------------------ First of all, some functions (libcalls and syscalls) mentioned in doc/TODO should be added. Then, more abstract problems: - Data flow graph is not really readable for complex projects, and it might be more suitable to use OpenDX here. - libbfd is far from being predictable; many distributions ship ancient or broken versions or have other problems; this and few other issues make Fenris not very stable on certain platforms. Please report all situations like compilation errors or runtime SEGVs, but also be polite :-) - Well, this isn't really a bug in fenris, but the way I fixed this problem might have some side effects, so be careful... Linux kernels have a bug that causes PTRACE_SINGLESTEP to skip whole signal handlers, no matter how complex. We fix it with hackish code that inserts int3 traps into signal handlers, but it is ugly and relies on presence of at least one leading NOP. - I am very tired or there is a bug in lynx SortaSGML parser, which, in certain cases, puts it into irreveraible
 mode; does not manifest
    with TagSoup parser (-tagsoup), links, Netscape, MSIE. Investigate it
    later - for now, please view reports with -tagsoup option or press
    Ctrl+V.

  - binary output for faster operations and easier parsing should be
    supported...

  - lookup functions should be cached, and number of calls to them
    minimized to speed up everything.

  - reloc tables in .o files should be parsed (for function signatures); 
    right now, we cheat in really lame way ;)

  - some problems in libdisasm are present due to broken opcode tables
    logic; I fixed some common problems, but if you see any
    "strange write!" or "strange read!" debug messages, please let me know.

  - segment matching is done with /8 mask. Some programs may have segments
    larger than 16MB, and this might cause minor problems. We probably
    should calculate segment start addresses. This would also fix a problem
    with Openwall non-executable stack patch (small integers reported as 
    addresses).

  - Architecture-dependent code is not separated in any way; future 
    versions should have GCC and x86 code modularized (read: in separate .h
    file) to make porting to new platforms or new compilers easier.
    Volunteers to do that needed.

  - Fenris does not support clone()d threads. This can be fixed, but
    requires some hacking around process tables and many routines.

  - Compilation takes insane amounts of memory and time (can take few hundred
    megs); this is due to GCC - it has problems with large number of inlined 
    functions. Workaround is to change many of them with less ellegant 
    but much more efficient #defines. Until then, you can './build minimal',
    which added -fno-inline to compilation options, or simply enable some
    swap memory and wait patiently.
    
  - Syscalls are reported AFTER a syscall is completed, except for some cases 
    that are known not to return (execve, exit). It would require major
    code rewrite to change it, and it won't happen in first release.
    Side effects:
    
      - if given syscall is blocking, possibly forever, it will be not 
        reported until something happens. You probably want to use strace
	do diagnose such conditions.
	
      - action is taken before syscall is displayed; if fenris is reporting
        to stderr and traced program writes to console, write() output
	will be shown before write() itself is reported. This might be
	confusing a little bit.
	
      - if something bad happens to process before syscall returns (e.g.
        kernel oops), currently called syscall parameters won't be
	reported.
	
    As side effects are not really that painful, while rewriting major
    portions of code is... well, it remains this way.

  - Parameter detection for local functions called back from libc (from
    functions like bsort, scandir and such) will be not accurate. This
    won't be fixed soon, I'm affraid.

  - Without -s option, if main() in traced applications is terminated
    by return or such (not by invoking exit()), atexit() handler, if
    declared, will be not traced properly.

  - As stated in ptrace() documentation, attaching to child processes
    causes changes in child-to-parent signal delivery semantics; thus,
    tracing certain program with -f option might be difficult (Midnight
    Commander is a good example). This is a common problem present in
    strace and ltrace as well.

  - Fenris does not deal too well with non-gcc generated code that do
    strange things with stack, calling conventions - I mean results
    are completely unaccurate and application stability is at risk ;)

  - Time sharing between multiple processes is nothing like round-robin;
    this shouldn't be a problem in real life, but if you have two
    processes doing extactly the same, one of them might be 'favorized'
    and other might have to wait until first one enters blocking syscall
    or such.

  - context saving (longjmp) is not yet supported. This will change as
    soon as longjmp is implemented. For now, for programs using this
    function (like bash), nest level might be artifically high.

  - C++ function name demangling support would be nice.

  - if a local function is called from within a libcall (good example: qsort
    in 'ls') with a text parameter that is recognized as a string and 
    auto-added, and this parameter points to a memory temporarily allocated 
    by qsort, which is later freed (but both malloc and free are not traced 
    inside qsort, of course), this might cause a nasty interference (
    already-have in malloc). No clue how to fix it for now.

  - Ragnarok does not necessarily link functions in the table too precisely:

    010  1439:00 local innafunkcja (g/8049758)
    011  1439:00 + innafunkcja = 0x804852c
    012  1439:00 + 8049758 = 8049758:100  (first seen in L main:malloc)
    013  1439:00   last input: L main:bzero
    014  1439:01  L strcpy (8049758, 8048614 "this is just a test") = 8049758

    ...in this example, 'innafunkcja' call will be reported as happening
    in line 013, not 010. The problem is that ragnarok reports LAST line
    of each call. This can be easily fixed.




[0x08] Thanks and credits
-------------------------

  This project started somewhere in October 2001, and, for a long time,
  the development crawled slowly in the very little free time I had those
  days. For the first development snapshot I've found on my disk, go to
  http://lcamtuf.coredump.cx/fenris/vintage.tgz - it is pretty amusing.
  Since then, many people provided invaluable information, suggestions,
  feedback and other support to this project and made it possible.
  I try to keep the following list pretty accurate, but if you feel you
  should be here, do not hesitate to tell me. Our contributors are:

    Rafal Wojtczuk (Nergal)		Mariusz Woloszyn (kil3r)
    Slawomir Krawczyk (Nises)		Wojtek Kaniewski (wojtekka)
    Bulba				bighawk
    none				dvorak
    charise	                        entropyd
    Solar Designer                      Wojtek Walczak (gminick)
    Martin Kluge                        Lluis Mora Hidalgo
    Robert W. Jaroszuk			Joe Van Andel
    Neil Jerram                         Hubert Lubaczewski
    Yair K				Gregory Wright
    Adam Byrtek                         Gordon Sadler
    Roger Luethi                        Mariusz Marcinkiewicz
    Lukasz Trabinski                    Piotr Meyer
    Marcin Kaminski			Artur Byszko
    Lukasz Biegaj                       Przemyslaw Skowron
    Gonçalo Gomes                       Timothy Bogdala
    Allen Noe                           piggy
    Stephen Kench                       Bartlomiej Lidke
    Marek Gutkowski                     Tadeusz Wlodarczyk
    Marcin Gozdalik

  Special thanks to developers of 'bastard' libdisasm library available 
  at http://bastard.sourceforge.net - portions of their code were used
  in this project.

  Optimized assembly language string operation routies come from Linux 2.2
  kernel sources.

  Thanks go to Maja for patience.


[0x09] Further reading
----------------------

  Some of less or more related publications you might find interesting:

  Intel 80386 Programmer's Reference
  http://www.online.ee/~andre/i80386/

  Assembly Language/x86 FAQs
  http://www.faqs.org/faqs/assembly-language/x86/

  Using and Porting GNU CC 
  http://www.cslab.vt.edu/manuals/gcc/gcc_toc.html

  Google Directory - Computers > Programming > Disassemblers
  http://directory.google.com/Top/Computers/Programming/Disassemblers/

  Tracey N., Clark  J., Mander K., McDermid J.
  "Automated test-data generation for exception conditions"
  http://www.cs.york.ac.uk/testsig/publications/njt-00.pdf 

  Beizer B. 
  "Software Testing Techniques, 2nd edition"
  Thomson Computer Press, 1990.

  Jones B., Sthamer H., Eyres D.
  "Automatic structural testing using genetic algorithms"
  Software Engineering Journal 1996

  "Abstract Interpretation" 
  http://www.cs.utah.edu/~ritwik/papers/COUSOT-ACM.pdf