This Gist covers 4 methods to embed arbitrary binary files (images, data, etc.) inside an executable.
From standard approaches (byte arrays) to low-level hacks like binary appending (copy /b), each method includes ready-to-use examples in Visual C++.
Use cases:
- Single-file deployment (no external resources).
- Hiding data inside .exe/.elf (no encryption).
- Quick prototyping or educational purposes.
- Methods 1-3 are "clean" and portable.
- Method 4 (
copy /b) is a hack and may cause problems with checksums or antivirus. - All examples assume x86_64 context (adaptable for other architectures).
Best for: Native Windows executables where you want integrated resource handling
- Right-click project β Add β Resource...
- Choose "Import..." and select your binary file (e.g.,
data.bin) - Set resource type as
RCDATAand assign an ID (e.g.,IDR_MY_DATA)
Visual Studio will automatically:
- Generate/modify
resource.hwith your ID - Create/update the
.rcfile with:IDR_MY_DATA RCDATA "file.bin"
This will be the code to access the resources:
main.cpp
#include <windows.h>
#include "resource.h" // Auto-generated by VS
void LoadEmbeddedData()
{
HRSRC hRes = FindResource(nullptr, MAKEINTRESOURCE(IDR_MY_DATA), RT_RCDATA);
DWORD dataSize = SizeofResource(nullptr, hRes);
HGLOBAL hData = LoadResource(nullptr, hRes);
const BYTE* pData = static_cast<const BYTE*>(LockResource(hData));
// Use pData (read-only) and dataSize...
// Note: LockResource doesn't need unlocking
}- π§ No manual compilation β VS handles
.rcβ.resconversion during build - π Data is read-only in memory (safe from modification)
- π¦ Supports any binary format (use
RT_RCDATAfor raw data) - β Possible external changes - Resources can be replaced by external tools
Best for: Precise binary embedding without compiler dependencies
HxD Editor - A utility to export the binary file offset as a C/C++ source file
- Open your binary file (
.bin,.dat, etc.) in HxD Editor - Go to File β Export β C and choose a path to save the source
.cppfile - Copy the source
.cppfile into your project folder and include it
The source file should look like:
/* [FILENAME] ([DATE])
StartOffset(h): 00000000, EndOffset(h): 000003FF, Length(h): 00000400 */
unsigned char rawData[1024] =
{
0x7F, 0x45, 0x4C, 0x46, 0x02, 0x01, 0x01, 0x00, // ELF magic bytes
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x00, 0x3E, 0x00, 0x01, 0x00, 0x00, 0x00,
// ... (truncated for brevity) ...
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};Now create a new H header file from Visual Studio and insert the following declarations:
embedded_data.h
#pragma once
extern unsigned char embedded_data[];
extern const int embedded_data_size;After remember to move the size of the bytes array into a const variable and assign it to the size of the array
embedded_data.cpp
#pragma once
#include "embedded_data.h" // include the header
const int embedded_data_size = 1024; // this is the value of the array size offset below
unsigned char embedded_data[embedded_data_size] =
{
0x7F, 0x45, 0x4C, 0x46, 0x02, 0x01, 0x01, 0x00, // ELF magic bytes
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x00, 0x3E, 0x00, 0x01, 0x00, 0x00, 0x00,
// ... (truncated for brevity) ...
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};At this point from any part of our application code we include the header that declares our offset and declare a binary data buffer in your code to be able to use it in memory
main.cpp
#include "windows.h"
#include "embedded_data.h"
void LoadEmbeddedData()
{
BYTE* embedded_data_buffer = new BYTE[embedded_data_size];
size_t embedded_data_buffer_size = embedded_data_size;
memcpy(embedded_data_buffer, embedded_data, embedded_data_size);
// At this point, embedded_data_buffer contains the embedded data
// You can do anything with the buffer and size
// Example: processing or writing to disk
delete[] embedded_data_buffer; // deallocate the buffer
}- β Manual binary precision β It allows direct embedding of binary data into source code, eliminating dependencies on automated tools.
- π Data is read-only in memory (safe from modification)
- π¦ Immutable data Embedded data is read-only, ensuring safety and integrity during execution.
- β Manual updates required - To change or update embedded data, you need to regenerate the offset and recompile the source code.
- β Avoid too large files - Embedding large files can increase the size of the source code, reduce readability, and cause compilation slowdowns or even errors.
Best for: Windows executables (MSVC/MinGW) requiring direct binary embedding
bin2obj - Included portable MinGW ucrt64 with objcopy utility and a little program bin2obj to make the process easier
1. Open a CMD window in the ucrt64 folder
Enter the command for a conversion to a 32-bit COFF:
objcopy -I binary -O pe-i386 -B i386 binary.bin binary.obj Enter the command for a conversion to a 64-bit COFF:
objcopy -I binary -O pe-x86-64 -B i386:x86-64 binary.bin binary.obj Alternatively you can use the small utility developed by me to automate the conversion process
Leave the Export a C/C+ header file to import the COFF object file checkbox checked to have the include file for the COFF symbol declarations exported automatically.
The header file that will be generated will look like this:
binary_data.h
#pragma once
// include this header in your application and link the COFF as additional dependencies
extern "C"
{
extern const uint8_t binary_filename_start[];
extern const uint8_t binary_filename_end[];
const size_t binary_filename_size = binary_filename_end - binary_filename_start;
}Place the header file into the project folder and the converted COFF file, otherwise if you have converted the COFF directly from the command line you will also have to write the header file that declares the COFF symbols
Remember to link the COFF from additional dependencies
2. Writing manually header file for COFF symbol declaration
- Open Visual Studio Developer Powershell command line
- Using the
cdcommand, reach the folder where the converted COFF is located. - Run the command
dumpbin /symbols binary.objto get the COFF symbols
The output will look like this:
Microsoft (R) COFF/PE Dumper Version 14.42.34433.0
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file binary.obj
File Type: COFF OBJECT
COFF SYMBOL TABLE
000 00000000 SECT1 notype External | _binary_filename_start
001 000069DD SECT1 notype External | _binary_filename_end
002 000069DD ABS notype External | _binary_filename_size
String Table Size = 0xD9 bytes
Summary
69DD .data- From Visual Studio create a new header file and write the declarations for the symbols
binary_data.h
#pragma once
extern "C"
{
extern const uint8_t binary_filename_start[];
extern const uint8_t binary_filename_end[];
const size_t binary_filename_size = binary_filename_end - binary_filename_start;
}- Copy the COFF object file into your project folder and link it from additional dependencies
Now you can go and declare a binary data buffer in your code to be able to use it in memory
main.cpp
#include "windows.h"
#include "binary_data.h"
void LoadEmbeddedData()
{
BYTE* binary_filename_buffer = new BYTE[binary_filename_size];
size_t binary_filename_buffer_size = binary_filename_size;
memcpy(binary_filename_buffer, binary_filename_start, binary_filename_buffer_size)
// At this point, binary_filename_buffer contains the embedded data
// You can do anything with the buffer and size
// Example: processing or writing to disk
delete[] binary_filename_buffer; // deallocate the buffer
}- β Efficient binary embedding β Converts binary data into a COFF object, allowing direct linking in Windows executables (ideal for MSVC/MinGW).
- π Data is read-only in memory (safe from modification)
- π¦ Immutable data Embedded data is read-only, ensuring safety and integrity during execution.
- β Manual updates required - To change or update embedded data, you need to regenerate the COFF and recompile the source code.
- β
Symbol consistency - If the binary file has the same name, the generated symbols (e.g.
binary_filename_start) will be identical, allowing easy updates without changes to the source code. - βοΈ Custom section alignment possible - You can specify additional options with objcopy to control the alignment of the section, which is useful for certain data types or alignment-sensitive binary structures.
Best for: Perfect for portable or self-extracting programs: no need to attach external files
This is a "dirty" method that allows you to append binary files (such as ZIP archives, images, DLLs, etc.) to an executable using the copy /b command. The resulting executable can be run as a normal program, but it also contains binary data that can be extracted and used during execution.
This method does not require any additional external tools, we will only need the copy command and the command prompt
First of all write in your application code a helper functions that gets the concatenated bytes to load them into memory in a buffer:
main.cpp
#include "windows.h"
// Copy and paste this helpers functions into your code
// This helper function is used to get the real size of the executable without the concatenated binary data.
DWORD GetRealExeSize(const TCHAR* exePath)
{
HANDLE hFile = CreateFile(exePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return 0;
HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (!hMapping)
{
CloseHandle(hFile);
return 0;
}
LPBYTE pBase = (LPBYTE)MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
if (!pBase)
{
CloseHandle(hMapping);
CloseHandle(hFile);
return 0;
}
IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)pBase;
if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
UnmapViewOfFile(pBase);
CloseHandle(hMapping);
CloseHandle(hFile);
return 0;
}
IMAGE_NT_HEADERS* pNtHeaders = (IMAGE_NT_HEADERS*)(pBase + pDosHeader->e_lfanew);
if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE)
{
UnmapViewOfFile(pBase);
CloseHandle(hMapping);
CloseHandle(hFile);
return 0;
}
DWORD maxEnd = 0;
IMAGE_SECTION_HEADER* pSection = IMAGE_FIRST_SECTION(pNtHeaders);
for (int i = 0; i < pNtHeaders->FileHeader.NumberOfSections; ++i)
{
DWORD sectionEnd = pSection[i].PointerToRawData + pSection[i].SizeOfRawData;
if (sectionEnd > maxEnd)
maxEnd = sectionEnd;
}
UnmapViewOfFile(pBase);
CloseHandle(hMapping);
CloseHandle(hFile);
return maxEnd;
}
// This function allows you to get a buffer in memory of the concatenated binary data.
BYTE* LoadAppendedData(DWORD& outSize)
{
TCHAR exePath[MAX_PATH] = { 0 };
GetModuleFileName(NULL, exePath, MAX_PATH);
DWORD32 exeSize = GetRealExeSize(exePath);
HANDLE hFile = CreateFile(exePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return nullptr;
LARGE_INTEGER fileSize = {};
if (!GetFileSizeEx(hFile, &fileSize))
{
CloseHandle(hFile);
return nullptr;
}
if (fileSize.QuadPart <= exeSize)
{
CloseHandle(hFile);
return nullptr;
}
DWORD appendedSize = (DWORD)(fileSize.QuadPart - exeSize);
BYTE* data = new BYTE[appendedSize];
SetFilePointer(hFile, exeSize, NULL, FILE_BEGIN);
DWORD bytesRead = 0;
ReadFile(hFile, data, appendedSize, &bytesRead, NULL);
CloseHandle(hFile);
if (bytesRead != appendedSize)
{
delete[] data;
return nullptr;
}
outSize = appendedSize;
return data; // Concatenated data loaded into memory
}
void LoadEmbeddedData()
{
DWORD embedded_data_size = 0;
BYTE* embedded_data = LoadAppendedBinary(embedded_data_size);
if (embedded_data)
{
// At this point, embedded_data contains the embedded data
// You can do anything with the buffer and size
// Example: processing or writing to disk
delete[] embedded_data; // deallocate the buffer
}
}Once this is done, compile the application and reopen the command prompt in the build folder (Debug/Release)
At this point we can execute the command to concatenate any binary data to the executable:
copy /b app.exe + mydata.bin final_app.exe- β Easy loading of concatenated data β It uses the Windows API to read data added to an executable, allowing you to manipulate combined files without additional dependencies like MFC.
- π¦ Immutable data β Embedded data is read-only, ensuring safety and integrity during execution.
- β Manual updates may be required β If the embedded data or the file structure changes (e.g., the executable size changes), you may need to update the offset that points to the start of the concatenated data and recompile the source code.
Both methods have their pros and cons, below is the summary table for everything
| Method | Best For | Pros | Cons | Example Use Cases |
|---|---|---|---|---|
| Method 1: Using Win32 Resource Files (.rc) | Native Windows executables where integrated resource handling is preferred | β
No manual compilation β VS handles .rc β .res conversionβ Data is read-only (safe from modification) β Supports any binary format |
β Possible external changes β Resources can be replaced by external tools | Embedding configuration files, certificates, or game assets; Storing default data for single-file apps |
| Method 2: Manual Hex Offset on Source Code | Precise binary embedding without compiler dependencies | β
Manual binary precision β Directly embeds binary data into source code β Data is read-only β Immutable data β No additional dependencies β Custom section alignment possible |
β Manual updates required β Must regenerate offsets and recompile β Avoid too large files β Large files reduce readability and cause compilation slowdowns |
Embedding small binary data (e.g., logos, icons, or small assets) for small projects |
| Method 3: Objcopy Binary COFF Linking (.obj) | Windows executables (MSVC/MinGW) requiring direct binary embedding | β
Efficient binary embedding β Converts binary into a COFF object for direct linking β Symbol consistency β Same file name results in identical symbols β Custom section alignment possible |
β Manual updates required β Regenerate the COFF and recompile for updates β External tool dependency β Requires bin2obj utility or manual symbol creation |
Embedding large binary files in executables, such as libraries or other large assets |
Method 4: Binary Concatenation with copy /b |
Self-extracting programs, or when no external files are allowed | β
Easy loading of concatenated data β No dependencies like MFC β Portable β Doesnβt need additional external libraries β Fast to implement β Simple concatenation command |
β Manual updates may be required β Changing the embedded data or file structure requires offset updates and recompilation β "Dirty" method β Can cause issues with checksums or antivirus detection |
Embedding data like ZIP files, DLLs, or other large data into executables for self-extraction |
In this Gist, we've covered four distinct methods for embedding binary data into a Win32 executable, each offering its own strengths and trade-offs:
- Method 1: Win32 Resource Files (.rc): Ideal for straightforward integration with Windows resource handling. Best for applications where the embedded data does not change often and is part of the resource management system.
- Method 2: Manual Hex Offset on Source Code: Provides precise control over the embedded data but requires manual management of offsets. Best suited for small to medium binary data embedded directly within the source code.
- Method 3: Objcopy Binary COFF Linking (.obj): A more sophisticated method for embedding large binary objects, ideal for developers using MSVC or MinGW who need to efficiently manage binary assets.
- Method 4: Binary Concatenation with
copy /b: A "dirty" but quick solution for appending data directly to an executable. This method is great for self-extracting applications but requires careful handling of offsets and file sizes.
- Keep Your Executable Small: While embedding binary data can make your application self-contained, avoid adding too much data to prevent bloating the executable and affecting performance.
- Use Proper Memory Management: Always ensure to free any allocated memory once you're done using the embedded data to prevent memory leaks.
- Consider Security: If you're planning to hide sensitive data (e.g., encryption keys), be mindful that these methods are not inherently secure and can be easily extracted by someone with access to the executable. You may want to consider additional security measures, like encryption.
Each of these methods is suitable for different scenarios, and the best choice depends on your specific needs: portability, ease of use, and how frequently the embedded data needs to be updated. For quick prototyping or simple use cases, copy /b might be the easiest choice, but for more robust applications, using Win32 resource files or manual hex offsets could be more effective.
Feel free to experiment with these methods and choose the one that fits best with your project goals!
If you have any suggestions, improvements, or questions, feel free to contribute or reach out! I'm open to discussing different techniques and ways to enhance the methods presented here.
Happy coding and stay creative! π
