Old Backdoor, New Obfuscation
When you’re hunting, sometimes you feel lucky because you spotted something that looks brand new, but sometimes it’s not new or… the code has been changed to bypass existing detections. Here is a perfect example. A few months ago, Juniper discovered[1] a backdoor targeting VMWare ESXi servers, more precisely, the OpenSLP service (CVE-2019-5544 and CVE-2020-3992).
If the backdoor isn’t new, I found new versions of it that implement more obfuscation techniques by reducing changes to be caught by antivirus tools and filters. The scripts, found on VT, have the following filename format: “esxi_ransomware_xxxxxxxx.py”. It seems that the attacker tested different obfuscation techniques. Sometimes, just having a look at the source code with a graphical overview is interesting:
Many text editors propose this kind of view. In the picture above, you can see patterns with only interesting lines at the end.
The backdoor has been obfuscated with many functions that look complicated, but most of them do… nothing! Example:
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
if opaque_fct_6_guXM09JTqW(1170448432, 34836967901, 30592200701, 23499594842, 7931033327):
if opaque_fct_7_2givpU14Oj(12913767465, 29715926998, 28391806664, 34224856236, 27002350942, 38119355106, 17984667519, 33397958160, 34307567544, 3134198737, 6433478414, 1333569498, 30190306077, 31065906546):
opaque_fct_3_HlBjJpTAMd(4559404501, 19631615206, 15232647523, 38155060881, 25231599065, 27560986774, 28564255047, 23742277226, 37444581463, 34726589553)
elif opaque_fct_3_Jvb1H08Kzj(1744861910, 8785099158, 15933986777):opaque_fct_6_78lRkhN51d(13973672458, 29300903469, 6016412088, 32808894927, 2647492267, 10754001214, 28891585111, 32994113503, 19424804608)
else:
form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD': 'POST'})
else:
opaque_fct_7_ueGht7ZaDw(34708030056, 3642393576, 19762095891, 22250089401, 11960747056)
The first if() condition will always be TRUE:
def opaque_fct_6_guXM09JTqW(opaque_fct_6_guXM09JTqW_0, opaque_fct_6_guXM09JTqW_1, opaque_fct_6_guXM09JTqW_2, opaque_fct_6_guXM09JTqW_3, opaque_fct_6_guXM09JTqW_4):
if (opaque_fct_6_guXM09JTqW_1 > opaque_fct_6_guXM09JTqW_0):
return True
if (opaque_fct_6_guXM09JTqW_4 <= opaque_fct_6_guXM09JTqW_1):
return True
if (opaque_fct_6_guXM09JTqW_1 > opaque_fct_6_guXM09JTqW_0):
return True
if (opaque_fct_6_guXM09JTqW_1 < opaque_fct_6_guXM09JTqW_0):
return False
if (opaque_fct_6_guXM09JTqW_3 >= opaque_fct_6_guXM09JTqW_1):
return False
if (opaque_fct_6_guXM09JTqW_1 < opaque_fct_6_guXM09JTqW_4):
return False
if (opaque_fct_6_guXM09JTqW_0 >= opaque_fct_6_guXM09JTqW_1):
return False
if (opaque_fct_6_guXM09JTqW_1 < opaque_fct_6_guXM09JTqW_4):
return False
if (opaque_fct_6_guXM09JTqW_1 < opaque_fct_6_guXM09JTqW_0):
return False
if (opaque_fct_6_guXM09JTqW_1 > opaque_fct_6_guXM09JTqW_0):
return True
if (opaque_fct_6_guXM09JTqW_0 <= opaque_fct_6_guXM09JTqW_1):
return True
if (opaque_fct_6_guXM09JTqW_0 >= opaque_fct_6_guXM09JTqW_1):
return False
Indeed, if you check the parameters, '34836967901' will always be bigger than '1170448432'. Other calls are useless (like the first call to opaque_fct_3_HlBjJpTAMd()).
If you remove all the junk code, the backdoor has precisely the same behavior as the Juniper blog post explained.
[1] https://blogs.juniper.net/en-us/threat-research/a-custom-python-backdoor-for-vmware-esxi-servers
Xavier Mertens (@xme)
Xameco
Senior ISC Handler - Freelance Cyber Security Consultant
PGP Key
Comments