Trickbot tricks again

After the disruption of the Trickbot botnet by Microsoft and “allies”, it had become quiet about Trickbot. At least for a couple of days. Just a few days after the partial takedown, comrade Emotet came to the rescue and executed Trickbot on infected systems [1] [2].

Afterwards Trickbot activities continued to rise, as you can see in various press reports. [3] [4] [5].

If you work on Trickbot, especially extracting Trickbot configs, you probably haven’t missed that something has changed in the last weeks. I recently downloaded some newer samples from Malware Bazaar and was wondering, why my config extraction did not work. So I started debugging my code and fixed it but something was strange.

Let’s take 6ca141e8ed2443113c9e497d231b93cf41d86b224993c48f589b375a830cd27c for example.

The extracted config looks like this:

<mcconf>
    <ver>100001</ver>
    <gtag>rob3</gtag>
    <servs>
        <srva>165.226.231.80:1273</srva>
        <srva>168.140.17.62:39938</srva>
        <srva>194.255.156.239:25317</srva>
        <srva>96.139.163.83:10616</srva>
        <srva>171.138.104.153:58232</srva>
     </servs>
     <autorun>
        <module name="pwgrab"/>
     </autorun>
</mcconf>

Normally, the port number looks different and there are usually much more C2’s inside the config. When I looked into a sandbox report, I could also see a C2 IP which is not included in the config.

I searched for existing blog posts or tweets about that and finally stumbled across a tweet from hatching and I knew, I have some work to do.



As usual, the Trickbot config blob is XOR and AES encrypted so we have to find new functions which are executed after the config decryption and ideally taking the config decryption output as an argument. If you are using BinDiff, you should be able to spot it quite fast, just watch out for this beauty:


I reimplemented the function in python and it looks like its working fine.

# e.g. 165.226.231.80
def convert_to_real_ip(ip_str):

    result_octets = []
    octets = ip_str.split(".")
    o1 = int(octets[0])
    o2 = int(octets[2])
    o3 = int(octets[3])
    o4 = int(octets[1])
    x = ((~o1 & 0xFF) & 0xb8 | (o1 & 0x47)) ^ ((~o2 & 0xFF) & 0xb8 | (o2 & 0x47))
    result_octets.append(str(x))
    o = (o3 & (~o2 & 0xFF)) | ((~o3 & 0xff) & o2)
    result_octets.append(str(((~o & 0xff) & o4) | (o & (~o4 & 0xff))))
    result_octets.append(str(o))
    result_octets.append(str(((~o2 & 0xFF) & o4) | ((~o4 & 0xff) & o2)))

    return ".".join(result_octets) + ":443"


fake_ips = ["165.226.231.80", "168.140.17.62", "194.255.156.239", "96.139.163.83", "171.138.104.153"]

for item in fake_ips:
    print(convert_to_real_ip(item))

The output and thus the correct C2 IPs look as follows:

66.85.183.5:443
185.163.47.157:443
94.140.115.99:443
195.123.240.40:443
195.123.241.226:443

I hardcoded port 443 into my function because I could also see it hardcoded in the Trickbot code but there might be other ports in the future as well. So far I only found the group tags rob3 and tar2 with identical configs using those fake IPs.

If any of you have more samples using fake C2 IPs with other group tags or other configs, I would appreciate a hint.

Sources:
[1] https://public.intel471.com/blog/trickbot-online-emotet-microsoft-cyber-command-disruption-attempts/
[2] https://duo.com/decipher/trickbot-up-to-its-old-tricks
[3] https://us-cert.cisa.gov/ncas/alerts/aa20-302a
[4] https://www.zdnet.com/article/fbi-warning-trickbot-and-ransomware-attackers-plan-big-hit-on-us-hospitals/
[5] https://www.securityweek.com/ryuk-ransomware-attacks-continue-following-trickbot-takedown-attempt

IOCs:

66.85.183.5:443
185.163.47.157:443
94.140.115.99:443
195.123.240.40:443
195.123.241.226:443

6ca141e8ed2443113c9e497d231b93cf41d86b224993c48f589b375a830cd27c
fc3da2468a121aff5433ea738221b5e9fd962c87041654b2c88f5291e0e15f22

Article Link: https://github.com/lazydaemon/trickbot/malware_analysis/reverse_engineering/2020/11/17/trickbots-latest-trick.html