Skip to main content

Hunting VIPER C2 Instances on Censys

  • 25 July 2024
  • 0 replies
  • 187 views

man viper-hunt

Hey all, this just serves as a quick way to hunt down some VIPER C2 instances if this is something you’re interested in tracking. I’ve tweeted about this previously so this will be a recap/update with some new hosts. Hunt.io also has a really good blog on their site about hunting on their platform, but I posted my tweet two months before their article so… take that VC investors.

 

Introduction

For some background, VIPER C2 is a framework primarily used by Asian TAs and is rarely hosted in any NATO/FVEY countries. Using IP geolocation alone can already help narrow down some more interesting hosts to look at without the need for any deep pivoting. A notable APT that has historically used VIPER is NAIKON/Lotus Panda, especially in combination with Asset Reconnaissance Lighthouse. So, how can we track VIPER instances? Very easily, we abuse TA laziness.

 

The Basic Path

Based on the Hunt article and maybe some cursory manual glances, you could easily come up with a basic query based on the page title and content length like below.

same_service(services.http.response.html_title="VIPER" and services.http.response.body_size:"692")

This query returns 262 IPs at the time of writing, all of which but one are labelled as a C2. The lone outlier, https://search.censys.io/hosts/159.75.148.143, matches the bill perfectly but we’ll go over why the detection likely breaks in the next section. For now, it’s an exercise to the reader.

 

The Certificate Path

VIPER issues a default certificate, which can be found here. Querying off of this gives us the vast majority of hosts already, simply search for

services.tls.certificates.leaf_data.fingerprint: 4de3278507c89d2242a12c20b74878e3f84970c463a924771f156a3da7d7b5a1

At the time of writing, this returns 308 IPs. Most of them follow the default deployment of the C2 panel on port 60000. Obviously, checking for non-default deployments can yield interesting results, so we can modify our query by writing it as below.

same_service(services.tls.certificates.leaf_data.fingerprint:"4de3278507c89d2242a12c20b74878e3f84970c463a924771f156a3da7d7b5a1" and not services.port:"60000")

This leaves us with 10 IP addresses, some of which aren’t even labeled as C2 (( shame on you @AidanH_Censys !! Obligatory this is a joke before I lose access )).

Let’s take a look at the unlabeled ones before we move on. Why are they not labeled? What can we learn from that to improve our detection mechanisms? Screenshots below are taken at time of writing.

 

Access Controlled

https://search.censys.io/hosts/111.230.102.189

This displays the default 4de certificate but has a non-”VIPER” title

My best guess is that the missing favicon + title broke the detection.

Next up is https://search.censys.io/hosts/117.50.205.15 (well, I’m proofreading and the data is now only available historically)

Once again nginx blocking us out

Here we have a different access policy hitting us, but the point of missing HTML title + favicon remains, being the likely culprit for missed labelling on these ports. We can expand our query easily by looking for non-labelled hosts as well. 

same_service(services.tls.certificates.leaf_data.fingerprint:"4de3278507c89d2242a12c20b74878e3f84970c463a924771f156a3da7d7b5a1" and not labels:c2)

This gives us some new IP addresses to look at as well !! How fun !!! I love edge cases !!!!

https://search.censys.io/hosts/45.136.15.175

At the time of writing, there are some Cobalt Strike ports that got taken down with some relatively unique watermarks based on USG data.


services.cobalt_strike.x86.watermark=987654321

Despite the port combo matching those from Hunt’s article, the watermark, path differences, and ASN make me conclude these are likely different actors.

Onto the VIPER port and seeing why it’s not getting detected.

Seems all normal right?

There’s one glaring issue… there is no entry for a favicon, only the html listing.

Welp, that one’s a little weird but it’s not something I haven’t ever seen before. But if I had a dime for every time this happened in this VIPER C2 writeup, I’d have 2 dimes.

https://search.censys.io/hosts/159.75.148.143

We have once again been played for fools by the scanner gods.

You  can’t really do much about it other than make your query robust to these glitches, hence why we included the “or *certificate*” part. This doesn’t go to say that there cannot be more certificates, because there are, which we’ll see in the next part. (From the examples above, the detection is likely written without the content length + title value from the previous section. I wonder why...)

 

The Favicon Path

If you’ve caught onto this scheme, congrats! If you haven’t using the favicon path yields even more results than the previous two. Beyond this, using the favicon method, we gain insight into specifically the non-default certificates that have been deployed alongside VIPER, most likely denoting some higher level of sophistication from the actor. These are the hosts we really want to be looking at. If we apply some lovely PEAK methodology, these would be the outlier investigation aspect of our baseline hunt. Enough mumbo jumbo for now, let’s see some IPs.

