Theory and practice of menus reversing
June 1999
by +Spath.
Courtesy of Reverser's pages of reverse engineering
A worthy addition for our papers section. I hope that many reversers will build on this and offer us additions and ameliorations and suggestion, so that we will be able to produce some 'basic' reversing papers (menus, save/save us crippled, internet background connections, printer smearing, etcetera) that could be used as 'quick reference' by fellolw reversers all around the word...
Note also the lookup table reversing and the quick crack of Jeremy Likness' software cracking challenge.
Unfortunately the file I downloaded is corrupted, and therefore you'll miss a small part at the bottom. I'm sure that +Spath will soon send the missing snippet.
There is a crack, a crack in everything That's how the light gets in
( )Beginner (X)Intermediate (x)Advanced ( )Expert

An essay to understand how menus and messages work, to be able to efficiently reverse function-disabled programs.
Theory and practice of menus reversing

Written by +Spath.

This text is an introduction to functions disabled programs ; I wrote it 
because I did not find any essay describing both graphical (menus related) 
and system (messages related) parts of this problem. My goal here was to 
explain basically how menus work and to give a few tips and examples, so 
that any beginner can start working in the right direction.

As you may have already understood, I'm not a native english speaker, so 
please forgive my mistakes.

  1) Creating a menu
  2) Modifying a menu
  3) Reversing a menu definition
  1) Messages and Windows
  2) Messages to an application
  1) Before you start
  2) A few tips
  1) CAP 1.0
  2) Sigmastat 2.0
  3) Bestwin  
  4) Flipper   

Tools required

One eye and one brain are required. This text is long, so some tea may help also.

Target's URL/FTP

See IV)

Program History



1) Creating a menu

Menus can be created using either a menu template or menu creation 

- Creating menus using a menu template :

This is the most common method ; most of the times menu templates are 
defined as resources and are contained in a separate file from real source 
code. A menu template completely defines a menu, including all functions, 
separators and all submenus. Here's a typical C/ASM menu template :

    POPUP "File"
        MENUITEM "&Open...", ID_BROWSE 
        MENUITEM "E&xit", ID_CANCEL

This defines a menu bar that contains one popup menu, named File (from 
here on, popup menus will be refered to as menus). In this menu, some items 
are defined, as well as a separator (that cause a horizontal line to be drawn 
between two menu items). Each item is described using this format :

MENUITEM  String,  MenuID   [Options]

String  : the menu item's characters enclosed in " ". The '&' symbol before 
          a letter means that this letter will be underlined in the menu 
          (most of the times to indicate a keyboard shortcut).

MenuID  : a number that becomes the low word of the wParam that is passed 
          with the WM_COMMAND message (I will discuss that in details 

Options : how the item is to appear : CHECKED, GRAYED (inactive and gray), 
          INACTIVE (inactive), MENUBARBREAK, MENUBREAK (both place item on 
          new line).

- Creating menus at run-time : 

Using menu creation functions, you can create menus at run time or 
add menu items to existing menus. You first can use the CreateMenu() 
function to create an empty menu bar and the CreatePopupMenu() function 
to create an empty menu. Then, to add items to a menu, AppendMenu(),
InsertMenuItem() and InsertMenu() can be used : AppendMenu() just 
appends a menu item to the end of a menu or submenu, while the two 
other ones let you insert a menu item at a specified position.
Both functions allow you to specify the attributes of the menu item, 
including whether it is enabled, disabled, grayed, checked, or unchecked.

After a menu has been loaded or created, it must be assigned to a window.  
The first method is to do it at class' registration : before creating a 
window, a program must register its class to Windows via RegisterClass() 
or RegisterClassEx(). The parameter of these functions is a pointer to a 

typedef struct _WNDCLASS {        typedef struct _WNDCLASSEX {  
    UINT    style;                    UINT    cbSize; 
    WNDPROC lpfnWndProc;              UINT    style;  
    int     cbClsExtra;               WNDPROC lpfnWndProc; 
    int     cbWndExtra;               int     cbClsExtra;
    HANDLE  hInstance;                int     cbWndExtra; 
    HICON   hIcon;                    HANDLE  hInstance; 
    HCURSOR hCursor;                  HICON   hIcon; 
    HBRUSH  hbrBackground;            HCURSOR hCursor; 
    LPCTSTR lpszMenuName;             HBRUSH  hbrBackground;
    LPCTSTR lpszClassName;            LPCTSTR lpszMenuName;
} WNDCLASS;                           LPCTSTR lpszClassName; 
                                      HICON   hIconSm; 
                                  } WNDCLASSEX;

The lpszMenuName parameter of this structure describes which menu will 
be associated to a window of this class (created with CreateWindow() or 
CreateWindowEx() ). The other method is to assign the menu after the 
window has been created, which can be done with SetMenu().

Just a remark about context menus (also called floating menus), which are 
the ones that are activated by right mouse button : they are most of the time 
displayed via TrackPopupMenu() or TrackPopupMenuEx() when a WM_CONTEXTMENU 
message is processed.

2) Modifying a menu

Several functions enable you to change menus or menuitems after they 
have been loaded or created.

- SetMenuItemInfo() allows you to change the attributes of an existing 
menu item ; attributes include its type, state, identifier, submenu, 
bitmaps, item data, and text. Before modifying a menu, GetMenuItemInfo() 
can be used to retrieve informations about an item. This function returns 
a MENUITEMINFO structure, which specifies the attributes to retrieve and 
receives their current values.

- ModifyMenu() replaces the specified menu item with a new item. It can 
be used to enable, disable, gray, check or uncheck the text string or the 
bitmap of a menu item. 

- EnableMenuItem(), the very famous one : this function specifically exists
to enable, disable, or gray a menu item.

    HMENU hMenu,	      // handle to menu
    UINT uIDEnableItem,	// menu item to enable, disable, or gray
    UINT uEnable	      // menu item flags

The menu item flag contains two informations, the action to perform on 
the item and the selecting method :

MF_ENABLED  (00h) : the function can be used. 
MF_GRAYED   (01h) : the function is grayed and cannot be chosen.
MF_DISABLED (02h) : the function is not grayed, but clicking does 
                    nothing (no WM_COMMAND message is sent).

MF_BYCOMMAND (00h)   : uIDEnableItem gives the menuID of the menu item 
                       (this is the default method).
MF_BYPOSITION (400h) : uIDEnableItem gives the zero-based relative 
                       position of the menu item.

