Detecting njRAT/Bladabindi Malware With Zeek – Zeek Roulette #1

Welcome to the first edition of Zeek Roulette, where I pick a random Zeek topic and try to solve it!

For this article I picked njRAT malware from Any.Run and tried to write a detector for it. There is a copy of the njRAT malware, PCAP, and its analysis available here:

If you download the PCAP file and run it through Zeek, you will see the following line in conn.log:

1681921215.277831	CF6WFfOHjuxFz6wtc	49228	15145	tcp	-	41.031513	1268	10585	S1	-	-	0	ShADaTdt	12	1999	10	5280	-

This is where the malware’s C2 happens.

First, we need to understand njRAT’s C2 protocol. After a little Googling I found this article and Suricata rule set:


Discovered almost a decade ago, njRAT, also known as Bladabindi, is the most active and prevalent remote access trojan. It allows attackers to do surveillance and control the victim’s computer. Its features include remote desktop, logging keystrokes, stealing credentials, capturing microphone and webcam, and many more. njRAT is mostly found to be delivered via phishing email campaigns containing malicious Word document attachments. It is also found to be delivered by masquerading as a legitimate application installer uploaded to file-sharing services and luring victims via drive-by download campaigns.

Since the leak of source code 2013, njRAT has become widely adopted by cybercriminals and APT actors including Gorgon Group and APT41. Numerous variants have been detected over the years. Some variants have been found to be communicating over standard HTTP protocol and others were found to be communicating over custom protocols over TCP. The packet begins with data length in a decimal format null-terminated string followed by command and then delimiter followed by exfiltrated data.

alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:”Zscaler Win32.Backdoor.NjRat – Data Exfil activity”; flow:to_server,established; content:”|00|inf”; offset:3; depth:4; pcre:”/\d{1,3}\x00\w{1,3}/”; pcre:”/(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?/”; flowbits:isset,ZS.njrat; flowbits:unset,ZS.njrat; classtype:trojan-activity; reference:url,;)

alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:”Zscaler Win32.Backdoor.NjRat – Data Exfil activity”; flow:to_server,established; content:”|00|ll”; offset:3; depth:3; pcre:”/^\d{1,3}\x00/”; pcre:”/(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?/”; flowbits:set,ZS.njrat; flowbits:noalert; classtype:trojan-activity; reference:url,;)

We can use the general information from the article quoted above to write Zeek detection logic too.

First, we know that each message fits a known format of message length (in ASCII, only counting characters coming after the NULL), a NULL character, a command (in ASCII), and then a delimiter. After the delimiter comes the remaining data, also delimited.

In the PCAP for this malware sample, the first message example is (<NULL> is literally 0x00 in this case):

156<NULL>ll|'|'|SGFjS2VkX0M0QkEzNjQ3|'|'|USER-PC|'|'|admin|'|'|23-04-19|'|'||'|'|Win 7 Professional SP1 x86|'|'|No|'|'|im523|'|'|..|'|'|UHJvZ3JhbSBNYW5hZ2VyAA==|'|'|152.inf|'|'|SGFjS2VkDQo3LnRjcC5ldS5uZ3Jvay5pbzoxNTE0NQ0KQWxsVXNlcnNQcm9maWxlDQpTeXN0ZW0uZXhlDQpUcnVlDQpUcnVlDQpUcnVlDQpUcnVlDQpUcnVlDQpUcnVlDQpUcnVlDQpUcnVl32.act|'|'|UHJvZ3JhbSBNYW5hZ2VyAA==

Here, the delimiter is:


This delimiter can change, so we will design our detector to be delimiter indifferent!

We can write a Spicy protocol analyzer to detect this type of C2. First, we use the following dynamic protocol detection (DPD) signature to trigger our Spicy njRAT C2 protocol analyzer with the message format specified above:

