2.3. The OllyDbg Debugger


2.3. The OllyDbg Debugger

This is an excellent debugger in its class. For example, it is capable of determining procedure parameters and loops, and of detecting constants, arrays, and strings. Such features have never been typical of the instruments in its class. This debugger supports all processors of the 80x86 family and correctly interprets most numeric formats. It is possible to load an executable module into this debugger, as well as to connect to the running process. In general, there are rich possibilities, some of which will be covered here.

2.3.1. Getting Started with OllyDbg

Debugger Windows

The main window of OllyDbg is shown in Fig. 2.16. In addition to the traditional main menu and the toolbar, the main window contains four informational panes. These are the disassembler window (top left), data window (bottom left), registers window (top right), and stack window (bottom right). In addition, other windows are available for use. The list of all available windows is presented in the View menu. Some of these windows will be described in this section; other ones you'll have to study on your own if you like this debugger and are going to use it regularly. I strongly recommend that you do so.

image from book
Figure 2.16: The OllyDbg debugger with a loaded program

Now, consider the windows shown in Fig. 2.16. These windows are the most important ones, without which it is impossible to debug applications.

The Disassembler Window

The disassembler window contains four columns:

  • Address — The command address column. This column contains the virtual address of the command, which is assigned when the command is loaded into the memory. By double-clicking this column, you'll convert all addresses into offsets counted from the current address ($, $-2, $+4, etc.).

  • Hex dump — The command code column. In this case, you'll see the code as such and the operand value. In addition, the column provides various icons that allow you to understand the program logic: For instance, they specify commands, to which there are jumps (>) and commands that carry out the jumps (ˆ for up and ˇ for down). The same column marks the loops that the debugger has successfully recognized. When you double-click this column, the address shown in the first column is highlighted in red. This means that you have set a breakpoint to that command (address).

  • Disassembly — This column contains the Assembly mnemonics for the command. If you double-click this command, the window for editing the Assembly command will appear. Here you can correct the command. The corrected command will be further used in the debugging process. Furthermore, the corrected program text can be written into the executable module. That's a great possibility, isn't it?

  • Comment — This column contains additional information about the command. Here the program specifies the names of API functions, library functions, etc. If you double-click this column, you'll be able to add your comment to each line of the Assembly code.

The Data Window

By default, this window contains three columns: Address, Hex dump, and the column for text interpretation of the contents of the cell (ASCII, Unicode, etc.). Interpretation of the second and third columns can be changed. For instance, you can choose to display and interpret cell contents as Unicode.

The Registers Window

The registers window can contain three sets of registers: general-purpose registers and FPU registers, general-purpose registers and MMX registers, and general-purpose registers and 3DNow registers. By double-clicking this window, you'll gain the possibility of editing the contents of the appropriate register.

The Stack Window

The stack window displays the stack content. The first column (Address) contains the cell address within the stack, the second column (Value) displays the cell content, and the third column (Comment) contains the possible comment to the cell value (see Fig. 2.16).

More about the Windows

When starting to work with the debugger, bear in mind the following issues:

  • By double-clicking the right mouse button within any window, you'll call the context menu. This menu is different for different windows. I strongly recommend that you carefully study these menus. Some information on this topic will be provided further on in this chapter.

  • Window contents are interdependent. For instance, consider the registers. By clicking one of the general-purpose registers with the right mouse button, it is always possible to interpret its contents as an address in the data area (follow in dump) or as an address in the stack area (follow in stack).

Debug Execution

Debugging is analyzing a program by executing it in different modes. This section covers different program execution modes in OllyDbg.

Assume that the executable code is loaded into the debugger. The disassembler window displays the Assembly code. The main modes of program execution available to you are as follows:

  • Step-by-step execution that bypasses procedures (step over). When you press <F8>, the current Assembly command is executed. By executing commands sequentially, you can watch three other windows to view how the contents of registers, the data section, and the stack section are changed. A specific feature of this mode is that if the next command happens to be the procedure call (CALL), then all commands that make up the procedure will be automatically executed as a single instruction.

  • Step-by-step execution that steps into the procedure (step into). To execute the program in this mode, press <F7>. The main difference from the previous mode is that when CALL commands are encountered, all instructions that make up the procedure will be executed sequentially.

Both of the preceding methods (step over and step into) can be automated using animation. This can be achieved by pressing the <Ctrl>+<F8> and the <Ctrl>+<F7> keyboard combinations for each mode, respectively. After you press these keyboard shortcuts, the step over and step into commands will be executed in the automatic mode with a small delay, one after another. After executing each instruction, the debugger windows will be refreshed so that you'll be able to trace the changes. The execution can be paused at any time by pressing the <Esc> key. Also, execution stops automatically when breakpoints are encountered (see Section 2.3.2) and when the program being debugged generates an exception.