To gray an item, you usually use 03h (MF_GRAYED + MF_DISABLED)

- DeleteMenu() or RemoveMenu() to delete a menu item from a menu,  
function. If the item being deleted is one that opens a submenu, 
DeleteMenu() deletes the associated submenu, discarding the menu handle
and freeing the memory used by the submenu. The RemoveMenu() function 
deletes a menu item, but if the item opens a submenu, the function
does not destroy the submenu or its handle, allowing the submenu to 
be reused.

3) Reversing a menu definition

Let's take the sample resource file of 1) and compile it : here's how 
it looks like in the EXE file :

10 00 46 00 69 00 6C 00-65 00 00 00 00 00 E8 03  ..F.i.l.e.......
26 00 4F 00 70 00 65 00-6E 00 2E 00 2E 00 2E 00  &.O.p.e.n.......
00 00 00 01 EA 03 26 00-45 00 78 00 65 00 63 00  ......&.E.x.e.c.
75 00 74 00 65 00 00 00-00 00 00 00 00 00 80 00  u.t.e...........
02 00 45 00 26 00 78 00-69 00 74 00 00 00 00 00  ..E.&.x.i.t.....

which can be decoded this way, if you remove the widechar format :

10h  "File" 
00h, 03E8h, "&Open..."
01h, 03EAh, "&Execute"
00h, 0002h, "E&xit"

The main modification of the resources (apart from the wide char format) 
is the inversion of the parameters (the order is now Options, MenuID, 

Before each option, you see the low word of the wParam value of the WM_COMMAND 
message that will be sent to the application when the function is selected ; 
this value is equal to the MenuID value defined in the resource file. 

Documented options values correspond to the associated item flags : they
are grayed (01h), inactive (02h), checked (08h), menubarbreak (20h), 
menubreak (40h). No option corresponds to value 00h, which means that the 
item is active. As you see, each option use only one bit of the byte, to 
ease combination (for instance, 09h is grayed + checked). Other values 
are :

04h (MF_BITMAP) to use a bitmap as the menu item.
10h (MF_POPUP) must be used for menu definition, it just crash USER.EXE
    when used with an item.
80h (MF_HILITE) is used for menuitem separators.

With the resource file of 1), the Execute option is grayed ; so to enable 
it, we just have to replace the 01 (2 bytes before the "&Execute" string) 
by 00.

Now let's see a VB menu definition : 

...mnuFile.....&  05 07 00 6D 6E 75 46 69-6C 65 00 13 03 05 00 26   
File.....&......  46 69 6C 65 00 07 FF FF-02 26 00 00 00 06 0B 00  
mnuFileSave.....  6D 6E 75 46 69 6C 65 53-61 76 65 00 13 03 0A 00  
&Save Text......  26 53 61 76 65 20 54 65-78 74 00 08 13 00 FF 02  
.......mnuFileSe  1D 00 00 00 07 0C 00 6D-6E 75 46 69 6C 65 53 65  
p_1.....-.....!.  70 5F 31 00 13 03 01 00-2D 00 06 FF FF 02 21 D1  
.....mnuFileExit  00 00 08 0B 00 6D 6E 75-46 69 6C 65 45 78 69 74  
.....E&xit......  00 13 03 05 00 45 26 78-69 74 00 08 11 00 FF 03  
........mnuAbout  02 1E 00 00 00 09 08 00-6D 6E 75 41 62 6F 75 74  
.....&About.....  00 13 03 06 00 26 41 62-6F 75 74 00 07 FF FF 02  
"......mnuAboutR  22 00 00 00 0A 0B 00 6D-6E 75 41 62 6F 75 74 52  
eg.....&Register  65 67 00 13 03 09 00 26-52 65 67 69 73 74 65 72  
..........mnuAbo  00 FF 02 1F 00 00 00 0B-0B 00 6D 6E 75 41 62 6F  
utAbo.....A&bout  75 74 41 62 6F 00 13 03-06 00 41 26 62 6F 75 74  
..........+@....  00 FF 03 03 04 06 00 00-00 DC 2B 40 00 01 00 01  

It can be interpreted this way :

05 0700 "mnuFile"00      1303 0500 "File"00       07FFFF02 2600     0000
06 0B00 "mnuFileSave"00  1303 0A00 "&Save Text"00 081300FF 021D00   0000
07 0C00 "mnuFileSep_1"00 1303 0100 "-"00          06FFFF02 21D1     0000
08 0B00 "mnuFileExit"00  1303 0500 "E&xit"00      081100FF 03021E00 0000
09 0800 "mnuAbout"00     1303 0600 "&About"00     07FFFF02 2200     0000
0A 0B00 "mnuAboutReg"00  1303 0900 "& Register"00  FF021F00          0000
0B 0B00 "mnuAboutAbo"00  1303 0600 "A&bout"00     FF030304 0600     0000

Here's a description of the fields :
1 : number of the item which will be defined.
2 : size of the next string.
3 : name of the menuitem as it has been entered during its creation in 
    VB ; this is a 00 terminated string.
4 : special delimiter between the two strings ; these values are used to 
    check that the strings' sizes are correct.
5 : size of the next string.
6 : name of the item as it will be displayed. Note that "-" is a special 
    value reserved for menuitem separators ; this is also a 00 terminated 

Other fields are left as targets for mighty reversers' curiosity. :)

Hey, but where are the MenuID values ? Well, they are not stored here, actually 
we will see later that they are not stored at all... 


1) Messages and Windows

When you type a key or move the mouse, the corresponding device driver 
converts the input into messages and places them in the system message 
queue. Then Windows gets messages from this queue, determines what 
window is currently active, and sends the message to the queue of the
thread that created this window.

A message is a structure declared as:

typedef struct MSG {
   HWND hwnd;     // handle of the window for whom the message is destined
   WORD message;  // type of message (WM_COMMAND, WM_PAINT,...)
   WPARAM wParam; // first parameter, depends on the type of message
   LONG lParam;   // second parameter, depends on the type of message
   DWORD time;    // time when the message occured
   POINT pt;      // location (X,Y) of the mouse when the message occured

Depending on which API you use (Win32 or Win16) the contents of wParam and 
lParam and the size of wparam can vary. For example, a WM_COMMAND message is :
in Win16 : wParam(16-bits) - Identifier for the command
           lParam(32-bits) - LOWORD: Window handle, HIWORD: Special modifier

in Win32 : wParam(32-bits) - LOWORD: Identifier, HIWORD: modifier
           lParam(32-bits) - Window Handle

Note that Softice does not handle these subtelties, thus it sometimes recognizes 
wrong wParam and lParam values (with accelerators for instance, see 2) ). 