signature dpd_njrat {
    ip-proto == tcp
    payload /^[0-9]+\x00[a-zA-Z]+\|/
    enable "spicy_NJRAT"

The Spicy code for this analyzer is pretty simple and follows:

module NJRAT;

function bytes2uint(input: bytes) : uint64 {
    local exp: uint64 = |input|;
    local sum: uint64 = 0;
    local val: uint64;
    local shift: uint64;

    for (c in input)
        val = c-48;
        shift = 10**exp;
        sum = sum + ( val * shift );
    return sum;

public type njRATMessages = unit {
    messages: njRATMessage[];

public type njRATMessage = unit {
    len: /[0-9]+/ &convert=bytes2uint($$);
    : /\x00/;
    payload: bytes &size=self.len;

Any time a njRATMessage is parsed, the Spicy EVT file specifies that the following event fires:

event NJRAT::message(c: connection, is_orig: bool, payload: string)

This event is handled in main.zeek to create an njrat.log entry where each line represents a command.

After installing this package, the connection log will now look like the following (note the service of spicy_njrat!):

1681921215.277831	CqlVyW1YwZ15RhTBc4	49228	15145	tcp	spicy_njrat	40.956665	1268	10585	S1	-	-	0	ShADaTdt	10	1895	8	2788	-

And the njrat.log will look like the following:

#separator \x09
#set_separator	,
#empty_field	(empty)
#unset_field	-
#path	njrat
#open	2023-04-20-12-16-33
#fields	ts	uid	id.orig_h	id.orig_p	id.resp_h	id.resp_p	is_orig	payload
#types	time	string	addr	port	addr	port	bool	string
1681921215.595297	CqlVyW1YwZ15RhTBc4	49228	15145	T	ll|'|'|SGFjS2VkX0M0QkEzNjQ3|'|'|USER-PC|'|'|admin|'|'|23-04-19|'|'||'|'|Win 7 Professional SP1 x86|'|'|No|'|'|im523|'|'|..|'|'|UHJvZ3JhbSBNYW5hZ2VyAA==|'|'|
1681921215.674016	CqlVyW1YwZ15RhTBc4	49228	15145	T	inf|'|'|SGFjS2VkDQo3LnRjcC5ldS5uZ3Jvay5pbzoxNTE0NQ0KQWxsVXNlcnNQcm9maWxlDQpTeXN0ZW0uZXhlDQpUcnVlDQpUcnVlDQpUcnVlDQpUcnVlDQpUcnVlDQpUcnVlDQpUcnVlDQpUcnVl
1681921221.103591	CqlVyW1YwZ15RhTBc4	49228	15145	T	act|'|'|UHJvZ3JhbSBNYW5hZ2VyAA==
1681921256.102097	CqlVyW1YwZ15RhTBc4	49228	15145	F	CAP|'|'|35|'|'|23
1681921256.170255	CqlVyW1YwZ15RhTBc4	49228	15145	T	CAP|'|'|\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00\xff\xdb\x00C\x00\x08\x06\x06\x07\x06\x05\x08\x07\x07\x07\x09\x09\x08\x0a\x0c\x14\x0d\x0c\x0b\x0b\x0c\x19\x12\x13\x0f\x14\x1d\x1a\x1f\x1e\x1d\x1a\x1c\x1c $.' ",#\x1c\x1c(7),01444\x1f'9=82<.342\xff\xdb\x00C\x01\x09\x09\x09\x0c\x0b\x0c\x18\x0d\x0d\x182!\x1c!22222222222222222222222222222222222222222222222222\xff\xc0\x00\x11\x08\x00\x17\x00#\x03\x01"\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\xff\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05\x04\x04\x00\x00\x01}\x01\x02\x03\x00\x04\x11\x05\x12!1A\x06\x13Qa\x07"q\x142\x81\x91\xa1\x08#B\xb1\xc1\x15R\xd1\xf0$3br\x82\x09\x0a\x16\x17\x18\x19\x1a%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xff\xc4\x00\x1f\x01\x00\x03\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\xff\xc4\x00\xb5\x11\x00\x02\x01\x02\x04\x04\x03\x04\x07\x05\x04\x04\x00\x01\x02w\x00\x01\x02\x03\x11\x04\x05!1\x06\x12AQ\x07aq\x13"2\x81\x08\x14B\x91\xa1\xb1\xc1\x09#3R\xf0\x15br\xd1\x0a\x16$4\xe1%\xf1\x17\x18\x19\x1a&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\xe6\xff\x00\xb2\xa6\x92(\x8b\x82T\x96\x00\x95\xc7Lf\xae&\x84\xdb3\xb0\xfeU\xd9hZ\x02\xdc\x04|\x82=3\x9cV\xbe\xa5j\x96\x0d\x06\xd8\xf7\x01"\xee\xda\xbb\xb0;\x9c}\x05}\\\xb1<\xab\x92:\xc8\xf8\xceyN\xa3\x93\xd27\xe9\xfa\x1eYu\xa5\x98\xf2\x0a\xe0\xd6\x1d\xcd\xb1BH\x15\xec\xde2[W\xd2\xed\xe6\x8d 2\xce\xc5\xfc\xc5\xe1\xca\xf6\x0c1\xc7\x18\xaf/\xbe\x88s\xc5V\x06\xb3\xc5\xd0\xf6\x8d%\xabZ;\xad7\xfcM\xaa?c[\x91;\xaf\xf39\xfd\xc4q\x9a*f\x8cn4Q\xec\x8e\x9es\xdf|>\xb3Z\xda\x85\x9a\x12\x1d\xba`\x8cc\xb53Q\x82\xfavm\x90n\xcf\xfbc\xfch\xa2\xbe~8\xd9\xa9\xf3Y~?\xe6vK+\xa4\xe0\xa3\xcc\xff\x00\x0f\xf29\xeb\xad\x03U\x9f\xa5\xb8\x1e\x99\x91\x7f\xc6\xb0.\xfc%\xae8$X\xff\x00\xe4T\xff\x00\xe2\xa8\xa2\xbbVs]+Y~?\xe6D2z\x09\xde\xef\xf0\xff\x00#4\xf8'Y\xcf6\xea\x0fq\xe6/\x1f\xad\x14QS\xfd\xa7Y\xf4_\xd7\xcc\xdf\xea4\xbb\xb3\xff\xd9
#close	2023-04-20-12-16-33

At this point the commands in the payloads could be split with the following function:

But, since we don’t know what delimiter the attackers will use I chose to leave the full string in the log.

Fixing The PCAP

Hah hah, not so fast! Now you would think this code would work with the PCAP referenced throughout this article, wouldn’t you? Well the PCAP, as downloaded from Any.Run, cuts the C2 connection short and therefore the Spicy analyzer will not detect it as njRAT. I fixed the PCAP by removing the last fragment of the C2 communications and saved it here:

njRAT IOCs And Zeek’s Intelligence Framework

We also see that Any.Run has some IOCs listed for this sample:

SHA256:  3f1a2a27304c02ea6e56bfd81b0bfc4cf8db5040c23f854d09b6728b1803a8b9



While not as robust as our Spicy njRAT C2 analyzer, we can add IOCs to Zeek’s intelligence framework with the following function:

The code I used was:

	local intel_item = [$indicator="", $indicator_type=Intel::DOMAIN, $meta=[$source="njRAT", $url=""]];

	intel_item = [$indicator="", $indicator_type=Intel::ADDR, $meta=[$source="njRAT", $url=""]];

	intel_item = [$indicator="3f1a2a27304c02ea6e56bfd81b0bfc4cf8db5040c23f854d09b6728b1803a8b9", $indicator_type=Intel::FILE_HASH, $meta=[$source="njRAT", $url=""]];

And we load the intelligence framework with the following load commands:

@load frameworks/intel/seen
@load base/frameworks/intel/files.zeek

Now, the intel.log for this PCAP looks like the following:

#separator \x09
#set_separator	,
#empty_field	(empty)
#unset_field	-
#path	intel
#open	2023-04-20-12-44-15
#fields	ts	uid	id.orig_h	id.orig_p	id.resp_h	id.resp_p	seen.indicator	seen.indicator_type	seen.where	seen.node	matched	sources	fuid	file_mime_type	file_desc
#types	time	string	addr	port	addr	port	string	enum	enum	string	set[enum]	set[string]	string	string	string
1681921215.261590	C1Xkzz2MaGtLrc1Tla	52145	53	Intel::DOMAIN	DNS::IN_REQUEST	zeek	Intel::DOMAIN	njRAT	-	-	-
1681921215.341329	CqlVyW1YwZ15RhTBc4	49228	15145	Intel::ADDR	Conn::IN_RESP	zeek	Intel::ADDR	njRAT	-	-	-
#close	2023-04-20-12-44-15


Note this will work for RAT variants, like KilerRAT too:

KilerRAT uses the following delimiter instead:


Since the delimiter was not hard coded into our detector, we will still detect this variant.

The Source Code

You can install or see the full source code of this package from:

You can install this package with:

zkg install

More Info

Here is a good link with more info:

You can read an update here: njRAT/Bladabindi Zeek Detector Update – Zeek Roulette #1 Part 2

If none of this made sense to you, check out my Zeek videos over at YouTube to learn more of the technology in this article.

One response to “Detecting njRAT/Bladabindi Malware With Zeek – Zeek Roulette #1”

Leave a Reply

Your email address will not be published. Required fields are marked *