GovCERT.ch Blog

Social Networks

Official GovCERT.ch Blog

Tofsee Spambot features .ch DGA - Reversal and Countermesaures

Today we came across an interesting malware sample that appeared in our malware zoo. The malware, which we identified as Tofsee, has tried to spam out hundreds of emails within a couple of minutes. However, this wasn’t the reason why it popped up on our radar (we analyze thousands of malware samples every single day, many of which are spambots too). The reason why this particular sample caught our attention were the domains queried by the malware. The domains appear to be algorithmically generated, and about half of the domains use the country code top level domain (ccTLD) of Switzerland:

>Wireshark screenshot of Tofsee DNS queries
Wireshark screenshot of Tofsee DNS queries (click to enlarge)

Domain generation algorithms (DGAs) that use the ccTLD for Switzerland are very rare. Gozi is currently the only malware covered by the DGArchive that uses .ch --- and only in 1 of over 90 different known configurations.

This blog post first describes the analysis of the DGA. We then give a reimplementation of the DGA in Python, as well as a list of the generated domains for the next 52 weeks. We conclude with measure that we took to deal with algorithmically generated .ch domains.

Analysis

We analyzed the following Tofsee sample, with a fairly recent compile timestamp of Fri, 16 Dec 2016 07:09:11:


md5	490e121113fbadd776ca270c6788c59a
sha	c294e79a5f0fbbffd535bb517f43bf69e9bbfb03
sha256	36704ec52701920451437a870e7d538eb409f50a4ae2f8231869500d1d6de159

Seeding

The following graph node show the seeding of the DGA. First, the number of seconds that have elapsed since 1 January 1974 are counted (offset 0x40A0A0). The difference between this date and the unix epoch --- 126230400 seconds --- is added to the result at 0x0040A0A8, effectively yielding the current unix time. It is unclear to us why the authors used this convoluted method to get the current time. The unix time then undergoes four integer division by 60,60,24 and 7, to get the number of weeks since epoch. This value is used as the seed for the upcoming domain generation algorithm. Domains are therefore valid for one week, starting on Thursday at midnight UTC.

Seeding of the DGA
Seeding of the DGA (click to enlarge)

Seeding also calls a pseudo random number generator (PRNG) and takes the result modulo 10 to get a value between 0 and 9. The random number generator is the standard linear congruential algorithm used by the Borland C/C++ compiler:


unsigned int rand()
{
    r2 = 22695477 * r2 + 1;
    return r2 >> 16;
}

The initial value of r2 is virtually unpredictable:


GetSystemTimeAsFileTime(&SystemTimeAsFileTime);
GetVolumeInformationA(0, 0, 4u, &VolumeSerialNumber, 0, 0, 0, 0);
r2 = (VolumeSerialNumber \
      ^ SystemTimeAsFileTime.dwHighDateTime \
      ^ GetTickCount()) & 0x7FFFFFFF;

DGA

The generated domains, along with the port 443 and an unknown constant 2, are stored 69 bytes apart in memory (called target in the next graph). In total 10 domains are generated:

Loop that generates 10 domains
Loop that generates 10 domains (click to enlarge)

At offset 0x040A0FC a random string is generated based on the seed, i.e., number of weeks. We will come back to this routine later. The random string is repeated once at 0x040A114; so that, for example, drs becomes drsdrs.
A random letter between "a" and "j" is then appended to the string to complete the second level domain. The picked letter is based on the unpredictable PRNG shown in the seeding section above for the first generated sld. After that, the DGA picks the remaining letters in order (offsets 0x0040A16D to 0040A175). For example, if in the first iteration the letter "c" is appended, then the following iterations append "d","e","f","g","h","j","a" and finally "b".
The top level domain is set to .ch for the first five domains, and to .biz for the remaining five domains. For any given run of the malware this will result in exactly one tld per generated second level domain per run of the malware, although a second level domain will be paired with both top level domains through additional runs of the malware.
Finally, let's come back to the random string generation routine called at 0x040A0FC:

Disassembly of the sld generation
Disassembly of the sld generation (click to enlarge)

This routine uses the seed r, i.e., the number of weeks since January 1st, 1970, to generate a random string as follows:


string = ""
DO
    string += r % 26 + 'a'
    r = r / 26            // integer division