Each thread of each application has an internal message queue, where Windows 
can put one or more messages ; then, the application retrieves the messages 
from the queue (in the same order they have been put into) and process them. 
That means the application
- reads the message from the queue by using GetMessage(). 
- translates keyboard messages (key up/down) into char messages by TranslateMessage().
- dispatches them to the appropriate window procedure by using DispatchMessage(). 

Actually, there are two ways Windows can send a message to an application :
it can either place the message in the thread's queue with PostMessage() 
(they are called queued messages) or it can send it directly to a window 
procedure with SendMessage() (they are called nonqueued messages).

Queued messages include all user input messages (like WM_MOUSEMOVE, 
WM_LBUTTONDOWN, WM_KEYDOWN, WM_CHAR, ...) and some system messages 
(WM_TIMER, WM_PAINT, WM_QUIT). All these messages use a MSG structure.

For nonqueued messages, only the first four arguments (ie not time and mouse 
position) are passed, but if the window procedure needs them, it can get them
via GetMessageTime() and GetMessagePos(). Windows typically sends nonqueued 
messages to notify a window of events that affect it (WM_ACTIVATE, 
WM_SETFOCUS, WM_SETCURSOR, ...) or when an application calls certain Windows 
functions (for example, Windows directly sends the WM_WINDOWPOSCHANGED 
message after an application uses the SetWindowPos() function to move 
a window). 

Note also that WM_PAINT is an exception to the FIFO message queue : this special 
message has the lowest priority, that means it is sent to the window procedure 
only when the queue contains no other message.

2) Messages to an application.

Now, the question is : how does an application know when Windows has
put a new message in its queue ? Well, it does not really know it. 
Actually, the application is just stuck in an infinite loop where it keeps 
on retrieving and dispatching messages. This is called the "message pump"
or "message loop", and its simplest form is  : 

; Process messages, quit when WM_QUIT received.
      push   0                  ; maximum value of message to filter
      push   0                  ; minimum value of message to filter
      push   0                  ; handle of the window (0 = all messages) 
      push   offset msgbuffer   ; where to store the message
      call   GetMessageA        ; get the message from the queue and store it
      or     eax,eax            ; if eax=0, WM_QUIT as been posted 
      je     end_loop           ; in that case, we leave the app
      push   offset msgbuffer   ; where the message is stored
      call   DispatchMessageA   ; send message to window procedure
      jmp    msg_loop           ; loop 

; Terminate program.
      push   [msgbuffer.msgWparam]
      call   ExitProcess

A window procedure is a function that receives and processes messages sent 
to the window. Every window class has a window procedure, and every window 
created with that class uses that same window procedure to respond to messages. 
Because a window procedure is shared by all windows belonging to the same 
class, it can process messages for several different windows. To identify 
the specific window affected by the message, a window procedure can examine 
the window handle passed as first parameter of a message.

Window procedures are usually just a big 'CASE' statement, where all types 
of messages are routed to different pieces of code :

; The window procedure, where messages are routed.
        mov    eax,[esp+8]        ; get message type
        cmp    eax, 0000000F      ; is it WM_PAINT ?
        je     paint_client       ; yes, go and paint something
        cmp    eax, 00000111      ; is it WM_COMMAND ?
        je     execute_command    ; yes, go to command procedure
        cmp    eax, 00000201      ; is it WM_LBUTTONDOWN ?
        je     LeftMouseDown      ; do something
        cmp    eax, 00000001      ; is it WM_CREATE ?
        je     creating_window
        cmp    eax, 00000002      ; is it WM_DESTROY
        je     start_destroy
        jmp    DefWindowProcA     ; delegate other message processing

Note that by default there is a call to DefWindowProcA(). This is the 
Windows default window procedure which MUST be called to handle any 
messages that a window procedure does not handle. A program can have 
many windows and therefore many window procedures : when a message is 
not processed by the main window procedure, the message is passed to 
the next one via DispatchMessage(). 

We saw that a WM_KEYDOWN/WM_KEYUP combination of messages can be 
translated into a WM_CHAR message (and reposted to the thread's own 
queue) by TranslateMessage(). But a WM_KEYDOWN can also be translated 
into a WM_COMMAND message by TranslateAccelerator(), the function which 
processes accelerator keys for menu commands (like Ctrl+C  for copy). 
Also, if the keyboard combination corresponds to a menu, WM_INITMENUPOPUP 
is sent to the application and the popup menu is opened. Note that you 
can differentiate a WM_COMMAND message translated from a keyboard input 
from a message triggered by the mouse, since TranslateAccelerator() sets 
the high-order word of the wParam parameter to 1 ; in this case, Softice 
wrongly thinks that it's the lparam hiword that is set.

In addition to the APIs we have covered, Windows has some other APIs that
you should be aware of that let you perform various actions on messages 
and the message queue :

PeekMessage() - read messages in the queue without removing them.
RegisterWindowMessage() - to declare specific message for your application.
WaitMessage() - suspend the thread until a message is put on the queue.

C) Messages and Menus

When the user activates an item on the menu bar, the owner window first 
receives a WM_SYSCOMMAND message. This message includes a flag that 
indicates whether the user activated the menu by using the keyboard 
(SC_KEYMENU) or the mouse (SC_MOUSEMENU). 

Next, before displaying any menus, Windows sends the WM_INITMENU message 
to the window procedure so that an application can modify the menus before 
the user sees them. Windows sends the WM_INITMENU message only once per 
menu activation.

When the user points to a menu item that opens a submenu, Windows sends 
to the owner window the WM_INITMENUPOPUP message before displaying the 
submenu. Again, this message allows the application to modify the submenu 
before it is displayed. Each time the user moves the highlighting 
from one item to another, Windows sends a WM_MENUSELECT message to the 
window procedure of the menu's owner window. This message identifies the 
currently selected menu item ; some applications (Words, Wordpad,...) handle 
this message to display informations about the selected menu item at the 
bottom of their main windows.

When the user chooses an item from a menu, Windows sends a WM_COMMAND 
message to the window procedure. The low-order word of the WM_COMMAND 
message's wParam parameter contains the identifier of the chosen item. 
The window procedure should examine the identifier and process the message 