Another method of the step-by-step program execution is the trace mode. Trace mode is similar to animation, but this time the debugger windows are not refreshed at each step. Two methods of tracing corresponding to step over and step into are executed using the <Ctrl>+<F12> and <Ctrl>+<F11> shortcuts. Tracing can be stopped with the same methods used for stopping animation. After execution of each command, information related to its execution is loaded into the special tracing buffer, which you can view using the View | Run trace menu commands. If desired, the contents of the trace buffer can be saved into a text file. Also, it is possible to define the conditions, under which the tracing would stop (set trace condition) — <Ctrl>+<T> (the breakpoint). The following trace conditions can be specified:

  • The range of addresses, in which the break would take place

  • Conditional expressions, such as EAX>100000, for which tracing would be stopped provided that the condition is true

  • A number of some command or a command set, for which tracing would stop

It is possible to instruct the debugger to execute the code until the return from the procedure is encountered (execute till return). In other words, the entire code of the current procedure will be executed, and the procedure would return control. To achieve this goal, the <Ctrl>+<F9> shortcut is used.

Finally, if in the course of tracing you have found yourself somewhere deep within the system code, it is possible to exit it by using the execute till user code command — the <Alt>+<F9> shortcut.

2.3.2. Breakpoints

Breakpoints are a powerful debugging tool. They allow you to understand program execution logic in the finest detail, providing snapshots of the registers, stack, and data at specified moments.

Ordinary Breakpoints

Ordinary breakpoints are set at a chosen command. To achieve this, use the <F2> key in the disassembler window or double-click the second column of this window (Hex dump). As a result, the address of the command (Address) in the first column will be highlighted in red. This type of breakpoint is helpful for finding the correlation between program execution logic (displaying specific windows, messages, etc.) and specific sections of program code. In addition, it is possible to check the status of registers, a variable, or the stack in any breakpoint of this type. If you press <F2> or double-click a breakpoint a second time, the breakpoint will be removed. Bear in mind that the break occurs before execution of the command, to which the breakpoint has been set.

Conditional Breakpoints

Conditional breakpoints are set by pressing the <Shift>+<F2> combination. In this case, the window with the combo box, into which the breakpoint can be entered will appear. The combo box field allows you to enter the condition, under which command execution must be interrupted provided that this condition is true. The debugger supports complex expressions containing various conditions. Here are several examples:

  • EAX == 1 — This condition instructs the debugger to interrupt execution at the marked command (before its execution). Program execution will be interrupted provided that the content of the EAX register is one.

  • EAX = 0 and ECX > 10 — These conditions instruct the debugger to interrupt program execution at the marked command provided that the content of the EAX register is zero and the content of the ECX register is greater than ten.

  • [STRING 427010] == "Error" — Program execution will be interrupted provided that the "Error" string is found at the 427010H address. Also, it is possible to write the following condition: EAX == "Error". In this case, the content of EAX will always be interpreted as a pointer to string.

  • [427070] = 1231 — This condition defines the breakpoint in case the content of the 427070H memory cell is equal to 1231H.

  • [[427070]] = 1231 — Indirect addressing is used. It is assumed that the cell with the 427070h address, in turn, contains the address of another cell, the content of which will be compared with the 1231H number.

Conditional Breakpoints with a Log

Conditional logging breakpoints are an extension for conditional breakpoints. To set a conditional logging breakpoint, press the <Shift>+<F4> shortcut. Any time such a breakpoint is actuated, this event is recorded in the log. To view the log contents, press the <Alt>+<L> shortcut or select the View | Log commands from the menu. It is possible to specify the record, as well as the expression whose value will appear in the log. Finally, it is possible to set the counter, which will specify how many times the record must be written into the log and whether it is necessary to interrupt program execution any time the breakpoint conditions are satisfied.

Breakpoint to Windows Messages

Because messages arrive to the window function (or, to be precise, to the window class function), to set a breakpoint to some windows message it is necessary to ensure that the application window is opened — in other words, that the windowing application has been started for execution. For simplicity, I have loaded a simple application with a single window into the debugger. Press the <Ctrl>+<F8> shortcut to start this application. The application window activates after a short delay (about 1 second). Pay attention to the part of the program that executes continuously. This is the message-processing loop. To reach the window function, it is necessary to call the list of windows created by the application being investigated. This can be achieved using the View | Windows menu. The result of this command is shown in Fig. 2.17.

image from book
Figure 2.17: The window displaying the list of windows created by the application being investigated

The window displayed in Fig. 2.17 allows the investigator to discover the window descriptor, its name, its identifier, and, most importantly, the address of the window procedure (ClsProc). The information about the address of the window procedure allows the investigator to find the window function and set there either a normal or a conditional breakpoint. However, when working with window functions, it is best to set breakpoints at window messages.

