This article shows one way of creating self-viewing data files. Examples of such things include self-extracting archives and self-playing videos. Self-viewing data files are executable files that put the data and the viewing program together in a nice neat little packa-ge.
This will require two programs, the player and the creator. The player will be embedded as a resource inside the creator. The creator extracts the player (aka the “stub”) and saves it as the target file, then embeds the data file in that as a resource. The player, when run will read the data file from it’s resources and display it.
To accomplish this, there will need to be a few functions that access or manipulate resources. Here are the ones required and who the onus falls on:
Step | Function | Responsible |
---|
1 | Insert viewing program as resource into creator program | You, programmer |
2 | Extract viewing program from resource and save to file | Creator program |
3 | Insert data file as resource into viewing program | Creator program |
4 | Extract data file from resource and save to file/display | Viewing program |
The obvious way to do this is to use the native resource functions. Unfortunately, the functions for updating resources is not available for Win9x systems, only the ones for reading them are. There is a way to add the missing support by using the MSLU but that adds to the size and requires including a DLL with the program. An easy alternative is to manually handle the “resources” by injecting them in and extracting them from the stub. This is the method used in this article. We’ll break down the work into the separate programs. First the player.
The Player (stub.exe)
Step 4
The viewing application (the stub) will need to be able to extract the data from itself. The data will be located at the end of the executable, followed by the size of the data (so that we know how much data there is), and finally a signature. Here is some (surprisingly) simple code—sans error checking—to read a chunk of plain text:
void ReadResource() {
CFile f;
DWORD sig =0;
LONGLONG size =0;
CString t =_T("");
//open itself for reading
if (f.Open(__argv[0], CFile::modeRead)) {
//read the last 4 bytes of the file and check if it is the signature
f.Seek(-((LONGLONG)sizeof(sig)), f.end);
f.Read(&sig;, sizeof(sig));
if (sig==0x12344321) {
//read the size
f.Seek(-((LONGLONG)(sizeof(size)+sizeof(sig))), f.end);
f.Read(&size;, sizeof(size));
if (size) {
//seek to the start of the data
f.Seek(-((LONGLONG)(size+sizeof(size)+sizeof(sig))), f.end);
//allocate a buffer for the data and read it
f.Read(t.GetBufferSetLength((UINT)size), (UINT)size);
//display the read data in the edit control
GetDlgItem(IDC_EDIT1)->SetWindowText(t);
}
}
f.Close();
}
}
Other types of data can be read in the same manner.
The Creator (create.exe)
Step 2
We can use the same technique as above to read the stub from the end of the creator program, but here is a way to do it with the normal method of using resources (which is supported):
//search for and locate the stub resource
HRSRC shstub=FindResource(NULL, MAKEINTRESOURCE(IDR_STUB), _T("BIN"));
if (shstub) {
//verify the size of the stub
DWORD rs=SizeofResource(NULL, shstub);
if (rs) {
//load the stub resource
HGLOBAL hstub=LoadResource(NULL, shstub);
if (hstub) {
//lock the stub resource to get a pointer to it's first byte
BYTE* stub=(BYTE*)LockResource(hstub);
if (stub) {
//create an executable file
CFile stubf;
if (stubf.Open("stub.exe", CFile::modeCreate|CFile::modeWrite)) {
//copy the stub to the file
stubf.Write(stub, rs);
stubf.Close();
}
UnlockResource(hstub);
}
}
}
}
Step 3
The stub is now written to the disk but is just a viewer. We have to inject the data file for it to display:
//get the data to be injected (simple edit box in this case)
UpdateData();
CString t =::GetWindowText(GetDlgItem(IDC_EDIT1));
CFile f;
LONGLONG size=0;
DWORD sig =0;
//open the player file
if (f.Open("myfile.exe", CFile::modeReadWrite)) {
//we need to check if there's already something there
//read the last 4 bytes from the player file and check if it's a signature
f.Seek(-((LONGLONG)sizeof(sig)), f.end);
f.Read(&sig;, sizeof(sig));
if (sig
==0x12344321) {
//there's already a resource there, so we'll have to replace it
//read the size of the existing resource
f.Seek(-((LONGLONG)(sizeof(size)+sizeof(sig))), f.end);
f.Read(&size;, sizeof(size));
if (size) {
//add the size of the size and signature
size+=sizeof(size)+sizeof(sig);
//truncate the file, leaving just the stub
f.SetLength(f.GetLength()-size);
}
}
//seek to the end of the file
f.Seek(0, f.end);
size=t.GetLength();
//write the data file to the stub
f.Write(t.GetBuffer(), (UINT)size);
//write the size of the data
f.Write(&size;, sizeof(size));
//write the signature
sig=0x12344321;
f.Write(&sig;, sizeof(sig));
f.Close();
}
Step 1
Finally, we need to insert the stub into the creator program. If you are using the resource method, then it can be added as any other resource, making sure to use type BIN and using stub.exe as the source. If you are using the manual method, then you will need to manually append stub.exe to creator.exe, add the size of stub.exe, then the signature.
Now, just compile stub.exe, then creator.exe. Run the creator program and create some data. Click the export player button, and select the filename to save. The creator will read the stub, save it to disk, and inject the data. Now run the self-contained data/viewer file. It will extract the data from itself and display it.
Note: there are a couple of drawbacks to using this method. First, while it works, it is not part of the PE format standard. Future revisions of the PE format may break this, especially with the crackdown on code execution. Another potential problem is that some virus scanners may detect the modification of an executable as suspicious activity and thus have a bad reaction, causing itchy, watery eyes and a runny nose. The virus scanner can be circumvented however, by writing the stub to a non-executable file (say myfile.bin), then renaming it after the data has been injected.