For context menus (opened by right button clicks), a WM_CONTEXTMENU 
message is sent, so that the application can process it and display 
its menu.


1) Before you start

Before you even think about cracking a function-disabled program, you 
should check a few things :

- is it the best solution ? If you can register with a code or a keyfile, 
try this instead, because this will remove all visible (and invisible so 
far) annoyances and/or limitations in one row.

- is the code of the disabled functions in the program ? If the code is 
not there, you will have to add it, and that's a different challenge... 
There are many clues that can lead you to a conclusion, like the registration 
scheme, the imported APIs, some text strings, etc

Ok, for some reason you decided to crack it this way. Now you must always 
keep in mind where you are and what you want to do ; basically, you will 
have to : 
- ungray the disabled function (or make it appear) in the menu.
- link this function to the correct piece of code.

Part one is most of the times fairly easy, and can lead to a typical 
error : you put a breakpoint on EnableMenuItem, find where the flag is 
stored and change it to MF_ENABLED. Now you have ungrayed the function, 
so you're pretty confident, but when you click... nothing happens :(

That's the second part, which can be more or less complicated, depending 
on how the program has been stripped : you must first find the piece of 
code that corresponds to the function, then you must find and modify the 
message loop or/and the window procedure.

2) A few tips

It's quite easy to ungray a item, just have a look at the imported APIs,
and find what is used to create and maybe modify the menus. The main 
difficultys is to find where the message loop and the appropriate window 
procedure are.  

A few ways to find a message loop :

- via the the messages specific APIs, like GetMessageA(), DispatchMessageA() 
or DefWindowProcA(), that are almost always called in each message loop.

- via the message values : some message are always processed, like 
WM_COMMAND (0111h) or WM_DESTROY (0002h). So you can search for 
00000111 in a disassembled listing to find a "cmp xxxx, 00000111h", 
that may be the 'case WM_COMMAND' statement.

- in SoftIce, with a BMSG command 
SoftIce's BMSG breakpoint is quite useful to break on specific messages. 
You can also log without poping the messages going to a window procedure 
with the 'L' option ; note that the messages are logged in the SoftIce 
history buffer, so be sure to have a well chosen size. Also use WMSG to 
have a list of messages recognised by SoftIce.

You can construct an equivalent BPX-style breakpoint using a conditional 
expression. Use the HWND command to get the address of the window procedure, 
then use the following BPX command : 
bpx postmessagea if @(ss:esp+8)==WM_COMMAND

A few ways to find a window procedure :

- via the RegisterClass() and RegisterClassExA() functions.
The window procedure address is the second parameter (for WNDCLASS) and
the third parameter (for WNDCLASSEX), so some

BPX RegisterClassA do "u @(@(ss:(esp+4))+4) ; d @(@(ss:(esp+4))+24)"
BPX RegisterClassExA do "u @(@(ss:(esp+4))+8) ; d @(@(ss:(esp+4))+28)"

will display the registered class and unassemble the piece of code of 
the corresponding window procedure. Also watch for CreateWindowA() and
CreateWindowExA(). Actually, this is interesting only if you find 
window procedures that belongs to the target ; most of the times, it
will not be the case, because the application will use system or 
application global classes (from Windows, MFC,...).

- with Softice, with HWND and CLASS : the first function gives informations 
about all windows of a process, including the window procedure address :

Window Handle   hQueue  SZ  QOwner    Class    Name     Window Procedure
0080(0)         206F    32  MSGSRV32  #32769 (Desktop)   17CF:00004C44
The second function gives informations about the classes used by a process :

:class notepad
Handle  Class Name               Owner           Wndw Proc       Styles
-------------------------- Application Private -----------------------------
403C    Notepad                  NOTEPAD         1467:00000350   07001000
--------------------------- Application Global -----------------------------
401C    SysAnimate32             COMCTL32        1467:000000FE   03004008
400C    msctls_hotkey32          COMCTL32        1467:000000E8   03004000
3A04    msctls_progress32        COMCTL32        1467:000000D2   03004003

A very underestimated command is "HWND -X", which gives you all the
informations you need about a window ; here's an example :
:hwnd -x notepad
Window Handle : (01B8) Level (1)
    Parent        : 16E7:000204CC
    Child         : 16E7:000235D8
    Next          : 16E7:00024F7C
    Owner         : NULL
    Window RECT   : (-4,-4) - (804,576)
    Client RECT   : (0,38) - (800,572)
    hQueue        : 238F
      Size        : 32
      QOwner      : NOTEPAD
    hrgnUpdate    : NULL
    wndClass      : 16E7:4184
    Class         : Notepad
    hModule       : NOTEPAD (1A07)
    lpfnWndProc   : 1467:0000033A

- with a BPR on the code of the application : when the DispatchMessage() 
function is called, you leave the message loop and start executing some 
code of the USER32 DLL. Most of the cases, the next piece of code of the
application that will be executed is the window procedure. Even if the 
window procedure's entry point is not contained in the application, this 
kind of breakpoint on the application is always interesting.

Note that you can easily find the memory space used as message queue,
since it uses its own selector. Here's an example :

TaskName   SS:SP      StackTop  StackBot  StackLow  TaskDB  hQueue  Events
Stat32d    0000:0000  00CFD000  00D00000            2D1E    2B27    0000
...                                                         ^^^^

:ldt 2b27
Sel.  Type      Base      Limit     DPL  Attributes
2B27  Data16    80155B80  0000009F  3    P   RW

The message queue for the Stat32d application start at address 80155B80
and is 160 bytes large. For details about how messages are stored in the 
message queue's memory space, read Matt Pietrek's book (or files).

Finding the piece of code corresponding to a missing function can be
the most difficult part of the job, because here you have to mostly use
your intuition. Hopefully, most applications use the standard Windows 
dialog boxes for opening, printing and saving files. These are the 
COMDLG32 DLL's GetOpenFileName (for Open) , GetSaveFileName (for Save and 
SaveAs), PrintDlgA (for Print).


1) Cryptographic Analysis Program (CAP) 1.0

Description:  a basic cryptanalyis tool 
Limitations:  Save, SaveAs and Print functions are disabled
Language:     delphi

To register, you must send to the author a number (derived from your 
HD number) and he sends you back the correct code : a very nice thing 
for us, because we are sure we already have all the code.

