Discovering an unknown infrastructure

Written on 2023/03/24

Emotet is back according to a Bleeping Computer article. It is back because emails are being sent again. This use-case started there, and ended-up in another completely (new?) world.

A full downloadable JSON export can be found at the end of this use-case should you want to analyze data on your side.

How to identify Emotet server infrastructure

First, we have to know how to identify Emotet C2 servers. Thanks to this video made by Charles BLANC ROLIN, we came to the following dork:

category:datascan subject.commonname:example.com subject.organization:“global security”

SPOILER: this is not enough to identify Emotet C2 servers. The verified dork is as follows:

category:datascan subject.commonname:example.com subject.organization:“global security” app.http.headermd5:“0703f8de6c918848f0335fb425ed3435” app.http.bodymd5:“465981b2c7142b9fb660b39e2de874c1”

For the record, here is the list of Emotet C2’s on last 30-days of data:

{"count":1,"ip":"213[.]239[.]212[.]5","port":443}
{"count":2,"ip":"164[.]90[.]222[.]65","port":443}
{"count":3,"ip":"186[.]250[.]48[.]5","port":443}
{"count":4,"ip":"188[.]44[.]20[.]25","port":443}
{"count":5,"ip":"182[.]162[.]143[.]56","port":443}
{"count":6,"ip":"79[.]137[.]13[.]24","port":443}
{"count":7,"ip":"146[.]59[.]151[.]250","port":443}
{"count":8,"ip":"54[.]37[.]136[.]187","port":443}
{"count":9,"ip":"91[.]207[.]181[.]106","port":443}
{"count":10,"ip":"178[.]128[.]31[.]80","port":443}
{"count":11,"ip":"178[.]128[.]82[.]218","port":443}
{"count":12,"ip":"116[.]125[.]120[.]88","port":443}
{"count":13,"ip":"190[.]90[.]233[.]69","port":443}
{"count":14,"ip":"104[.]248[.]155[.]133","port":443}
{"count":15,"ip":"159[.]89[.]202[.]34","port":443}

But let’s continue our journey with our use-case to demonstrate the usefulness of our approach. Now, let’s look at the data field, where you’ll find the raw response to our application request to try and learn more. Simply click on Data tab:

Nice patterns here. We can refine a search directly in raw data field in a few simple ways. One is to use the app.http.bodymd5 filter to only search for assets with the string Unauthorized in the response body. Also, we want to keep results which have the HTTP/1.1 401 Unauthorized with an OpenResty product and a User-Agent header.

To get the app.http.bodymd5 value, just click on Analytics tab and click on the HTTP Body MD5 value:

For the product, click on Software tab and click on the Product value:

category:datascan app.http.bodymd5:“e06d1ba70f1331e9f9a113cc2f887d3f” productvendor:“OpenResty” product:“OpenResty” data:“HTTP/1.1 401 Unauthorized” data:“User-Agent”

Remember how we started with 14,423 results and we are now at 19,222? Also note that we removed filters against TLS certificate fields to stick only with the content enriched from data field.

UPDATE: it appears this strange User-Agent can be found on assets not bound to OpenResty. The Server header is either stripped or another software is used. From now on, we will dig into both conditions: statistics with OpenResty and without OpenResty. New results count without this filter: 23,486.

That’s for the last 30 days of data. We have months of historical records, and we will analyze them too.

Fetch deduplicated data

We may have duplicates in our data as we scan from multiple continents. So we may see some assets from Europe, others from Asia or from US. We have to deduplicate them in order to have a clear view on the number of identified assets.

To do that, you have to use our CLI tools. Follow the guide to install them. Once this is done, we can continue.

The simple command to deduplicate results is as follows:

onyphe -export 'category:datascan app.http.bodymd5:"e06d1ba70f1331e9f9a113cc2f887d3f" product:"OpenResty" data:"HTTP/1.1 401 Unauthorized" data:"User-Agent" | uniq ip,port | addcount'

NOTE: you have to use the Export API as we are beyond the 10,000 limit imposed by the Search API.

We can do better by using a regex against the User-Agent string to filter out of scope thingies:

onyphe -export 'category:datascan app.http.bodymd5:"e06d1ba70f1331e9f9a113cc2f887d3f" product:"OpenResty" data:"HTTP/1.1 401 Unauthorized" data:"User-Agent" | regex data="User-Agent: ([a-f0-9]{28})" outputfield=useragent | dedup ip,port | fields ip,port,useragent | addcount'

We have 6,498 unique ip/port pairs with such a strange User-Agent as a result.

And without the OpenResty product filter:

onyphe -export 'category:datascan app.http.bodymd5:"e06d1ba70f1331e9f9a113cc2f887d3f" !product:"OpenResty" data:"HTTP/1.1 401 Unauthorized" data:"User-Agent" | regex data="User-Agent: ([a-f0-9]{28})" outputfield=useragent | dedup ip,port | fields ip,port,useragent | addcount'

