source files and libraries are all linked into a single executable, many function boundaries are eliminated through inlining and are simply pasted into the code that calls them. The machine is eliminating redundant structural details that are not needed for efficiently running the code. All of these transforma- tions affect the reversing process and make it somewhat more challenging. I will be dealing with the process of reconstructing the structure of a program in the reversing projects throughout this book.
How do software developers break down software into manageable chunks? The general idea is to view the program as a set of separate black boxes that are responsible for very specific and (hopefully) accurately defined tasks. The idea is that someone designs and implements a black box, tests it and confirms that it works, and then integrates it with other components in the system. A program can therefore be seen as a large collection of black boxes that interact with one another. Different programming languages and devel- opment platforms approach these concepts differently, but the general idea is almost always the same.
Likewise, when an application is being designed it is usually broken down into mental black boxes that are each responsible for a chunk of the applica- tion. For instance, in a word processor you could view the text-editing compo- nent as one box and the spell checker component as another box. This process is called encapsulation because each component box encapsulates certain func- tionality and simply makes it available to whoever needs it, without exposing unnecessary details about the internal implementation of the component.
Component boxes are frequently developed by different people or even by different groups, but they still must be able to interact. Boxes vary in size: Some boxes implement entire application features (like the earlier spell checker example), while others represent far smaller and more primitive functionality such as sorting functions and other low-level data management functions.
These smaller boxes are usually made to be generic, meaning that they can be used anywhere in the program where the specific functionality they provide is required.
Developing a robust and reliable product rests primarily on two factors: that each component box is well implemented and reliably performs its duties, and that each box has a well defined interface for communicating with the outside world.
In most reversing scenarios, the first step is to determine the component structure of the application and the exact responsibilities of each component.
From there, one usually picks a component of interest and delves into the details of its implementation.
The following sections describe the various technical tools available to soft- ware developers for implementing this type of component-level encapsulation in the code. We start with large components, such as static and dynamic mod- ules, and proceed to smaller units such as procedures and objects.
Low-Level Software 27
06_574817 ch02.qxd 3/16/05 8:35 PM Page 27
Modules
The largest building block for a program is the module. Modules are simply binary files that contain isolated areas of a program’s executable (essentially the component boxes from our previous discussion). There are two basic types of modules that can be combined together to make a program: static libraries and dynamic libraries.
■■ Static libraries: Static libraries make up a group of source-code files that are built together and represent a certain component of a program.
Logically, static libraries usually represent a feature or an area of func- tionality in the program. Frequently, a static library is not an integral part of the product that’s being developed but rather an external, third- party library that adds certain functionality to it. Static libraries are added to a program while it is being built, and they become an integral part of the program’s binaries. They are difficult to make out and iso- late when we look at the program from a low-level perspective while reversing.
■■ Dynamic libraries: Dynamic libraries (called Dynamic Link Libraries, or DLLs in Windows) are similar to static libraries, except that they are not embedded into the program, and they remain in a separate file, even when the program is shipped to the end user. A dynamic library allows for upgrading individual components in a program without updating the entire program. As long as the interface it exports remains constant, a library can (at least in theory) be replaced seamlessly—without upgrading any other components in the program. An upgraded library would usually contain improved code, or even entirely different func- tionality through the same interface. Dynamic libraries are very easy to detect while reversing, and the interfaces between them often simplify the reversing process because they provide helpful hints regarding the program’s architecture.
Common Code Constructs
There are two basic code-level constructs that are considered the most funda- mental building blocks for a program. These are procedures and objects.
In terms of code structure, the procedure is the most fundamental unit in soft- ware. A procedure is a piece of code, usually with a well-defined purpose, that can be invoked by other areas in the program. Procedures can optionally receive input data from the caller and return data to the caller. Procedures are the most commonly used form of encapsulation in any programming language.
28 Chapter 2
The next logical leap that supersedes procedures is to divide a program into objects. Designing a program using objects is an entirely different process than the process of designing a regular procedure-based program. This process is called object-oriented design (OOD), and is considered by many to be the most popular and effective approach to software design currently available.
OOD methodology defines an object as a program component that has both data and code associated with it. The code can be a set of procedures that is related to the object and can manipulate its data. The data is part of the object and is usually private, meaning that it can only be accessed by object code, but not from the outside world. This simplifies the design processes, because developers are forced to treat objects as completely isolated entities that can only be accessed through their well-defined interfaces. Those interfaces usu- ally consist of a set of procedures that are associated with the object. Those pro- cedures can be defined as publicly accessible procedures, and are invoked primarily by clients of the object. Clients are other components in the program that require the services of the object but are not interested in any of its imple- mentation details. In most programs, clients are themselves objects that simply require the other objects’ services.
Beyond the mere division of a program into objects, most object-oriented programming languages provide an additional feature called inheritance.
Inheritance allows designers to establish a generic object type and implement many specific implementations of that type that offer somewhat different functionality. The idea is that the interface stays the same, so the client using the object doesn’t have to know anything about the specific object type it is dealing with—it only has to know the base type from which that object is derived.
This concept is implemented by declaring a base object, which includes a dec- laration of a generic interface to be used by every object that inherits from that base object. Base objects are usually empty declarations that offer little or no actual functionality. In order to add an actual implementation of the object type, another object is declared, which inherits from the base object and contains the actual implementations of the interface procedures, along with any support code or data structures. The beauty of this system is that for a single base object there can be multiple descendant objects that can implement entirely different functionalities, but export the same interface. Clients can use these objects with- out knowing the specific object type they are dealing with—they are only aware of the base object’s type. This concept is called polymorphism.