services.http.response.favicons.hashes="sha256:d2224a6a27d5c404a59d16789536dc3a076765e21fec2fd823cf76989378ede1"

Blah Blah MD5, SHA1, take it or leave it. If there were SHA512 I’d use it because I’m all about collision reduction baby. Anyways, we get 312 (it was 313 when I started writing this…) IPs back for us to look at. Wow, that’s 4(5) more than our results from the certificate, I wonder how many won’t display the default certificate…  Is it 4? Is it 5? Is it the 8 and one third percent change of Samoa Joe beating Scott Steiner? No, but it is oddly enough is the closest to the actual answer: 9. We can easily check this with the query below.

services.http.response.favicons.hashes="sha256:d2224a6a27d5c404a59d16789536dc3a076765e21fec2fd823cf76989378ede1" and not services.tls.certificates.leaf_data.fingerprint: 4de3278507c89d2242a12c20b74878e3f84970c463a924771f156a3da7d7b5a1

Small sidenote, you may have noticed the sporadic switching between using same_service() and not using it. It all depends on the context of the search and laziness of the query writer (( I am very lazy )). The VIPER default certificate doesn’t show up anywhere other than its own port, which is the same port the fingerprint shows up on. Both of these being hashes on top of it all makes our query more tolerant to not using the same_service modifier. Our beginning query on the other hand is an example where I would encourage the modifier. While results are the same regardless, the keys and values being queried are much more generic and are in general more likely to occur in unrelated hosts.

 

Analyzing Certificate Outliers

With our query in hand and data ahead, here’s an overview of all the IPs and their associated certificates.

https://search.censys.io/hosts/110.41.34.125 → https://search.censys.io/certificates/4ff65aff8e18373c9d29378e58ce3bb2cb4a34e7263362c17610bcac7de26c1b

2024-05-08T02:33:07 to 2034-05-06T02:33:07

https://search.censys.io/hosts/175.27.156.88 → https://search.censys.io/certificates/78e5c0ab3e2a670e06719a746f8ad2cb90f861bfebf45243635c229888856aeb

2023-12-18T06:10:42 to 2033-12-15T06:10:42 

https://search.censys.io/hosts/121.5.63.55 → https://search.censys.io/certificates/82fbceed51846e0730c743dfed7465282d1291057ed143dce73232eb62de5c10

2021-12-29T08:24:10 to 2031-12-27T08:24:10

https://search.censys.io/hosts/39.98.168.196 → https://search.censys.io/certificates/8cd989b7c0dd3d7f5ff429cdacdee05b5a740ed882c0c3fd1cf44738448160ad

2024-06-18T03:44:28 to 2034-06-16T03:44:28

https://search.censys.io/hosts/61.164.242.162 → https://search.censys.io/certificates/652a885d8a06a30220661f0a1a071269636dc7fa1de9c536bbaa27a5a24b34ae

2024-04-28T15:31:47 to 2034-04-26T15:31:47

https://search.censys.io/hosts/124.70.99.224 → https://search.censys.io/certificates/db07d6c6a42654311fd6f4522b01c48539ec55995ca557a1fad03d2f539df0f9

2024-05-10T09:04:36 to 2034-05-08T09:04:36

https://search.censys.io/hosts/120.78.133.59 → No certificate

https://search.censys.io/hosts/74.48.184.88 → https://search.censys.io/certificates/bc5e8cc0684f26e9033a0185d303992d3995c3a98ba53ae6905a9ba1335e37dc

2023-12-22T14:16:42 to 2033-12-19T14:16:42

https://search.censys.io/hosts/198.46.190.54 → https://search.censys.io/certificates/91e9945881eb56c058a20d3ad9bc5d436563f79691cfa4e4dc3ec17751a78079

2024-04-09T02:13:12 to 2034-04-07T02:13:12

From this small investigation, we’ve got various indicators of custom deployments with relatively recent certificate issuing dates, as well as some historical indicators of running or past campaigns. Certificate generation has to occur before the portal launches, thus the timeframe of initial startup and issuing time will be very close to each other. From their documentation: 

VIPER has a built-in self-signed SSL certificate for front-end and back-end Https connection. If there is a traffic monitoring device in the network environment where your PC is located, it is recommended to replace the built-in SSL certificate to bypass traffic monitoring.

It’s always interesting to think about the default vs non-default dilemma. On one hand, running a default deployment lets you blend in with the rest of the “generally malicious” crowd, but it also immediately puts you into that malicious bracket. Having a non-default deployment can allow you to bypass trivial detection mechanisms as we’ve seen, The drawback here is the fact that we can gain atomic indicators such as certificate issuing time that allows us to not only narrow done the operational windows of actors, but also track emerging/ongoing campaigns. If this is something you’re interested in looking at, Unit42 has two great articles within the context of ASEAN APTs here and here. Shameless self-plug but I also used this methodology in my analysis of Mustang Panda’s recent deployments. I guess if you’re reading this far I might as well give a small spoiler. 

