news.mlab.sh
Back to the feed
threat-intel

A polymorphic phishing page (that occasionally breaks itself), (Thu, Aug 27th)

Medium
Summary

This article details a sophisticated phishing page that employs a polymorphic obfuscation technique to evade detection. The page initially presents as broken, causing a 30-second delay and high CPU usage, due to a global variable 'k' being used as a loop counter in a JavaScript function. Despite the initial failure, subsequent attempts to load the page consistently resulted in different, yet functionally equivalent, versions of the phishing page, each with altered HTML elements and JavaScript code. The author investigated this phenomenon, discovering that the page was not permanently broken but rather generated multiple variants, with a small percentage (around 2%) resulting in non-functional pages. The investigation suggests a conventional polymorphic obfuscator, potentially leveraging a simple renaming and reordering mechanism, rather than an LLM-based approach, as the transformations between page versions were systematic and consistent. The article highlights the ongoing evolution of phishing techniques and the challenges they pose to traditional detection methods.

As I’ve mentioned before in some of my diaries, from time to time, I like to go over phishing messages that get caught in my various spam traps or sent to us here at the Internet Storm Center. After looking at enough phishing messages, one quickly gets used to seeing the same lures, the same credential-harvesting pages and, quite often, the same obfuscation techniques over and over again. But even something that seems to be "run-of-the-mill" at first glance can sometimes turn out to be quite interesting.

One such message was recently sent to our handler inbox, and as you can see, there was very little about it that would indicate that it would be worth a deeper look. The link in the message pointed to a URL with the following, quite usual, structure: `hxxps[:]//addresses[.]performs[.]vu/communications.html?good=[recipient_address]`

Nevertheless, what happened after the link was opened was somewhat less usual. Instead of displaying a phishing page, the browser remained effectively stuck for about 30 seconds, while utilization of one CPU core in the virtual machine I was using quickly rose to 100 %. Since retrieving the HTML source itself was almost instantaneous, it seemed clear that the delay wasn't caused by the server, and instead something in the page itself was preventing the browser from finishing its work.

Although a quick look at the source code showed that almost all of the page consisted of heavily obfuscated JavaScript, the reason for the unusual behavior fortunately wasn't too difficult to identify. Among other things, the script contained two functions, which are slightly reformatted here for easier readability:

```javascript function _il(m) { for(k=0; 64>k; k++) { m[_lV(_ie(),k)]=k } return m }

function _YF(m,h) { var v=""; for(k=m; k<=h; k++) { v=v+String.fromCharCode(k) } return v } ```

The first function is part of a decoding routine, and its loop counter is expected to go from 0 to 63. The second function is a helper used by the same routine to construct strings from ranges of character codes – it is used (among other places) in the `_ie()` function, which is called by the first function. The problem is that `k` isn't declared locally in either one of these functions.

This becomes important because `_ie()`, which is called during every iteration of the first loop, uses `_YF()` several times to construct the Base64 alphabet. Its final call is `_YF(47,47)`, which produces the ‘/’ character (ASCII code 47). Since the counter `k` used by `_YF()` is global, this final call also changes the value of `k` used by the outer loop. `_YF(47,47)` first sets `k` to 47, executes its loop once and then increments `k` to 48. At that point, the condition `k <= 47` is no longer true, so `_YF()` returns with the global value of `k` left at 48. Control then returns to the outer for loop, whose own increment changes `k` from 48 to 49. Since 49 is still smaller than 64, another iteration starts and `_ie()` is called again. Its final `_YF(47,47)` call once more leaves `k` at 48. The outer loop therefore never progresses beyond 49.

The resulting sequence therefore looks roughly like this:

``` 48 -> 49 48 -> 49 48 -> 49 ... ```

This explained both why the page never rendered and why the browser was keeping one CPU core rather busy. Changing the inner routine to use its own local counter was sufficient to let the decoding process finish. After removing the remaining layers of obfuscation, what emerged was an otherwise completely unremarkable credential-stealing page.

At this point, the most likely explanation seemed fairly straightforward – the authors of the page had simply shot themselves in the foot by using a broken obfuscation mechanism. Nevertheless, this proved not to be the case, since when I accessed the original URL again a little later, the page loaded normally. Another attempt to load the page was also successful, as were several subsequent ones.

More interestingly, while all of the resulting pages ultimately displayed the same credential-stealing form, their source code wasn't the same. Function and variable names differed across page loads, functions appeared in a different order, numerical constants were expressed using different arithmetic operations and a large encoded block of code, which contained the actual payload with the form, changed as well. Even the innocuous-looking page title varied between requests using words like

Read the full article at SANS Internet Storm Center