(Friday)Usually a deployed application in the file system looks something like this:

')
Completely unprotected from tools like a reflector or IlSpy, but what if it becomes:

At least a light stupor hacker neophyte is provided. Looks nice,
and antiviruses are not interested.Short
In the pictures there is a module Booster.exe, and we'll talk about it, this is a very small program and it can do the following:
- pack a set of dll'ok in a set of pictures, while squeezing and encrypting
- load dll's from a set of pictures, find types with the Main method in them and run them
This takes into account that the assemblies can use each other (the
AssemblyResolve event at the AppDomain is used for this).
If you remove all checks, the launch code looks like this:
AssemblyProvider.LoadAssemblies(imagesFolder).CallMain();
Packing code as follows:
var config = CommandLineParser.Parse(args); AssemblyProvider.PackAssemblies(config["img"], config["imgout"], config["asm"]);
Packaging
Let's start with packaging, the
PackAssemblies method takes the path to the catalog with pictures, the path to the catalog where you want to put the pictures with code, and the path to the catalog with assemblies. The first thing we collect information about the pictures, we need to know how much data can be stored in them:
Dictionary<string, long> imagesVolume = new Dictionary<string, long>(); var imageFiles = Directory.GetFiles(imagesFolder, "*.*").Where(file => file.ToLower().EndsWith("bmp") || file.ToLower().EndsWith("png") || file.ToLower().EndsWith("jpg") || file.ToLower().EndsWith("jpeg")) .ToList(); foreach (string file in imageFiles) imageSet.Append(file);
The formula is simple, the width is multiplied by the height and subtracted 4 bytes for the size of the data. ImageSet additionally monitors the looped run through the pictures (to be different), and the search for pictures that can accommodate the size of the assembly.
It now remains to run through the assemblies, download each in binary form, and merge with a picture that can accommodate the size of the data. Briefly for a separate assembly like this:
byte[] packed = AssemblyPacker.Pack(file); string imageFile = imageSet.FindImage(packed.Length); if (imageFile != null) { using (Image img = Image.FromFile(imageFile)) { Bitmap bmp = SteganographyProvider.Injection(img, packed);
AssemblyPacker compresses the byte array using
GZipStream , since assembly size is block aligned, this can significantly reduce the size.
Example of the end of the assembly file:

, the part finished off with zeros is highlighted, and below two hundred and two more zeros, which will shrink perfectly.
Then the compressed array is 'encrypted', I did not use crypto-providers, the array simply xor with a set of pseudo-random numbers, and the size of the array serves as a generator. Those. when the encryption operation is repeated, we get the original array. The algorithm is very simple, but gives a result similar to white noise.
Distribution of data in the raw assembly:

Distribution after processing close to
normal
Having received the packed assembly we pass to the most interesting part, merge with the picture. Where we stumble on the first problem. .NET, when saving a picture, uses WinApi's GdipSaveImageToFile method (
you can see here ), which in addition to the image link and file name accepts a link to image processors (encoders), among which may be archivers, optimizers, etc. Each of which can change the pixel values, which will damage the stored data. The simplest solution would seem to be not to transfer handlers, but the
GdipSaveImageToFile method itself is smart, besides the list of handlers sent, it also focuses on the image format (clsid, the third parameter of the method), while, for example, for png, it can even score on our list of handlers and decide what to apply yourself. I didn’t manage to find a combination for which a lossless image compression would work, so I use the following solution:
Bitmap bmp = SteganographyProvider.Injection(img, packed); FieldInfo fi = bmp.GetType().GetField("nativeImage", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); object d = fi.GetValue(bmp); IntPtr nativeImage = (IntPtr)d; Guid clsid = FindEncoder(ImageFormat.Bmp.Guid).Clsid; GdipSaveImageToFile(new HandleRef(bmp, nativeImage), outImagefile, ref clsid, new HandleRef(null, IntPtr.Zero));
Those. no matter what the input format is, when saving, I point out that we are working with BMP, while the final image is not optimized in any way, which is immediately apparent from the final weight of the image. But you can be sure that you read the pixels that are recorded.
Having dealt with storage, proceed to the merger. For 32-bit images, each pixel is encoded by four bytes (argb), for 24-bit ones by three (rgb), the original algorithm was focused on writing 4 bytes using the alpha channel, which made it possible to get images by eye almost indistinguishable from the original, but in total Having come to bmp, I refused the alpha channel.
So, every byte from our array is mapped to one pixel, while, we divide the byte into three numbers, the number of ones, ten and hundreds, i.e. for byte 158 we get three numbers, (1, 5, 8), after which we replace the units in the rgb components with the resulting numbers, for example, the pixel (7.155.72) turns into (1, 155, 78).
pix = MixByteAndPixel(pix, ByteDecomposition(data[index], false)); ((byte*)bmd.Scan0)[offset] = pix.blue; ((byte*)bmd.Scan0)[offset + 1] = pix.green; ((byte*)bmd.Scan0)[offset + 2] = pix.red;
The merge algorithm is obtained as follows; we add 4 bytes to the beginning of the data array, in which the length of the array is written. Pass through the image and merge the bytes with pixels, if there are free pixels in the image, merge with random values (otherwise, the final image clearly shows the border where the data ended, as on CD / DVD discs you can see a blank area). Save the resulting image.
Original (left) and picture with the assembly inside (right):

Launch
For start we will need Booster.exe and final images. It is enough to place Booster.exe in the catalog with pictures and run it. Or run with the parameter img = path_to_kartinkam.
In this case, for the pictures, all the packing operations are applied in the reverse order:
- We read the pixel values, we take units from each pixel of the rgb component, and restore the original byte.
- After the first four bytes, we get the size of the remaining data.
- Read the original array with the packed and encrypted assembly. The rest of the image is ignored.
- Decrypt the original array.
- Unzip the array and get the assembly directly in raw form.
- Load the assembly
- Upon successful loading, register the assembly in AssemblyResolver, in case the assemblies use each other
internal static AssembliesSet LoadAssemblies(string imagesFolder) { AssembliesSet set = new AssembliesSet(); foreach (string file in Directory.GetFiles(imagesFolder, "*.bmp")) { byte[] data = null; using (Image img = Image.FromFile(file)) { data = SteganographyProvider.Extraction(img); } data = AssemblyPacker.UnPack(data); set.TryAppendAssembly(data); } return set; }
And the final part, we look for the Main method in the loaded collections for the available types, and call it:
internal void CallMain() { foreach (var type in CollectExportedTypes()) { MethodInfo main = type.GetMethod("Main"); if (main != null) { ParameterInfo[] paramsInfo = main.GetParameters(); object[] parameters = new object[paramsInfo.Length]; for (int i = 0; i < paramsInfo.Length; i++) { parameters[i] = GetDefaultValue(paramsInfo[i].ParameterType); } main.Invoke(null, parameters); } } }
We are testing
Let's make a project with three assemblies, A, B and C, while A and B will use assembly C. Like this, for example:
/ * picture in the comment below * /We pack into pictures, and run:
/ * picture in the comment below * /As you can see from the conclusion, all the assemblies are loaded and the code is executed, including the dependency of the assemblies from each other (calling the CommonClass class Run method).
Conclusion
Initially, I really wanted to use the png format, for some reason I was sure that it uses lossless compression algorithms, but it turned out there are losses, insignificant for the image, but critical for steganography. If someone knows how to save Bitmap in png without loss, please unsubscribe.
I hope it was interesting.
Download and play can be here .
PS Something strange is happening with pictures on Habré, consistently losing links.
UPD . Thanks to mayorovp
habraiser for commenting on PNG, it’s really possible to save it by standard means without losses. Now the algorithm takes into account the presence of the alpha channel, and if there is one, it decomposes each byte into 4 components to reduce distortion at the output. In the example of a byte equal to 158, the decomposition will be as follows:
In the first step, we divide hundreds, tens and units, we get a vector (a1, a2, a3, a4) with values (1, 5, 8, 0), then we find a4 depending on the conditions:
if (a2 >= 5 && a3 >= 5) { a4 = 2; a2 -= 5; a3 -= 5; } else if (a2 >= 5) { a4 = 3; a2 -= 5; } else if (a3 >= 5) { a4 = 4; a3 -= 5; } else a4 = 5;
The final vector will be such (1, 0, 3, 2).
Thus, all values of units of rgba components will be within 0-5, which in theory should smooth the picture. You can come up with a more optimal encoding.