If you are experienced in analyzing software applications and in localization, then you have surely encountered some programs written in Visual Basic™. Doesn't it surprise you that there is almost no information on how to edit VB forms and controls? In this article, I'm going to fill this gap as best as I can. (Actually, I wrote it in early 2007, and all the time since then it had been waiting to be published.)
As a matter of fact, software localizers and even advanced users have no problem whatsoever with using tools like Restorator or Resource Hacker to edit program interfaces. Alas, those tools don't recognize VB resources at all. It is not that the VB format is very complicated - I guess, the reason for the lack of good third-party resource editors capable of handling VB files is that nobody wants that much to spend a lot of time on making such a dedicated tool. Therefore, we have to learn the format ourselves. First, we need to know how to find those resources in a VB program. Let's consider the original entry point - all you need to go to it in the HIEW hex editor is to load the EXE file in that editor, and then to press the following keys in succession: Enter, Enter, F8, F5. (Experienced users probably know how to do the same via the command console, but never mind.) Here's what you'll see after doing that:
|push 0004042E8 ;'VB5!'|
call ThunRTMain ;MSVBVM60 --?2
Now we'll read the VBHeader structure at the 0004042E8 address. It may sound funny but all we need for our little research is this VBHeader.
Field Type Description|
Signature String * 4 "VB5!" signature
RuntimeBuild Integer Runtime flag
LanguageDLL String * 14 Language DLL
BackupLanguageDLL String * 14 Backup language DLL
RuntimeDLLVersion Integer Version of the runtime DLL
LanguageID Long Application language
BackupLanguageID Long Used with backup language DLL
aSubMain Long Procedure to start after the application is launched
aProjectInfo Long Pointer to ProjectInfo
ThreadFlags Long Thread flags
ThreadCount Long Number of threads
(the meaning of this field is unclear as VB
doesn't let you make multithreaded application)
FormCount Integer Number of forms in this application
ExternalComponentCount Integer Number of external OCX components
aGUITable Long Pointer to GUITable
aExternalComponentTable Long Pointer to ExternalComponentTable
aComRegisterData Long Pointer to ComRegisterData
oProjectExename Long Pointer to the string containing EXE filename
oProjectTitle Long Pointer to the string containing project's title
oHelpFile Long Pointer to the string containing name of the Help file
oProjectName Long Pointer to the string containing project's name
A huge structure, isn't it? Actually, everything is very simple: We only need FormCount, which holds the number of forms, and aGUITable, the pointer to the form-defining structures. Here's how the GUITable structure looks:
Field Type Description|
SectionHeader Long Address of section header
unknown(59) Byte Unused bytes
FormSize Long Block size, describing the form and its controls
un1 Long Unused DWORD
aFormPointer Long Pointer to the block describing the form and its controls
un2 Long Unused DWORD
There are as many such structures as project forms, and all of them go one after another. To get the address of the beginning of a form's code, add 93 to aFormPointer. This address indicates how many bytes the form length data takes. There's a catch, though: It takes either 2 or 4 bytes. Read a dword, then AND it with &H80000000, and you'll get the number of bytes the data takes. If the dword contains the &H80000000 flag, then the form length data takes 4 bytes, otherwise it takes 2 bytes. The form length is followed by the description of the form and of its controls. At last, you've got what you need. Now it's time to learn more about the binary format of VB forms and controls.
When VB 1.0 for DOS was popular, all forms were stored in the binary format by default; at that time, that didn't sound odd at all. But now, when we are used to editing .frm files in Notepad (or another text editor), it may be hard to imagine that the same forms may be saved as a packed binary. Why packed? Simply to get the information on the form's last control, you need to parse all the previous controls one by one. Hence you need to repack the whole structure just to add a new property for a control! That is, you first decompile the structure, then make modifications to it, and then recompile it (VB does the same, only automatically). Surely, it sounds rather complicated, but that's the only way to do it. The problem is most severe if there is an ActiveX or a UserControl on the form, which you need to select properly so that not to change some of its unknown properties. Programmers were probably impressed with those complications, because so far there are no good program interface editors for VB, and software localizers tend to keep clear of VB programs. Hopefully, this article will tell you something about the making of VB forms, and you'll learn to take them apart and then to put them back together. In a packed binary, each object starts with the Name property and ends with a descriptor, which tells you about other objects following (if any), their nesting and their end, as well as about the menu. The alternation of properties is very simple: First, there goes the property descriptor, then the value itself, and then the next descriptor. The descriptors FF00-FF05 are reserved, and here's what they are for:
|Public Const vbFormNewChildControl = &H1FF|
Public Const vbFormExistingChildControl = &H2FF
Public Const vbFormChildControl = &H3FF
Public Const vbFormEnd = &H4FF
Public Const vbFormMenu = &H5FF
Now there is a problem to be solved: Where do we get the descriptors of each control's properties? Well, I've already solved this problem by compiling a table (open it from this link), which I made by extracting those properties from VB TypeLibs and making corrections here and there. Here's a real example:
|00 00 00 00-00 00 00 00-00 00 04 00-00 00 0D 00 ?? ?? ?|
54 65 73 74-45 78 61 6D-70 6C 65 5F-31 00 0D 01 TestExample_1 ??
27 00 53 61-6D 70 6C 65-20 70 72 6F-67 72 61 6D ' Sample program
20 66 6F 72-20 61 72 74-20 61 62 6F-75 74 20 65 for art about e
64 69 74 69-6E 67 2E 2E-2E 00 03 08-00 00 80 19 diting... ?? ??
01 00 42 00-23 3E 04 00-00 6C 74 00-00 36 04 00 ? B #>? lt 6?
00 00 00 01-00 02 00 20-20 10 00 00-00 00 00 E8 ? ? ? ?
02 00 00 26-00 00 00 10-10 10 00 00-00 00 00 28 ? & ??? (
01 00 00 0E-03 00 00 28-00 00 00 20-00 00 00 40 ? ?? ( @
00 00 00 01-00 04 00 00-00 00 00 80-02 00 00 00 ? ? ??
00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00
00 00 00 00-00 80 00 00-80 00 00 00-80 80 00 80 ? ? ?? ?
00 00 00 80-00 80 00 80-80 00 00 80-80 80 00 C0 ? ? ?? ??? +
C0 C0 00 00-00 FF 00 00-FF 00 00 00-FF FF 00 FF ++
00 00 00 FF-00 FF 00 FF-FF 00 00 FF-FF FF 00 00
The byte 0D indicates that the name of the form consists of 0Dh characters. Then goes the TestExample_1 name, ending with a null byte, followed by 0Dh. We can get the meaning of the next byte, 01h, from my forms table - it is a Caption, so it must be followed by a string length, and then by the string itself. The way Visual Basic™ handles strings is weird too: for some object properties, it uses the ASCII format; for others, Unicode. And there is no simple algorithm to get the format - all you can do, is remember all the properties whose strings are stored in Unicode (knowing that the rest are stored in ASCII). For example, Caption and Name are always stored in ASCII, while Tag, Connect, and some others are stored in Unicode. Well, let's get back to our data, shall we. According to my table, 03 stands for BackColor, so the following 4 bytes are a 32-bit color code. The next byte, 19, is ScaleMode, and the following Word defines the scale. The byte 42 is WhatsThisButton, with the following byte being either True (FF) or False (0). Now let's take a look at the most interesting thing in VB forms, that is, the following byte, 23 - it is an Icon. Actually, VB forms are stored in .frm files, while graphics and other voluminous data go into .frx files. Each .frm refers to a specific address in a .frx, putting into that file all pieces of data, one after another. After compilation, the contents of the .frx file is included into the .frm file, so in this case, the byte 23 is followed by a stdole.Picture icon. If that icon is a default one, taken from MSVBVM60.DLL, then the byte 23 is followed by FFFFFFFF, otherwise it is followed by the number of bytes the picture takes. To get the whole icon, we must count off that number of bytes after the address. 3E 04 00-00 = 43E = 1086 bytes. After that many bytes, the icon ends and the form (which we are decompiling) continues:
FF 00 00 35-FF 00 00 24-05 00 46 6F-72 6D 31 00 5 $? Form1|
35 3C 00 00-00 59 01 00-00 CC 15 00-00 03 0C 00 5< Y? ??
00 46 03 FF-01 55 00 00-00 01 06 00-46 72 61 6D F? ?U ?? Fram
65 31 00 03-01 11 00 42-75 74 74 6F-6E 20 69 73 e1 ??? Button is
20 69 6E 20-68 65 72 65-00 03 00 00-00 00 04 FF in here ? ?
FF FF 00 05-78 00 A0 05-AF 14 37 05-12 01 00 1B ?x ??? 7??? ?
01 00 00 00-BC 02 A4 2C-02 00 0E 43-65 6E 74 75 ? +??,? ?Centu
72 79 20 47-6F 74 68 69-63 FF 01 2B-00 00 00 03 ry Gothic ?+ ?
08 00 43 6F-6D 6D 61 6E-64 31 00 04-01 09 00 45 ? Command1 ??? E
6E 61 62 6C-65 20 4D 65-00 04 78 00-58 02 BF 13 nable Me ?x X?+?
EF 01 11 02-00 FF 02 03-AE 00 00 00-02 06 00 4C ???? ??? ?? L
61 62 65 6C-31 00 01 01-6A 00 54 65-73 74 20 65 abel1 ??j Test e
78 61 6D 70-6C 65 20 66-6F 72 20 56-42 20 44 65 xample for VB De
63 6F 6D 70-69 6C 65 72-2E 20 50 6C-7A 20 6A 75 compiler. Plz ju
73 74 20 65-6E 61 62 6C-65 20 74 68-65 20 62 75 st enable the bu
74 74 6F 6E-2E 2E 2E 20-4D 61 79 62-65 20 74 68 tton... Maybe th
69 73 6F 6E-65 20 69 73-20 61 20 76-65 72 79 20 isone is a very
73 69 6D 70-6C 65 20 61-6E 64 20 65-61 73 79 20 simple and easy
77 6F 72 6B-00 03 00 00-00 00 04 FF-FF FF 00 05 work ? ? ?
78 00 78 00-AF 14 47 04-12 00 00 25-01 00 00 00 x x ? G?? %?
BC 02 A4 2C-02 00 0E 43-65 6E 74 75-72 79 20 47 +??,? ?Century G
6F 74 68 69-63 FF 02 04-50 00 00 00-2E F4 B5 01 othic ??P .? ?
C9 42 34 4B-9A 3F 43 B2-41 04 7C 5E-00 00 00 00 +B4K??C A?|^
Here's the byte 24 - it is a LinkTopic, which is followed by a string. We already know how to extract strings, so let's go on. As for the byte 35, there is no such OpCode in the table, but it is only the linear dimensions of the client part of the form. The byte 35 is followed by 4 dwords, namely ClientLeft, ClientTop, ClientWidth, and ClientHeight. Then there is the byte 46, which is a StartUpPosition - one byte that defines the form's position at launch (in the center of the screen, anywhere, or in the center of the parent form). Now we've reached the most interesting part: FF01. In this article, I've already mentioned the constants that define the end of some controls and the beginning of others. FF01 is a vbFormNewChildControl, which says that a control, placed on the form, goes next. First, as usual, there is a dword, which holds the size of data in the next control, then the name of that control, and then its properties. The bytes 01 (Caption), 03 (BackColor), 04 (ForeColor), and 05 (linear dimensions) are decompiled just like the linear dimensions of the client part of the form, but with a minor difference: Each size takes not 4, but 2 bytes. Let's continue: the byte 12 is a TabIndex, which defines the tabbing through controls. Alas, many software developers forget to set the TabIndex properly, and those users who prefer the keyboard-only approach might curse the programmer when using it or just quit using the program altogether. And I understand those users, because each programmer must set that index, otherwise it's just another botched-up job. TabIndex is defined by two bytes, so you cannot have more than 65,535 controls on a form (though that sounds more than enough for me). The next byte is 1B, which defines a very interesting property: Font. Unlike other properties, this one is described by the VB-specific stdole.Font class. Writing a VB decompiler without using Visual Basic™ is a helluva job simply because you don't have all the classes which otherwise would be at your disposal, built in VB libraries. What we see in the end of all controls, is FF0204. As we already know, the byte 02 stands for vbFormExistingChildControl (meaning the contol must be closed), and the byte 04 stands for vbFormEnd, which closes the form. Here's the result of decompiling (I've taken the listing from my VB Decompiler):
|Begin VB.Form TestExample_1 'Offset: 000010FA|
Caption = "Sample program for art about editing..."
BackColor = &H80000008&
ScaleMode = 1
WhatsThisButton = 0 'False
Icon = "TestExample_1.frx":0
LinkTopic = "Form1"
ClientLeft = 60
ClientTop = 345
ClientWidth = 5580
ClientHeight = 3075
StartUpPosition = 3 'Windows Default
Begin VB.Frame Frame1 'Offset: 000015A6
Caption = "Button is in here"
BackColor = &H0&
ForeColor = &HFFFFFF&
Left = 120
Top = 1440
Width = 5295
Height = 1335
TabIndex = 1
Name = "Century Gothic"
Size = 14,25
Charset = 0
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
... and so on.
Now take a look at an example of unlocking a locked menu and showing a hidden button.
I've written this simple program just to show you how to unlock a menu.
The "Save" menu command is locked, and you need to unlock it. At first, you need to know how to deactivate a menu. There are two ways of doing that: Either set the menu's Enabled property to False when developing the form, or set that property to False when the form is launched. Let's imagine that the property is only set to False while the menu is designed, so just decompile the project and take a look at its innards. To make things easier, don't decompile everything by hand, but use our decompiler for that (even the Lite version will do). There is only one form in the Forms section. Try to find the menu:
|Begin VB.Menu mnuFile 'Offset: 000011B3|
Caption = "File"
Begin VB.Menu mnuSave 'Offset: 000011CF
Caption = "Save"
Enabled = 0 'False
Begin VB.Menu Separator 'Offset: 000011F0
Caption = "-"
Begin VB.Menu mnuExit 'Offset: 0000120B
Caption = "Exit"
We've got it: "Enabled = 0", that's just what we need! Let's fix it - open the program in HIEW and go to the address 11F0:
|00 00 02 07-00 6D 6E 75-53 61 76 65-00 13 03 09 O mnuSave ! 0|
00 53 61 76-65 20 20 20-20 20 00 05-00 FF 02 1A Save O>
Everything is as usual: First goes Name, then Caption (03), and then Enabled (05). Then follows the byte 00, which means False. Change it to FF (True), and try to launch the program - the menu is unlocked and, when you select it, a MessageBox saying "cool" opens. The job is done, wasn't it pretty easy?
To demonstrate how the technique works, I've made a simple program that waits for 3 seconds after being launched, and only then shows the initially hidden "Save" button. Let's use the decompiler to take a look at the innards of the "Save" button:
|Begin VB.CommandButton cmdSave 'Offset: 00001175|
Caption = "Save"
Left = 1680
Top = 1800
Width = 1335
Height = 375
Visible = 0 'False
TabIndex = 1
The first thing you've probably noticed in the code listing is "Visible = 0", which corresponds to 09 in the table. Go to the offset 1175, and then go through all the properties until the byte 09. What you see then is 00, meaning False, so you change it to FF (True), and the job is done. But hold on: I've designed this program specially to make it possible to fix it in various ways. Let's consider another technique, shall we. How can you make a button visible exactly in 3 seconds? Surely, you can make a loop which starts when the program is launched, but the loop speed will depend on the CPU. You can also use GetTickCount, but checking it regularly in a While loop is somewhat inconvenient too. Such techniques are clumsy, so programmers tend to use timers instead. A timer is a special, hidden control on the form, whose event is executed every N milliseconds, where N is the timer's Interval. So let's check out if the form has such a timer:
|Begin VB.Timer Timer1 'Offset: 00001155|
Interval = 3000
Left = 2880
Top = 0
Width = 59400
Height = 8
Gotcha! 3000 milliseconds is exactly 3 seconds. If you change 3000 to 1, the job seems to be done. Therefore, it's easier to use the previous method; as for the timer, just switch it off by setting its interval to 0.
When reading this article, you surely thought about how to add a new property to a packed record inside a binary file. Well, to do that, you need to decompile the form, add that property, and then recompile the form. Clearly, the data wouldn't fit into the old place, so you'll have either to create a new section in the file, or to extend the last section and to redirect the data to the new place. Moreover, you'll have to specify the address to the form's new position in the form data structure. Keep in mind that if the user is going to add properties from time to time, you must provide some extra bytes for extending each form in the new section. Surely, you need to store all that information on the extra bytes and on the beginning and the length of each form somewhere, so you'll have to create a structure for that purpose. However, you only need to know that much if you are going to develop your own VB resource editor. If all you are going to do is to study the innards of some programs, this article will do.
Hopefully, this article has told you all you ever wanted to know about VB-specific interface editing. If you still have some unanswered questions, feel free to ask them at our forum dedicated to VB decompiling issues.
(C) Sergey Chubchenko, VB Decompiler's main developer
* Microsoft, Windows, and Visual Basic are registered trademarks of Microsoft Corporation.