Corelight Bright Ideas Blog: NDR & Threat Hunting Blog

Detecting Gozi Banking Malware

Written by Keith J. Jones | Sep 7, 2023 11:02:01 PM

As a principal security researcher on Corelight’s Labs team, I help to solve difficult network security research problems at scale. Corelight’s customers might recognize some of my work if you see the packages “VPN Insights” or “App ID” on your sensors. Outside of my day-to-day role, I have a hobby podcast called eCrimeBytes where we lightheartedly discuss an electronic crime case each week.

In one of my recent episodes, I was discussing how the Gozi banking malware group was finally sentenced to prison. So naturally I wondered if I could detect the Gozi banking malware with Zeek. Not familiar with Zeek logs? You can get a sense for how Zeek works and how it is the foundation for Corelight’s network evidence here. You can also find out more on the Zeek Project website.

During my research, I found this sample of the Gozi banking malware in the wild here:
https://malware-traffic-analysis.net/2023/07/12/index.html

You can download a PCAP of the infection from this link too. This is the same PCAP I used to develop this detection logic in Zeek.

According to the notes from Malware Traffic Analysis, the malware C2 information is summarized by:

GOZI/ISFB C2 TRAFFIC:

- 151.248.117.244 port 80 - diwdjndsfnj.ru - GET /uploaded/[long base64 string with backslashes and underscores].pct
- 151.248.117.244 port 80 - diwdjndsfnj.ru - POST /uploaded/[long base64 string with backslashes and underscores].dib
- 151.248.117.244 port 80 - diwdjndsfnj.ru - GET /uploaded/[long base64 string with backslashes and underscores].pmg
- 151.248.117.244 port 80 - iwqdndomdn.su - GET /uploaded/[long base64 string with backslashes and underscores].pmg
- 151.248.117.244 port 80 - iwqdndomdn.su - POST /uploaded/[long base64 string with backslashes and underscores].dib

GOZI/ISFB MODULES (ENCRYPTED DATA BINARIES):

- 91.199.147.95 port 80 - 91.199.147.95 - GET /vnc32.rar
- 91.199.147.95 port 80 - 91.199.147.95 - GET /vnc64.rar
- 91.199.147.95 port 80 - 91.199.147.95 - GET /stilak32.rar
- 91.199.147.95 port 80 - 91.199.147.95 - GET /stilak64.rar
- 91.199.147.95 port 80 - 91.199.147.95 - GET /cook32.rar
- 91.199.147.95 port 80 - 91.199.147.95 - GET /cook64.rar

 

At first I thought it would be too simple to detect this malware family through the RAR files it downloads, but after searching through several months of operational traffic from our Polaris Program I found these RAR files to be unique to this malware! I chose to include these file names in my detection methodology by looking for the regular expression: /\/(stilak|cook|vnc)(32|64)\.rar$/

The other detection methodology is to look for long URLs that are base64 encoded in the unique manner Gozi uses. First, Gozi uses a real word for the first URL subdirectory. That real word is "uploaded" in the sample above, but I've seen other words used here. This word is unimportant to the malware and can be ignored for our purposes.

Then, Gozi will base64 encode the encrypted C2 data and add several random forward slashes to make it look like a URL. Gozi will eventually remove these slashes when it decodes this C2 string.

In addition, Gozi encodes the base64 "+", "/", "\n", and "\r" characters as "_2B", "_2F", "_0A", and "_0D". Putting all of that information together leads us to a regular expression of: /^\/\w+\/([a-zA-Z0-9\/]|_\/?2\/?F|_\/?2\/?B|_\/?0\/?A|_\/?0\/?D){200,}\.[a-zA-Z0-9]+$/

However, I saw some rare collisions with the prior regular expression that look like false positives on a customer network. Therefore, I only alert on URLs that have at least 10 forward slashes. When I added this condition to my filter, the false positive collisions went away. The full Unix find command combining both detection methodologies follows (note, use gawk on MacOS):


find /logs -name "http*" | parallel -j 10 zcat {} :::: - | zeek-cut host uri | awk -F '\t' '$2 ~ /\/(stilak|cook|vnc)(32|64)\.rar$/ || ($2 ~ /^\/\w+\/([a-zA-Z0-9\/]|_\/?2\/?F|_\/?2\/?B|_\/?0\/?A|_\/?0\/?D){200,}\.[a-zA-Z0-9]+$/ && gsub(/\//, "/", $2) > 10)'

 

