Guildma is now using Finger and Signed Binary Proxy Execution to evade defenses

Published: 2021-05-31
Last Updated: 2021-06-01 11:00:57 UTC
by Renato Marinho (Version: 1)
0 comment(s)

 

We recently identified a new Guildma/Astaroth campaign targeting South America, mainly Brazil, using a new variant of the malware. Guildma is known by its multiple-staged infection chain and evasion techniques to reach victim’s data and exfiltrate them. In a previous diary [1] at Morphus Labs, we analyzed a Guildma variant which employed an innovative strategy to stay active, using Facebook and YouTube to get a new list of its C2 servers.

The innovation this time is the use of Finger, an old service designed to retrieve information about a particular user or host on a network but employed by Guildma to retrieve the command that will download and start the new victim’s computer infection. In addition, Guildma is bringing its own legit binary to the victim’s machine to employ a technique named Signed Binary Proxy Execution, reducing the chances of being detected.

In today’s diary, check the results of the analysis of this new variant along with MITRE ATT&CK TTPs and IOCs. To start, look at Figure 1. This is the traffic generated by the new variant while contacting attackers’ Finger server and receiving back the malicious command to be executed.

Figure 1 – Guilma traffic while contacting attackers’ Finger server

 

Threat Analysis

                

Figure 2 – New Guildma variant analysis

               The ongoing campaign starts with an e-mail phishing with a link to a ZIP file which contains an LNK. If the user executes the LNK file, instead of opening a supposed PDF with a proof of payment (Comprovante.pdf7.lnk), it will execute Windows native binary Finger.exe do retrieve the malicious command from attacker’s server on port TCP/79 and pass it to ‘cmd’ to get it executed.

                The malicious LNK file is prepared to ‘cmd.exe’ with an obfuscated argument, as seen in Figure 3.

Figure 3 – LNK content

               Analyzing the environment variables created by the above argument, it is possible to see the arguments which will be passed to ‘cmd.exe’. Surprisingly, it calls finger.exe, a native Windows binary to an old service, and pipes its results to a new cmd, as seen in Figure 4.

Figure 4 – Deobuscated arguments

               The result of the finger execution is another obfuscated command with a list of environment variables, as seen in Figure 5.

Figure 5 – Result of finger execution

               Once executed, the above command will create a JS file containing a VB Script on “%Public%\Videos\” and execute it. This execution will result in five more files downloaded and stored into a random path into Videos, as seen in Figure 6. The download is performed using the legitimate binary bitsadmin.exe.

Figure 6 – JS and random directory created by Guildma to store malicious artifacts

               The downloaded files are listed in Figure 7.

Figure 7 – Downloaded artifacts

               The ‘ctfmon.exe’, despite the name, is in fact, a copy of a legitimate binary named ‘coregen.exe’ which is part of Microsoft Silverlight product, as seen in Figure 8.

Figure 8 – ‘coregen.exe’ legitimate binary brought over by the attackers

               The ‘coregen.exe’ binary is used to load ‘helper.dll’ in a technique named Signed Binary Proxy Execution (T1218) [2]. It is like DLL Side Loading attack, but here the DLL name is passed as argument, as seen in Figure 9. In other words, the attacker is bringing the ‘coregen.exe’ legitimate binary to the victim’s machine and using it as a rundll32 to have its malicious DLL loaded into it as a strategy to evade security controls.

Figure 9 – Coregen.exe used to load malicious DLL

               This type of misuse of ‘coregen.exe’ is mapped by Stronic [3], as seen in Figure 10.

Figure 10 – Possible misuse of ‘coregen.exe’ by Stronic

 

Once loaded, the ‘helper.dll’ will decrypt and load the other DLLs ‘log32.dll’ and ‘log33.dll’ previously downloaded. In the Figure 11 I highlight the routing which decrypts the DLL contents.

 

Figure 11 – Log32.dll decrypt routine

And finally, once loaded, Log32.dll will perform multiple anti-debugging, anti-vm and a series of system verification, like keyboard type and system language, the presence of a DLL belonging to Diebold Warsaw (wslbscr32.dll), before unpacking and launching information stealer procedures.

Final Considerations

Reflecting on the use of Finger on this new variant, a possible reason that came to my mind was the attempt to bypass security filters that are usually applied to the HTTP/HTTPS traffic. Even employees in home office, may have some type of web browsing filter applied by the company, like web proxies. However, it may not be so common for home firewalls to make a more restrictive Internet outgoing filter, preventing, for example, the exit to the TCP/79 port. In the end, as much as the content travels in clear text on Finger, the attacker may end up having more luck with this strategy than if he used the most common path.

