A step-by-step analysis of a new version of Darkside Ransomware (v. 2.1.2.3)

Summary

Darkside ransomware is the malware family responsible for the Colonial Pipeline attack on May 7 2021 as described at https://www.zdnet.com/article/darkside-the-ransomware-group-responsible-for-colonial-pipeline-cyberattack-explained/. The binary contains an encrypted configuration that will be decrypted using a custom algorithm, which reveals a 22-byte buffer that describes different actions performed by the malware. These actions include: checking the system language and avoiding to encrypt Russian language machines, deleting Shadow copies, wiping Recycle Bin, ignore specific files, directories and file extensions, killing specific processes, deleting specific services, etc. The ransomware can perform privilege escalation using the CMSTPLUA COM interface and achieves persistence by installing itself as a service. The files are encrypted using the custom Salsa20 implementation, with the Salsa20 matrix being encrypted by the public RSA key hard-coded in the binary. Darkside uses multithreading with I/O completion ports to communicate between the main thread and the worker threads responsible for file encryptions. It’s important to mention that the process generates a random Salsa20 matrix using the RDRAND and RDSEED instructions, as opposed to earlier versions that use the RtlRandomEx function.

Analyst: @GeeksCyber

Technical analysis

SHA256: 0A0C225F0E5EE941A79F2B7701F1285E4975A2859EB4D025D96D9E366E81ABB9

The malware comes with an encrypted configuration that is decrypted using a custom algorithm:

Figure 1

The custom decryption algorithm consists of 4 subtraction operations by 0x10101010 each time and then some addition operations, as shown below:

Figure 2

For each DLL to be loaded, there is a hash function that is applied to the DLL name, and the 4-byte result is compared to hardcoded values:

Figure 3

For example, the following value corresponds to kernel32.dll:

Figure 4

The following DLLs are expected to be loaded: ntdll, kernel32, advapi32, user32, gdi32, ole32, oleaut32, shell32, shlwapi, wininet, netapi32, wtsapi32, activeds, userenv, mpr, rstrtmgr. The process retrieves the address of multiple export functions based on similar hash values computed using the same algorithm:

Figure 5

The decrypted configuration is presented below and is composed of the RSA-1024 exponent (0x010001 = 65537), 0x80-byte RSA-1024 modulus, victim UID, 22 configurations bytes (will be detailed further on) and the aPLib-compressed configuration:

Figure 6

The binary uses an aPLib-decompression algorithm to decrypt different strings. The following list represents the directories to avoid in the encryption process:

Figure 7

The following files will be ignored by the ransomware:

Figure 8

If the file’s extension belongs to the following list, then the file will not be encrypted by the process:

Figure 9

The binary intends to delete folders that contain the word “backup” in their name:

Figure 10

A feature not used by the malware would use the following strings decompressed as the other ones (our guess is that the actor would try to kill the SQL-related processes in order to encrypt databases):

Figure 11

The following processes will not be terminated by the file:

Figure 12

If a process name contains any of the following strings, it will be killed by the binary:

Figure 13

There is also a list of services to be stopped and deleted, as shown in the figure below:

Figure 14

The list of C2 servers is also obtained using the same algorithm:

Figure 15

The process reveals a message that will be utilized to set a custom wallpaper that contains important instructions for the victim:

Figure 16

The content of the ransom note is also written in the process memory, as shown in figure 17:

Figure 17

The following table describes the actions that the malware takes depending on the configuration decrypted above:

OffsetEnabledDescription
0x00YesFAST encryption mode
0x01YesUnknown (not used)
0x02NoAttempt to log on as a user on the machine
0x03YesEncrypt DRIVE_REMOVABLE, DRIVE_FIXED and DRIVE_REMOTE type of drives
0x04YesRetrieve the domain controllers and probably an attempt to spread further
0x05YesCheck system language and avoid the Russian language
0x06YesDelete volume shadow copies
0x07YesDelete files and folders from Recycle Bin
0x08NoSelf deletion
0x09YesIgnore specific directories
0x0aYesIgnore specific files
0x0bYesIgnore specific file extensions
0x0cYesWipe “backup” directories
0x0dYesUnknown (not used)
0x0eYesKill specific processes
0x0fYesStop and delete specific services
0x10YesSet Desktop wallpaper
0x11YesDrop ransom note
0x12YesChange icon of new encrypted files
0x13YesCreate a mutex
0x14YesUnknown (not used)
0x15YesCommunication with the C2 servers