You will see this regular expression detects 94 lines in the Zeek http.log file for our PCAP sample above:


$ cat http.log | zeek-cut host uri | gawk -F '\t' '$2 ~ /\/(stilak|cook|vnc)(32|64)\.rar$/ || ($2 ~ /^\/\w+\/([a-zA-Z0-9\/]|_\/?2\/?F|_\/?2\/?B|_\/?0\/?A|_\/?0\/?D){200,}\.[a-zA-Z0-9]+$/ && gsub(/\//, "/", $2) > 10)' | wc -l
      94

 

We now need to move this logic into Zeek code so we can run it on a live sensor.

We will catch the URIs in Zeek's HTTP request event. The HTTP request event is documented at: https://docs.zeek.org/en/master/scripts/base/bif/plugins/Zeek_HTTP.events.bif.zeek.html#id-http_request

Translating the logic from the find command naturally leads us to the rest of the code here:


module GoziMalwareDetector;

export {
	## Log stream identifier.
	redef enum Log::ID += {
		LOG
	};

	## The notice when the C2 is observed.
	redef enum Notice::Type += {
		GoziActivity,
	};

	## Record type containing the column fields of the log.
	type Info: record {
		## Timestamp for when the activity happened.
		ts: time &log;
		## Unique ID for the connection.
		uid: string &log;
		## The connection's 4-tuple of endpoint addresses/ports.
		id: conn_id &log;
		## The Gozi C2 HTTP method.
		http_method: string &log &optional;
		## The Gozi C2 command, still encoded and encrypted.
		payload: string &log &optional;
	};

	## Default hook into Gozi logging.
	global log_gozi: event(rec: Info);

	## A default logging policy hook for the stream.
	global log_policy: Log::PolicyHook;
}

# Regex - make them globals so they are compiled only once!
global rar_regex = /.*\/(stilak|cook|vnc)(32|64)\.rar$/;
global b64_regex = /^\/[^[:blank:]]+\/([a-zA-Z0-9\/]|_\/?2\/?F|_\/?2\/?B|_\/?0\/?A|_\/?0\/?D){200,}\.[a-zA-Z0-9]+$/;

redef record connection += {
	gozi: Info &optional;
};

# Initialize logging state.
hook set_session(c: connection)
{
	if ( c?$gozi )
		return;

	c$gozi = Info($ts=network_time(), $uid=c$uid, $id=c$id);
}

function log_gozi_detected(c: connection)
{
	if ( ! c?$gozi )
		return;

	Log::write(GoziMalwareDetector::LOG, c$gozi);

	NOTICE([
	    $note=GoziMalwareDetector::GoziActivity,
	    $msg=fmt("Potential Gozi banking malware activity between source %s and dest %s with method %s and URI %s", c$id$orig_h, c$id$resp_h, c$gozi$http_method, c$gozi$payload),
	    $conn=c,
	    $identifier=cat(c$id$orig_h, c$id$resp_h)]);

	delete c$gozi;
}

event http_request(c: connection, method: string, original_URI: string,
    unescaped_URI: string, version: string)
{
	hook set_session(c);

	local uri: string = to_lower(unescaped_URI);

	# We use the entropy check below to throw out long "normal" URIs that might make it through our checks.
	# Since the underlying Gozi C2 data is encrypted, entropy should be higher than "normal".  I chose this threshold based upon empiracal tests.
	if ( uri == rar_regex || ( unescaped_URI == b64_regex && count_substr(unescaped_URI, "/") > 10 && find_entropy(unescaped_URI)$entropy > 4 ) ) {
		c$gozi$http_method = method;
		c$gozi$payload = unescaped_URI;
		log_gozi_detected(c);
		return;
	}
}

event zeek_init() &priority=5
{
	Log::create_stream(GoziMalwareDetector::LOG, [
	    $columns=Info,
	    $ev=log_gozi,
	    $path="gozi",
	    $policy=GoziMalwareDetector::log_policy]);
}

 

After running my initial logic on a network for a while, I saw a handful of false positives with very deep URLs with many subdirectories. Since Gozi C2 traffic is encrypted before it is base64 encoded, the entropy should be high on the C2 traffic. Therefore, I added an entropy test on those base64 string candidates, only allowing detections when the entropy is greater than 4 bits per character.

The next question becomes: How many of Gozi's variants will this logic detect?

Good question.