Finally, it is interesting to highlight the use of Signed Binary Proxy Execution technique by the new Guildma variant. Binaries signed with trusted digital certificates can execute on Windows systems protected by digital signature validation – specially those signed by Microsoft, as ‘coregen.exe’.

There are mitigations and detection strategies for Signed Binary Proxy Execution mapped on MITRE ATT&CK [2] which include restricting the execution of particularly vulnerable binaries to privileged accounts that need to use them and establish a baseline for processes and command line parameters for signed binaries to monitor and spot uncommon usage. There is a great project named LOLBAS [5] (Living Off The Land Binaries and Scripts) which maps ‘coregen.exe’ and other binaries that could be abused in a similar way.

References

[1] https://isc.sans.edu/diary/Guildma+malware+is+now+accessing+Facebook+and%A0YouTube+to+keep+up-to-date/25222

[2] https://attack.mitre.org/techniques/T1218/

[3]https://strontic.github.io/xcyclopedia/library/coregen.exe-3BF709AEDF5042C39515756FB72E9EC0.html

[4]https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/finger

[5] https://github.com/LOLBAS-Project/LOLBAS

 

IOCs

Category

Type

Value

Comment

Artifacts dropped

sha256

412a6b755b2029126d46e7469854add3faa850f5a4700dd1e078fcc536ca418a

ctfmon.exe (coregen.exe) - legitimate file being used to start malicious helper.dll

Artifacts dropped

sha1

5f536e6701d928dd262d475cd6987777b9fa5e33

ctfmon.exe (coregen.exe) - legitimate file being used to start malicious helper.dll

Artifacts dropped

md5

3bf709aedf5042c39515756fb72e9ec0

ctfmon.exe (coregen.exe) - legitimate file being used to start malicious helper.dll

Artifacts dropped

sha256

4fe8e09c61858df60222c5188af91b934d1358ee802d6dc06b4a25e162a71413

helper.dll

Artifacts dropped

sha1

cc19f43dbc98a5f471bb9fc926da6e9b190a925c

helper.dll

Artifacts dropped

md5

1d270124b1e61f21eed666afc4e60d9a

helper.dll

Artifacts dropped

sha256

7889a7cc80dabc034cd02a3667e1f0028332669ca5ccf9a66b4f853064968158

log32.dll

Artifacts dropped

sha1

883bba850a4a6b84bb734841de823c25e09cc4dd

log32.dll

Artifacts dropped

md5

ea6ebcf305585d692fc4d519c94ed215

log32.dll

Artifacts dropped

sha256

5abfff61dcde664006db334859055d22da3b419e2fa2ae734bec48688c564dea

log33.dll

Artifacts dropped

sha1

6aa3cd190f670671c2a93076dc1a77a551dfc3d3

log33.dll

Artifacts dropped

md5

126058c017ca37541da16c5ab6d91257

log33.dll

Payload installation

sha256

9f61fc62aa9734406c164decc00f9c027574c4c5f6865d5fb297fb431f75c3bb

Rt6.js

Payload installation

sha1

77f1cc8b7ce1cbffe91f050cb1e7f790de62e257

Rt6.js

Payload installation

md5

50222aecc6a722564bb5844fa07af4d0

Rt6.js

Network activity

ip-dst

45.79.215.94

 

Network activity

domain

martin21.xyz

 

Network activity

domain

martin23.xyz

 

Network activity

domain

martin24.xyz

 

Network activity

domain

martin05.xyz

 

Network activity

domain

martin17.xyz

 

Network activity

domain

martin27.xyz

 

Network activity

domain

martin06.xyz

 

Network activity

domain

martin03.xyz

 

Network activity

domain

martin04.xyz

 

Network activity

domain

martin02.xyz

 

Network activity

domain

martin01.xyz

 

Network activity

domain

martin08.xyz

 

Network activity

domain

martin07.xyz

 

Network activity

domain

martin10.xyz

 

Network activity

domain

martin11.xyz

 

Network activity

domain

go8357.xyz

 

Network activity

domain

alinester07.xyz

 

Network activity

domain

martin19.xyz

 

Network activity

domain

martin18.xyz

 

Network activity

domain

martin16.xyz

 

Network activity

domain

martin15.xyz

 

Network activity

domain

martin14.xyz

 

Network activity

domain

martin13.xyz

 

Network activity

domain

