Greythorne's Guide to Breakpoints
Rhayader's Guide to SoftICE conditional breakpoints and macros
You've just downloaded SoftICE, read 101 different tutorials and finally selected a target, the time has come to make your first forays into the world of reverse engineering. This guide describes the very basic first few steps you should take to configure your SoftICE to the task at hand. Where appropriate I'll highlight the subtle differences between Windows 95/98 & Windows NT installations, setup.exe beckons.
1. Running setup.exe launches the installation, shortly afterwards your first dialog box will be the 'Welcome', explaining the NuMega copyright law, a click away is the full License Agreement, the terms of which you will of course accept. Next up will be the 'please insert your name, company & serial number' dialog.
All of these fields are self-explanatory, so fill them in not forgetting to use your serial number supplied with the software. If you should lose your serial you might consider using the DevStudio generator temporarily whilst you wait for NuMega to confirm your original, when you have received it you can simply edit the change in your registry.
Moving on swiftly, you'll be asked next to select an installation directory, Windows 95/98 users beware here of the LFN (long file name) trap, install to C:\SoftICE if you want to be safe. Under NT this is not an issue. Your next choice will be the 4 components checkboxes, unless you are keenly trying to save disk space I suggest installing everything, now might be a good time to make a mental note of the "Online Books".
The final stage is perhaps the most perilous, the 'Select Display Adapter' dialog.
SoftICE does now (version 4) support a pretty extensive list of display adapters and if your lucky the installation program will select an appropriate adapter to test. Selecting Test is a pre-requisite for the installation to proceed, if you are stuck at the end with only Standard VGA SoftICE will appear as a window (similar to a DOS box). This is not ideal and is likely to give you severe eye-strain so consider changing your graphics card.
If you are running an NT installation, the next dialog will prompt you when to run SoftICE, Boot/System/Automatic or Manual, unless you have a pressing need not to start SoftICE at start-up I recommend the boot option. After this you'll be invited to select your mouse type, consult your PC's manual for details, the mouse support in SoftICE is really a cosmetic feature. In Windows 95/98 installations you'll next be asked whether setup should modify autoexec.bat, again like Windows NT unless you have some pressing reason not to have SoftICE always loaded allow the program to modify autoexec.bat.
Upon your next click SoftICE will install, although you'll have to reboot to make it active. Before you go for the big switch lets do some configuration.
Setting up SoftICE for reversing is quite different between Windows 95/98 & NT.
Under NT SoftICE is configured using the SoftICE Loader (select from your Start Menu). Selecting Edit/SoftICE Initialization Settings is the way to go. The general tab shows the initialisation string which you'll probably want to change, (read the manual for detailed descriptions of the various switches), my own is as follows :-
CODE ON; FAULTS OFF; I3HERE OFF; WD 3; WF; X;
The 2 other important tabs are the Symbols & Exports. If you possess the SDK for your OS as I do, you can use SoftICE's loader to translate the SDK's .DBg files into corresponding .nms files, which can then be loaded by SoftICE (if you wan't these files then e-mail me and I might be persuaded to give them to you). Those without the SDK should use the exports tab to add the following dll's from their %WINNT%/System32 directory :-
advapi32.dll, comctl32.dll, comdlg32.dll, gdi32.dll, kernel32.dll, msvbvm(50/60).dll (if present), msvcrt.dll (if present), ole32.dll, oleaut32.dll, shell32.dll, user32.dll, version.dll.
Under Windows 9x SoftICE's options are controlled by the file winice.dat which you can open with any text editor, NotePad will do. At the very end of the file is this large section :-
; ***** Examples of sym files that can be included if
you have the SDK *****
; Change the path to the appropriate drive and directory
; ***** Examples of export symbols that can be included for Windows 95 *****
; Change the path to the appropriate drive and directory
EXP=c:\windows\system\advapi32.dll <-- advapi32.dll exports will be loaded.
;EXP=c:\windows\system\comctl32.dll <-- comctl32.dll exports will not be loaded.
The semi-colon acts as a comment, like a REM statement, reading closely you'll see that if you don't have the SDK only the last section will apply. As a general rule remove the semi-colons from all of the files that are present on your system, if you are especially keen to save memory you can elect not to load components such as mspwl32.dll (password file management) or some of the network related dll's (msnet32.dll). If you scroll up several lines you'll also find the INIT="" statement which is equivalent to the NT Loader's initialization string.
Here are the 5 most frequently SoftICE related questions I am asked :-
Q. I enter SoftICE and type bpx GetDlgItemTextA and get "error
symbol not defined".
A. You did of course remember to remove the semi-colons from your winice.dat.
Q. SoftICE keeps popping up when my system crashes, how do
I stop this?.
A. You can toggle SoftICE's fault trapping with the FAULTS=ON/OFF switch, placing your desired choice in the initialization string is highly recommended.
Q. I'm a PhotoShop/<other application> user & SoftICE's
Ctrl+D hotkey is incredibly annoying / inconvenient, is there
any way to change it?.
A. You did of course read the SoftICE Command Reference, in particular the ALTKEY command. Place it in your initialization string for added convenience.
Q. Is there anyway to dump the SoftICE code window to a file?.
A. There is indeed, you firstly use a command such as this :-
u cs:eip l 500
The u 'unassembles' code at the address cs:eip (the current EIP), the l specifies a length of 500 bytes. Once you've done this you should exit SoftICE and select File / Save SoftICE History As from Symbol Loader, with any luck the resulting log file will contain your code dump.
Q. Is there anyway I can dump memory contents from SoftICE
to a file?.
A. Indeed there is, you can use a number of 'dumping tools', those such as ADump & SoftDump are less SoftICE intrusive and use memory mapped files (you move your desired dump into a committed area of memory), I however recommend IceDump which extends the functionality of your pagein command.
Here are some breakpoints you might consider setting (courtesy of +gthorne).
Reading & Writing Files (These are generic calls to read/write
to a file, usually binary in nature) : ReadFile, WriteFile.
More on locating file accesses : SetFilePointer, GetSystemDirectory, GetSystemDirectoryA.
These are the most common calls to read/write from/to a *.ini file or a file of similar format.
For 16-bit Windows : GetPrivateProfileString, GetPrivateProfileInt,
For 32-bit Windows : GetPrivateProfileStringA, GetPrivateProfileIntA, WritePrivateProfileStringA, WritePrivateProfileIntA.
File Accesses (Interrupts) : bpint 21 if (ah==3d), bpint 2f if (ah==01).
Create or delete a new key in the registry : RegCreateKey,
RegDeleteKey, RegCreateKeyA, RegDeleteKeyA.
Read a value from the currently open registry key : RegQueryValue, RegQueryValueA.
Open or close a registry key : RegCloseKey, RegOpenKey, RegCloseKeyA, RegOpenKeyA.
Get text or integer from a dialog box : GetWindowText, GetDlgItemText, GetWindowTextA, GetDlgItemTextA, GetDlgItemInt.
Open a message box, usually one that says "invalid registration" : MessageBox, MessageBoxA, MessageBoxExA, MessageBeep.
.....and other ways to display text... SENDMESSAGE, WSPRINTF
These get the time and date : GetSystemTime, GetLocalTime, SystemTimeToFileTime.
CreateWindow, CreateWindowExA, ShowWindow, BitBlt (a type of memory move).
GetDriveType (if eax=5 then it is a CD-ROM check), GetDriveTypeA, GetDriveType, GetLogicalDrives, GetLogicalDrivesA, GetLogicalDriveStrings, GetLogicalDriveStringsA
Interrupt 2F is the Mscdex Interrupt : bpint 2f, al=0 ah=15 (checks if Mscdex installed).
BMSG xxxx WM_GETTEXT (good for passwords).
BMSG xxxx WM_COMMAND (good for OK buttons).
The xxxx is of course the hwnd value, but note, assuming you are using wm_command to try to locate the button push, you hwnd the result and see the hwnd of the button is 0324 and the hwnd of the window is 0129. To find the button, use the window value, not the button value to bmsg on (the other just won't work), so for the example here, to find our button push we would :-
BMSG 0129 WM_COMMAND
These aren't the only Win32 API calls you need to know in order to crack. There are many many more that programs will use, many are derivatives of these. Try substituting a W for the A at the end of some calls, or placing an Ex right before the A. Also, in SoftICE, typing 'EXP GETPRIVATEPROFILE' will give you a list of all of the procs to read from .ini files, and there are more than the ones I have listed.
The above statement referring to many many calls is actually an understatement. Windows being as overbloated a mess of code as it is, the lists of calls is rather insane.
In my early cracking, I usually set a BPX for GetDlgItemTextA & GetWindowTextA inside SoftICE whenever I found a program that asked for a serial. Then I'd enter a dummy code and 'hope' that SoftICE would pop up. Most of the time it works, the problem is, after I hit F12 (P RET), I usually get lost inside the code.
Let's take a look at GetWindowTextA first. It's declared as :-
int GetWindowText( HWND hWnd, LPTSTR lpString, int nMaxCount
GetWindowTextA uses _stdcall calling convention. That means that arguments will be pushed right to left. Since SoftICE pops up before the prologue code is executed, the EBP stack frame isn't set up yet. So we had to use ESP to address the argument. Here's how the stack will look like when SoftICE pops up :-
[ESP+0Ch] - nMaxCount
[ESP+08h] - lpString
[ESP+04h] - hwnd
[ESP+00h] - return EIP
When the function returns, GetWindowTextA will place the text it retrieved to the location pointed to by lpString (LPTSTR is a long pointer to a null terminated string). Thus, we had to use SoftICE's indirection operator (it's the * character, same as C language. For example, the command :-
This means, "show in data window, the location pointed to by the content of esp+8". Since, this is a very common operation, SoftICE had a shorthand for it: esp->8. Alright then, now we can set a breakpoint such as this :-
BPX getwindowtexta DO "D esp->8;"
When we hit F12, we return to the caller and the text we entered will sit nicely at the top of the data window, waiting for us to set up a BPR with it. Why don't we do a return to the caller automatically? Well, in my case, the screen flashes, and I hate it. But, if you want to try, you can set the breakpoint as :-
BPX getwindowtexta DO "D esp->8;P RET;"
You probably knew that GetWindowTextA and GetDlgItemtTextA will fail in a Delphi program. Yet, putting a breakpoint on Windows message (BMSG) will place you too far from the calculation routine. So, I usually put a breakpoint when the program needs to find out, whether we are a registered user or not. For most programs this registration information is usually stored in the Windows' registry, some use ini file, and sometimes, programs use a regfile. In this post I only tackle the registry. I'm sure you can extend this to include the .INI and regfile.
Putting a breakpoint on registry's API is easy. First, you must make sure that ADVAPI32.DLL is loaded by SoftICE. You can check it with the "EXP regqueryvalueex" command. If SoftICE has already loaded ADVAPI32.DLL, it will respond with the address of the routine. To set a breakpoint on a registry read, we use the command :-
You can start the program that you want to break and SoftICE will break everytime the program reads the registry. The problem here lies in "everytime". There will be hundreds of calls that read the registry and it's not fun to hit Ctrl+D several hundred times. So, we have to find a way to make SoftICE stop only on the value that we are interested with. We're going to use conditional breakpoint to do that :-
HKEY hKey, // handle of key to query
LPTSTR lpValueName, // address of name of value to query
LPDWORD lpReserved, // reserved
LPDWORD lpType, // address of buffer for value type
LPBYTE lpData, // address of data buffer
LPDWORD lpcbData // address of data buffer size
When SoftICE breaks, the stack will look like this :-
[ESP+18h] - lpcbData
[ESP+14h] - lpData << this is where the return data will be put by Windoze
[ESP+10h] - lpType
[ESP+0Ch] - lpReserved
[ESP+08h] - lpValueName << the name of the data that will be retrieved
[ESP+04h] - hKey
[ESP+00h] - return EIP
I guess you already know that using a breakpoint action DO "D ESP->14;" will show the retrieved data in SoftICE's data window after the function return. Now, say for example, RegMon tells us that the program reads the info from :-
Then, we can put a breakpoint in SoftICE such as this :-
BPX RegQueryValueExA IF *(ESP->8) == 'Regi' DO "D
And SoftICE will only break when the registry read the ones that start with "Regi". Here is the explanation :-
1. You knew what ESP->8 is. Since Windoze passed an LPTSTR (pointer to null-terminated string) to RegQueryValueExA, ESP->8 will evaluate to "the address pointed to by the content of [ESP+8]". For more information with the operator, see User Guide Chapter 8, sub: Operators. Also, see User Guide Chapter 7, sub: Referencing the Stack in Conditional Breakpoint.
2. The SoftICE indirection operator * in *(ESP->8) will retrieve the content. Thus, the expression *(ESP->8) will tell SoftICE that we need "the value stored in the address pointed to by the content of [ESP+8]".
3. Now, the expression *(ESP->8) == 'Regi' means that the expression will evaluate to TRUE, only if "the value stored in the address pointed to by the content of [ESP+8]" is "equal" to "Regi". Why only use four character? Well, the * operator, only returns a DWORD value (32-bit). So, we can only use the first 32-bit which is equal to four characters. Also, SoftICE will convert 'Regi' to 0x69676552 (it's Regi in little endian format). So, it's case-sensitive. I probably should note that, there is two equal signs, and it's a single quote. The use of two equal sign and single quote is the same as C language.
Since the * operator return DWORD value, there will be a problem if we want to retrieve only a WORD (16-bit) value. We had to use the SoftICE Word() function. You can see User Guide Chapter 8, sub Forming Expressions, sub Built-in Functions, for the Word() function. But, since most program align values/structures in 4-byte boundary, the use of Word() function is quite rare. The example that will follow is retrieve 16-bit value, yet we can safely use it without the Word() function.
If you can understand the above breakpoint, you probably began to wonder, "How should I write the breakpoint if the program reads a key and a name, and the first four char is not equal?" Say, for example, it reads from :-
HKEY_CURRENT_USER\Software\Applied Insights\AI Explorer\UN
HKEY_CURRENT_USER\Software\Applied Insights\AI Explorer\SN
For the username and serial respectively. Issuing another breakpoint in RegQueryValueExA won't work (duplicate breakpoint). But, we can add more conditions to SoftICE. And we can set our breakpoint like this :-
BPX RegQueryValueExA IF (*(ESP->8) == 'UN') || (*(ESP->8)
== 'SN') DO "D ESP->14;"
The logical OR operator (||, two pipe characters) will make the expression evaluate to TRUE if one of the condition (or both) is TRUE. Problem is, when you try to write it in SoftICE, you will hit the SoftICE 80 characters command line limitation. Thus, we have to create a macro for it (User Guide Chapter 11, sub: Working with Persistent Macros). We can declare the macro such as :-
BPX RegQueryValueExA IF (*(ESP->8) == '%1') || (*(ESP->8)
== '%2') DO "D ESP->14;"
And, name it to something like bpregqv. And when we want to use it, we just use :-
BPREGQV UN SN
We can safely leave the single quote there in the macro because the value name will always be a string (Unlike the return value which can be an integer).