I found some of the below variants of Gozi in Any.run, and when I spot checked the PCAPs Gozi was indeed detected:

  • Ursnif
  • Snifula
  • ISFB
  • Dreambot
  • Papras
  • sniful

You can view, download and install the full source code via Corelight's repository here:

https://github.com/corelight/zeek-gozi-detector

Below are examples of Gozi detections in Zeek logs. These logs are also found in Readme.md in the link above.

gozi.log:


#separator \x09
#set_separator	,
#empty_field	(empty)
#unset_field	-
#path	gozi
#open	2023-07-24-19-00-13
#fields	ts	uid	id.orig_h	id.orig_p	id.resp_h	id.resp_p	
http_method	payload
#types	time	string	addr	port	addr	port	string	string
1689201023.169002	ClEkJM2Vm5giqnMf4h	10.7.12.121	49799	151.248.117.244	80 GET	
/uploaded/8jvrTb2D/c4CxLmogLgZQGC_2FQ_2B2b/Ma3ylmhq8i/MeT_2Fmtq1zDpHZZQ/2OknTIetuvPf/SqlzkcwbzWM/
aFx0b70stnXODu/WDQ2wUhiUaYRbirzPbAvc/2V_2Feb1BeDPQaU0/WZs_2FbMUKJ37c4/Gf5YYgB_2B8BS7mcYa/jECotoj7
R/7bH1bkdIXbwbqpU0Nryv/_2BfEwnTZ0On333QjdJ/fdFWYGQpQofXObilmWG0P_/2BS5YP7Tcj18X/cQoSxxb6/FYqDyT3s
va2N6amcI32HXsv/n6pffb_2FO/UxvFILD91uIk2oNQx/DEmgyRV_2FLi/3pF67pmCpVP/mFnn0G63A1Sv9N/05KeIqFG3zHY
uPPOU/1P.pct
1689201024.619787	ClEkJM2Vm5giqnMf4h	10.7.12.121	49799	151.248.117.244	80 GET 
/uploaded/IfjXSAQlkk/LWNR4tkuOMiXdy5H_/2F55TZ5Ulkmf/KZSJQNnlKNL/oXNw6o8qzjnHJh/42iAsB5AuQ8n_2FyMP9
YN/iAK5Z4rM1kiabXH3/Tbf6F39oZk8a8mY/9CVAQTHkAlTev6Vstr/ir_2FniSr/BvZGpW5mVB5nr9pTRisy/xPxWcp7jW1do
RbtDMhT/9qJZuk9sdFnLKTsj12Ald9/3B3TYfDuu18i7/7LNZfim3/7nt5T5HqNSKfJKcDB7vJpeR/BoPpPolfrA/Z_2F56SDX
pQJw4TYJ/45pcw80raC9d/yeDiXFQDftm/RIxazSR_2BPJ0F/wL6l8KasDC6CAzVNLSL_2/F535_2BDNH1/tcGkkEMCZrS/i.pct
1689201025.545122	ClEkJM2Vm5giqnMf4h	10.7.12.121	49799	151.248.117.244	80 GET 
/uploaded/MsY1SyBRpp3LKx93fMeITx/w33QoEW7tSfHh/8_2Fqcx1/mAOJzt3teF4AUCrc8IBMkbm/8laKdMc_2F/A0s_2B6
tfYEvyAcP_/2Bk_2Buvkjcq/80G4xrhnFAN/TCW76WZrbPDSMY/_2FhKbl7f8pmHGBET0Y_2/BvcMUzja7B7xdYYK/qwpg5fbr
Mu6AsJ8/Dejh0cXcFfYe8h75Yx/Fn3LZFhMy/29bQzFKZyZHoHgfyV1XJ/Lw1D14ehZUiPYfQxTWv/LlBNcxqMhthgso7PYl8t
Lq/JhO8H_2FKdEez/1uHkLyhp/SD1X5u0VHgJvzUkDhpUUhKb/Q6cyGyHP3A/UPnKN8bJKuY7qHpRC/r94GeIuEWP3a/G9Zo2.pct
1689201032.086164	C4J4Th3PJpwUYZZ6gc	10.7.12.121	49800	151.248.117.244	80 GET	
/uploaded/DjeJ0blPQ/_2BkfrDEoFQgD04wO2F7/Ojqqobto35jEVZ1IQyU/G7zu4_2BFUfhIMJcKkibbg/fjjRaEElICvmR/
e5DoJnsG/vsx3T8eOiuXp0AlWknwttvf/A_2FrNprrb/bHnMsv4916Q0BUf_2/B3XIECBmUK_2/FW3G5XPXaPV/ySf6P_2BIXQ
e7C/q0IvZNIlHZt2c8lCjnMGY/BP81zPWMMzAUn3VS/Y_2BCg7CLJsM0vz/MloZ0Th38yNZOadE6L/qwrs9PKza/13Lw9jqWnb
yh08rIXwcG/mMM0HdBwcj6NPi6_2FH/5qnQe2GM1T/ZuHEvxYT/j.pmg
1689201032.792223	CtPZjS20MLrsMUOJi2	10.7.12.121	49801	91.199.147.95	80	GET	/vnc32.rar
1689201033.823106	CtPZjS20MLrsMUOJi2	10.7.12.121	49801	91.199.147.95	80	GET	/vnc64.rar
1689201045.338855	C4J4Th3PJpwUYZZ6gc	10.7.12.121	49800	151.248.117.244	80	POST
/uploaded/3lFSQwjxUfg8HgrTtqSS/ZKopMRdt0Jtv6ehOunO/ppBgdtF3YUX5Co9W0vX2OQ/UNZWu2BHmWHLi/4Pta3IUW/h8
js8gl66mR4P51kp_2B1rV/l_2BDjFJoW/DhHjLJGFTtgPfY5qz/0jnqt8GbX_2F/M2R1QPMjPhA/1LDVR2FItKJXdJ/1qIKPwGy
BNh80d_2BqfF_/2F6OR9A8MzQ8A1Mu/qqkL0hlaa4U4qkx/TrAV_2BpGP_2B6FnQc/TZAppMESi/SnlO5AU6khpDGRiOveBi/R4
uVGlbArQZk3cDBamb/rekwL.dib
1689201092.103798	C37jN32gN3y3AZzyf6	10.7.12.121	