martin12.xyz

 

Network activity

domain

martin31.xyz

 

Network activity

domain

martin30.xyz

 

Network activity

domain

martin29.xyz

 

Network activity

domain

martin28.xyz

 

Network activity

domain

martin26.xyz

 

Network activity

domain

martin25.xyz

 

Network activity

domain

martin22.xyz

 

--

Renato Marinho
Morphus Labs| LinkedIn|Twitter

Keywords:
0 comment(s)

Quick and dirty Python: nmap

Published: 2021-05-31
Last Updated: 2021-05-31 19:20:50 UTC
by Rick Wanner (Version: 1)
0 comment(s)

Continuing on from the "Quick and dirty Python: masscan" diary, which implemented a simple port scanner in Python using masscan to detect web instances on TCP ports 80 or 443.  Masscan is perfectly good as a blunt instrument to quickly find open TCP ports across large address spaces, but for fine details it is better to use a scanner like nmap that, while much slower, is able to probe the port to get a better idea of what is running.

First lets backtrack.  Since the previous diary, I converted the masscan code to a function and created another function to parse the masscan results to return the list of IPs on which masscan detected open ports.  The current script scan_web.py script is:

#!/usr/local/bin/python3
import sys,getopt,argparse
import masscan
import pprint

def scan_masscan(ips):

   try:
      maso = masscan.PortScanner()
      maso.scan(ips, ports='80,443')
   except:
      print("Error:", sys.exc_info()[0])
      sys.exit(1)

   return(maso)

def parse_masscan_host_list(massout):

    #initialize
    host_list = list()

    # Build a list from the massscan output
    for host in massout.all_hosts:
       host_list.append(host)
    return(host_list)

def main():
   # read in the IP parameter 
   parser = argparse.ArgumentParser()
   parser.add_argument('IP', help="IP address or range")
   args=parser.parse_args()
   ip=args.IP

   maso=scan_masscan(ip)

   if int(maso.scanstats['uphosts']) > 0:
      host_list=parse_masscan_host_list(maso)
      pprint.pprint(host_list)

   else:
      print("No Masscan results")
      sys.exit(1)

if __name__ == "__main__":
    main()

Running the script results in a list of IPs where either 80 or 443 were detected open by masscan.

# ./scan_web.py 45.60.103.0,45.60.31.34,1.2.3.4
[2021-05-31 18:28:51,335] [DEBUG] [masscan.py 10 line] Scan parameters: "masscan -oX - 45.60.103.0,45.60.31.34,1.2.3.4 -p 80,443"
['45.60.103.0', '45.60.31.34']

Extending this script to pass the masscan output list to nmap is relatively easy as well.  As somebody pointed out on a comment to the last diary, there are a lot of Python nmap modules and they all provide differing functionality.  After messing with a few of them, as the comment stated, the libnmap module appears to be the most functional and easiest to use.  libnmap does not implement nmap functionality, it needs nmap already installed on the device and interfaces with that version.  I will not be going over nmap functionality in this diary. If you are not clear on the nmap command parameters you can find a quick tutorial in this older diary.

To implement the nmap scan will require two functions.  One to run the scan, and one to parse the results.

The scanning function:

def scan_nmap(ip_list):
    print("Starting nmap for: {0}".format(ip_list))
    nm = NmapProcess(ip_list, options="-Pn -n -A -sT -p80,443 -r --max-retries 2 --host-timeout 2h --open --reason")
    nrc = nm.run()
    if nrc != 0:
        print("nmap scan failed: {0}".format(nm.stderr))
        exit(0)
    try:
        nparse = NmapParser.parse(nm.stdout)
    except NmapParserExcetion as e:
        print("Exception raised while parsing scan: {0}".format(e.msg))

    return(nparse)

and the function to parse and output the scan result.  This example is almost verbatim from the libnmap documentation.

def print_nmap(nmap_report):
    print("Starting Nmap {0} ( http://nmap.org ) at {1}".format(
        nmap_report.version,
        nmap_report.started))

    for host in nmap_report.hosts:
        if len(host.hostnames):
            tmp_host = host.hostnames.pop()
        else:
            tmp_host = host.address

        print("Nmap scan report for {0} ({1})".format(
            tmp_host,
            host.address))
        print("Host is {0}.".format(host.status))
        print("  PORT     STATE         SERVICE")

        for serv in host.services:
            pserv = "{0:>5s}/{1:3s}  {2:12s}  {3}".format(
                    str(serv.port),
                    serv.protocol,
                    serv.state,
                    serv.service)
            if len(serv.banner):
                pserv += " ({0})".format(serv.banner)
            print(pserv)
    print(nmap_report.summary)

