<jupyter_start><jupyter_text>Ransomware2.0(C) 2021 <NAME> e <NAME> il corso di Sicurezza Informatica del Professor <NAME> UniBSDistributed under MIT licencehttps://opensource.org/licenses/MIT Caricare i files malware.zip, src_modified.zip e dataset_malwares.csvdataset: https://www.kaggle.com/amauricio/pe-files-malwares  init<jupyter_code># dipendenze

!pip install pefile
!pip install adversarial-robustness-toolbox
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf
# carica il dataset

data = pd.read_csv("dataset_malwares.csv")
print(data.shape)
mw = data[data['Malware'] == 1]
sw = data[data['Malware'] == 0]

print("Malware: {}".format(len(mw)))
print("Non malware: {}".format(len(sw)))
# carica l'eseguibile originale

!unzip "malware.zip"

import pefile
pe =  pefile.PE('Rasomware2.0.exe')
print(pe.dump_info())<jupyter_output>Archive:  malware.zip
  inflating: Rasomware2.0.exe        
----------Parsing Warnings----------

Byte 0x00 makes up 63.0068% of the file's contents. This may indicate truncation / malformation.

----------DOS_HEADER----------

[IMAGE_DOS_HEADER]
0x0        0x0   e_magic:                       0x5A4D    
0x2        0x2   e_cblp:                        0x90      
0x4        0x4   e_cp:                          0x3       
0x6        0x6   e_crlc:                        0x0       
0x8        0x8   e_cparhdr:                     0x4       
0xA        0xA   e_minalloc:                    0x0       
0xC        0xC   e_maxalloc:                    0xFFFF    
0xE        0xE   e_ss:                          0x0       
0x10       0x10  e_sp:                          0xB8      
0x12       0x12  e_csum:                        0x0       
0x14       0x14  e_ip:                          0x0       
0x16       0x16  e_cs:                          0x0       
0x18       0x18  e_lfarlc:                   [...]<jupyter_text>estrazione features dal PE file Estraiamo 51 features dal PE file<jupyter_code>def extract_features(pefile):
  dict = pefile.dump_dict()

  dos_header = np.asarray(pd.DataFrame.from_dict(dict.get('DOS_HEADER')))[2]
  file_header = np.asarray(pd.DataFrame.from_dict(dict.get('FILE_HEADER')))[2]
  optional_header = np.asarray(pd.DataFrame.from_dict(dict.get('OPTIONAL_HEADER')))[2]

  record = np.concatenate([dos_header[1:15], dos_header[16:18], dos_header[19:],
                           file_header[1:3], file_header[4:8],
                           optional_header[1:9], optional_header[10:19], optional_header[21:23],
                           optional_header[20:21], optional_header[23:]])

  return record
ransomware_x = extract_features(pe)
# convertiamo a dataframe + descrizione delle colonne

rw = pd.DataFrame([ransomware_x], columns=['e_magic', 'e_cblp', 'e_cp', 'e_crlc', 'e_cparhdr',
       'e_minalloc', 'e_maxalloc', 'e_ss', 'e_sp', 'e_csum', 'e_ip', 'e_cs',
       'e_lfarlc', 'e_ovno', 'e_oemid', 'e_oeminfo', 'e_lfanew', 'Machine',
       'NumberOfSections', 'PointerToSymbolTable', 'NumberOfSymbols', 'SizeOfOptionalHeader', 
       'Characteristics', 'Magic', 'MajorLinkerVersion', 'MinorLinkerVersion', 'SizeOfCode',
       'SizeOfInitializedData', 'SizeOfUninitializedData',
       'AddressOfEntryPoint', 'BaseOfCode', 'ImageBase', 'SectionAlignment',
       'FileAlignment', 'MajorOperatingSystemVersion',
       'MinorOperatingSystemVersion', 'MajorImageVersion', 'MinorImageVersion',
       'MajorSubsystemVersion', 'MinorSubsystemVersion', 'SizeOfHeaders',
       'CheckSum', 'SizeOfImage', 'Subsystem', 'DllCharacteristics',
       'SizeOfStackReserve', 'SizeOfStackCommit', 'SizeOfHeapReserve',
       'SizeOfHeapCommit', 'LoaderFlags', 'NumberOfRvaAndSizes'])