Let's have a look at the imported APIs : for the "menus" part, we have
everything we want : CreateMenu(), EnableMenuitem(), InsertMenu(), 
RemoveMenu(), SetMenu(),... The message part is interesting : we find 
no GetMessage() imported, but the message queue is tested with 
PeekMessage() (maybe a specificity of delphi ?)

Let's put a breakpoint on EnableMenuItem() to see what functions are 
disabled : since MF_GRAYED value is 01, we just have to break for any 
flag whose bit 0 is set, that means with a 

:BPX EnableMenuItem IF ((@(ss:(esp+0c))&1)==1) 

Here are the calls to EnableMenuItem() that trigger the breakpoint :

 MenuId  0588 058C 058C 058C 05D4 05D4 0614 0614 0620 0620
 FctId    20   04   05   07   49   4A   51   52   56   57
 Flag     03   03   03   03   03   03   03   03   03   03
In this case, all these functions are grayed and disabled (03h). 
He, but we see that only in menu 058C we find 3 disabled functions whose 
FctId values (04, 05, 07) correspond to the order of the 3 disabled 
functions we are interested in (Save, SaveAs, Print). With the STACK 
command, we find out that EnableMenuItem is invoked from 42639B for 
these 3 functions.

:00426383  mov    eax, dword ptr [4*eax+004996D8]   ; flag
:0042638A  or     eax, 00000000
:0042638D  push   eax
:0042638E  movzx  eax, word ptr [esi+34]
:00426392  push   eax                               ; function id
:00426393  mov    eax, edi
:00426395  call   00426194
:0042639A  push   eax                               ; menu handle
:0042639B  Call   EnableMenuItem

At line 426383, a lookup table seems to be used to get the flag 
corresponding to every function ; for our 3 disabled functions,
eax=0 and ds:[4996D8]=03 is used each time. Let's take Hiew,
and change this byte at .996D8 into 00 : now, all these functions 
are enabled.

Let's disassemble the program and search for an line containing 0111 
(WM_COMMAND) in the disassembled listing ; here's what we find :

:00423177  cmp   dword ptr [edx+04], 00000111  ; is it WM_COMMAND ?
:0042317E  je    004231A0                      ; yes : go on
:00423180  mov   esi, dword ptr [edx+04]
:00423183  cmp   esi, 00000200
:00423189  jle   00423193
:0042318B  cmp   esi, 0000020A
:00423191  jle   004231A0

Do you notice how the range 200-20A has a special meaning for this 
piece of code ? Well, a quick look at our message reference shows 
that this range contains all mouse related messages. This means that 
this piece of code is very likely to be a part of the message processing. 
So let just put a breakpoint to pop on any WM_COMMAND message :

:bpx 42317E if (ZFL==true)   

Now we click on Save... the breakpoint is triggered.
Let's trace a bit to see how the message will be processed. As expected, 
we soon reach a DispatchMessageA() procedure, where the message is 
sent to the appropriate window procedure.

To find this procedure, let's do something elegant, that means a  
little "breakpoint combo" :

:bc *
:map32 cap_prj          

Owner     Obj Name  Obj#  Address        Size      Type
CAP_PRJ   CODE      0001  0137:00401000  0009753C  CODE  RO

So we know that the code of this program is stored in memory
between 137:401000 and 137:49853C ; I therefore put a bpr on this
zone so that any piece of code from CAP_PRJ that is executed will
trigger this breakpoint. Then, 

:bpr cs:401000 cs:49853c r     
:bd 0                          
:bpx 42317E if (ZFL==true) do "g DispatchMessageA ; be 0; g"

The breakpoint at 42317E will trigger first when a WM_COMMAND message 
will be received. Then, it will process to the next DispatchMessageA 
procedure, enable the first breakpoint and run. From here only the 
code from DispatchMessageA() and its subprocedures will be executed, 
so BP0 will trigger on the first opcode executed from CAP_PRJ, which 
must be the window procedure entry point.

The breakpoint is triggered at 428F48 ; if we trace a bit, we find 
this piece of code :

:004266B4  push  ebx
:004266B5  cmp   byte ptr [eax+2D], 00      ; test a value
:004266B9  je    004266CC                   ; leave if 0
:004266BB  cmp   word ptr [eax+62], 0000    ; test another value
:004266C0  je    004266CC                   ; leave if 0
:004266C2  mov   ebx, eax
:004266C4  mov   edx, eax
:004266C6  mov   eax, dword ptr [ebx+64]
:004266C9  call  [ebx+60]
:004266CC  pop   ebx
:004266CD  ret

Let's see what happens at 4266B4 when we try a few options in the 
menu : 

Function  Status      EAX     [EAX+2D]    [EAX+62] 
Open     enabled    101ABD0      01          48
Save     disabled   101AEDC      00          48    
SaveAs   disabled   101AE58      00          48
Print    disabled   1019698      00          48
Exit     enabled    1018108      01          48

It's quite clear now, we just have to change the values at [eax+2D]
from 0 to 1 to enable the registered functions.

2) SigmaStat 2.0

Description:   a statistical tool
Limitations:   Save disabled
Language:      C++ / MFC

This is a typical C++ program, but it uses the famous Microsoft 
Fundation Classes (MFC 3.0 here), and that changes everything ! MFCs 
really add a shell around your application to completely handle 
interaction with the user : the message loop, the window procedures and 
even all the common functions of a File menu (New, Save, Print...) are 
executed within the DLL.

Here's how menus are handled by MFC : every time you click on a menu, 
- MFC gets the menu handle via GetMenu(), then the number of items in 
  this menu via GetMenuItemCount().
- for each item of this menu, it checks if this starts a submenu via 
  GetSubMenu(). If a submenu is here, another sub-loop is entered,
  where it starts by getting the number of items via GetMenuItemCount().  
- for each item in any menu/submenu, MFC gets the messageID associated
  to this item via GetMenuItemID() and disable it if needed with

Here's this last part (the ORD_3BF procedure at offset 6854h of 
MFC30.text segment) ; this piece of code is executed for each item

of each menu, and each time you select this menu :

:7FB27848  cmp    dword ptr [esp+10],01   ; should we disable ?
:7FB2784D  sbb    eax,eax                 ; yes / no     
:7FB2784F  and    eax,03                  ; keep the two lower bits
:7FB27852  mov    ah,04                   ; set MF_BYPOSITION
:7FB27854  push   eax                     ; pass the flag
:7FB27855  push   dword ptr [esi+08]      ; choose the function
:7FB27858  push   dword ptr [ecx+04]      ; choose the menu
:7FB2785B  Call   EnableMenuItem          ; enable or disable it