...	

/uploaded/VcSCrD_2F_2/BKyBAIxpStofT6/txC4Pni9EG_2BFbKqUx_2/Fw9s4Zg2fwdtvoMJ/Fr6UyR1uP9PRBN1/FQZ6hz6M
b_2Fs_2F49/_2BkvjXF7/w8MfT_2B32RaOM5ijT_2/BzGKFuqMa5OzlLI_2BT/A_2FGOoDi_2FP_2F8zb2Yr/pIb51vNHV25_2/B
GIXUCRW/oMLsPVA84EAhSj9fCXt0ilY/Cxf2YThLeu/NIEKMHtLXO5SEVrsR/EHwwPqidwENp/fDqtP7kFnSX/5o3VYS7mpe5E1c
/x6KP6J7oE0yDXxAJaeyUe/6JuH70X_2Fjs56xO/bGXUaoK1Jmw6y_2/FvqA1eXlmviDm7Rk39/4JHyQ0dF/t.pmg
#close	2023-07-24-19-00-14

 

notice.log:


#separator \x09
#set_separator	,
#empty_field	(empty)
#unset_field	-
#path	notice
#open	2023-07-24-19-00-13
#fields	ts	uid	id.orig_h	id.orig_p	id.resp_h	id.resp_p	fuid	file_mime_type	file_desc	proto	note	msg	sub	src	dst	p	n	peer_descr	actions	email_dest	suppress_for	remote_location.country_code	remote_location.region	remote_location.city	remote_location.latitude	remote_location.longitude
#types	time	string	addr	port	addr	port	string	string	string	enum	enum	string	string	addr	addr	port	count	string	set[enum]	set[string]	interval	string	string	string	double	double
1689201023.169002	ClEkJM2Vm5giqnMf4h	10.7.12.121	49799	151.248.117.244	80	-	-	-	tcp	GoziMalwareDetector::GoziActivity
Potential Gozi banking malware activity between source 10.7.12.121 and dest 151.248.117.244 with 
method GET and URI 
/uploaded/8jvrTb2D/c4CxLmogLgZQGC_2FQ_2B2b/Ma3ylmhq8i/MeT_2Fmtq1zDpHZZQ/2OknTIetuvPf/SqlzkcwbzWM/aFx0b7
0stnXODu/WDQ2wUhiUaYRbirzPbAvc/2V_2Feb1BeDPQaU0/WZs_2FbMUKJ37c4/Gf5YYgB_2B8BS7mcYa/jECotoj7R/7bH1bkdIXb
wbqpU0Nryv/_2BfEwnTZ0On333QjdJ/fdFWYGQpQofXObilmWG0P_/2BS5YP7Tcj18X/cQoSxxb6/FYqDyT3sva2N6amcI32HXsv/n6
pffb_2FO/UxvFILD91uIk2oNQx/DEmgyRV_2FLi/3pF67pmCpVP/mFnn0G63A1Sv9N/05KeIqFG3zHYuPPOU/1P.pct
-	10.7.12.121	151.248.117.244	80	-	-	Notice::ACTION_LOG	(empty)	3600.000000	-	-	-	-	-
1689201032.792223	CtPZjS20MLrsMUOJi2	10.7.12.121	49801	91.199.147.95	80	-	-	-	tcp	
GoziMalwareDetector::GoziActivity	
Potential Gozi banking malware activity between source 10.7.12.121 and dest 91.199.147.95 with method 
GET and URI /vnc32.rar	-	10.7.12.121	91.199.147.95	80	-	-	Notice::ACTION_LOG	(empty)	3600.000000	-	-	-	-	-
1689204683.938169	CilF4d1b6woayAE906	10.7.12.121	50413	151.248.117.244	80	-	-	-	tcp	
GoziMalwareDetector::GoziActivity	
Potential Gozi banking malware activity between source 10.7.12.121 and dest 151.248.117.244 with method 
GET and URI /uploaded/bc1bm932Gi8AkB7CMdoi/dV6NGeXlcWdoFbsJnFC/FQZnIx7u0wQZs8gXewgk2O/_2BqEL_2F5vUH/Vjp
cgRrm/NgFQBF4d0ml2qUjPnz05CNh/WBeUsAyX8h/DYU_2Be6ioc_2FA9o/bkBn4zKxTE1N/1SebIT_2BB2/5hdp86s2tIvCxD/tZmU
gcI6tODvZKdoJyys1/e4sK8m7GZS45MABe/ErGNCS3Es2MiUbX/8g2aKSm8DCj6uNwDbP/RnZ8rlx9a/lrUNLg4HRLMm6ysR4pwf/Vt
ZBd5aLMF2Q_2FrqZr/qT7tLbEkySOgW4gIG9cEFr/b3tW5AqoOLBZY/_2FK4oG1/WJk_2B.pmg	-	10.7.12.121	151.248.117.244	80	-	-	Notice::ACTION_LOG	(empty)	3600.000000	-	-	-	-	-
#close	2023-07-24-19-00-14