Thus, click the window shown in Fig. 2.17 and choose Message breakpoint on ClassProc from the context menu. Another window will appear, in which it is possible to set the following breakpoint parameters:

  • Choose the message from the drop-down list. Note the following:

    • Instead of the message as such, it is possible to choose an event, which might be indicated by several messages, such as creation or deletion of a window or a keyboard event.

    • It is possible to choose user-defined messages.

  • List the windows that will be tracked to determine whether this message arrives from one of them. Include the given window, all windows with a given title, or all windows.

  • Define a counter to determine how many times the breakpoint is actuated.

  • Specify whether the program execution should be interrupted when the breakpoint is actuated.

  • Define whether the record should be written into the log when the breakpoint is actuated.

On your own, practice setting the preceding breakpoints. Also, trace the contents of the stack window, a useful and instructive occupation.

Breakpoints to the Import Functions

To obtain the list of all names imported into the module being debugged, press the <Ctrl>+<N> shortcut. Further, click the window with right mouse button, and you'll be able to carry out the following actions:

  • Set a breakpoint at the call of the imported function (Toggle breakpoint on import)

  • Set a conditional breakpoint at the call to the imported function (Conditional breakpoint on import)

  • Set a conditional breakpoint with logging at the call to the imported function (Conditional log breakpoint on import)

  • Set breakpoints at all links to the specified name (Set breakpoint on every reference)

  • Set breakpoints with logging at all references to the given name (Set log breakpoint on every reference)

  • Remove all breakpoints (Remove all breakpoints)

Breakpoints at the Memory Area

The OllyDbg debugger allows you to set a single breakpoint at the memory area. To achieve this, choose the disassembler window or the data window. Then use the context menu and choose the Breakpoint | Memory on access commands or Breakpoint | Memory on write commands. After that, the newly-specified breakpoint will be ready to use. The first type of a breakpoint is possible both for the code and for the data, and the second type is possible only for the code. Breakpoints can be deleted by choosing the Breakpoint | Remove memory breakpoint commands from the context menu.

Breakpoints in the Memory Window

The memory window displays the memory blocks reserved for the program being debugged or by the program being debugged on its own. In this window, it is also possible to set one breakpoint. To achieve this, use the right-click menu and choose the Set memory breakpoint on access command or the Set memory breakpoint on write command. To remove the breakpoint, right-click the memory window and choose the Remove memory breakpoint command.

Hardware Breakpoints

Normal breakpoints use the standard INT 3 interrupt vector. The use of such breakpoints can considerably slow down the execution of the program being debugged. However, Intel Pentium microprocessors also provide four debug registers — DR0-DR3 (see Section 1.2.1). These registers can contain four breakpoints, virtual addresses of the current program. When the address used by a command turns out to equal the address contained in one of these registers, the processor generates an exception that is trapped by the debugger. Hardware breakpoints do not slow down the execution of the program being debugged. However, there are only four of them. To set a hardware breakpoint, go to the disassembler window and choose the Breakpoint | Hardware on execution commands from the context menu. As an alternative, use the Breakpoint | Hardware on access or Breakpoint | Hardware on write commands from the main menu. To delete hardware breakpoints, use the Breakpoint | Remove hardware breakpoints commands from the context menu.

2.3.3. Other Capabilities

The Watch expressions Window

OllyDbg provides a special window for watching expressions. Recall that you encountered expressions when I described conditional breakpoints. It is possible to use complex expressions containing both memory cells and registers, and these expressions might be as complicated as desired. To open the Watch expressions window, use the View | Watches menu commands. When the Watch expressions window opens, click the right mouse button and choose the Add Watches command. After that, you can define an expression that the debugger will watch — in other words, display its value. Fig. 2.18 shows the Watch expressions window containing a list of four expressions, the values of which are watched and displayed when executing any processor command.

image from book
Figure 2.18: The Watch expressions window

Searching for Information

OllyDbg allows you to search for any kind of information. Consider some of its functional capabilities.

By pressing the <Ctrl>+<B> shortcut, the search window appears, in which you can define the string that will be sought in the module loaded into the debugger. The search string can be entered in the form of the sequence of characters, sequence of bytes, or sequence of Unicode characters.

For searching, use the <Ctrl>+<F> shortcut (for a single command) or the <Ctrl>+<S> combination (for a sequence of commands).

The <Ctrl>+<L> combination repeats the last search operation.

Correcting an Executable Module

OllyDbg provides excellent capabilities of correcting executable modules. You can save the module being debugged, along with corrections, and even create a new executable module. This can be achieved easily. Just click the right mouse button in the disassembler window and choose the Copy to execution | Selection commands from the context menu. As a result, the entire disassembled module will be copied into the new window. After that, click the right mouse button in that window and choose the Save file command. You'll have the possibility to choose the name, under which to save the new executable module. This is convenient: First, you can create any number of versions of the corrected file. Second, the check for the correctness of the modified code is carried out without exiting the debugger.

There are still lots of important and interesting issues related to OllyDbg. Alas, everything has its natural end, and the limited volume of this book requires me to proceed with considering several other topics that are no less interesting and important.