# relativa y

rwy = pd.DataFrame([[1]], columns=['Malware'])<jupyter_output><empty_output><jupyter_text>preprocessing<jupyter_code>X = data.drop(["Name","Malware","TimeDateStamp"], axis=1)

# teniamo le prime 51, quelle che ci interessano
X = X[X.columns[:51]]

# aggiungiamo il nuovo record al dataset
X = X.append(rw)
y = pd.DataFrame(data['Malware'])
y = y.append(rwy)

print(X.shape)
print(y.shape)
X.hist(figsize=(30,30))
plt.show()
# scaling

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler();
X_standardized = pd.DataFrame(scaler.fit_transform(X), columns=X.columns)

X_standardized.hist(figsize=(30,30))
plt.show()<jupyter_output><empty_output><jupyter_text>training del modello<jupyter_code># per risultati riproducibili
import random
import os

os.environ['PYTHONHASHSEED'] = '0'
random.seed(42)

tf.random.set_seed(42)
np.random.seed(42)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
    X_standardized, y, test_size=0.33, random_state=42
    )
print("Malware: {}".format(len(y_train == 1)))
print("Non malware: {}".format(len(y_train == 0)))
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation
from keras.utils.vis_utils import plot_model
from keras import backend as K
# From https://datascience.stackexchange.com/questions/45165/how-to-get-accuracy-f1-precision-and-recall-for-a-keras-model
# on 10th May 2020
def recall_m(y_true, y_pred):
    true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
    possible_positives = K.sum(K.round(K.clip(y_true, 0, 1)))
    recall = true_positives / (possible_positives + K.epsilon())
    return recall

def precision_m(y_true, y_pred):
    true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
    predicted_positives = K.sum(K.round(K.clip(y_pred, 0, 1)))
    precision = true_positives / (predicted_positives + K.epsilon())
    return precision

def f1_m(y_true, y_pred):
    precision = precision_m(y_true, y_pred)
    recall = recall_m(y_true, y_pred)
    return 2*((precision*recall)/(precision+recall+K.epsilon()))