See what happens ? If the value at [esp+10] is zero, then the carry flag
is set and the SBB EAX,EAX instruction sets EAX at 0 minus 1 = FFFFFFFFh. 
If the value at [esp+10] is not zero, then the carry flag is not set and 
EAX is set to 0. As a result, the EAX value passed as flag is either 0403h 
(if [esp+10] was 0) or 0400h ; as usual, the two lower bits are the most 
important ones for us.

Tracing back where the [esp+10] value is set to 0, we quickly find this 
interesting piece of code :

:0040D599  mov    [ebp-04],ecx
:0040D59C  push   00              ; set the flag here !
:0040D59E  mov    eax,[ebp+08]    
:0040D5A1  mov    eax,[eax]
:0040D5A3  mov    ecx,[ebp+08]
:0040D5A6  call   [eax]           ; call 7FB27836 (to EnableMenuItem)               
:0040D5A8  jmp    0040D5AD        ; stupid compiler flush the PIQ :(
:0040D5AD  pop    edi
:0040D5AE  pop    esi
:0040D5AF  pop    ebx

This piece of code can be found 3 times in the code of Stat32, that's 
right 3 times the same piece of code, separated by a bunch of useless CCh
bytes ; obviously the Microsoft compiler use a standard "disable procedure"
that is just put as many times as necessary in the application's code.
Now there's something very interesting about that : if we replace the 
"push 00" by a "push 01", not only the items are enabled, but the 
corresponding functions are executed... the job is done :)

Just for fun (and knowledge), let's see how messages are processed by MFC ;
A breakpoint on GetMessageA() gives us the location of the message loop 
(in MFC30 DLL) :

:7FB24CEF  push   edi
:7FB24CF0  lea    esi,[ecx+30]
:7FB24CF3  mov    edi,ecx
:7FB24CF5  push   00                          ; (0,0,0) <=> get all the
:7FB24CF7  push   00                          ; messages
:7FB24CF9  push   00
:7FB24CFB  push   esi                         ; message buffer offset
:7FB24CFC  call   GetMessageA                 ; get message from the queue
:7FB24D02  test   eax,eax                     ; WM_QUIT received ?
:7FB24D04  jz     7FB24D31                    ; yes : leave program
:7FB24D06  cmp    dword ptr [edi+34],0000036A ; message 36A received ?
:7FB24D0D  jz     7FB24D29                    ; yes : ignore it
:7FB24D0F  push   esi
:7FB24D10  mov    eax,[edi]
:7FB24D12  mov    ecx,edi
:7FB24D14  call   [eax+38]                    ; handling of 201h and 202h
:7FB24D17  test   eax,eax                     ; (mouse-related messages)
:7FB24D19  jnz    7FB24D29
:7FB24D1B  push   esi
:7FB24D1C  call   TranslateMessage            ; translate keyboard inputs
:7FB24D22  push   esi
:7FB24D23  call   DispatchMessageA            ; send message to wndproc
:7FB24D29  mov    eax,00000001
:7FB24D2E  pop    edi
:7FB24D2F  pop    esi
:7FB24D30  ret

Let's try to break in this message loop when a WM_COMMAND message is 
received ; therefore we will watch the message buffer. The ESI value at 
line 7FB24CFB is always 5D7048, and this is pointing to a data section 
of Stat32d. There starts a MSG structure where every message taken from 
the queue is temporarly stored before being processed ; at offset 4 of 
this structure is stored wParam. So to break on a WM_COMMAND message we 
can use a :

:bpx 7FB24D02 if (@(ds:(5D704C))==0111)

Now if we try the same combo as in CAP, we get lost, because we are just 
bouncing between MFC30.DLL code and the program's lookup table.

Let's try the dead-listing method : we disassemble MFC30.DLL and use the 
same method, that means searching for "00000111" ; here's the first hit :

:7FB21074  cmp   ebx, 00000111    ; 111h = WM_COMMAND
:7FB2107A  je    7FB2110C
:7FB21080  cmp   ebx, 0000004E    ;  4Eh = WM_NOTIFY
:7FB21083  je    7FB21138
:7FB21089  cmp   ebx, 00000006    ;  06h = WM_ACTIVATE
:7FB2108C  je    7FB21176

We can stop here, this is the good one. Why ? Because in every MFC 
application, message processing is splitted between the CCmdTarget objects 
and the CWin objects that process most of the other Windows messages. The 
values of the 3 first messages are found here, so this must be the piece of 
code we are looking for. If we look 7FB2110C, it's quite promising :

:7FB2110C  mov    ecx,[ecx]
:7FB2110E  mov    esi,[ebp+10]   ; lParam is the first parameter
:7FB21111  push   esi            ; 
:7FB21112  mov    edi,[ebp+0C]   ; wParam is the second one
:7FB21115  push   edi            ; 
:7FB21116  mov    [ebp-14],ecx
:7FB21119  mov    ecx,[ebp-10]
:7FB2111C  mov    eax,[ebp-14]
:7FB2111F  call   [eax+48]       ; call WM_COMMAND processing

Ok, now we trace a bit in the WM_COMMAND processing to find where all 
different messages are routed, and we quickly find this piece of code :

:7FB29DBF  push   00
:7FB29DC1  mov    eax,[esi]      ; get adress
:7FB29DC3  push   00
:7FB29DC5  mov    ecx,esi
:7FB29DC7  push   ebp
:7FB29DC8  push   edi
:7FB29DC9  call   [eax+14]       ; execute the function

Look at this nice lookup table, it's just a pure array of code offsets, which
is the core of the message routing mechanism :