The malware uses the NtQueryInstallUILanguage and NtQueryDefaultUILanguage APIs to determine the language of the system and compares the result with 0x419 (Russian language identifier). If there is a match between these two values, then the malware exits:

Figure 18

There is a call to the RegCreateKeyExW function, which is supposed to create (or open if it already exists) the “Software\Microsoft\Cryptography” registry key, as follows:

Figure 19

The malware extracts the “MachineGuid” value from the above registry key, as presented in the next figure:

Figure 20
Figure 21

A custom hashing algorithm that generates 8 lowercase hexadecimal characters is implemented by the process (the “MachineGuid” value is the input, and the algorithm applies 8 times):

Figure 22
Figure 23

The value computed above (let’s call it RansomPseudoValue) will be used in the following constructions:

  • Service name: <RansomPseudoValue>
  • Service display name: <RansomPseudoValue>
  • Ransom note: README<RansomPseudoValue>.TXT
  • Wallpaper: %PROGRAMDATA%\<RansomPseudoValue>.BMP
  • Each encrypted file will have the following name: <Original filename><RansomPseudoValue>
  • Icon file: %PROGRAMDATA%\<RansomPseudoValue>.ico
  • Registry key created: HKCR\<RansomPseudoValue>\DefaultIcon=%PROGRAMDATA%\<RansomPseudoValue>.ico

The binary uses the SHTestTokenMembership API to verify if the user belongs to the Administrators groups (0x220 = 544 in decimal):

Figure 24

We’ll split the analysis into 3 different parts depending on the user’s privileges: low level privileges, administrative privileges, and SYSTEM privileges.

Low Level privileges

The malware attempts a UAC bypass that uses the CMSTPLUA COM interface as described at https://gist.github.com/api0cradle/d4aaef39db0d845627d819b2b6b30512. It utilizes ZwOpenProcessToken to open the access token associated with the process (0x8 = TOKEN_QUERY – required to query an access token):

Figure 25

The NtQueryInformationToken function is used to get the group accounts associated with the token (0x2 = TokenGroups) and it checks if the administrators group can be found in the TOKEN_GROUPS structure:

Figure 26

There is a call to the CoInitialize routine in order to initialize the COM library on the current thread, as highlighted in figure 27:

Figure 27

As presented so far, the binary uses a lot of lower level APIs (from ntdll). It allocates a new memory area using the ZwAllocateVirtualMemory API (0x3000 = MEM_COMMIT | MEM_RESERVE and 0x4 = PAGE_READWRITE):

Figure 28

We have encountered a call to an undocumented API function called LdrEnumerateLoadedModules:

Figure 29

The file executes CoGetObject with the object name as Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}, as highlighted below:

Figure 30

Basically, it will relaunch the malware with SYSTEM privileges:

Figure 31
Figure 32

Administrative privileges

As in the first case, the binary uses ZwOpenProcessToken to open the access token associated with the process (0x8 = TOKEN_QUERY – required to query an access token):

Figure 33

The NtQueryInformationToken API is utilized to retrieve the token’s user account (0x1 = TokenUser):

Figure 34

The malicious process uses LookupAccountSidW to obtain the name of the account associated with the SID provided as the input, as shown in figure 35:

Figure 35

There are 3 different comparison operations that compare the domain name (the name of the computer in our case) with “NT AUTHORITY”, “AUTORITE NT” and “NT-AUTORITAT” (basically, it tries to determine if the user account is SYSTEM or not):

Figure 36

The OpenSCManagerW routine is utilized to establish a connection to the service control manager:

Figure 37

The process tries to open a service called <RansomPseudoValue> (which doesn’t exist at this time):

Figure 38

Because the service doesn’t exist, it will be created by the malware for persistence purposes, as shown in the following pictures:

Figure 39
Figure 40

The newly created service is started, and the binary launches itself as a service:

Figure 41

SYSTEM privileges

The malicious binary can run with no arguments, one, two, or three arguments (these cases will be described later on). As we can see below, it uses CommandLineToArgvW to obtain pointers to the command line arguments (argv[0] is the executable name) + the number of arguments:

Figure 42

