Post

Detecting And Decoding Ezpass Phishing Activity In The Wild Part 2

Detecting And Decoding Ezpass Phishing Activity In The Wild Part 2

Disclaimer: This article is for research and educational purposes. Do not visit phishing websites without proper precautions.

To recap the previous section, I found a pattern in how the E-ZPass sites are named and that the domains share the same HTTP Headers as the IPs that resolve them. This exposed a ton of IOCs, including IP addresses used by our threat actor. But I thought I hit a dead end. I was looking around, and I thought I ran out of pivots. The main issue was that I had yet to make a connection to a phishing site!

I didn’t understand why. My theory was that Internet Service Providers (ISPs) and platforms such as Google flagged the phishing sites to prevent people from visiting them. I had discovered a pattern, and it would make sense that the big players found it, too. Also, it doesn’t help that they change their domains and IPs frequently. I started looking around online for what I could do. The most significant help was that a User-Agent can impact connections to the phishing site. There may be something the threat actor programmed into their site that prevents specific user agents from connecting.

But what is a user agent? A user agent is a string sent by a web browser, app, or client to a server in an HTTP request header. Think of it as your web browser’s fingerprint when connecting to a web server. I had been trying to connect to these phishing sites through a sandboxed Windows environment all this time.

The Phishing Site

Since these phishing sites are transported via text message, it makes sense that a threat actor would only expect phone browsers to visit their websites. Also, going directly to the domain doesn’t work. You need a path. Fortunately, this website provides the latest smishing texts. And I gathered that there was a small pool of paths to follow when visiting a phishing domain. The most common ones were /pay /us /portal. We will visit e-zpassny[.]comloh[.]win/pay for this article.

The threat actor expects you to enter your phone number, and it will bring you to this page:

The victim will then proceed to enter their credit card information. I will review this later, but you cannot enter bogus details here.

There you have it. Hook, line, and sinker. I should note that NY’s real E-ZPass website used to look like this:

Network Connections

So now we have successful network connections. What do they say…? Well, it seems our threat actor knows how to obfuscate their trail. One popular method threat actors use to cover their tracks is to appear legitimate using Cloudflare and Google Trust certificates. These IP addresses are Cloudflare.

Fortunately, the HTTP Header covered in my previous write-up is still valid; I can still see our threat actor’s infrastructure. But we can’t focus on one domain’s successful connection. We need to replicate these results. Passing Dragon doesn’t keep their sites up long, leaving room for error. I was able to connect to a different website with slightly different results. e-zpass[.]com-etcicb[.]cc/pay resolving through the IP 49[.]51[.]184[.]123.

Our threat actor abuses Let’s Encrypt as well.

So, who is hosting these IPs that provide hosting services to phishing sites? In my research, AS 132203 has been a consistent ASN (autonomous system number). There are others, but this one has been one of the most consistent. Tencent, a Chinese-based conglomerate, hosts this and the other ASNs hosting E-ZPass phishing sites.

The Code

Someone has to code the website. This means there may be some clues and idiosyncrasies native to the coder. One tool in our arsenal is the view-source function on browsers. This exposes the code on the HTML page. Which, at a glance, is simple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<!DOCTYPE html>  
<html lang="en">  
<head>  
<script type="module" crossorigin src="./assets/fliceXIj.js"></script>  
  
<meta charset="UTF-8">  
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">  
<title id="homeTitle"></title>  
<link rel="icon" id="homeIcon" href="./favicon.png" type="image/svg+xml">  
  
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">  
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">  
<input type="hidden" id="_o_djbempljhcmhlpfacalomonjpalpko" data-inspect-config="3">  
<title></title>  
  
<style>  
.loading-overlay {  
position: fixed;  
top: 0;  
left: 0;  
width: 100%;  
height: 100%;  
background: rgba(255, 255, 255, 1); /* 不透明背景 */  
display: flex;  
align-items: center;  
justify-content: center;  
z-index: 9999;  
}  
  
.loading-gif {  
width: 16px;  
height: 16px;  
}  
  
body {  
padding-top: 0;  
}  
</style>  
  
<script type="module" crossorigin src="./assets/ClRQhJQ2.js"></script>  
<link rel="stylesheet" crossorigin href="./assets/bZRoCO7p.css">  
  
<script type="module">  
import.meta.url; import("_").catch(() => {});  
(async function* () {})().next();  
if (location.protocol === "file:") {  
window.__vite_is_modern_browser = true;  
}  
</script>  
  
<script type="module">  
!function() {  
if (window.__vite_is_modern_browser) return;  
console.warn("vite: loading legacy chunks, syntax error above and the same error below should be ignored");  
var e = document.getElementById("vite-legacy-polyfill"),  
n = document.createElement("script"

The HTML page has fewer than 60 lines of code. The Chinese part refers to transparency in RGB. However, two assets within this HTML page need scrutiny: ClRQhJQ2[.]js and bZRoCO7p[.]css. Let’s start with bZRoCO7p[.]css.

CSS controls visual aspects such as colors, fonts, spacing, and positioning of elements. CSS allows developers to separate content (HTML) from presentation, making maintaining and updating designs easier.

After reviewing the CSS code, I found a mix of English and Chinese. One section refers to the App Store and Google Play Store, as we saw on our phishing page.

This does not necessarily mean anything. The author of this code may have copied and pasted from a different resource. However, an HTTP address was also hardcoded: hxxx://43[.]153[.]53[.]236.

The HTTP function is down now and has been since December. But before that, it would give 没有找到站点 or “Site not found,” which was likely a ruse. The path was also needed, and enumerating the string on the first part of the path would have been challenging, if not impossible. I found these comments in the second phishing site I visited as well.

As for ClRQhJQ2[.]js, we have a problem. The code is heavily obfuscated with references upon references to dynamic functions. The code needs to be analyzed and vetted. It has over 38 thousand lines of code. Upon initial review, the JS is the lifeblood of the phishing site. I can assess that with some certainty because there is a section where we find credit card references.

Additionally, we find API calls. But it is a mystery where they are going.

The JS has encoding and even encryption embedded, making analysis challenging.

Part Two Conclusion

After trial by fire, we successfully connected to E-ZPass phishing sites through our sandbox. These are the facts I have gathered during this investigation on Passing Dragon:

  1. The victim can only connect to these sites through their phone.

  2. Passing Dragon is abusing Cloudflare, Google Trust Services, and Let’s Encrypt to lure their victims with “trusted” connections.

  3. Passing Dragon uses Tencent to host their infrastructure with a hard-coded IP address owned by Tencent.

  4. The CSS and JavaScript code authors are bilingual with coding artifacts in English and Chinese.

  5. The authors are sophisticated developers in JavaScript with heavy obfuscation.

Let’s update our Diamond Model to reflect our discoveries.

This post is licensed under CC BY 4.0 by the author.