That’s 1,334 unique ip/port pairs on last 30-days of data.

What about previous months of data?

By default, the search runs over the last 30-days of data. Let’s run multiple searches, one for each month of collected information:

# Last month, with OpenResty product (February 2023):
onyphe -export 'category:datascan app.http.bodymd5:"e06d1ba70f1331e9f9a113cc2f887d3f" product:"OpenResty" data:"HTTP/1.1 401 Unauthorized" data:"User-Agent" -monthago:1 | regex data="User-Agent: ([a-f0-9]{28})" outputfield=useragent | dedup ip,port | fields ip,port,useragent | addcount'

# Without OpenResty product:
onyphe -export 'category:datascan app.http.bodymd5:"e06d1ba70f1331e9f9a113cc2f887d3f" !product:"OpenResty" data:"HTTP/1.1 401 Unauthorized" data:"User-Agent" -monthago:1 | regex data="User-Agent: ([a-f0-9]{28})" outputfield=useragent | dedup ip,port | fields ip,port,useragent | addcount'

6,203 unique ip/port with OpenResty and 1,524 without.

# And before (January 2023):
onyphe -export 'category:datascan app.http.bodymd5:"e06d1ba70f1331e9f9a113cc2f887d3f" product:"OpenResty" data:"HTTP/1.1 401 Unauthorized" data:"User-Agent" -monthago:2 | regex data="User-Agent: ([a-f0-9]{28})" outputfield=useragent | dedup ip,port | fields ip,port,useragent | addcount'

# Without OpenResty product:
onyphe -export 'category:datascan app.http.bodymd5:"e06d1ba70f1331e9f9a113cc2f887d3f" !product:"OpenResty" data:"HTTP/1.1 401 Unauthorized" data:"User-Agent" -monthago:2 | regex data="User-Agent: ([a-f0-9]{28})" outputfield=useragent | dedup ip,port | fields ip,port,useragent | addcount'

5,564 unique ip/port with OpenResty and 1,567 without.

# And before (December 2022):
onyphe -export 'category:datascan app.http.bodymd5:"e06d1ba70f1331e9f9a113cc2f887d3f" product:"OpenResty" data:"HTTP/1.1 401 Unauthorized" data:"User-Agent" -monthago:3 | regex data="User-Agent: ([a-f0-9]{28})" outputfield=useragent | dedup ip,port | fields ip,port,useragent | addcount'

# Without OpenResty product:
onyphe -export 'category:datascan app.http.bodymd5:"e06d1ba70f1331e9f9a113cc2f887d3f" !product:"OpenResty" data:"HTTP/1.1 401 Unauthorized" data:"User-Agent" -monthago:3 | regex data="User-Agent: ([a-f0-9]{28})" outputfield=useragent | dedup ip,port | fields ip,port,useragent | addcount'

4,057 unique ip/port with OpenResty and 2,450 without.

# And before (November 2022):
onyphe -export 'category:datascan app.http.bodymd5:"e06d1ba70f1331e9f9a113cc2f887d3f" product:"OpenResty" data:"HTTP/1.1 401 Unauthorized" data:"User-Agent" -monthago:4 | regex data="User-Agent: ([a-f0-9]{28})" outputfield=useragent | dedup ip,port | fields ip,port,useragent | addcount'

# Without OpenResty product:
onyphe -export 'category:datascan app.http.bodymd5:"e06d1ba70f1331e9f9a113cc2f887d3f" !product:"OpenResty" data:"HTTP/1.1 401 Unauthorized" data:"User-Agent" -monthago:4 | regex data="User-Agent: ([a-f0-9]{28})" outputfield=useragent | dedup ip,port | fields ip,port,useragent | addcount'

2,864 unique ip/port with OpenResty and 3,755 without.

# And before (October 2022):
onyphe -export 'category:datascan app.http.bodymd5:"e06d1ba70f1331e9f9a113cc2f887d3f" product:"OpenResty" data:"HTTP/1.1 401 Unauthorized" data:"User-Agent" -monthago:5 | regex data="User-Agent: ([a-f0-9]{28})" outputfield=useragent | dedup ip,port | fields ip,port,useragent | addcount'

# Without OpenResty product:
onyphe -export 'category:datascan app.http.bodymd5:"e06d1ba70f1331e9f9a113cc2f887d3f" !product:"OpenResty" data:"HTTP/1.1 401 Unauthorized" data:"User-Agent" -monthago:5 | regex data="User-Agent: ([a-f0-9]{28})" outputfield=useragent | dedup ip,port | fields ip,port,useragent | addcount'

596 unique ip/port with OpenResty and 5,993 without.

# And before (September 2022):
onyphe -export 'category:datascan app.http.bodymd5:"e06d1ba70f1331e9f9a113cc2f887d3f" product:"OpenResty" data:"HTTP/1.1 401 Unauthorized" data:"User-Agent" -monthago:6 | regex data="User-Agent: ([a-f0-9]{28})" outputfield=useragent | dedup ip,port | fields ip,port,useragent | addcount'