The WTSQueryUserToken API is utilized to obtain the primary access token of the logged-on user specified by session 1:

Figure 43

OpenWindowStationW is used to open the “Winsta0” windows station (the interactive window station), 0x40000 – WRITE_DAC – modify the DACL in the security descriptor for the object:

Figure 44

The DACL (discretionary access control list) of the “Winsta0” windows station is modified by calling the NtSetSecurityObject routine with the 0x4 = DACL_SECURITY_INFORMATION parameter:

Figure 45

There is a call to OpenDesktopW that is utilized to open the “Default” desktop object with the argument 0x40081 = WRITE_DAC | DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS, as follows:

Figure 46

The DACL of the “Default” desktop object is modified by calling the NtSetSecurityObject function with the 0x4 = DACL_SECURITY_INFORMATION parameter:

Figure 47

The malware creates a mutex called “Global\4787658f1cc4202b8a15e05dd0323fde” (this value has been computed before this operation and represents a custom “hash” value of the malware), which makes sure that there is only one instance of the ransomware running at a time (if the mutex already exists, then the malware quits):

Figure 48
Figure 49

The ransomware forces the system not to enter sleep mode and not to turn off the display while the process is running, one of the parameters being 0x80000001 = ES_CONTINUOUS | ES_SYSTEM_REQUIRED:

Figure 50

The file changes the privilege to SE_PRIVILEGE_ENABLED in order to enable the token’s privileges (note the TOKEN_PRIVILEGES structure) by a function call to ZwAdjustPrivilegesToken:

Figure 51

The CreateThread API is used to create a new thread, as described in the next figure:

Figure 52

A list of valid drives on the system is extracted using the GetLogicalDriveStringsW routine:

Figure 53

The ransomware is looking for DRIVE_REMOVABLE (0x2) and DRIVE_FIXED (0x3) drives, as highlighted in figure 54:

Figure 54

All files and directories from Recycle Bin are deleted by the process. It starts to enumerate via a FindFirstFileExW API call:

Figure 55

As presented below, the files are deleted using the DeleteFileW function, and the directories are removed using the RemoveDirectoryW routine:

Figure 56