WHILE r
string = reverse(string)

For example, as of December 20, 2016, the number of week since epoch is 2450. Dividing by 26 results in 94 with a remainder of 6, so the first letter is g. Dividing 94 by 26 results in 3 with remainder 16, so the second letter is q. Dividing 3 by 26 results in 0, so we append letter d and exit the loop. The random string gqd is reversed resulting in dqg.

Reimplementation

The following reimplementation of the DGA in Python prints all 20 potential domains for any given date. Please note that each actual run of the malware will only generate and test one domain per second level domain.


from datetime import datetime
import time
import argparse

def dga(r):
    domain = ""
    while True:
        domain += chr(r % 26 + ord('a'))
        r //= 26
        if not r:
            break
    for i in range(10):
        for tld in ['.biz', '.ch']:
            yield 2*domain[::-1] + chr(i + ord('a')) + tld

def printdomains(date):
    unixtimestamp = time.mktime(date.timetuple())
    seed = int(unixtimestamp // 60 // 60 // 24 // 7)

    for domain in dga(seed):
        print(domain)


if __name__=="__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("-d", "--date", help="date for which to generate domains")
    args = parser.parse_args()
    if args.date:
        d = datetime.strptime(args.date, "%Y-%m-%d")
    else:
        d = datetime.now()
    printdomains(d)

List of Domains

The following table lists all generated domains of the next 52 weeks. The domains are given as brace expansions, so dqgdqg{a..j}.{ch,biz} stands for 20 different domains. All times are given in CET.

start end domains
2016-12-15 01:00:00 2016-12-22 00:59:59 dqgdqg{a..j}.{ch,biz}
2016-12-22 01:00:00 2016-12-29 00:59:59 dqhdqh{a..j}.{ch,biz}
2016-12-29 01:00:00 2017-01-05 00:59:59 dqidqi{a..j}.{ch,biz}
2017-01-05 01:00:00 2017-01-12 00:59:59 dqjdqj{a..j}.{ch,biz}
2017-01-12 01:00:00 2017-01-19 00:59:59 dqkdqk{a..j}.{ch,biz}
2017-01-19 01:00:00 2017-01-26 00:59:59 dqldql{a..j}.{ch,biz}
2017-01-26 01:00:00 2017-02-02 00:59:59 dqmdqm{a..j}.{ch,biz}
2017-02-02 01:00:00 2017-02-09 00:59:59 dqndqn{a..j}.{ch,biz}
2017-02-09 01:00:00 2017-02-16 00:59:59 dqodqo{a..j}.{ch,biz}
2017-02-16 01:00:00 2017-02-23 00:59:59 dqpdqp{a..j}.{ch,biz}
2017-02-23 01:00:00 2017-03-02 00:59:59 dqqdqq{a..j}.{ch,biz}
2017-03-02 01:00:00 2017-03-09 00:59:59 dqrdqr{a..j}.{ch,biz}
2017-03-09 01:00:00 2017-03-16 00:59:59 dqsdqs{a..j}.{ch,biz}
2017-03-16 01:00:00 2017-03-23 00:59:59 dqtdqt{a..j}.{ch,biz}
2017-03-23 01:00:00 2017-03-30 01:59:59 dqudqu{a..j}.{ch,biz}
2017-03-30 02:00:00 2017-04-06 01:59:59 dqvdqv{a..j}.{ch,biz}
2017-04-06 02:00:00 2017-04-13 01:59:59 dqwdqw{a..j}.{ch,biz}
2017-04-13 02:00:00 2017-04-20 01:59:59 dqxdqx{a..j}.{ch,biz}
2017-04-20 02:00:00 2017-04-27 01:59:59 dqydqy{a..j}.{ch,biz}
2017-04-27 02:00:00 2017-05-04 01:59:59 dqzdqz{a..j}.{ch,biz}
2017-05-04 02:00:00 2017-05-11 01:59:59 dradra{a..j}.{ch,biz}
2017-05-11 02:00:00 2017-05-18 01:59:59 drbdrb{a..j}.{ch,biz}
2017-05-18 02:00:00 2017-05-25 01:59:59 drcdrc{a..j}.{ch,biz}
2017-05-25 02:00:00 2017-06-01 01:59:59 drddrd{a..j}.{ch,biz}
2017-06-01 02:00:00 2017-06-08 01:59:59 dredre{a..j}.{ch,biz}
2017-06-08 02:00:00 2017-06-15 01:59:59 drfdrf{a..j}.{ch,biz}
2017-06-15 02:00:00 2017-06-22 01:59:59 drgdrg{a..j}.{ch,biz}
2017-06-22 02:00:00 2017-06-29 01:59:59 drhdrh{a..j}.{ch,biz}
2017-06-29 02:00:00 2017-07-06 01:59:59 dridri{a..j}.{ch,biz}
2017-07-06 02:00:00 2017-07-13 01:59:59 drjdrj{a..j}.{ch,biz}
2017-07-13 02:00:00 2017-07-20 01:59:59 drkdrk{a..j}.{ch,biz}
2017-07-20 02:00:00 2017-07-27 01:59:59 drldrl{a..j}.{ch,biz}
2017-07-27 02:00:00 2017-08-03 01:59:59 drmdrm{a..j}.{ch,biz}
2017-08-03 02:00:00 2017-08-10 01:59:59 drndrn{a..j}.{ch,biz}
2017-08-10 02:00:00 2017-08-17 01:59:59 drodro{a..j}.{ch,biz}
2017-08-17 02:00:00 2017-08-24 01:59:59 drpdrp{a..j}.{ch,biz}
2017-08-24 02:00:00 2017-08-31 01:59:59 drqdrq{a..j}.{ch,biz}
2017-08-31 02:00:00 2017-09-07 01:59:59 drrdrr{a..j}.{ch,biz}
2017-09-07 02:00:00 2017-09-14 01:59:59 drsdrs{a..j}.{ch,biz}
2017-09-14 02:00:00 2017-09-21 01:59:59 drtdrt{a..j}.{ch,biz}
2017-09-21 02:00:00 2017-09-28 01:59:59 drudru{a..j}.{ch,biz}
2017-09-28 02:00:00 2017-10-05 01:59:59 drvdrv{a..j}.{ch,biz}
2017-10-05 02:00:00 2017-10-12 01:59:59 drwdrw{a..j}.{ch,biz}
2017-10-12 02:00:00 2017-10-19 01:59:59 drxdrx{a..j}.{ch,biz}
2017-10-19 02:00:00 2017-10-26 01:59:59 drydry{a..j}.{ch,biz}
2017-10-26 02:00:00 2017-11-02 00:59:59 drzdrz{a..j}.{ch,biz}
2017-11-02 01:00:00 2017-11-09 00:59:59 dsadsa{a..j}.{ch,biz}
2017-11-09 01:00:00 2017-11-16 00:59:59 dsbdsb{a..j}.{ch,biz}
2017-11-16 01:00:00 2017-11-23 00:59:59 dscdsc{a..j}.{ch,biz}
2017-11-23 01:00:00 2017-11-30 00:59:59 dsddsd{a..j}.{ch,biz}
2017-11-30 01:00:00 2017-12-07 00:59:59 dsedse{a..j}.{ch,biz}
2017-12-07 01:00:00 2017-12-14 00:59:59 dsfdsf{a..j}.{ch,biz}

Actions taken

To prevent that the Tofsee botnet operators are able to abuse the Swiss domain name space (ccTLD .ch) for hosting their botnet Command&Control infrastructure (C&C), we have discussed further actions with the registry of ccTLD .ch (SWITCH). Together with SWITCH and the Registrar of Last Resort (RoLR), all possible DGA domain name combinations have been set to non registrable at the registry level. It is therefore not possible to register any of the DGA domain names for the next 12 months.

Share on Twitter Share on Facebook

When Mirai meets Ranbyus

When we read the blog post about Mirai’s new DGA feature, a recommended read, we decided to try to re-implement its DGA. This was quite a challenge, as we’re not too familiar with the MIPS architecture and could not easily find any x86 sample with DGA.

The main DGA generation loop did not look too difficult to implement. Here is a copy of it, with some register renaming we did to facilitate a Python implementation:

Mirai DGA loop
Mirai DGA loop(click to enlarge)

We were initially surprised by the observation that, while we see 0x61 (a lowercase ‘a’) added at the end, we don’t seem to see any modulo 26 operation, so we would expect the base name to contain non-printable characters. Nevertheless, we tried to mimic the code instruction by instruction in Python; after repeatedly fixing typos, we came up with the following abomination of code:

def calcDGA(day,month,year,seed=0):
    msk32 = 0xffffffff
    yearVal = (year*8) & msk32
    yearValB = 0x3ffff00 # lowest byte is irrelevant
    monValB = 0x51eb851f # “|month” can be ignored
    msk1 = 0xfffffff0
    msk2 = 0xfffffffe
    res = ""
    
    for i in xrange(12):
        daySeed = day ^ seed
        yearVal -= year
        yearVal &= msk32
        monMask = month & msk2
        month2 = (month * 4) & msk32
        yearVal ^=  year
        month2 ^=  month
        dayMask = day & 0x1fff
        month = (monMask << 4) & msk32
        monVal = year & msk1
        monMask = (monMask*2) & msk32
        daySeed = (daySeed*4) & msk32
        month = (month-monMask) & msk32
        monVal = (monVal << 17) & msk32
        yearVal = yearVal >> 11
        dayMask  ^=  daySeed
        month2 >>= 8
        year = monVal ^ yearVal
        dayMask = (dayMask<<4) & msk32
        yearVal = (day >> 15) & msk32
        month ^=  month2
        day = yearVal ^ dayMask        
        monMask = year ^ month ^ day
        dayMask = ((monMask * monValB) >> 32) & msk32
        dayMask >>= 3    
        yearVal = ((seed*8 + day) << 8) & yearValB
        monVal = seed >> 6
        seed = monVal ^ yearVal
        monVal = (dayMask << 3) & msk32
        yearVal = (dayMask << 5) & msk32
        yearVal = (yearVal - monVal + dayMask) & msk32
        monMask = (monMask - yearVal) & msk32
        monMask = (monMask + 0x61) & msk32
        res += chr(monMask & 0xff)
        yearVal = (year * 8) & msk32
    
    suffix = ""
    if day&1 == 0:
        suffix = "online"
    if day%3 == 0:
        suffix = "tech"
    if day%4 == 0:
        suffix = "support"
    return res,suffix

Yes, this re-implementation is ugly – more than ugly. But surprisingly it produces printable output, as you can easily try it out yourself. While playing with it, we noticed that the letter ‘z’ seems to be absent – only 25 letters of the possible 26 appear. This already triggered some memories, but unfortunately we did not follow them yet. So, where hides the mapping to lowercase letters? The solution lies in the variable monValB, which contains 0x51eb851f, approximating 8/25 modulo 32 (0x51eb851f*25 = 0x8’0000’0007); the subsequent division by 8 (right-shifting of 3 bits) results in a division by 25 (stored in dayMask) – the compiler seems to avoid actual division instructions. Just after that, dayMask is again multiplied by 25, which is done by shifting it left 5 bits (multiplication with 32), shifting the same value once more by 3 bits (multiplication with 8), taking the difference (multiplication with 32-8=24), and finally adding dayMask once more (multiplication with 25). So, the compiler optimizes multiplications as well. After a final subtraction, that results in a “modulo 25”, and then 0x61 is added – note that “z” can never be produced this way, as suspected. Mystery solved.

Actually we see a similar trick later on in the code, the multiplication with 0xAAAAAAAB is in fact an approximation of 2/3 and used to calculate the modulo 3 value in the TLD selection code.

Now we tried to beautify the code, and after several copy and paste steps in Python, we reached the following:

def calcDGA(day,month,year,seed=0):
    msk32 = 0xffffffff
    
    monVal = 0x51eb0000 + month
    yearVal = (year*8) & msk32
    yearValB = 0x3ffff00
    
    msk1 = 0xfffffff0
    msk2 = 0xfffffffe
    
    res = ""
    
    for i in xrange(12):
        yearVal = (((yearVal - year) ^ year) & msk32) >> 11
        year = (((year & msk1) << 17) & msk32)  ^ yearVal
        dayMask = (((day & 0x1fff) ^ (((day ^ seed)*4) & msk32))<<4)&msk32
        yearVal = (day >> 15) & msk32
        month =  ((14*(month&msk2))&msk32)^((month^((month*4)&msk32)) >> 8)
        day = yearVal ^ dayMask
        monMask = year ^ month ^ day
        yearVal = ((seed*8 + day) << 8) & yearValB
        monVal = seed >> 6
        seed = monVal ^ yearVal
        res += chr(monMask % 25 + 0x61)
        yearVal = (year * 8) & msk32
    
    suffix = ""    
    if day%2 == 0:
        suffix = "online"
    if day%3 == 0:
        suffix = "tech"
    if day%4 == 0:
        suffix = "support"
    return res,suffix

Which still works fine. But during the modification of this code, it started to look more and more like a DGA we already knew – especially after we saw the multiplication with 14. Actually, it looked very similar to the Ranbyus DGA:

FOR i = 0 TO 13
     day = (day >> 15) ^ 16 * (day & 0x1FFF ^ 4 * (seed ^ day))
     year = ((year & 0xFFFFFFF0) << 17) ^ ((year ^ (7 * year)) >> 11)
     month = 14 * (month & 0xFFFFFFFE) ^ ((month ^ (4 * month)) >> 8)
     seed = (seed >> 6) ^ ((day + 8 * seed) << 8) & 0x3FFFF00
     domain[i] = ((day ^ month ^ year) % 25) + 'a'

We gave it a shot, and tried the C code. The only slight modifications required were to change the length of the base names from 14 down to 12, and the different selection of TLDs. Also, dga.c produces 30 domains for one day, while Mirai only uses the first one of them. Anyway, the code indeed produces the same base names:

Date       | Ranbyus DGA        | Mirai Base   | Mirai DGA

2016-12-04 | vmdefmnsndojtu.tw  | vmdefmnsndoj | vmdefmnsndoj.tech
2016-12-05 | xpknpxmywqsrce.net | xpknpxmywqsr | xpknpxmywqsr.support
2016-12-06 | lvfjcwwobycjik.com | lvfjcwwobycj | -
2016-12-07 | nympompksmfxsf.pw  | nympompksmfx | -
2016-12-08 | kedbuffigfjsfq.in  | kedbuffigfjs | kedbuffigfjs.online
2016-12-09 | pjapobyvmsnesx.me  | pjapobyvmsne | pjapobyvmsne.support
2016-12-10 | gjnhvqeopqkjjf.cc  | gjnhvqeopqkj | -
2016-12-11 | lokwpdhjqhcvtn.su  | lokwpdhjqhcv | -
2016-12-12 | frwhojupdcrcat.tw  | frwhojupdcrc | -
2016-12-13 | hufuhuerwgsigx.net | hufuhuerwgsi | -
2016-12-14 | bwhrdaumwuvnrb.com | bwhrdaumwuvn | bwhrdaumwuvn.support
2016-12-15 | daldkougomttqb.pw  | daldkougomtt | -
2016-12-16 | gjuyvvwwmbwvdn.in  | gjuyvvwwmbwv | -
2016-12-17 | lgigrtmnssejtd.me  | lgigrtmnssej | -
2016-12-18 | vsuuqkqbavvaiu.cc  | vsuuqkqbavva | -
2016-12-19 | bpmsfckfkrprom.su  | bpmsfckfkrpr | bpmsfckfkrpr.support
2016-12-20 | oornsduuwjlifv.tw  | oornsduuwjli | oornsduuwjli.tech
2016-12-21 | qjqubpciajocbs.net | qjqubpciajoc | qjqubpciajoc.tech
2016-12-22 | exvdaajegjuror.com | exvdaajegjur | exvdaajegjur.support
2016-12-23 | gsqvwjkbmwadex.pw  | gsqvwjkbmwad | -
2016-12-24 | poorcetnmjfchv.in  | poorcetnmjfc | poorcetnmjfc.online
2016-12-25 | ulficqrujekplt.me  | ulficqrujekp | ulficqrujekp.support
2016-12-26 | ltwehsdfhkegkh.cc  | ltwehsdfhkeg | -
2016-12-27 | qqnbfqrdjyjkqb.su  | qqnbfqrdjyjk | qqnbfqrdjyjk.support
2016-12-28 | xtldkfvredxhll.tw  | xtldkfvredxh | -
2016-12-29 | aojpocslpwsuik.net | aojpocslpwsu | aojpocslpwsu.support
2016-12-30 | tyxdowioaygktb.com | tyxdowioaygk | -
2016-12-31 | vtrndmhsgadage.pw  | vtrndmhsgada | vtrndmhsgada.online

You can find a longer list of domains here: Mirai DGA botnet domains

As described in the above blog post, the DGA is not always applied. A wrapping code checks if the current date is in November or start of December (1st to 3rd); on these days the DGA is turned off, on all other days turned on. But the programmers forgot to consider the year in their code: it will turn off the DGA again in November 2017 for another month. So this DGA selection code is a bit buggy. Maybe the programmers don’t plan to use the DGA for such a long time – for sure there will be other samples out until then.

Please note that our interpretation of the TLD selection algorithm differs from the one described in the blog post: the code ends in three separate if statements, not in one combined if-elif-elif construct. It actually checks whether the residual value is divisible by 2, 3, or 4. However, there are cases where neither matches, e.g., 0xef2f25d5 representing December 12th, and no valid domain is produced by the algorithm. This bug is not too critical though: at some later day, a valid domain will be generated anyway.

Also interesting is the distribution of the 3 TLDs support, tech, online and the days where no TLD can be generated. Theoretically one would expect 25% “online”, 25% “support”, 16.6% “tech”, and 33.3% unassigned days. But, as pointed out in the comments of the blog post, the relevant residual value can only take one of 31 values (because only the day of the month and initial seed determine it), and so it is not so surprising that the distribution is unusual. Consequently, the TLD pattern is repeating for every month.

Share on Twitter Share on Facebook

SMS spam run targeting Android Users in Switzerland

MELANI / GovCERT.ch received several reports today about malicious SMS that have been sent to Swiss mobile numbers. The SMS is written in German and claims to come from the Swiss Post. But in fact, the SMS has been sent by hackers with the aim to infect Smartphones in Switzerland with a Trojan horse.

Malicious SMS
Malicious SMS pretends to come from the Swiss Post (click to enlarge)

The SMS contains a link to a website. If the user clicks on the link in the SMS, he will get redirected to a hijacked website that hosts an App that installs malware on the victims Smartphone. As the served file is an Android application package (APK), only Android users are affected by this threat.

By default, Google does not allow Apps from 3rd parties (such as 3rd party App stores or from the internet) to be installed. However, the user has the possibility of allowing the installation of 3rd party Apps by changing the Android Security settings. In most cases, users do not change theses settings, so common Android users should be safe. Yet there were some articles in some Swiss newspapers this week that showed its readers how to enable the installation of Android Apps from 3rd party (aka “unknown sources”) in order to install the new Nintendo game Pokemon GO, as the App isn't in the Swiss version of the Google Play Store yet. Even before the launch of the game in Switzerland, the App went viral and obviously many Android users in Switzerland wanted to access the game before the launch of the App in the Swiss App store. As a result of this, some Android users may followed the instructions of the Swiss news papers and have enabled the installation of Apps from 3rd parties, making themselves vulnerable to this type of attack.

Malicious Swiss Post Android App
Malicious Swiss Post Android App (click to enlarge)

The App requests permission to erase all data on the victims phone (see screenshot above). In addition, it calls out to a botnet command&control server (C&C) in order to receive further commands from the attackers. According to FireEye, the App is part of a larger cybercrime operation with the aim of stealing login credentials of popular Apps such as Uber, Viber and Facebook (phishing / Smishing).

In the last SMS spam campaign we have observed in Switzerland a few weeks ago, we noticed that the malicious App has been downloaded more than 15'000 times.

In general, we highly recommend Android users to disable the installation of 3rd party Apps from unknown sources. To ensure that the installation of 3rd party Apps is disabled, go to settings -> Security on your Android device and make sure that the option Unknown Sources is disabled:

Malicious Swiss Post Android App
Malicious Swiss Post Android App (click to enlarge)

We recommend to never change this setting, even when you are instructed by to do so (as strangers may try to convince you to do so in order to place malware on your smartphone).

Indicator of Compromise

Android APK download URL:

hXXp://ieej.lv/swissp
hXXp://riorancholeakletter.com/sp.apk

Android APK (malware):

Filename: sp.apk
MD5 hash: c121a1ae8a4ee564fd6bd079ad5d3373

Android malware botnet C&C:

hXXp://85.93.5.146/?action=command Share on Twitter Share on Facebook

Show older posts (blog index)