dGhlcmUgd2lsbCBiZSBhbm90aGVyIHRhbGs

 

I Walk A Lonely Road, The Only One That I Have Ever Known

Guess what, we’re back with a singular non-labeled IP. Our favorite custom of adding the “and not labels:c2” is back. Go at it, you should be a pro by now.

 

services.http.response.favicons.hashes="sha256:d2224a6a27d5c404a59d16789536dc3a076765e21fec2fd823cf76989378ede1" and not labels:c2

 

Our only non-labeled favicon IP actually has some really interesting intel that we can gain.

https://search.censys.io/hosts/8.210.53.160

Ain’t that a title, still the default viper certificate though
青云-内网综合渗透平台-V3.1 → “Qingyun-Intranet Comprehensive Penetration Platform-V3.1”

 

It also happens to host a few other services, like two MinIO consoles, a Redis server, an IP address location tool, and some fun vhosts like an exam subdomain for… overseas data investigations. (OCRd the screenshot)

This is one of many rabbitholes you can find yourself stumbling down if you look at random IPs on the internet. I’m not going to keep this too off-topic so dig into this yourselves if you want.

 

Any Pandas Around?

With our knowledge from above, we can write some queries to check for the presence of any VIPER + ARL instances scanned on the web. Please note that this isn’t a definitive indicator of Lotus Panda presence, but rather an exercise in applying a form of the ABLE framework to our Hypothesis Driven Hunting process. Don’t you love industry jargon? So, the method I prefer using is combining the favicon, default certificate, and the ARL product into a query. 

same_service(services.http.response.favicons.hashes="sha256:d2224a6a27d5c404a59d16789536dc3a076765e21fec2fd823cf76989378ede1" or services.tls.certificates.leaf_data.fingerprint: 4de3278507c89d2242a12c20b74878e3f84970c463a924771f156a3da7d7b5a1) and services.software.product="Asset Reconnaissance Lighthouse (ARL)"

This returns a surprising amount of results, 61 to be exact, and while there are quite a few that very obviously aren’t APTs, some others do stand out. 

https://search.censys.io/hosts/83.229.123.136

From all the various services, the ones that make  it interesting are the Cobalt Strike services as well as the open directory. At the time of writing it hosted an hta file which is a relatively common initial access vectors across a variety of ASEAN APTs. 

Once again have have the services.cobalt_strike.x86.watermark=987654321indicator, as well as the update-.]rss path for the payload. If we do a bit of searching around drb_ra’s feed bot we can see the configuration is somewhat common and doesn’t explicitly link these two IPs together.

Other interesting hosts might be ones that seem geographically out of place considering the origin of the software. Looking at hosts in the United States, for example, might provide some intriguing results. Do your own digging, the information’s out there, you just have to find it :)

I haven’t checked every IP in there, and today might have totally different results than another day, but continuous monitoring of these activities is necessary will help you and your organization create more resilient cyber defense capabilities. Invest in tools and talent passionate about these causes, because there is so much more out there than anyone will ever be able to tell you.

 

Future Work

I want to make very clear that these are by no means the “best” queries for any of this, but, they work and are better than relying solely on tags and labels! My queries are always based on simple patterns I observe whether from the scanner or from visiting the page itself, and those patterns might be different for you. There are a plethora of ways you can track these C2s. If you want to go even further, you can detect C2 versions by checking the JavaScript framework being imported. You can use all the JARMs, JA3 and 4s you could imagine, as long as it works in providing you results over extended periods of time. Write specific, but resilient queries!!!  I’m just one thing on this planet facing the Sisyphean task of thrunting every APT on the planet, join me in the resistance against cybercrime, rise up against the malware overlords… wait this isn’t the right platform for that. 

 

Fin~

Below is the combined query and what I use myself to track these instances. It returns 313 IPs at the time of writing, with only 3 total being untagged and mentioned above. 

same_service((services.http.response.html_title="VIPER" and services.http.response.body_size:{691,692}) or (services.http.response.favicons.hashes="sha256:d2224a6a27d5c404a59d16789536dc3a076765e21fec2fd823cf76989378ede1" or services.tls.certificates.leaf_data.fingerprint: 4de3278507c89d2242a12c20b74878e3f84970c463a924771f156a3da7d7b5a1))

If you find any improvements or cases that break this detection logic, please let me know or edit it yourself and have at it !!

That’s all from me, I hope you enjoyed reading :)

3a0c5f8214b496ed96f5935ab2a3d055f7b8229db726925cc83d0bd4759ace35 🦝

 

Be the first to reply!

Reply