Skip to content

Latest commit

 

History

History
155 lines (102 loc) · 10.2 KB

0xe - njRAT.md

File metadata and controls

155 lines (102 loc) · 10.2 KB

njRAT

Background

  • ZScaler reports that njRat was first observed in the wild in 2013 and was prevalent for many years. Written in .NET, it contains a lot of standard RAT functionalities:
    • Loading other malware
    • Stealing private cryptocurrency keys
    • Keylogging, password stealing
    • Self-propagation, and more

References

ZScaler - njRAT pushes Lime ransomware and Bitcoin wallet stealer

BlackBerry - Threat Thursday: Don't Let njRAT Take Your Cheddar

Analysis

Hash Type File Hash
MD5 ba17472a8155ff38b3b6fa9a17f5aa70
SHA1 fd81eb5f702b8c0d8a790bf6cced05f3d2b4ae7e
SHA-256 ed04bf92f6fdea80a5580a3dc115ab38332ad30418d4611f8942e3f8f18b45fb

Analysis of the Main Function

  • The main() function starts by immediately performing a sleep for 3 seconds before it proceeds to initializes the configuration.
    • If the call to Settings.InitializeSettings fails, the main() function exits with code 0.

Pasted image 20241113183750

  • The Settings.InitializeSettings method uses simple Base64 decoding followed by AES decryption to de-obfuscate config settings.

Pasted image 20241113183912

  • After the configuration is initialized, the sample proceeds to call methods under its Mutex class, invoking the CreateMutexHandle method. This method generates a call to the Windows API CreateMutexHandle, which will return a handle to the newly created mutex object.

Pasted image 20241113184410

  • Next, from the main() function, there is a sequence of calls to methods under the Settings class. At this point, the Settings class clearly contains a lot of the initialization functionality.

Pasted image 20241113184743

  • A call is made to a less commonly seen Windows API, InternetGetConnectedState, which returns a BOOL value -- TRUE if there is an internet connection, otherwise FALSE.
  • If no internet connectivity is found, the main() function performs an indefinite sleep. The while loop with an empty catch block ensures that this logic will simply repeat every 5 minutes until the host is online.

Pasted image 20241113185430

  • Once internet connectivity is established, execution proceeds to the Installation.Install method, which creates a file named $77helppanel.exe under some type of "ServiceProfiles" folder under a "Templates" directory.
  • Next, the sample performs a basic success check and then proceeds to set the desired access rights for the file path. In particular, it denies the current user READ access, making the user much less likely to notice the presence of the executable.
  • Next, it uses process.Kill() to kill other instances of itself. After some more hiding of file system and process artifacts, it establishes persistence via a Registry Run key.

Pasted image 20241113190429

  • The specimen checks the domain group of the current user to determine if they are an admin user (member of the Administrators built-in role).
  • If they are, the ASEP key is set under the HKLM hive (effective for all users). If they are not, the ASEP key is simply set under the current user profile (HKCU).

Pasted image 20241113191204

  • Some pre-launch commands are run to start the main .exe and delete the temporary batch file from disk.

  • The AddException method is called next, which creates a Microsoft Defender exclusion for all exe, bat, dll, and .ps1 files in a hidden window.

Pasted image 20241114125632

  • Returning back to the main() function, a scheduled task is created as an additional persistence mechanism.

Pasted image 20241113192002

  • Once the local initialization on the endpoint is complete and persistence has been established, njRAT proceeds to the MessageRead.DiscordNotif method where it uploads basic host and user data to a Discord channel with a notification message to the operator.

Pasted image 20241114125842

  • After returning to the main() function, it calls PreventSleep which uses the windows API SetThreadExecutionState with the appropriate flags to prevent the system from sleeping.

Pasted image 20241114132837

  • At the end of the main() function, njRAT invokes an infinite loop with for (;;). This loop ensures that the client is online and perpetually connected with a built-in reconnect delay for redundancy.

Pasted image 20241114133027

A Closer Look at the Config

  • As we saw before, the config data is base64 encoded and encrypted with AES-256. In order to obtain the plaintext, we'll simply have to re-create that process in our own controlled environment.

Pasted image 20241114134851

  • The decryption method used by the malware author works like this:

    • Takes an array of encrypted bytes as a parameter (cipherBytes) and prepares an empty array into which it will output the decrypted bytes.
    • Creates an AES cryptographic object with Aes.Create()
    • Uses a pre-written password and salt to generate a key and IV using PBKDF2 (Password-Based Key Derivation Function 2).
    • Extracts 32 bytes for the AES encryption key (AES-256 requires a 256-bit key, which is 32 bytes).
    • Extracts 16 bytes for the initialization vector (IV) (AES in CBC mode uses a 128-bit IV, which is 16 bytes).
    • Creates a MemoryStream object to temporarily store the decrypted data in memory.
    • Creates a CryptoStream object, which is used to perform the AES decryption transform.
    • The encrypted data is then written to the CryptoStream object, decrypted, and the output is finally passed to the byte array.
  • We can accomplish the same exact thing with a quick Python script. We'll have to use the AES class from the Crypto.Cipher module and the PBKDF2 class from the Crypto.Protocol.KDF module.

  • Once we import those, we'll have some useful functions. If you're lazy, I have included the script below so you can simply copy and paste.

from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
import base64
import io

def decrypt_bytes(cipher_bytes):
    # Password and salt from original function written by TA
    password = b"0x49,0x76,0x61,10,0x20,0x4D,0x65,100,0x76,0x65,100,0x65,0x76"
    salt = bytes([73, 118, 97, 110, 32, 77, 101, 100, 118, 101, 100, 101, 118])

    # Derive the key (AES-256) and IV
    key = PBKDF2(password, salt, dkLen=32, count=1000) 
    iv = PBKDF2(password, salt, dkLen=16, count=1000) 

    # Initialize AES decryptor
    aes = AES.new(key, AES.MODE_CBC, iv)

    # Decrypt the data
    with io.BytesIO() as memory_stream:
        try:
            decrypted_data = aes.decrypt(cipher_bytes)
            memory_stream.write(decrypted_data)
            return memory_stream.getvalue()
        except Exception as e:
            print(f"Decryption error: {e}")
            return memory_stream.getvalue()

base64_ciphertext = "ENCRYPTED_CONFIG_TEXT_GOES_HERE"  

ciphertext = base64.b64decode(base64_ciphertext)

plaintext = decrypt_bytes(ciphertext)
print("Decrypted plaintext:", plaintext.decode('utf-8', errors='ignore')) 
  • The output from the script appears like this:

Pasted image 20241114134813

  • Using the script as well as our findings from the analysis phase, we are left with some IOCs that we can incorporate into our detection logic.

IOCs

IOC Description
SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\$77helppanel.exe ASEP key for persistence
Creation of a scheduled task named $77helppanel.exe Scheduled task for persistence
Creation of %TEMP%\*.bat Batch file used for pre-launch commands.
Network callouts to *discord.com/api/webhooks/1306009594367180840/Zg6W2rH_yPNkl7Hn5Z-GWjtm8W94xN_PzceHo8g5RjjoNr4vkRdq1c70arvb91az-VPT Discord webhook URL
HTTP GET requests for *h53p.io/p_2522c7w8u1.png The URI of the logo imported in the config