:005E7664 72 87 5A 00 32 82 5A 00-C0 F4 44 00 2C 82 5A 00  r.Z.2.Z...D.,.Z.
:005E7674 26 82 5A 00 20 82 5A 00-1A 82 5A 00 14 82 5A 00  &.Z. .Z...Z...Z.
:005E7684 6C 87 5A 00 10 F1 44 00-02 82 5A 00 FC 81 5A 00  l.Z...D...Z...Z.
:005E7694 F6 81 5A 00 66 87 5A 00-EA 81 5A 00 E4 81 5A 00  ..Z.f.Z...Z...Z.
:005E76A4 90 29 40 00 60 87 5A 00-DE 81 5A 00 5A 87 5A 00  .)@.`.Z...Z.Z.Z.
:005E76B4 D2 81 5A 00 CC 81 5A 00-C6 81 5A 00 80 EF 44 00  ..Z...Z...Z...D.
:005E76C4 BA 81 5A 00 54 87 5A 00-AE 81 5A 00 A8 81 5A 00  ..Z.T.Z...Z...Z.
:005E76D4 4E 87 5A 00 9C 81 5A 00-48 87 5A 00 42 87 5A 00  N.Z...Z.H.Z.B.Z.

A trained eye could recognise this kind of lookup tables just by looking at the
hex dump of the code section. Indeed, since these are all word-aligned offsets 
to the application's code, so you will always find 4 pairs of columns equal to 
"XX 00" (where XX is the first byte of the offset) at offsets 2, 6, 10 and 14 
of this table.

:map32 stat32d
Owner     Obj Name  Obj#  Address        Size      Type
STAT32D   .text     0001  0137:00401000  001D5784  CODE  RO

Here, XX can have any value between 40h and 5Dh, which are all printable 
chars, but as you see most of the times you find ("Z",00h) pairs.

Reversing a MFC disabled target can be very painful, because most of the 
work is done in the DLL while, even if you switch to/from the application 
very often. Not to mention that both MFC DLL's code and the code inserted by 
MFC in your application are ugly.

3) BestWin

Description:  an encryption tool
Limitations:  the Decrypt function is disabled (that's annoying :)
Language:     C (compiled with a french version of lcc-win32 2.4 in his 
                 c:\lcc\bestwin directory :)

Before the fortress' attack, Jeremy Likness proposed a software cracking 
challenge, quite pretentiously called "New Chaos Protection", to protect 
an implementation of his encryption algorithm (which is also weak btw, 
but that's another story). Here follows a description and a quick crack 
of his method.  

A quick disassembly in W32dasm gives these interesting menu references :

      File {Popup}
           Encrypt  [ID=00C8h]
           Decrypt  [ID=00D2h]
           Set Key  [ID=00DCh]
           Exit     [ID=012Ch]

The MenuID value for "Decrypt" is D2h, so let's search for "000000D2" in the 
disassembled listing :

:00402677  cmp    dword ptr [ebp+0C], 000000D2  ; Decrypt asked ?
:0040267E  je     004026B1                      ; yes : go on
:004026B1  push   755647E4                      
:004026B6  call   PROT._pGetFuncbyCode          ; which code to run ?
:004026BB  add    esp, 00000004                 
:004026BE  mov    dword ptr [ebp-04], eax       ; EAX = code offset
:004026C1  call   [ebp-04]                      ; execute our code
:004026C4  jmp    004026EA

When "Decrypt" is selected, BestWin asks PROT.DLL (via 
PROT._pGetFuncbyCode) which piece of code is associated to this 
function, then it runs it. 

Let's load BestWin in SoftIce to have a closer look at this 
_pGetFuncbyCode... what ? SoftIce is asking for source files ?? 
Looks like Jeremy forgot to remove the symbols :) Let's have a look :

.text(014F:00401000, 00002B88 bytes)
      0137:00401ABA _Decrypt
      0137:004028A4 _DefaultFunc
      0137:00401E4F _EnableDecrypt
      0137:00401CC4 _SetKey

Hehehe, our job is done : the _Decrypt label at 401ABA is the beginning 
of the decryption routine. Therefore, we only need to associate this 
piece of code to the "Decrypt" function of the menu in the WM_COMMAND 
message processing, just like that :

004026BE  E8F7F3FFFF    call 401ABA  ; _Decrypt procedure
004026C3  90   

Transfer interrupted!

We also need to enable the Decrypt function in the menu ; _pGetFuncbyCode is also used to "hide" functions that does not belong to any menu. For instance, when you set the key, this piece of code is run : :00401E1C push 75564714 :00401E21 call PROT._pGetFuncbyCode :00401E26 add esp, 00000004 :00401E29 mov dword ptr [ebp+FFFFFBAC], eax :00401E2F call dword ptr [ebp+FFFFFBAC] If we are registered, the _EnableDecrypt function is executed, which ungray Decrypt in the menu. If not, the infamous messagebox is displayed. As you see, we did not even have to study PROT.DLL to nullify this protection, we simply had to replace the relative calls by the absolute correct ones, just like this : 00401E2F E81B000000 call 401E4F ; _EnableDecrypt procedure 00401E34 90 nop His protection is based on a dialog between the main program (BestWin) and its protection DLL (PROT.DLL) : first, the "you must register" messagebox is set as default function (via PROT._SetDefaultFunc). Then, when a registered item is selected, BestWin asks PROT.DLL (via PROT._pGetFuncbyCode) which piece of code to run. If we are not registered, the offset of the default function is returned, and the evil message is displayed. Another major error is that all the functions are first defined (via PROT._AddFunc) with the offset of the correct piece of code (so that _pGetFuncbyCode knows the correct offset to give back to registered users). A pretty interesting idea, badly implemented : quite disappointing for such a hype name. 4) Flipper Location: Description: crackme Limitation: File menu is grayed for the first 10 seconds Language: Visual Basic 5.0 Well, this is not a full "disabled functions" protection, but it is small and does everything we need to understand how VB handles menus. Visual Basic handles all the GUI by itself, so that the coder has no idea what is done ; so we can assume that all VB programs define menus the same way. All is done in the __vbaI4ErrVar function. First some empty menus are created with CreateMenu(). Then each menu and each item are added with InsertMenuA() ; note that all the parameters of InsertMenuA() are stored in a structure based on EBP ; actually, EBP gets only two values in this piece of code, one for all the items and one for all the menus. :0F05191A push [ebp-10] ; address of menuitem text string :0F05191D push [ebp-0C] ; handle of the new item :0F051920 mov eax, dword ptr [ebp-08] ; get flag :0F051923 or ah, 04 ; add MF_BYPOSITION :0F051926 push eax ; pass the new flag :0F051927 push [ebp+18] ; the position of the new item :0F05192A push [ebp+0C] ; menu handle :0F05192D call InsertMenuA The first parameter (menu handle) is returned by a previous call to CreateMenu(). The second parameter (position) is always 0FFFFFFFFh, so that each new item is always appended to the existing menu. The third parameter (flag) has a default value equal to 0, which is reloaded after each item insertion. The fifth parameter (menuitem string) is just copied from the menu definition (see "Reversing a menu definition"). The most interesting parameter is obviously the fourth one, the item's message ID. As we saw before, it's not stored in the menu definition, contrary to what most of the other languages do. Here VB is tricky, because it distributes message IDs at run-time, by this piece of code : :0F051834 mov eax, dword ptr [ebx] ; get previous ID message :0F051836 inc eax ; increment :0F051837 mov word ptr [edi+000000D0], ax ; :0F05183E mov dword ptr [ebx], eax ; store new ID message For instance, here's the message distribution for this application : 1 : Menu "File" 2 : Item "SaveText" 3 : Item Separator 4 : Item "Exit" 5 : Menu "About" 6 : Item "Register" 7 : Item "About" It's clear and simple (something surprising from Visual Basic). When you start the application, the "File" menu is grayed for 10 seconds. A breakpoint on InsertMenuA() show that when the menu is created, the flag used is 400h (MF_BYPOSITION), so that the menu is enabled ; later, the menu is grayed using EnableMenuItem() with a 403h flag. After 10 seconds, the same API function is used again to ungray the menu. For now, let's skip the ungraying part and focus on the message routing (you'll see later why) : :hwnd app1 Window Handle hQueue SZ QOwner Class Name Window Procedure 0E2C(1) 0D87 32 APP1 ThunderRT5Form 1497:000007DE 0E30(2) 0D87 32 APP1 Edit 1497:000007DE 0E34(2) 0D87 32 APP1 ThunderRT5Timer 1497:000007DE ... All window procedures' adresses are pointing to the KERNEL DLL, so to find which part of app1 is involved in menu handling, we will use again a breakpoint on the application's code : :map32 app1 Owner Obj Name Obj# Address Size Type APP1 .text 0001 014F:00401000 000069A4 CODE RO :bmsg 02EC WM_COMMAND do "bd * ; bpr 14f:401000 14f:408000 r ; g" The breakpoint is triggered in MSVBVM50.DLL, at F03A2FD, where the adress of the window procedure is fetched from a table in app1. :0F03A2F8 mov ecx,[eax] ; get table base address :0F03A2FA mov eax,[ebp+14] ; get item :0F03A2FD push dword ptr [eax*4+ecx+0C] ; calculate address :0F03A301 call 0F03A546 and if we trace a bit, we find this piece of code : :0F01E597 mov eax, dword ptr [ebp+08] ; get address from stack ... :0F01E5A7 call eax ; go ! All the EAX values lead to a kind of table structure, where we jump to the real window's procedure, after the first stack parameter is adjusted to a fixed value (4114C5) : :004024FB sub dword ptr [esp+04], 00000033 ; About item :00402503 jmp 00403B0A :00402508 sub dword ptr [esp+04], 0000004F ; Register item :00402510 jmp 00403CC9 :00402515 sub dword ptr [esp+04], 00000057 ; Exit item :0040251D jmp 00403DDF :00402522 sub dword ptr [esp+04], 00000047 ; SaveText item :0040252A jmp 00403E43 :0040252F sub dword ptr [esp+04], 0000003F ; triggered each second :00402537 jmp 004040A1 ; during 10 s countdown :0040253C sub dword ptr [esp+04], 0000004B :00402544 jmp 0040425E For the SaveText item, we jump at 402508, then at 403CC9. Now let's find which piece of code we would like to execute : well, the rtcMsgBox is called at 404030, and just a bit upper, we find this interesting API calls : :00403FE0 call __vbaFileOpen ... :0040400F call __vbaPrintFile ... :00404029 call __vbaCloseFile and again a bit upper, this part : :00403E85 mov ax, word ptr [00408030] ; registered flag ... :00403E8D cmp ax, 03FB ; bigger than 3FBh ? :00403EC4 jnl 00404035 ; yes : do nothing :00403ECA cmp ax, 001B ; smaller than 1Bh ? :00403ECE jle 00404035 ; yes : do nothing :00403ED4 cmp ax, 00FE ; min registered value :00403ED8 jle 00404030 ; yes : go to rtcMsgBox Just change [408030] into FFh, and it's done. Ok, now let's ungray this File menu : we saw that the initial state of this item is "enabled", and that it is grayed afterwards. And guess how ? It's done by using the same table : :004024D4 sub dword ptr [esp+04], 00000037 ; gray File menu :004024DC jmp 00403A04 This is a nice place to patch ; let's replace this JMP to the graying function by a harmless one. Oh, but look how the next lines look interesting : :004024E1 sub dword ptr [esp+04], 0000FFFF :004024E9 jmp 00403B00 :004024EE sub dword ptr [esp+04], 0000FFFF :004024F6 jmp 00403B05 Not only is the FFFFh value a bit strange, but between the two JMPed addresses there are only 5 bytes ; let's see what code is at 403B00 : :00403B00 xor eax, eax :00403B02 ret 0004 Well, it looks harmless enough to me, so we just patch at 4024DC to force a jump to this address, and everything works fine. Most of the times, programs that display a message box for disabled functions are much easier to reverse than those that use grayed items. Indeed, in this case, lazy protectionists will use a switch between real and fake code after message decoding instead of trying to (for instance) modify the menu definition.

Final Notes
I hope that you now have a good overview of how menus work ; of course, comments 
and corrections are welcome. This is really a large subject, and there are still 
plenty of interesting things to talk about in this field (subclassing, superclassing, 
adding functions, ...) ; if you're interested in this subject, I suggest that you have 
a look at +HCU project 6 and Reverser+'s filemon essays. Good night, dear reader.

+Spath. ( (05/99)   

+ORC, Reverser+, +Greythorne and all +HCU members.
+Frog's Print, BeLZeBuTH, Ethan, CyberbobJr, KellogS, Jeff, Rhayader, Eternal Bliss, 
CrackZ, Iczelion, Virogen.

All my admiration goes to Razzia+, Stone, mammon_, Iceman, Quine and 
especially to The Owl. Thanks for giving me inspiration.

Ob Duh
I wont even bother explaining you that you should BUY this target program if you intend to use it for a longer period than the allowed one. Should you want to STEAL this software instead, you don't need to crack its protection scheme at all: you'll find it on most Warez sites, complete and already regged, farewell, don't come back.

You are deep inside reverser's page of reverse engineering, choose your way out:




redhomepage red links red anonymity +ORC redstudents' essays redacademy database redbots wars
redantismut redtools redcocktails redjavascript wars redsearch_forms redmail_reverser
redIs reverse engineering illegal?