# Without OpenResty product:
onyphe -export 'category:datascan app.http.bodymd5:"e06d1ba70f1331e9f9a113cc2f887d3f" !product:"OpenResty" data:"HTTP/1.1 401 Unauthorized" data:"User-Agent" -monthago:6 | regex data="User-Agent: ([a-f0-9]{28})" outputfield=useragent | dedup ip,port | fields ip,port,useragent | addcount'

0 unique ip/port with OpenResty and 5,253 without.

# And before (August 2022):
onyphe -export 'category:datascan app.http.bodymd5:"e06d1ba70f1331e9f9a113cc2f887d3f" product:"OpenResty" data:"HTTP/1.1 401 Unauthorized" data:"User-Agent" -monthago:7 | regex data="User-Agent: ([a-f0-9]{28})" outputfield=useragent | dedup ip,port | fields ip,port,useragent | addcount'

# Without OpenResty product:
onyphe -export 'category:datascan app.http.bodymd5:"e06d1ba70f1331e9f9a113cc2f887d3f" !product:"OpenResty" data:"HTTP/1.1 401 Unauthorized" data:"User-Agent" -monthago:7 | regex data="User-Agent: ([a-f0-9]{28})" outputfield=useragent | dedup ip,port | fields ip,port,useragent | addcount'

0 unique ip/port with OpenResty and 3,617 without.

Statistics with OpenResty product

Top 10 countries for last 30-days:

Top 10 organizations for last 30-days:

Conclusion

To sum-it up:

Month With OpenResty Without OpenResty Sum
Aug 2022 0 3,617 3,617
Sep 2022 0 5,253 5,252
Oct 2022 596 5,993 6,589
Nov 2022 2,864 3,755 6,619
Dec 2022 4,057 2,450 6,507
Jan 2023 5,564 1,567 7,131
Feb 2023 6,203 1,524 7,727
Mar 2023 6,498 1,334 7,832


We have shown how to start from a first search and how to pivot on different values to uncover a larger scope of similar patterns. We don’t know what we have found here, is it legit, like a product now shipped by default on some hosting providers? Or is it malicious like an emerging botnet or a successor of an old one? Maybe time will tell, or someone already has some clues and we just re-descovered something known.

Both infrastructures look very similar, one appears older than the other, and one decreases over time while the other is increasing. Is it an update to an infrastructure or a complete replacement? This User-Agent header is really interesting. Usually, that’s HTTP clients that send such header. We have seen that this infrastructure bound to OpenResty didn’t exist on August or September 2022, but started to pop in October 2022. Since then, it has never ceased to increase to be bigger than the older vanishing one.

Sample output for latest detections:

{"@timestamp":"2023-03-23T13:39:33[.]000Z","ip":"202[.]148[.]149[.]32","port":8443,"useragent":"641c5694c83a455641c5694c83ad"}
{"@timestamp":"2023-03-23T12:36:01[.]000Z","ip":"45[.]159[.]92[.]200","port":8080,"useragent":"641c47b07429255641c47b074296"}
{"@timestamp":"2023-03-23T12:25:07[.]000Z","ip":"172[.]107[.]242[.]151","port":443,"useragent":"641c4522e3dd555641c4522e3dd7"}
{"@timestamp":"2023-03-23T12:08:19[.]000Z","ip":"172[.]107[.]242[.]151","port":8080,"useragent":"641c4132077af55641c4132077c0"}
{"@timestamp":"2023-03-23T12:05:02[.]000Z","ip":"103[.]176[.]90[.]111","port":443,"useragent":"641c406eba56c55641c406eba572"}
{"@timestamp":"2023-03-23T11:26:25[.]000Z","ip":"50[.]114[.]101[.]127","port":443,"useragent":"641c3761c455755641c3761c455a"}
{"@timestamp":"2023-03-23T10:44:37[.]000Z","ip":"185[.]221[.]219[.]13","port":80,"useragent":"641c2d955e5c855641c2d955e5d1"}
{"@timestamp":"2023-03-23T10:38:35[.]000Z","ip":"51[.]15[.]177[.]196","port":80,"useragent":"641c2c29f17b955641c2c29f17c3"}
{"@timestamp":"2023-03-23T10:14:01[.]000Z","ip":"45[.]8[.]178[.]94","port":8080,"useragent":"641c26680f54655641c26680f547"}
{"@timestamp":"2023-03-23T08:54:34[.]000Z","ip":"102[.]129[.]200[.]192","port":443,"useragent":"641c13c9f1f6155641c13c9f1f68"}
[..]

Download full raw JSON export for last 30-days of data we have for the OpenResty infrastructure.

You may continue this journey on analyzing IP addresses and their profile by reading this other use-case.