We recently used this logic outside the confines of our labs and detected the Gozi C2 communication for one of our customers on their live network.

Also, in case you are wondering, here are the sources that I used throughout this article:

https://malware-traffic-analysis.net/2023/07/12/index.html

https://www.malware-traffic-analysis.net/2023/03/06/index.html

https://unit42.paloaltonetworks.com/march-wireshark-gozi-answers/

https://medium.com/@enyel.salas84/unveiling-the-gozi-infection-detecting-gozi-in-an-active-directory-ad-environment-using-9773baebe25f

https://www.microsoft.com/en-us/wdsi/threats/malware-encyclopedia-description?Name=Trojan:Win32/Gozi&threatId=-2147225522

https://github.com/mlodic/ursnif_beacon_decryptor

https://github.com/0ver-fl0w/ISFB_Tools

https://twitter.com/Unit42_Intel/status/1633934017031467010

https://app.any.run/tasks/5e4dbbe3-78a0-4c63-80f8-203b7ad91cd0/

https://app.any.run/tasks/d1a96aea-a514-4f86-acd7-e9391a8ec959/

https://app.any.run/tasks/1406d885-93e3-43e6-8182-667adb6f7ff7/

https://app.any.run/tasks/f3771459-62f8-48b8-a1bf-a1b182af3599/

https://app.any.run/tasks/15800ac5-44bf-4628-9653-f0364c80fc79/

https://app.any.run/tasks/59a8fe6a-e6b2-4e09-9333-dd19227c0e51/

https://www.blackberry.com/us/en/solutions/endpoint-security/ransomware-protection/ursnif

Looking for more tips like these? Follow me on YouTube. I have a dedicated Zeek playlist. I also recommend subscribing to our Corelight blog to keep up with what else me and my colleagues find in the wild. If you’re interested in finding command and control on your network, check out our C2 Collection. These are just a few of the many detections that Corelight’s network evidence provides.