model = Sequential()
model.add(Dense(80, input_dim=X_train.shape[1]))
model.add(Dropout(0.2))
model.add(Dense(240))
model.add(Dropout(0.2))
model.add(Dense(80))
model.add(Dropout(0.2))
model.add(Dense(units=1))
model.add(Activation('sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['acc',f1_m,precision_m, recall_m])
plot_model(model,
           rankdir="LR",
           expand_nested=True,
           show_shapes=True)
model.fit(
    X_train, y_train,
    batch_size=64, epochs=10, validation_split=0.05)
loss, accuracy, f1_score, precision, recall = model.evaluate(X_test, y_test, verbose=0)
print("Accuracy: %.2f" % accuracy)
print("Precision: %.2f" % precision)
print("Recall: %.2f" % recall)
print("F1: %.2f" % f1_score)
# previsione sul record aggiunto
model.predict(scaler.transform(rw))<jupyter_output><empty_output><jupyter_text>FSG<jupyter_code>from art.classifiers import TensorFlowV2Classifier
from art.attacks.evasion import FastGradientMethod
classifier = TensorFlowV2Classifier(model=model, nb_classes=2, input_shape=X_train.shape[1], loss_object=tf.keras.losses.binary_crossentropy)
attacker = FastGradientMethod(estimator=classifier, minimal=True)
# record originale
print(np.asarray(rw))
# genera il nuovo record
ransomware_adv = attacker.generate(scaler.transform(rw))

# stampa le features originali, questo é come deve diventare il PE file
radv = scaler.inverse_transform(ransomware_adv)
print(radv)
# previsione sul nuovo record

model.predict(ransomware_adv)
# non ha molto senso che ci siano features negative.
# proviamo a settare i valori negativi a zero e vediamo se misclassifica ancora
# inoltre convertiamo a int (necessario per scrivere poi il PE file)

import math

for i in range(51):
  if radv[0][i] < 0:
    radv[0][i] = 0;

  # usiamo l'arrotondamento con la previsione migliore

  radv[0][i] = math.ceil(radv[0][i])
  #radv[0][i] = math.floor(radv[0][i])
  #radv[0][i] = int(round(radv[0][i]))

print(radv)
model.predict(scaler.transform(radv))<jupyter_output><empty_output><jupyter_text>ora non resta che cambiare il sorgente/PE file per farlo assomigliare a radvhttps://docs.microsoft.com/en-us/archive/msdn-magazine/2002/february/inside-windows-win32-portable-executable-file-format-in-detailhttps://docs.microsoft.com/en-us/windows/win32/debug/pe-formatoverview<jupyter_code># questi valori non vanno toccati
non_toccare = ['e_magic', 'e_lfanew', 'Machine', 'SizeOfOptionalHeader', 'Characteristics', 'Magic',
'Subsystem', 'DllCharacteristics', 'LoaderFlags', 'NumberOfRvaAndSizes']

# questi valori possono essere cambiati agendo direttamente sul PE file
# l'eseguibile dovrebbe funzionare lo stesso
mod_pe = ['e_cblp', 'e_cp', 'e_crlc', 'e_cparhdr', 'e_minalloc', 'e_maxalloc', 'e_ss', 'e_sp', 
          'e_csum', 'e_ip', 'e_cs', 'e_lfarlc', 'e_ovno', 'e_oemid', 'e_oeminfo', 'MajorImageVersion', 
       'MinorImageVersion', 'MajorLinkerVersion', 'MinorLinkerVersion',
       'CheckSum', 'SizeOfStackCommit', 'SizeOfHeapCommit', 'SizeOfStackReserve', 'SizeOfHeapReserve',
        'MajorOperatingSystemVersion', 'MinorOperatingSystemVersion', 'MajorSubsystemVersion', 'MinorSubsystemVersion']

# come sopra ma con alcune limitazioni (es. multiplo di 512)
mod_pe_limitazioni = ['ImageBase', 'SectionAlignment',
       'FileAlignment', 'SizeOfHeaders', 'SizeOfImage']

# questi valori vanno cambiati agendo sul codice
mod_src = ['NumberOfSections', 'PointerToSymbolTable', 'NumberOfSymbols', 'SizeOfCode',
      'SizeOfInitializedData', 'SizeOfUninitializedData', 'AddressOfEntryPoint', 'BaseOfCode']<jupyter_output><empty_output><jupyter_text>Versione 1 : Modifica del PE file<jupyter_code># proviamo a cambiare solo i valori safe, modificando solo il PE file

new = rw.copy()
adv = pd.DataFrame(radv, columns=rw.columns)

for i in mod_pe:
  new[i] = adv[i]
model.predict(scaler.transform(new))<jupyter_output><empty_output><jupyter_text>obiettivo raggiunto, l'eseguibile viene misclassificato.ora va scritto il nuovo PE file che andrà testato sul sistema<jupyter_code>def modify_pe(pe, adv):
  pe.DOS_HEADER.e_cblp = int(adv['e_cblp'][0])
  pe.DOS_HEADER.e_cp = int(adv['e_cp'][0])
  pe.DOS_HEADER.e_crlc = int(adv['e_crlc'][0])
  pe.DOS_HEADER.e_cparhdr = int(adv['e_cparhdr'][0])
  pe.DOS_HEADER.e_minalloc = int(adv['e_minalloc'][0])
  pe.DOS_HEADER.e_maxalloc = int(adv['e_maxalloc'][0])
  pe.DOS_HEADER.e_ss = int(adv['e_ss'][0])
  pe.DOS_HEADER.e_sp = int(adv['e_sp'][0])
  pe.DOS_HEADER.e_csum = int(adv['e_csum'][0])
  pe.DOS_HEADER.e_ip = int(adv['e_ip'][0])
  pe.DOS_HEADER.e_cs = int(adv['e_cs'][0])
  pe.DOS_HEADER.e_lfarlc = int(adv['e_lfarlc'][0])
  pe.DOS_HEADER.e_ovno = int(adv['e_ovno'][0])
  pe.DOS_HEADER.e_oemid = int(adv['e_oemid'][0])
  pe.DOS_HEADER.e_oeminfo = int(adv['e_oeminfo'][0])

  pe.OPTIONAL_HEADER.MajorImageVersion = int(adv['MajorImageVersion'][0])
  pe.OPTIONAL_HEADER.MinorImageVersion = int(adv['MinorImageVersion'][0])
  pe.OPTIONAL_HEADER.MajorLinkerVersion = int(adv['MajorLinkerVersion'][0])
  pe.OPTIONAL_HEADER.MinorLinkerVersion = int(adv['MinorLinkerVersion'][0])
  pe.OPTIONAL_HEADER.MajorOperatingSystemVersion = int(adv['MajorOperatingSystemVersion'][0])
  pe.OPTIONAL_HEADER.MinorOperatingSystemVersion = int(adv['MinorOperatingSystemVersion'][0])
  pe.OPTIONAL_HEADER.MajorSubsystemVersion = int(adv['MajorSubsystemVersion'][0])
  pe.OPTIONAL_HEADER.MinorSubsystemVersion = int(adv['MinorSubsystemVersion'][0])
  pe.OPTIONAL_HEADER.CheckSum = int(adv['CheckSum'][0])
  pe.OPTIONAL_HEADER.SizeOfStackReserve = int(adv['SizeOfStackReserve'][0])
  pe.OPTIONAL_HEADER.SizeOfStackCommit = int(adv['SizeOfStackCommit'][0])
  pe.OPTIONAL_HEADER.SizeOfHeapReserve = int(adv['SizeOfHeapReserve'][0])
  pe.OPTIONAL_HEADER.SizeOfHeapCommit = int(adv['SizeOfHeapCommit'][0])

  return pe
pe = modify_pe(pe, adv)
pe.write(filename='modified.exe')
# verifichiamo che il file sia stato scritto correttamente

pex = pefile.PE('modified.exe')
xx = extract_features(pex)
xxx = [xx]
model.predict(scaler.transform(xxx))
!zip modified modified.exe<jupyter_output>adding: modified.exe (deflated 76%)<jupyter_text>l'eseguibile funziona, cifra e decifra, al momento di chiudersi crasha ma non é un problema.  Versione 2: Modifica del codice sorgente proviamo a cambiare solo il codice sorgente.l'analisi precedente suggerisce di ridurre il più possibile il parametro 'SizeOfCode' e di aumentare 'MajorSubsytemVersion'. La nuova versione del malware cifra solamente i file, non tocca registro o blocca il mouse, non chiede nulla e apre solo una finestra vuota. Viene inoltre compilata con la versione 4.5 di .net framework invece che con la 4.<jupyter_code>!unzip "src_modified.zip"
spe = pefile.PE('src_modified.exe')
fx = extract_features(spe)
fxx = pd.DataFrame([fx],columns = rw.columns)
model.predict(scaler.transform(fxx))<jupyter_output><empty_output><jupyter_text>come speravamo l'eseguibile viene misclassificato. vediamo quali feature sono cambiate rispetto all'originale<jupyter_code>print(np.asarray(rw))
print("------------------------------------------------------------------------")
print(np.asarray(fxx))
rw == fx<jupyter_output><empty_output><jupyter_text>cosa sarebbe successo se avessimo semplicemente ricompilato con .net 4.5?<jupyter_code>!unzip "malware_45.zip"
spe45 = pefile.PE('malware_45.exe')
fx45 = extract_features(spe45)
fxx45 = pd.DataFrame([fx45],columns = rw.columns)
model.predict(scaler.transform(fxx45))
print(np.asarray(fxx))
print("------------------------------------------------------------------------")
print(np.asarray(fxx45))<jupyter_output>[[  23117     144       3       0       4       0   65535       0     184
        0       0       0      64       0       0       0     128     332
        3       0       0     224      34     267      48       0    6144
     2048       0   14182    8192 4194304    8192     512       4       0
        0       0       6       0     512       0   32768       2   34144
  1048576    4096 1048576    4096       0      16]]
------------------------------------------------------------------------
[[  23117     144       3       0       4       0   65535       0     184
        0       0       0      64       0       0       0     128     332
        3       0       0     224      34     267      48       0  137728
     4608       0  145854    8192 4194304    8192     512       4       0
        0       0       6       0     512       0  163840       2   34144
  1048576    4096 1048576    4096       0      16]]<jupyter_text>ricompilare sarebbe stato sufficiente, la riduzione di codice non ha influito molto. Questo ci porta a concludere che la rete fa molto affidamento su features che non sono strettamente legate al comportamento benevolo/malevolo dell'eseguibile.  Modifica del Modello visto che le features in mod_pe possono essere cambiate facilmente dall'attaccante, tanto vale toglierle<jupyter_code>X = data.drop(["Name","Malware","TimeDateStamp"], axis=1)

# teniamo le prime 51
X = X[X.columns[:51]]

# togliamo le features modificabili
X2 = X.drop(mod_pe, axis=1)
rw2 = rw.drop(mod_pe, axis=1) # originale

# aggiungiamo il nuovo record al dataset
X2 = X2.append(rw2)
y2 = pd.DataFrame(data['Malware'])
y2 = y2.append(rwy)

print(X2.shape)
print(y2.shape)
# scaling

from sklearn.preprocessing import StandardScaler

scaler2 = StandardScaler();
X_standardized = pd.DataFrame(scaler2.fit_transform(X2), columns=X2.columns)

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
    X_standardized, y, test_size=0.33, random_state=42
    )
model2 = Sequential()
model2.add(Dense(80, input_dim=X_train.shape[1]))
model2.add(Dropout(0.2))
model2.add(Dense(240))
model2.add(Dropout(0.2))
model2.add(Dense(80))
model2.add(Dropout(0.2))
model2.add(Dense(units=1))
model2.add(Activation('sigmoid'))
model2.compile(loss='binary_crossentropy', optimizer='adam', metrics=['acc',f1_m,precision_m, recall_m])
model2.fit(
    X_train, y_train,
    batch_size=64, epochs=10, validation_split=0.05)
loss, accuracy, f1_score, precision, recall = model2.evaluate(X_test, y_test, verbose=0)
print("Accuracy: %.2f" % accuracy)
print("Precision: %.2f" % precision)
print("Recall: %.2f" % recall)
print("F1: %.2f" % f1_score)
# previsione sull'eseguibile originale
model2.predict(scaler2.transform(rw2))
# previsione sull'eseguibile con sorgente modificato
rws = fxx.drop(mod_pe, axis=1)

model2.predict(scaler2.transform(rws))
# previsione sull'eseguibile ricompilato
rws45 = fxx45.drop(mod_pe, axis=1)

model2.predict(scaler2.transform(rws45))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1379415347137637, 'fraction_numerical': 0.039037758830694276, 'mean_word_length': 3.910586124401914, 'pattern_counts': {'":': 0, '<': 50, '<?xml version=': 0, '>': 49, 'https://': 5, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '467714', 'license': 'MIT', 'n_tokens_mistral': 6266, 'n_tokens_neox': 5967, 'n_words': 1174, 'path': 'attacco.ipynb', 'repo_name': 'CyberSecLabBS/Ransomware2.0', 'size': '16449'}