The output from the finished script is:

# ./scan_web.py 45.60.103.0,45.60.31.34,1.2.3.4
[2021-05-31 19:00:56,329] [DEBUG] [masscan.py 10 line] Scan parameters: "masscan -oX - 45.60.103.0,45.60.31.34,1.2.3.4 -p 80,443"
Starting nmap for: ['45.60.103.0', '45.60.31.34']
Starting Nmap 7.91 ( http://nmap.org ) at 1622487670
Nmap scan report for 45.60.103.0 (45.60.103.0)
Host is up.
  PORT     STATE         SERVICE
   80/tcp  open          http
  443/tcp  open          https
Nmap scan report for 45.60.31.34 (45.60.31.34)
Host is up.
  PORT     STATE         SERVICE
   80/tcp  open          http
  443/tcp  open          https
Nmap done at Mon May 31 19:01:49 2021; 2 IP addresses (2 hosts up) scanned in 40.03 seconds

In about 80 lines of python code. I have implemented a simple script that can quickly scan a large address space using the very quick masscan and then send the output to nmap to do detailed scanning of a single port. This script is the basic framework I use for dozens of scripts to scan an entire ASN looking for devices that may be at risk for the current vulnerability of the week.

The final version of the scan_web.py script is:

#!/usr/local/bin/python3
import sys,getopt,argparse
import masscan
from libnmap.process import NmapProcess
from libnmap.parser import NmapParser, NmapParserException
import pprint

def scan_masscan(ips):

   try:
      maso = masscan.PortScanner()
      maso.scan(ips, ports='80,443')
   except:
      print("Error:", sys.exc_info()[0])
      sys.exit(1)

   return(maso)

def parse_masscan_host_list(massout):

    #initialize
    host_list = list()

    # Build a list from the massscan output
    for host in massout.all_hosts:
       host_list.append(host)
    return(host_list)

def scan_nmap(ip_list):
    print("Starting nmap for: {0}".format(ip_list))
    nm = NmapProcess(ip_list, options="-Pn -n -A -sT -p80,443 -r --max-retries 2 --host-timeout 2h --open --reason")
    nrc = nm.run()
    if nrc != 0:
        print("nmap scan failed: {0}".format(nm.stderr))
        exit(0)
    try:
        nparse = NmapParser.parse(nm.stdout)
    except NmapParserExcetion as e:
        print("Exception raised while parsing scan: {0}".format(e.msg))
    pprint.pprint(nparse)
    return(nparse)

def print_nmap(nmap_report):
    print("Starting Nmap {0} ( http://nmap.org ) at {1}".format(
        nmap_report.version,
        nmap_report.started))

    for host in nmap_report.hosts:
        if len(host.hostnames):
            tmp_host = host.hostnames.pop()
        else:
            tmp_host = host.address

        print("Nmap scan report for {0} ({1})".format(
            tmp_host,
            host.address))
        print("Host is {0}.".format(host.status))
        print("  PORT     STATE         SERVICE")

        for serv in host.services:
            pserv = "{0:>5s}/{1:3s}  {2:12s}  {3}".format(
                    str(serv.port),
                    serv.protocol,
                    serv.state,
                    serv.service)
            if len(serv.banner):
                pserv += " ({0})".format(serv.banner)
            print(pserv)
    print(nmap_report.summary)

def main():
   # read in the IP parameter 
   parser = argparse.ArgumentParser()
   parser.add_argument('IP', help="IP address or range")
   args=parser.parse_args()
   ip=args.IP

   maso=scan_masscan(ip)

   if int(maso.scanstats['uphosts']) > 0:
      host_list=parse_masscan_host_list(maso)
      nreport = scan_nmap(host_list)
      print_nmap(nreport)
   else:
      print("No Masscan results")
      sys.exit(1)

if __name__ == "__main__":
    main()

 

Caveat1: Never scan an IP range you don't have permission to scan.  While port scanning is not illegal in most jurisdictions it is questionable ethically to scan things you don't own or have permission to scan.

Caveat2: I am not a professional Python programmer.  My scripting gets the job done that I need it to do.  I know there are many smart people out there who can write way better code than I can. 

-- Rick Wanner MSISE - rwanner at isc dot sans dot edu - Twitter:namedeplume (Protected)

Keywords: masscan nmap Python
0 comment(s)

Comments


Diary Archives