The binary uses COM objects and WMI commands to delete volume shadow copies. It calls the CoCreateInstance function to create a single object of the class IWbemLocator with the CLSID {dc12a687-737f-11cf-884d-00aa004b2e24} (Ref. https://forum.powerbasic.com/forum/user-to-user-discussions/source-code/25222-wmi-wrapper-functions):

Figure 57

There is also a new IWbemContext interface with the CLSID {44aca674-e8fc-11d0-a07c-00c04fb68820} (Ref. https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wmi/3485541f-6950-4e6d-98cb-1ed4bb143441) created via a CoCreateInstance function call:

Figure 58

Using the IWbemLocator object, the process calls the ConnectServer API to connect to the local “ROOT\CIMV2” namespace and retrieves a pointer to a IWbemServices object, as follows:

Figure 59

There is a call to CoSetProxyBlanket performed by the ransomware, as described in the next figure (0xA = RPC_C_AUTHN_WINNT – NTLMSSP, 0x3 = RPC_C_AUTHN_LEVEL_CALL and 0x3 = RPC_C_IMP_LEVEL_IMPERSONATE):

Figure 60

The process executes the following SQL query “SELECT * FROM Win32_ShadowCopy” to obtain an enumerator of all shadow copies, and then it deletes each of the shadow copy objects via the DeleteInstance method:

Figure 61

A list of all services and their status is retrieved by calling the EnumServicesStatusExW function (0x30 = SERVICE_WIN32, 0x3 = SERVICE_STATE_ALL):

Figure 62

Each service name is compared to the list that was decrypted at the beginning of the analysis:

Figure 63

The malware opens the targeted services by calling the OpenServiceW routine (0x10020 = DELETE | SERVICE_STOP):

Figure 64

Every targeted service is stopped and deleted using ControlService and DeleteService, as displayed in figure 65:

Figure 65

The NtQuerySystemInformation API returns an array of SYSTEM_PROCESS_INFORMATION structures (one for each process running on the system, 0x5 = SystemProcessInformation):

Figure 66

Each process name is compared to the list that was decrypted in the beginning, as displayed below:

Figure 67

For every targeted process, the binary opens the process and terminates it and all of its threads:

Figure 68

The binary creates an ico file called <RansomPseudoValue>.ico, as displayed below:

Figure 69

A new registry key called <RansomPseudoValue> is created using the RegCreateKeyExW function, as shown in figure 70:

Figure 70

The DefaultIcon subkey is created, and it specifies the path for the newly created ico file:

Figure 71

The malware calls the SHChangeNotify routine to notify the shell to update its icon cache (0x08000000 = SHCNE_ASSOCCHANGED, 0x1000 = SHCNF_FLUSH):

Figure 72

A new file called %PROGRAMDATA%\<RansomPseudoValue>.BMP is created using the CreateFileW function:

Figure 73

Moving forward, there is a registry key opened by calling the RegCreateKeyExW API, as shown in the next picture:

Figure 74

The “WallPaper” value inside the registry key is changed to the location of the newly created BMP file:

Figure 75

After all of these activities, the Desktop has been changed to the following image:

Figure 76

Thread activity – sub_4095AB

The thread starts by decrypting the following information:

Figure 77

The version of the Darkside ransomware is also decrypted and represents the latest version analyzed in the wild (2.1.2.3):

Figure 78

Another JSON structure is decrypted by the binary and will be used to collect data about the local machine:

Figure 79

One more time, the process checks the type of the drives and is looking for DRIVE_REMOVABLE (0x2), DRIVE_FIXED (0x3) and DRIVE_REMOTE (0x4):

Figure 80

The GetDiskFreeSpaceExW function is used to retrieve information about the targeted drives, such as the total amount of space and the total amount of free space:

Figure 81

NtDuplicateToken is utilized to duplicate an existing token and to obtain a handle to a new access token (0xC = TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY and 0x2 = TokenImpersonation):

Figure 82

The thread’s impersonation token is changed via a call to the ZwSetInformationThread routine, as shown in figure 83 (0x5 = ThreadImpersonationToken):

Figure 83

The ransomware retrieves the username associated with the current thread, as well as the NetBIOS name of the local machine:

Figure 84
Figure 85

The current language of the machine is retrieved from the “LocaleName” value, as presented below:

Figure 86

NetGetJoinInformation is used to get the join status information for the local computer:

Figure 87

The product name of Windows can be extracted by querying the “ProductName” value and the Windows product ID can be extracted by querying the “ProductId” value, as shown in the following pictures:

Figure 88
Figure 89

The malware constructs the following JSON, which contains data to be exfiltrated to the C2 server:

Figure 90

The final data looks like in the following JSON form:

Figure 91

The data from above is encrypted by a custom encryption algorithm:

Figure 92
Figure 93

The result of the encryption operation is base64-encoded, as shown below:

Figure 94
Figure 95

The following function is used to generate 2 random 4-byte values that will be utilized in the network communications. It uses instructions such as RDRAND and RDSEED to generate random numbers (if these are supported), but we’ll provide a deeper understanding of it when we discuss file encryption (it’s also used to generate the Salsa20 matrix):

Figure 96

The parameters of the network request have the following structure: random_number1=base64(encryptionresult)&random_number2=victim_uid:

Figure 97

The InternetOpenW function is called using a user agent decrypted by the malware as a parameter:

Figure 98

InternetConnectW is utilized to connect to one of the C2 servers (baroquetees[.]com) on port 443:

Figure 99

The process creates an HTTP request handle using the HttpOpenRequestW routine, as shown in figure 100:

Figure 100

There is also a call to the InternetSetOptionW API that is used to set the security flags for the handle (0x1f = INTERNET_OPTION_SECURITY_FLAGS):

Figure 101

The binary sends the POST request to the C2 server using HttpSendRequestW:

Figure 102
Figure 103

The status code returned by the server is retrieved using the HttpQueryInfoW API (0x13 = HTTP_QUERY_STATUS_CODE):

Figure 104

Interestingly, the ransomware doesn’t expect a 200 status code but a 500 (Internal Server Error). If the status code isn’t 500, then the process repeats the steps described so far using the second C2 server, rumahsia[.]com:

Figure 105
Figure 106

This last idea concludes our analysis of this thread. We continue to analyze the main thread.

The binary enumerates the volumes available on the machine and uses the CreateFileW routine to open them:

Figure 107

DeviceIoControl is utilized to get information about the type, size, and nature of a disk partition (0x70048 = IOCTL_DISK_GET_PARTITION_INFO_EX):

Figure 108

A new thread is created by the file using CreateThread:

Figure 109

Thread activity – sub_407558

The only action the thread does is using the GetLogicalDriveStringsW API to retrieve the valid drives on the local machine:

Figure 110

If a volume doesn’t have a drive letter associated with it, then the ransomware does that using the SetVolumeMountPointW API, as highlighted in the following picture:

Figure 111

The malicious process targets the following types of drives – DRIVE_REMOVABLE (0x2), DRIVE_FIXED (0x3) and DRIVE_REMOTE (0x4):

Figure 112

The CreateFileMappingW function is used to create a named file mapping object (name “Local\\job0-<Process Id>” means the object is created in the session namespace):

Figure 113

The binary maps a view of the file mapping into the address space of the process by calling the MapViewOfFile routine (0xf001f = FILE_MAP_ALL_ACCESS):

Figure 114

A named event object called “Local\\job0-<Process Id>-Event” is created by the binary:

Figure 115

The ransomware launches itself with 3 parameters, and the new process will execute the encryption operations:

Figure 116

OpenMutexW is utilized to open a named mutex called “Global\\T-job0-<Process Id>” (which doesn’t exist at this time) – 0x100000 = SYNCHRONIZE:

Figure 117

The event object created earlier is opened by calling the OpenEventW API (0x1f0003 = EVENT_ALL_ACCESS), as displayed in figure 118:

Figure 118

The file creates an I/O completion port that isn’t associated with a file handle, which will be used by the main thread to send data that will be encrypted to worker threads:

Figure 119

Two different threads that will take care of the files’ encryption are created using the CreateThread routine:

Figure 120

The ransom note README<RansomPseudoValue>.TXT is created and populated in every directory the malware encrypts:

Figure 121

The process doesn’t encrypt some certain files, as displayed in the next figure:

Figure 122

A list of file extensions decrypted at the beginning of the execution is also excluded from the encryption process:

Figure 123

Every targeted file is opened and read using the CreateFileW and ReadFile functions:

Figure 124
Figure 125

The file extension is changed to also include <RansomPseudoValue>, as shown below:

Figure 126

There is a second function call to CreateIoCompletionPort that associates the existing I/O completion port with the FileHandle parameter:

Figure 127

The RSA public exponent and the RSA modulus will be used in the encryption process of the Salsa20 matrix, as we’ll describe later on:

Figure 128

The ransomware checks to see if the RDRAND and RDSEED instructions are supported by the processor. If that’s the case, it will use one of them to generate 56 random bytes, and 8 NULL bytes are added to the resulting buffer (Salsa20 matrix -> custom Salsa20 implementation). If none of these are supported, the malware uses the rdtsc instruction to generate deterministic timestamps that will provide a 64-byte Salsa20 matrix:

Figure 129
Figure 130

The thread poses a custom implementation of the RSA-1024 algorithm (it doesn’t rely on Windows APIs). Basically, the data d will produce a ciphertext = (d^exponent)%modulus. The raw modulus calculation is performed using addition and subtraction and part of the implementation is presented in the following figures:

Figure 131
Figure 132

The Salsa20 matrix is encrypted using the custom RSA implementation, as shown in figure 133:

Figure 133

There is a custom “hash” function applied to the above encryption result, which produces a 16-byte output:

Figure 134

The file content that will be encrypted is appended to the buffer that will be sent to the worker threads:

Figure 135

The Salsa20 matrix is also added to the buffer, and it will be utilized by the worker threads to encrypt the files:

Figure 136

Thread activity – sub_405E7C (File encryption)

The file content is encrypted using a custom Salsa20 implementation and the ciphertext overwrites the plaintext in the buffer:

Figure 137

A snippet of the custom implementation is presented below:

Figure 138

The encrypted content is written to the initial file, followed by the encrypted Salsa20 matrix and the hash value, as displayed in the following figures:

Figure 139
Figure 140

This last idea concludes our analysis of this thread. We continue to analyze the main thread.

If the current directory contains “backup”, then the malware deletes it:

Figure 141

The main thread sends the buffer described above (which includes file content to be encrypted etc.) to the worker threads by calling the PostQueuedCompletionStatus routine:

Figure 142

We’ve also identified a function that we believe it’s used to propagate the malware to domain controllers (we didn’t have one in our environment). It calls functions such as DsGetDcNameW, DsGetDcOpenW and DsGetDcNextW:

Figure 143

Darkside enumerates all network shares using the NetShareEnum API and encrypts each one of them by the main encryption routine described so far:

Figure 144

Thread activity – sub_4096A4

The following JSON is decrypted by the thread:

Figure 145

The file opens the following registry key by calling RegCreateKeyExW:

Figure 146

The Product ID is retrieved again by calling the RegQueryValueExW function:

Figure 147

The machine GUID is extracted from the registry and represents a unique identifier for the machine:

Figure 148
Figure 149

After the encryption finishes, the malware sends encryption statistics to the C2 server, such as: victim ID, uid, number of encrypted files, size of encrypted files, number of skipped files and elapsed time. The final JSON structure looks like the following:

Figure 150

As already described so far regarding the C2 communication, the buffer is encrypted with a custom algorithm and base64-encoded. The request sent to the C2 server is presented in the next picture:

Figure 151

If the self deletion feature would be enabled, Darkside would delete itself using ShellExecuteW:

Figure 152
Figure 153

As we specified at the beginning of the analysis, the binary can run with different parameters:

  • 1 parameter: filename – only this file will be encrypted
  • 2 parameters: “-path” directory – only this directory will be encrypted
  • 3 parameters: “-work” worker0 job0-<Process Id> – this is spawned by the initial process, already described

A particular case is handled by the ransomware differently when it deals with a shortcut file (.lnk file). Basically, the binary wants to extract the full path to the file from this link. It calls the CoCreateInstance API with the CLSID of {000214F9-0000-0000-C000-000000000046} (IShellLinkW interface):

Figure 154

Unfortunately, Scylla didn’t help us here and it couldn’t provide us the methods. We’ve found that the next 2 function calls are used to extract the path of the file/directory:

Figure 155
Figure 156

The file extracted above is encrypted as usual:

Figure 157

References

MSDN: https://docs.microsoft.com/en-us/windows/win32/api/

Fakenet: https://github.com/fireeye/flare-fakenet-ng

Any.run: https://any.run/report/0a0c225f0e5ee941a79f2b7701f1285e4975a2859eb4d025d96d9e366e81abb9/e7a712f5-961a-45b4-a7e5-a0f7196113a5

VirusTotal: https://www.virustotal.com/gui/file/0a0c225f0e5ee941a79f2b7701f1285e4975a2859eb4d025d96d9e366e81abb9/detection

Analysis of Darkside Ransomware v1.8.6.2: https://chuongdong.com/reverse%20engineering/2021/05/06/DarksideRansomware/

Fireeye report: https://www.fireeye.com/blog/threat-research/2021/05/shining-a-light-on-darkside-ransomware-operations.html

https://gist.github.com/api0cradle/d4aaef39db0d845627d819b2b6b30512

https://forum.powerbasic.com/forum/user-to-user-discussions/source-code/25222-wmi-wrapper-functions

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wmi/3485541f-6950-4e6d-98cb-1ed4bb143441

INDICATORS OF COMPROMISE

C2 domains: baroquetees[.]com, rumahsia[.]com

SHA256: 0A0C225F0E5EE941A79F2B7701F1285E4975A2859EB4D025D96D9E366E81ABB9

Created files: README<RansomPseudoValue>.TXT, %PROGRAMDATA%\<RansomPseudoValue>.BMP, %PROGRAMDATA%\<RansomPseudoValue>.ico

Service Name: <RansomPseudoValue>, Service display name: <RansomPseudoValue>

Registry key: HKCR\<RansomPseudoValue>\DefaultIcon=%PROGRAMDATA%\<RansomPseudoValue>.ico

User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:79.0) Gecko/20100101 Firefox/80.0 (prone to False Positives)

4.6 11 votes
Article Rating
4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Lee

This was a great in-depth article and very thorough! Best one I have read so far.

Murrij

Thank you for this. Awesome. Learned and will be learning tons from this.

Henry

So either we should install an Anti Virus or add Russian as an additional language to our keyboard languages 🤣

Johann Smith

Well put together, will be rereading this quite a bit. Thanks for sharing.