Upload 17 files
Browse filescode : Add MalConv code
- .gitattributes +2 -0
- Reference/Malware_Detection_by_Eating_a_Whole_EXE.ipynb +0 -0
- Reference/ember-malconv/README.md +36 -0
- Reference/ember-malconv/malconv.h5 +3 -0
- Reference/ember-malconv/malconv.py +177 -0
- Reference/ember-malconv/multi_gpu.py +47 -0
- fig/malconv-arch.png +0 -0
- paper/1710.09435v1.pdf +3 -0
- paper/2012.09390v1.pdf +3 -0
- paper/PaperReview.md +209 -0
- requirements.txt +7 -0
- src/__pycache__/model.cpython-310.pyc +0 -0
- src/__pycache__/utils.cpython-310.pyc +0 -0
- src/model.py +148 -0
- src/predict.py +137 -0
- src/train.py +157 -0
- src/tune_model.py +148 -0
- src/utils.py +287 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
paper/1710.09435v1.pdf filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
paper/2012.09390v1.pdf filter=lfs diff=lfs merge=lfs -text
|
Reference/Malware_Detection_by_Eating_a_Whole_EXE.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
Reference/ember-malconv/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
This directory is provided as a courtesy. It includes the MalConv model to which we compared to in https://arxiv.org/abs/1804.04637.
|
| 2 |
+
|
| 3 |
+
For more details about MalConv, please see (and cite) the [original paper](https://arxiv.org/abs/1710.09435).
|
| 4 |
+
|
| 5 |
+
```
|
| 6 |
+
Raff, Edward, et al. "Malware detection by eating a whole exe." arXiv preprint arXiv:1710.09435 (2017).
|
| 7 |
+
```
|
| 8 |
+
|
| 9 |
+
If you use the pre-trained weights or code in your work, we also ask that you please cite [our paper](https://arxiv.org/pdf/1804.04637.pdf) for the implementation of MalConv, as it differs in a few subtle ways from the original.
|
| 10 |
+
|
| 11 |
+
```
|
| 12 |
+
H. Anderson and P. Roth, "EMBER: An Open Dataset for Training Static PE Malware Machine Learning Models”, in ArXiv e-prints. Apr. 2018.
|
| 13 |
+
|
| 14 |
+
@ARTICLE{2018arXiv180404637A,
|
| 15 |
+
author = {{Anderson}, H.~S. and {Roth}, P.},
|
| 16 |
+
title = "{EMBER: An Open Dataset for Training Static PE Malware Machine Learning Models}",
|
| 17 |
+
journal = {ArXiv e-prints},
|
| 18 |
+
archivePrefix = "arXiv",
|
| 19 |
+
eprint = {1804.04637},
|
| 20 |
+
primaryClass = "cs.CR",
|
| 21 |
+
keywords = {Computer Science - Cryptography and Security},
|
| 22 |
+
year = 2018,
|
| 23 |
+
month = apr,
|
| 24 |
+
adsurl = {http://adsabs.harvard.edu/abs/2018arXiv180404637A},
|
| 25 |
+
}
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
## Can I use this code to train MalConv on my own dataset?
|
| 29 |
+
The code provided is instructional and nonfunctional. With a few minor changes, it can be made functional. In particular, you must provide a URL to fetch file contents by sha256 hash.
|
| 30 |
+
|
| 31 |
+
## How does this MalConv model differ from that of Raff et al.?
|
| 32 |
+
* Our model was trained on binary files from labeled samples in the EMBER training set.
|
| 33 |
+
* The original paper used `batch_size = 256` and `SGD(lr=0.01, momentum=0.9, decay=UNDISCLOSED, nesterov=True )`. We used
|
| 34 |
+
`decay=1e-3` and `batch_size=100`.
|
| 35 |
+
* It is unknown whether the original paper used a special symbol for padding.
|
| 36 |
+
* The paper allowed for up to 2MB malware sizes, we use 1MB because of memory limits on a commonly-used Titan X.
|
Reference/ember-malconv/malconv.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:957fd697149066ee2926b18d7008997479952fe083e2b3f8e3fa4a35e84b6bd3
|
| 3 |
+
size 12550712
|
Reference/ember-malconv/malconv.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python
|
| 2 |
+
'''defines the MalConv architecture.
|
| 3 |
+
Adapted from https://arxiv.org/pdf/1710.09435.pdf
|
| 4 |
+
Things different about our implementation and that of the original paper:
|
| 5 |
+
* The paper uses batch_size = 256 and SGD(lr=0.01, momentum=0.9, decay=UNDISCLOSED, nesterov=True )
|
| 6 |
+
* The paper didn't have a special EOF symbol
|
| 7 |
+
* The paper allowed for up to 2MB malware sizes, we use 1.0MB because of memory on a Titan X
|
| 8 |
+
'''
|
| 9 |
+
|
| 10 |
+
def main():
|
| 11 |
+
from keras.layers import Dense, Conv1D, Activation, GlobalMaxPooling1D, Input, Embedding, Multiply
|
| 12 |
+
from keras.models import Model
|
| 13 |
+
from keras import backend as K
|
| 14 |
+
from keras import metrics
|
| 15 |
+
import multi_gpu
|
| 16 |
+
import os
|
| 17 |
+
import math
|
| 18 |
+
import random
|
| 19 |
+
import argparse
|
| 20 |
+
import os
|
| 21 |
+
import numpy as np
|
| 22 |
+
import requests
|
| 23 |
+
|
| 24 |
+
batch_size = 100
|
| 25 |
+
input_dim = 257 # every byte plus a special padding symbol
|
| 26 |
+
padding_char = 256
|
| 27 |
+
|
| 28 |
+
parser = argparse.ArgumentParser()
|
| 29 |
+
parser.add_argument('--gpus', help='number of GPUs', default=1)
|
| 30 |
+
|
| 31 |
+
args = parser.parse_args()
|
| 32 |
+
ngpus = int(args.gpus)
|
| 33 |
+
|
| 34 |
+
if os.path.exists('malconv.h5'):
|
| 35 |
+
print("restoring malconv.h5 from disk for continuation training...")
|
| 36 |
+
from keras.models import load_model
|
| 37 |
+
basemodel = load_model('malconv.h5')
|
| 38 |
+
_, maxlen, embedding_size = basemodel.layers[1].output_shape
|
| 39 |
+
input_dim
|
| 40 |
+
else:
|
| 41 |
+
maxlen = 2**20 # 1MB
|
| 42 |
+
embedding_size = 8
|
| 43 |
+
|
| 44 |
+
# define model structure
|
| 45 |
+
inp = Input( shape=(maxlen,))
|
| 46 |
+
emb = Embedding( input_dim, embedding_size )( inp )
|
| 47 |
+
filt = Conv1D( filters=128, kernel_size=500, strides=500, use_bias=True, activation='relu', padding='valid' )(emb)
|
| 48 |
+
attn = Conv1D( filters=128, kernel_size=500, strides=500, use_bias=True, activation='sigmoid', padding='valid')(emb)
|
| 49 |
+
gated = Multiply()([filt,attn])
|
| 50 |
+
feat = GlobalMaxPooling1D()( gated )
|
| 51 |
+
dense = Dense(128, activation='relu')(feat)
|
| 52 |
+
outp = Dense(1, activation='sigmoid')(dense)
|
| 53 |
+
|
| 54 |
+
basemodel = Model( inp, outp )
|
| 55 |
+
|
| 56 |
+
basemodel.summary()
|
| 57 |
+
|
| 58 |
+
print("Using %i GPUs" %ngpus)
|
| 59 |
+
|
| 60 |
+
if ngpus > 1:
|
| 61 |
+
model = multi_gpu.make_parallel(basemodel,ngpus)
|
| 62 |
+
else:
|
| 63 |
+
model = basemodel
|
| 64 |
+
|
| 65 |
+
from keras.optimizers import SGD
|
| 66 |
+
model.compile( loss='binary_crossentropy', optimizer=SGD(lr=0.01,momentum=0.9,nesterov=True,decay=1e-3), metrics=[metrics.binary_accuracy] )
|
| 67 |
+
|
| 68 |
+
def bytez_to_numpy(bytez,maxlen):
|
| 69 |
+
b = np.ones( (maxlen,), dtype=np.uint16 )*padding_char
|
| 70 |
+
bytez = np.frombuffer( bytez[:maxlen], dtype=np.uint8 )
|
| 71 |
+
b[:len(bytez)] = bytez
|
| 72 |
+
return b
|
| 73 |
+
|
| 74 |
+
def getfile_service(sha256,url=None,maxlen=maxlen):
|
| 75 |
+
if url is None:
|
| 76 |
+
raise NotImplementedError("You must provide your own url for getting file bytez by sha256")
|
| 77 |
+
r = requests.get( url, params={'sha256':sha256} )
|
| 78 |
+
if not r.ok:
|
| 79 |
+
return None
|
| 80 |
+
return bytez_to_numpy( r.content, maxlen )
|
| 81 |
+
|
| 82 |
+
def generator( hashes, labels, batch_size, shuffle=True ):
|
| 83 |
+
X = []
|
| 84 |
+
y = []
|
| 85 |
+
zipped = list(zip(hashes, labels))
|
| 86 |
+
while True:
|
| 87 |
+
if shuffle:
|
| 88 |
+
random.shuffle( zipped )
|
| 89 |
+
for sha256,l in zipped:
|
| 90 |
+
x = getfile_service(sha256)
|
| 91 |
+
if x is None:
|
| 92 |
+
continue
|
| 93 |
+
X.append( x )
|
| 94 |
+
y.append( l )
|
| 95 |
+
if len(X) == batch_size:
|
| 96 |
+
yield np.asarray(X,dtype=np.uint16), np.asarray(y)
|
| 97 |
+
X = []
|
| 98 |
+
y = []
|
| 99 |
+
|
| 100 |
+
import pandas as pd
|
| 101 |
+
train_labels = pd.read_csv('ember_training.csv.gz')
|
| 102 |
+
train_labels = train_labels[ train_labels['y'] != -1 ] # get only labeled samples
|
| 103 |
+
labels = train_labels['y'].tolist()
|
| 104 |
+
hashes = train_labels['sha256'].tolist()
|
| 105 |
+
|
| 106 |
+
from sklearn.model_selection import train_test_split
|
| 107 |
+
hashes_train, hashes_val, labels_train, labels_val = train_test_split( hashes, labels, test_size=200 )
|
| 108 |
+
|
| 109 |
+
train_gen = generator( hashes_train, labels_train, batch_size )
|
| 110 |
+
val_gen = generator( hashes_val, labels_val, batch_size )
|
| 111 |
+
|
| 112 |
+
from keras.callbacks import LearningRateScheduler
|
| 113 |
+
|
| 114 |
+
base = K.get_value( model.optimizer.lr )
|
| 115 |
+
def schedule(epoch):
|
| 116 |
+
return base / 10.0**(epoch//2)
|
| 117 |
+
|
| 118 |
+
model.fit_generator(
|
| 119 |
+
train_gen,
|
| 120 |
+
steps_per_epoch=len(hashes_train)//batch_size,
|
| 121 |
+
epochs=10,
|
| 122 |
+
validation_data=val_gen,
|
| 123 |
+
callbacks=[ LearningRateScheduler( schedule ) ],
|
| 124 |
+
validation_steps=int(math.ceil(len(hashes_val)/batch_size)),
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
basemodel.save('malconv.h5')
|
| 128 |
+
|
| 129 |
+
test_labels = pd.read_csv('ember_test.csv.gz')
|
| 130 |
+
labels_test = test_labels['y'].tolist()
|
| 131 |
+
hashes_test = test_labels['sha256'].tolist()
|
| 132 |
+
|
| 133 |
+
test_generator = generator(hashes_test,labels_test,batch_size=1,shuffle=False)
|
| 134 |
+
test_p = basemodel.predict_generator( test_generator, steps=len(test_labels), verbose=1 )
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
if __name__ == '__main__':
|
| 138 |
+
print('*'*80)
|
| 139 |
+
print('''
|
| 140 |
+
This is nonfunctional demonstration code that is provided for convenience. It shows
|
| 141 |
+
- The MalConv structure used in our paper
|
| 142 |
+
- Training procedure used in the paper
|
| 143 |
+
- How to load the weights for the MalConv model that we used.
|
| 144 |
+
|
| 145 |
+
It may be made functional by modifying the code to retrieve file contents by sha256
|
| 146 |
+
from a user-defined URL.
|
| 147 |
+
|
| 148 |
+
You may use the provided weights under the Ember AGPL-3.0 license included in the parent directory.
|
| 149 |
+
We also ask that you cite the original MalConv paper and refer to the Ember paper as the implementation.
|
| 150 |
+
|
| 151 |
+
(1) E. Raff, J. Barker, J. Sylvester, R. Brandon, B. Catanzaro, C. Nicholas, "Malware Detection by Eating a Whole EXE", in ArXiv e-prints. Oct. 2017.
|
| 152 |
+
|
| 153 |
+
@ARTICLE{raff2017malware,
|
| 154 |
+
title={Malware detection by eating a whole exe},
|
| 155 |
+
author={Raff, Edward and Barker, Jon and Sylvester, Jared and Brandon, Robert and Catanzaro, Bryan and Nicholas, Charles},
|
| 156 |
+
journal={arXiv preprint arXiv:1710.09435},
|
| 157 |
+
year={2017}
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
(2) H. Anderson and P. Roth, "EMBER: An Open Dataset for Training Static PE Malware Machine Learning Models”, in ArXiv e-prints. Apr. 2018.
|
| 161 |
+
|
| 162 |
+
@ARTICLE{2018arXiv180404637A,
|
| 163 |
+
author = {{Anderson}, H.~S. and {Roth}, P.},
|
| 164 |
+
title = "{EMBER: An Open Dataset for Training Static PE Malware Machine Learning Models}",
|
| 165 |
+
journal = {ArXiv e-prints},
|
| 166 |
+
archivePrefix = "arXiv",
|
| 167 |
+
eprint = {1804.04637},
|
| 168 |
+
primaryClass = "cs.CR",
|
| 169 |
+
keywords = {Computer Science - Cryptography and Security},
|
| 170 |
+
year = 2018,
|
| 171 |
+
month = apr,
|
| 172 |
+
adsurl = {http://adsabs.harvard.edu/abs/2018arXiv180404637A},
|
| 173 |
+
}
|
| 174 |
+
''')
|
| 175 |
+
print('*'*80)
|
| 176 |
+
|
| 177 |
+
#main() # uncomment this line after fixing the URL NotImplementedError above
|
Reference/ember-malconv/multi_gpu.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from keras.layers import concatenate
|
| 2 |
+
from keras.layers.core import Lambda
|
| 3 |
+
from keras.models import Model
|
| 4 |
+
|
| 5 |
+
import tensorflow as tf
|
| 6 |
+
|
| 7 |
+
def make_parallel(model, gpu_count):
|
| 8 |
+
def get_slice(data, idx, parts):
|
| 9 |
+
shape = tf.shape(data)
|
| 10 |
+
size = tf.concat([ shape[:1] // parts, shape[1:] ],axis=0)
|
| 11 |
+
stride = tf.concat([ shape[:1] // parts, shape[1:]*0 ],axis=0)
|
| 12 |
+
start = stride * idx
|
| 13 |
+
return tf.slice(data, start, size)
|
| 14 |
+
|
| 15 |
+
outputs_all = []
|
| 16 |
+
for i in range(len(model.outputs)):
|
| 17 |
+
outputs_all.append([])
|
| 18 |
+
|
| 19 |
+
#Place a copy of the model on each GPU, each getting a slice of the batch
|
| 20 |
+
for i in range(gpu_count):
|
| 21 |
+
with tf.device('/gpu:%d' % i):
|
| 22 |
+
with tf.name_scope('tower_%d' % i) as scope:
|
| 23 |
+
|
| 24 |
+
inputs = []
|
| 25 |
+
#Slice each input into a piece for processing on this GPU
|
| 26 |
+
for x in model.inputs:
|
| 27 |
+
input_shape = tuple(x.get_shape().as_list())[1:]
|
| 28 |
+
slice_n = Lambda(get_slice, output_shape=input_shape, arguments={'idx':i,'parts':gpu_count})(x)
|
| 29 |
+
inputs.append(slice_n)
|
| 30 |
+
|
| 31 |
+
outputs = model(inputs)
|
| 32 |
+
|
| 33 |
+
if not isinstance(outputs, list):
|
| 34 |
+
outputs = [outputs]
|
| 35 |
+
|
| 36 |
+
#Save all the outputs for merging back together later
|
| 37 |
+
for l in range(len(outputs)):
|
| 38 |
+
outputs_all[l].append(outputs[l])
|
| 39 |
+
|
| 40 |
+
# merge outputs on CPU
|
| 41 |
+
with tf.device('/cpu:0'):
|
| 42 |
+
merged = []
|
| 43 |
+
for outputs in outputs_all:
|
| 44 |
+
merged.append(concatenate(outputs, axis=0))
|
| 45 |
+
|
| 46 |
+
return Model(inputs=model.inputs, outputs=merged)
|
| 47 |
+
|
fig/malconv-arch.png
ADDED
|
paper/1710.09435v1.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:369986a8098764ec67dc68d52c3b5de29a4c3117d4aa7f8463d39c208349f1f7
|
| 3 |
+
size 385364
|
paper/2012.09390v1.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9af4474974c56852bda85326d80dd23bffbec3c1e42ff46681d6b3e2bddab766
|
| 3 |
+
size 1007625
|
paper/PaperReview.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Malware Detection by Eating a Whole EXE
|
| 2 |
+
|
| 3 |
+
--
|
| 4 |
+
### Instroduction
|
| 5 |
+
|
| 6 |
+
[ENG]
|
| 7 |
+
The introduction of MalConv addresses the fundamental limitations of traditional signature-based malware detection systems and the complexities of dynamic analysis approaches. Current anti-virus technologies rely on manually crafted rules that are specific to particular malware families and cannot recognize new variants, making them increasingly ineffective against the millions of new malware samples discovered daily. Dynamic analysis, while intuitive, presents significant challenges including high computational requirements, potential detection by sophisticated malware, and discrepancies between analysis and target environments
|
| 8 |
+
|
| 9 |
+
The core innovation lies in processing raw byte sequences from entire executable files without requiring domain knowledge or feature engineering. This approach presents unique challenges not encountered in traditional machine learning domains: handling multi-modal byte information (text, code, images), managing spatial correlations with discontinuities, processing sequences exceeding two million time steps, and addressing multiple levels of concept drift over time
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
[KOR]
|
| 13 |
+
MalConv의 도입배경은 전통적인 시그니처 기반 악성코드 탐지 시스템의 근본적 한계와 동적 분석 방법의 복잡성을 해결하는 데 있습니다. 현재 안티바이러스 기술은 특정 악성코드 패밀리에 특화된 수동 제작 규칙에 의존하며 새로운 변종을 인식할 수 없어, 매일 발견되는 수백만 개의 새로운 악성코드 샘플에 대해 점점 비효율적이 되고 있습니다. 동적 분석은 직관적이지만 높은 계산 요구사항, 정교한 악성코드의 탐지 가능성, 분석 환경과 대상 환경 간의 불일치 등 심각한 문제를 제시합니다.
|
| 14 |
+
|
| 15 |
+
핵심 혁신은 도메인 지식이나 특징 공학 없이 전체 실행 파일의 원시 바이트 시퀀스를 처리하는 것입니다. 이 접근법은 전통적인 기계학습 영역에서 만나지 못한 고유한 도전과제들을 제시합니다: 다중 모달 바이트 정보(텍스트, 코드, 이미지) 처리, 불연속성을 가진 공간 상관관계 관리, 200만 시간 단계를 초과하는 시퀀스 처리, 시간에 따른 다중 레벨 개념 드리프트 해결
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
--
|
| 20 |
+
### 2. Related work
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
[ENG]
|
| 24 |
+
The application of neural networks to extremely long sequences represents a significant computational challenge that MalConv addresses at an unprecedented scale. Prior work in this area includes WaveNet, which processes audio sequences of up to 16,000 time steps per second, still two orders of magnitude smaller than MalConv's capability. ByteNet and similar architectures for machine translation handle relatively shorter sequences compared to the malware detection problem.
|
| 25 |
+
|
| 26 |
+
The use of dilated convolutions, popularized by WaveNet and ByteNet for capturing wide receptive fields, was explored but found ineffective for binary data. Unlike spatially consistent domains like images and audio, the values in dilated convolution "holes" are not easily interpolated for binary content, leading to poor performance. RNN-based approaches face memory and computational complexity limitations when dealing with sequences of this magnitude
|
| 27 |
+
|
| 28 |
+
[KOR]
|
| 29 |
+
극도로 긴 시퀀스에 대한 신경망 적용은 MalConv가 전례 없는 규모로 해결하는 중요한 계산적 도전을 나타냅니다. 이 영역의 선행 연구로는 초당 최대 16,000 시간 단계의 오디오 시퀀스를 처리하는 WaveNet이 있지만, 여전히 MalConv 능력보다 두 자릿수 작습니다. 기계 번역을 위한 ByteNet과 유사한 아키텍처들은 악성코드 탐지 문제에 비해 상대적으로 짧은 시퀀스를 처리합니다.
|
| 30 |
+
|
| 31 |
+
넓은 수용 필드를 포착하기 위해 WaveNet과 ByteNet에서 인기를 얻은 팽창 컨볼루션의 사용이 탐구되었지만 바이너리 데이터에 대해서는 비효과적임이 발견되었습니다. 이미지와 오디오 같은 공간적으로 일관된 도메인과 달리, 팽창 컨볼루션 "구멍"의 값들은 바이너리 콘텐츠에 대해 쉽게 보간되지 않아 성능 저하를 초래합니다. RNN 기반 접근법은 이러한 규모의 시퀀스를 다룰 때 메모리와 계산 복잡도 제한에 직면합니다
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
#### 2.1 Neural Network for Long Sequence
|
| 35 |
+
|
| 36 |
+
[EMG]
|
| 37 |
+
The application of neural networks to extremely long sequences represents a significant computational challenge that MalConv addresses at an unprecedented scale. Prior work in this area includes WaveNet, which processes audio sequences of up to 16,000 time steps per second, still two orders of magnitude smaller than MalConv's capability. ByteNet and similar architectures for machine translation handle relatively shorter sequences compared to the malware detection problem.
|
| 38 |
+
|
| 39 |
+
The use of dilated convolutions, popularized by WaveNet and ByteNet for capturing wide receptive fields, was explored but found ineffective for binary data. Unlike spatially consistent domains like images and audio, the values in dilated convolution "holes" are not easily interpolated for binary content, leading to poor performance. RNN-based approaches face memory and computational complexity limitations when dealing with sequences of this magnitude
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
[KOR]
|
| 43 |
+
극도로 긴 시퀀스에 대한 신경망 적용은 MalConv가 전례 없는 규모로 해결하는 중요한 계산적 도전을 나타냅니다. 이 영역의 선행 연구로는 초당 최대 16,000 시간 단계의 오디오 시퀀스를 처리하는 WaveNet이 있지만, 여전히 MalConv 능력보다 두 자릿수 작습니다. 기계 번역을 위한 ByteNet과 유사한 아키텍처들은 악성코드 탐지 문제에 비해 상대적으로 짧은 시퀀스를 처리합니다.
|
| 44 |
+
|
| 45 |
+
넓은 수용 필드를 포착하기 위해 WaveNet과 ByteNet에서 인기를 얻은 팽창 컨볼루션의 사용이 탐구되었지만 바이너리 데이터에 대해서는 비효과적임이 발견되었습니다. 이미지와 오디오 같은 공간적으로 일관된 도메인과 달리, 팽창 컨볼루션 "구멍"의 값들은 바이너리 콘텐츠에 대해 쉽게 보간되지 않아 성능 저하를 초래합니다. RNN 기반 접근법은 이러한 규모의 시퀀스를 다룰 때 메모리와 계산 복잡도 제한에 직면합니다
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
#### 2.2 Neural Netwokrs for Malware Detectioon
|
| 50 |
+
|
| 51 |
+
[ENG]
|
| 52 |
+
Previous applications of neural networks to malware detection have relied heavily on domain knowledge and feature extraction, limiting their generalizability. Saxe and Berlin (2015) used histogram-based features including byte entropy values, ASCII string lengths, and PE metadata, discarding most information about actual binary content. Kolosnjaji et al. (2016) applied LSTM networks to API call sequences from dynamic analysis, focusing on only 60 kernel API calls.
|
| 53 |
+
|
| 54 |
+
The work most closely related to MalConv in terms of feature representation is the PE-header network by Raff et al. (2017), which achieved high accuracy using only 300 bytes from PE headers. However, this approach still requires domain knowledge about executable file structure and cannot process the entire binary content. Most existing work relies on sophisticated emulation environments and manual feature engineering, creating barriers to reproduction and extension.
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
[KOR]
|
| 58 |
+
악성코드 탐지에 대한 신경망의 이전 적용들은 도메인 지식과 특징 추출에 크게 의존하여 일반화 가능성을 제한했습니다. Saxe와 Berlin(2015)은 바이트 엔트로피 값, ASCII 문자열 길이, PE 메타데이터를 포함한 히스토그램 기반 특징을 사용하여 실제 바이너리 콘텐츠에 대한 대부분의 정보를 버렸습니다. Kolosnjaji 등(2016)은 동적 분석의 API 호출 시퀀스에 LSTM 네트워크를 적용하여 단지 60개의 커널 API 호출에만 집중했습니다.
|
| 59 |
+
|
| 60 |
+
특징 표현 측면에서 MalConv와 가장 밀접한 관련이 있는 작업은 PE 헤더의 300바이트만을 사용하여 높은 정확도를 달성한 Raff 등(2017)의 PE-헤더 네트워크입니다. 그러나 이 접근법은 여전히 실행 파일 구조에 대한 도메인 지식이 필요하고 전체 바이너리 콘텐츠를 처리할 수 없습니다. 기존 작업의 대부분은 정교한 에뮬레이션 환경과 수동 특징 공학에 의존하여 재현과 확장에 장벽을 만듭니다.
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
--
|
| 64 |
+
### 3. Training data
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
[ENG]
|
| 68 |
+
The training data for MalConv consists of two primary groups with distinct collection methodologies to ensure robust evaluation. Group B contains 400,000 files split evenly between benign and malicious classes, provided by an anti-virus industry partner and representing files encountered on real machines. The Group B test set includes 77,349 files with 40,000 malicious and the remainder benign samples.
|
| 69 |
+
|
| 70 |
+
Group A data follows the conventional academic approach, with benign samples from clean Microsoft Windows installations and common applications, while malware comes from the VirusShare corpus. The Group A test set contains 43,967 malicious and 21,854 benign files. A critical finding revealed that training on Group A-style data results in severe overfitting, with models learning to recognize "from Microsoft" rather than genuinely benign characteristics.
|
| 71 |
+
|
| 72 |
+
An extended dataset of 2,011,786 binaries (1,000,020 benign and 1,011,766 malicious) was later obtained to demonstrate MalConv's continued improvement with increased training data, while byte n-gram approaches showed performance plateauing.
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
[KOR]
|
| 77 |
+
MalConv의 훈련 데이터는 강건한 평가를 보장하기 위해 서로 다른 수집 방법론을 가진 두 가지 주요 그룹으로 구성됩니다. 그룹 B는 안티바이러스 업계 파트너가 제공한 40만 개의 파일을 포함하며, 악성과 정상 클래스로 균등하게 분할되어 실제 기계에서 발견되는 파일을 대표합니다. 그룹 B 테스트 세트는 77,349개 파일을 포함하며 이중 40,000개가 악성이고 나머지가 정상 샘플입니다.
|
| 78 |
+
|
| 79 |
+
그��� A 데이터는 전통적인 학술적 접근법을 따르며, 정상 샘플은 깨끗한 Microsoft Windows 설치와 일반적인 애플리케이션에서, 악성코드는 VirusShare 코퍼스에서 가져왔습니다. 그룹 A 테스트 세트는 43,967개의 악성과 21,854개의 정상 파일을 포함합니다. 중요한 발견은 그룹 A 스타일 데이터에 대한 훈련이 심각한 과적합을 초래하며, 모델이 진정한 정상 특성보다는 "Microsoft에서 나온" 것을 인식하도록 학습한다는 것입니다.
|
| 80 |
+
|
| 81 |
+
MalConv의 훈련 데이터 증가에 따른 지속적인 성능 향상을 보여주기 위해 2,011,786개 바이너리(정상 1,000,020개, 악성 1,011,766개)의 확장 데이터셋이 나중에 획득되었으며, 바이트 n-그램 접근법은 성능 정체를 보였습니다
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
--
|
| 85 |
+
### 4. Model Archtecture
|
| 86 |
+
|
| 87 |
+
[ENG]
|
| 88 |
+
The MalConv architecture is designed with three key requirements: scalability with sequence length, ability to consider both local and global context across entire files, and explanatory capability for analysis of flagged malware. The model processes raw byte sequences through an embedding layer that maps each byte (0-255) to an 8-dimensional learned feature vector, avoiding the false assumption that certain byte values are intrinsically closer than others.
|
| 89 |
+
|
| 90 |
+
The core architecture employs gated convolution with 128 filters, using large convolutional filter width of 500 bytes combined with an aggressive stride of 500. This design choice addresses GPU memory constraints while enabling efficient data-parallel training. The gated convolution approach follows Dauphin et al. (2016), incorporating element-wise multiplication with sigmoid activation to control information flow.
|
| 91 |
+
|
| 92 |
+
Temporal max-pooling extracts the maximum activation across the entire sequence, allowing the model to detect informative features regardless of their location within the binary. This design addresses the high positional variation in executable files where contents can be arbitrarily rearranged while maintaining functionality. The final component is a fully connected layer with softmax activation for binary classification.
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
[KOR]
|
| 96 |
+
MalConv 아키텍처는 세 가지 핵심 요구사항으로 설계되었습니다: 시퀀스 길이에 따른 확장성, 전체 파일에 걸친 지역적 및 전역적 컨텍스트 고려 능력, 플래그된 악성코드 분석을 위한 설명 능력. 모델은 각 바이트(0-255)를 8차원 학습된 특징 벡터로 매핑하는 임베딩 레이어를 통해 원시 바이트 시퀀스를 처리하여, 특정 바이트 값이 다른 값보다 본질적으로 더 가깝다는 잘못된 가정을 피합니다.
|
| 97 |
+
|
| 98 |
+
핵심 아키텍처는 128개 필터를 가진 게이트 컨볼루션을 사용하며, 500바이트의 큰 컨볼루션 필터 폭과 500의 공격적인 스트라이드를 결합합니다. 이 설계 선택은 GPU 메모리 제약을 해결하면서 효율적인 데이터 병렬 훈련을 가능하게 합니다. 게이트 컨볼루션 접근법은 Dauphin 등(2016)을 따르며, 정보 흐름을 제어하기 위해 시그모이드 활성화와 요소별 곱셈을 통합합니다.
|
| 99 |
+
|
| 100 |
+
시간적 최대 풀링은 전체 시퀀스에 걸쳐 최대 활성화를 추출하여, 모델이 바이너리 내 위치에 관계없이 정보적 특징을 탐지할 수 있게 합니다. 이 설계는 기능을 유지하면서 콘텐츠가 임의로 재배열될 수 있는 실행 파일의 높은 위치 변동을 해결합니다. 최종 구성요소는 이진 분류를 위한 소프트맥스 활성화를 가진 완전 연결 레이어입니다
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
#### 4.1 On Failed Archtectures
|
| 106 |
+
|
| 107 |
+
[ENG]
|
| 108 |
+
Extensive experimentation with alternative architectures revealed several fundamental challenges in processing extremely long sequences for malware detection. Deep convolutional networks with up to 13 layers suffered from gradient vanishing problems and required rapid compression of state size per layer due to memory constraints, ultimately inhibiting learning. The standard approach of doubling convolutional filters after each pooling round becomes computationally intractable with 2 million time steps.
|
| 109 |
+
|
| 110 |
+
Chunking approaches, where files are divided into 500-10,000 byte segments for independent processing, achieved reasonable training accuracies up to 95% but failed to generalize with test accuracies dropping to 65-80%. This failure occurs because much of a binary's content may be non-informative for maliciousness decisions, and training on random chunks encourages overfitting to training data rather than learning discriminative features.
|
| 111 |
+
|
| 112 |
+
RNN-based architectures performed poorly when applied after convolutions, as reshaping temporal CNN outputs into fixed-sized chunks imposes an artificial prior that activation patterns must appear at consistent frequencies. The malware "image" approach, treating bytes as grayscale pixels with arbitrary width selection, introduces false spatial correlations and fails to handle variable file sizes meaningfully.
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
[KOR]
|
| 117 |
+
대안적 아키��처에 대한 광범위한 실험은 악성코드 탐지를 위한 극도로 긴 시퀀스 처리에서 몇 가지 근본적인 도전과제를 드러냈습니다. 최대 13층의 깊은 컨볼루션 네트워크는 그래디언트 소실 문제를 겪었고 메모리 제약으로 인해 레이어당 상태 크기의 급속한 압축이 필요했으며, 궁극적으로 학습을 저해했습니다. 각 풀링 라운드 후 컨볼루션 필터를 두 배로 늘리는 표준 접근법은 200만 시간 단계에서 계산적으로 다루기 어려워집니다.
|
| 118 |
+
|
| 119 |
+
파일을 500-10,000바이트 세그먼트로 나누어 독립적으로 처리하는 청킹 접근법은 최대 95%의 합리적인 훈련 정확도를 달성했지만 테스트 정확도가 65-80%로 떨어지면서 일반화에 실패했습니다. 이 실패는 바이너리 콘텐츠의 대부분이 악성 여부 결정에 비정보적일 수 있고, 무작위 청크에 대한 훈련이 판별적 특징을 학습하기보다는 훈련 데이터에 과적합을 장려하기 때문에 발생합니다.
|
| 120 |
+
|
| 121 |
+
RNN 기반 아키텍처는 컨볼루션 후에 적용될 때 성능이 저조했는데, 이는 시간적 CNN 출력을 고정 크기 청크로 재구성하는 것이 활성화 패턴이 일관된 빈도로 나타나야 한다는 인위적인 사전 지식을 부과하기 때문입니다. 바이트를 임의의 폭 선택으로 그레이스케일 픽셀로 취급하는 악성코드 "이미지" 접근법은 거짓 공간 상관관계를 도입하고 가변 파일 크기를 의미 있게 처리하지 못합니다.
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
--
|
| 126 |
+
### 5. Results
|
| 127 |
+
|
| 128 |
+
#### 5.1 Malware classification
|
| 129 |
+
|
| 130 |
+
[ENG]
|
| 131 |
+
MalConv demonstrates superior performance across multiple metrics and test sets, achieving balanced accuracy between Group A and Group B evaluations. The model achieves 88.1% accuracy on Group A and 89.6% on Group B, with AUC scores of 98.5% and 95.8% respectively. Notably, MalConv shows the smallest performance difference between test groups, indicating robust feature learning that generalizes well across different data distributions.
|
| 132 |
+
|
| 133 |
+
The application of DeCov regularization significantly improves model accuracy by up to 4.8 percentage points, primarily through better calibration of decision thresholds rather than fundamental concept changes. When trained on the extended 2 million sample dataset, MalConv's performance increases substantially: Group A accuracy improves to 94.0% and Group B to 90.9%, while Group B AUC reaches 98.2%.
|
| 134 |
+
|
| 135 |
+
Comparative analysis reveals that byte n-gram models, while achieving high Group B performance (92.5% accuracy, 97.9% AUC), show significant performance gaps between test groups and demonstrate brittleness to single-byte modifications. The PE-header network achieves slightly higher Group A accuracy (90.8%) but significantly reduced Group B performance, indicating limited feature diversity.
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
[KOR]
|
| 140 |
+
MalConv는 여러 메트릭과 테스트 세트에서 우수한 성능을 보여주며, 그룹 A와 그룹 B 평가 간 균형잡힌 정확도를 달성합니다. 모델은 그룹 A에서 88.1%, 그룹 B에서 89.6%의 정확도를 달성하며, AUC 점수는 각각 98.5%와 95.8%입니다. 특히 MalConv는 테스트 그룹 간 가장 작은 성능 차이를 보여, 서로 다른 데이터 분포에서 잘 일반화되는 강건한 특징 학습을 나타냅니다.
|
| 141 |
+
|
| 142 |
+
DeCov 정규화의 적용은 근본적인 개념 변화보다는 결정 임계값의 더 나은 보정을 통해 모델 정확도를 최대 4.8 퍼센트 포인트까지 크게 향상시킵니다. 200만 샘플의 확장 데이터셋에서 훈련될 때 MalConv의 성능은 상당히 증가합니다: 그룹 A 정확도는 94.0%로, 그룹 B는 90.9%로 향상되며, 그룹 B AUC는 98.2%에 도달합니다.
|
| 143 |
+
|
| 144 |
+
비교 분석은 바이트 n-그램 모델이 높은 그룹 B 성능(92.5% 정확도, 97.9% AUC)을 달성하지만 테스트 그룹 간 상당한 성능 격차를 보이고 단일 바이트 수정에 대한 취약성을 보인다는 것을 드러냅니다. PE-헤더 네트워크는 약간 더 높은 그룹 A 정확도(90.8%)를 달성하지만 그룹 B 성능이 현저히 감소하여 제한된 특징 다양성을 나타냅니다.
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
#### 5.2 Maunal Analysis
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
[ENG]
|
| 151 |
+
MalConv's interpretability is enhanced through a sparse Class Activation Map (sparse-CAM) approach that adapts Zhou et al. (2016)'s methodology for the extreme sequence lengths encountered in malware detection. Using global max-pooling instead of average pooling produces naturally sparse activation maps, returning one 500-byte region per convolutional filter (maximum 128 regions per binary) as important for classification decisions.
|
| 152 |
+
|
| 153 |
+
Analysis of 224 randomly selected binaries from Group A test set reveals that MalConv utilizes significantly more diverse information sources compared to byte n-gram approaches. While previous byte n-gram models obtained almost all information from PE-headers, MalConv derives only 58-61% of its information from this region, indicating broader feature utilization across different binary sections.
|
| 154 |
+
|
| 155 |
+
The model demonstrates sophisticated understanding of binary structure, with activations distributed across .rsrc sections (16%), .text and CODE sections (14%), indicating utilization of resource directories and executable code as features. Notably, the model shows balanced activation patterns for UPX1 sections (indicating packed executables) for both benign and malicious samples, suggesting it has learned to avoid the common but unhelpful association between packing and maliciousness.
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
[KOR]
|
| 161 |
+
MalConv의 해석가능성은 악성코드 탐지에서 접하는 극도의 시퀀스 길이에 대해 Zhou 등(2016)의 방법론을 적응시킨 희소 클래스 활성화 맵(sparse-CAM) 접근법을 통해 향상됩니다. 평균 풀링 대신 전역 최대 풀링을 사용하면 자연스럽게 희소한 활성화 맵이 생성되어, 분류 결정에 중요한 것으로 컨볼루션 필터당 하나의 500바이트 영역(바이너리당 최대 128개 영역)을 반환합니다.
|
| 162 |
+
|
| 163 |
+
그룹 A 테스트 세트에서 무작위로 선택된 224개 바이너리의 분석은 MalConv가 바이트 n-그램 접근법에 비해 훨씬 더 다양한 정보 소스를 활용한다는 것을 드러냅니다. 이전 바이트 n-그램 모델이 PE-헤더에서 거의 모든 정보를 얻었던 반면, MalConv는 이 영역에서 정보의 58-61%만을 도출하여 다른 바이너리 섹션에 걸쳐 더 광범위한 특징 활용을 나타냅니다.
|
| 164 |
+
|
| 165 |
+
모델은 .rsrc 섹션(16%), .text 및 CODE 섹션(14%)에 걸쳐 분산된 활성화로 바이너리 구조에 대한 정교한 이해를 보여주며, 리소스 디렉토리와 실행 코드를 특징으로 활용함을 나타냅니다. 특히 모델은 정상과 악성 샘플 모두에 대해 UPX1 섹션(패킹된 실행 파일을 나타냄)에 대한 균형잡힌 활성화 패턴을 보여, 패킹과 악성성 간의 일반적이지만 도움이 되지 않는 연관성을 피하도록 학습했음을 시사합니다.
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
#### 5.3 The Failure of Batch-Normlaization
|
| 169 |
+
|
| 170 |
+
[ENG]
|
| 171 |
+
One of the most significant findings in MalConv research is the complete failure of batch normalization, a technique that typically accelerates convergence and improves generalization in deep learning. Models incorporating batch normalization consistently failed to learn, achieving at best 60% training accuracy and 50% test accuracy across multiple framework implementations including PyTorch, TensorFlow, Chainer, and Theano.
|
| 172 |
+
|
| 173 |
+
Kernel density estimation analysis reveals the root cause: binary executable data exhibits multi-modal activation distributions fundamentally different from the approximately Gaussian distributions assumed by batch normalization. While image processing networks show smooth, unimodal activation patterns suitable for normalization, MalConv activations display distinct multi-modal characteristics with multiple peaks.
|
| 174 |
+
|
| 175 |
+
This multi-modal nature stems from the diverse byte content within executables, where the same byte value can represent ASCII text, binary code, structured data, or embedded images depending on context. The violation of batch normalization's normality assumption leads to degraded performance, with the technique only functioning when trained on homogeneous sub-regions of 500-10,000 bytes, though these models still failed to generalize to test data.
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
[KOR]
|
| 180 |
+
One of the most significant findings in MalConv research is the complete failure of batch normalization, a technique that typically accelerates convergence and improves generalization in deep learning. Models incorporating batch normalization consistently failed to learn, achieving at best 60% training accuracy and 50% test accuracy across multiple framework implementations including PyTorch, TensorFlow, Chainer, and Theano.
|
| 181 |
+
|
| 182 |
+
Kernel density estimation analysis reveals the root cause: binary executable data exhibits multi-modal activation distributions fundamentally different from the approximately Gaussian distributions assumed by batch normalization. While image processing networks show smooth, unimodal activation patterns suitable for normalization, MalConv activations display distinct multi-modal characteristics with multiple peaks.
|
| 183 |
+
|
| 184 |
+
This multi-modal nature stems from the diverse byte content within executables, where the same byte value can represent ASCII text, binary code, structured data, or embedded images depending on context. The violation of batch normalization's normality assumption leads to degraded performance, with the technique only functioning when trained on homogeneous sub-regions of 500-10,000 bytes, though these models still failed to generalize to test data.
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
--
|
| 192 |
+
### 6. Conclusion
|
| 193 |
+
|
| 194 |
+
[ENG]
|
| 195 |
+
MalConv represents a paradigm shift in malware detection by demonstrating that neural networks can successfully learn to identify malicious software directly from raw byte sequences without domain knowledge. The model's ability to process entire PE files up to 2 million bytes establishes it as the first architecture capable of handling such extreme sequence lengths in cybersecurity applications.
|
| 196 |
+
|
| 197 |
+
The research contributions extend beyond malware detection to the broader machine learning community by identifying unique challenges in processing multi-modal sequential data and proposing effective solutions. The discovery of batch normalization's failure provides valuable insights for future work on non-standard data distributions, while the sparse-CAM interpretability approach offers a practical method for understanding model decisions on extremely long sequences.
|
| 198 |
+
|
| 199 |
+
Future research directions include developing architectures that better handle multi-modal data, improving memory efficiency for even longer sequences, and integrating semantic understanding of code structure. The model's demonstrated scalability with increased training data suggests continued performance improvements as larger datasets become available, positioning MalConv as a foundation for next-generation cybersecurity solutions.
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
[KOR]
|
| 204 |
+
MalConv는 신경망이 도메인 지식 없이 원시 바이트 시퀀스에서 직접 악성 소프트웨어를 식별하도록 성공적으로 학습할 수 있음을 보여줌으로써 악성코드 탐지의 패러다임 전환을 나타냅니다. 최대 200만 바이트의 전체 PE 파일을 처리할 수 있는 모델의 능력은 사이버보안 애플리케이션에서 이러한 극도의 시퀀스 길이를 처리할 수 있는 최초의 아키텍처로 확립됩니다.
|
| 205 |
+
|
| 206 |
+
연구 기여는 악성코드 탐지를 넘어 다중 모달 순차 데이터 처리의 고유한 도전과제를 식별하고 효과적인 해결책을 제안함으로써 더 광범위한 기계학습 커뮤니티로 확장됩니다. 배치 정규화 실패의 발견은 비표준 데이터 분포에 대한 향후 작업에 귀중한 통찰을 제공하며, sparse-CAM 해석가능성 접근법은 극도로 긴 시퀀스에서 모델 결정을 이해하는 실용적인 방법을 제공합니다.
|
| 207 |
+
|
| 208 |
+
향후 연구 방향에는 다중 모달 데이터를 더 잘 처리하는 아키텍처 개발, 더 긴 시퀀스를 위한 메모리 효율성 향상, 코드 구조의 의미론적 이해 통합이 포함됩니다. 훈련 데이터 증가에 따른 모델의 입증된 확장성은 더 큰 데이터셋이 사용 가능해짐에 따라 지속적인 성능 향상을 시사하며, MalConv를 차세대 사이버보안 솔루션의 기반으로 위치시킵니다.
|
| 209 |
+
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
tensorflow>=2.8.0
|
| 2 |
+
numpy>=1.21.0
|
| 3 |
+
pandas>=1.3.0
|
| 4 |
+
scikit-learn>=1.0.0
|
| 5 |
+
matplotlib>=3.5.0
|
| 6 |
+
seaborn>=0.11.0
|
| 7 |
+
tqdm>=4.62.0
|
src/__pycache__/model.cpython-310.pyc
ADDED
|
Binary file (3.68 kB). View file
|
|
|
src/__pycache__/utils.cpython-310.pyc
ADDED
|
Binary file (8.17 kB). View file
|
|
|
src/model.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import tensorflow as tf
|
| 2 |
+
from tensorflow.keras import layers, Model
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
class DeCorrelationLoss(tf.keras.layers.Layer):
|
| 6 |
+
"""논문의 정확한 DeCov 정규화 구현"""
|
| 7 |
+
|
| 8 |
+
def __init__(self, lambda_decov=1e-4, **kwargs):
|
| 9 |
+
super(DeCorrelationLoss, self).__init__(**kwargs)
|
| 10 |
+
self.lambda_decov = lambda_decov
|
| 11 |
+
|
| 12 |
+
def build(self, input_shape):
|
| 13 |
+
super(DeCorrelationLoss, self).build(input_shape)
|
| 14 |
+
|
| 15 |
+
def call(self, inputs):
|
| 16 |
+
batch_size = tf.cast(tf.shape(inputs)[0], tf.float32)
|
| 17 |
+
|
| 18 |
+
# 중심화
|
| 19 |
+
inputs_centered = inputs - tf.reduce_mean(inputs, axis=0, keepdims=True)
|
| 20 |
+
|
| 21 |
+
# 공분산 행렬 계산
|
| 22 |
+
covariance = tf.matmul(inputs_centered, inputs_centered, transpose_a=True) / (batch_size - 1)
|
| 23 |
+
|
| 24 |
+
# 대각선 제거
|
| 25 |
+
covariance_off_diagonal = covariance - tf.linalg.diag(tf.linalg.diag_part(covariance))
|
| 26 |
+
|
| 27 |
+
# DeCov 손실
|
| 28 |
+
decov_loss = 0.5 * tf.reduce_sum(tf.square(covariance_off_diagonal))
|
| 29 |
+
|
| 30 |
+
self.add_loss(self.lambda_decov * decov_loss)
|
| 31 |
+
return inputs
|
| 32 |
+
|
| 33 |
+
class MalConv(Model):
|
| 34 |
+
"""논문 정확 사양 MalConv 모델"""
|
| 35 |
+
|
| 36 |
+
def __init__(self,
|
| 37 |
+
max_input_length=2_000_000,
|
| 38 |
+
embedding_size=8,
|
| 39 |
+
filter_size=500,
|
| 40 |
+
stride=500,
|
| 41 |
+
num_filters=128,
|
| 42 |
+
fc_size=128,
|
| 43 |
+
use_decov=True,
|
| 44 |
+
lambda_decov=1e-4,
|
| 45 |
+
**kwargs):
|
| 46 |
+
super(MalConv, self).__init__(**kwargs)
|
| 47 |
+
|
| 48 |
+
self.max_input_length = max_input_length
|
| 49 |
+
self.use_decov = use_decov
|
| 50 |
+
|
| 51 |
+
# 논문 정확 사양: 0-255 바이트만 사용
|
| 52 |
+
self.embedding = layers.Embedding(
|
| 53 |
+
input_dim=256, # 수정: 257→256
|
| 54 |
+
output_dim=embedding_size,
|
| 55 |
+
input_length=None, # 가변 길이 지원
|
| 56 |
+
mask_zero=False,
|
| 57 |
+
name='byte_embedding'
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
# 게이트 컨볼루션 (논문 Figure 1)
|
| 61 |
+
self.conv_A = layers.Conv1D(
|
| 62 |
+
filters=num_filters,
|
| 63 |
+
kernel_size=filter_size,
|
| 64 |
+
strides=stride,
|
| 65 |
+
padding='valid',
|
| 66 |
+
activation='relu',
|
| 67 |
+
name='conv_A'
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
self.conv_B = layers.Conv1D(
|
| 71 |
+
filters=num_filters,
|
| 72 |
+
kernel_size=filter_size,
|
| 73 |
+
strides=stride,
|
| 74 |
+
padding='valid',
|
| 75 |
+
activation='sigmoid',
|
| 76 |
+
name='conv_B'
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
# 전역 최대 풀링
|
| 80 |
+
self.global_max_pool = layers.GlobalMaxPooling1D(name='global_max_pool')
|
| 81 |
+
|
| 82 |
+
# 완전연결층
|
| 83 |
+
self.fc = layers.Dense(fc_size, activation='relu', name='fc_layer')
|
| 84 |
+
|
| 85 |
+
# DeCov 정규화
|
| 86 |
+
if use_decov:
|
| 87 |
+
self.decov_layer = DeCorrelationLoss(lambda_decov=lambda_decov)
|
| 88 |
+
|
| 89 |
+
self.dropout = layers.Dropout(0.5, name='dropout')
|
| 90 |
+
self.output_layer = layers.Dense(1, activation='sigmoid', name='output')
|
| 91 |
+
|
| 92 |
+
def call(self, inputs, training=None):
|
| 93 |
+
# 1. 바이트 임베딩
|
| 94 |
+
x = self.embedding(inputs)
|
| 95 |
+
|
| 96 |
+
# 2. 게이트 컨볼루션 (논문 핵심)
|
| 97 |
+
conv_a = self.conv_A(x)
|
| 98 |
+
conv_b = self.conv_B(x)
|
| 99 |
+
gated_conv = layers.multiply([conv_a, conv_b], name='gated_conv')
|
| 100 |
+
|
| 101 |
+
# 3. 전역 최대 풀링
|
| 102 |
+
pooled = self.global_max_pool(gated_conv)
|
| 103 |
+
|
| 104 |
+
# 4. 완전연결층
|
| 105 |
+
fc_out = self.fc(pooled)
|
| 106 |
+
|
| 107 |
+
# 5. DeCov 정규화 (penultimate layer)
|
| 108 |
+
if self.use_decov:
|
| 109 |
+
fc_out = self.decov_layer(fc_out)
|
| 110 |
+
|
| 111 |
+
# 6. 드롭아웃
|
| 112 |
+
if training:
|
| 113 |
+
fc_out = self.dropout(fc_out, training=training)
|
| 114 |
+
|
| 115 |
+
# 7. 출력
|
| 116 |
+
output = self.output_layer(fc_out)
|
| 117 |
+
|
| 118 |
+
return output
|
| 119 |
+
|
| 120 |
+
def create_malconv_model (max_input_length=2_000_000):
|
| 121 |
+
"""논문 완전 동일 사양 모델"""
|
| 122 |
+
model = MalConv(max_input_length=max_input_length)
|
| 123 |
+
|
| 124 |
+
# 논문 정확한 옵티마이저 + 스케줄러
|
| 125 |
+
initial_lr = 0.01
|
| 126 |
+
lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay(
|
| 127 |
+
initial_learning_rate=initial_lr,
|
| 128 |
+
decay_steps=1000,
|
| 129 |
+
decay_rate=0.96, # 논문에서 언급된 지수 감소
|
| 130 |
+
staircase=True
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
optimizer = tf.keras.optimizers.SGD(
|
| 134 |
+
learning_rate=lr_schedule,
|
| 135 |
+
momentum=0.9,
|
| 136 |
+
nesterov=True
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
model.compile(
|
| 140 |
+
optimizer=optimizer,
|
| 141 |
+
loss='binary_crossentropy',
|
| 142 |
+
metrics=['accuracy',
|
| 143 |
+
tf.keras.metrics.Precision(name='precision'),
|
| 144 |
+
tf.keras.metrics.Recall(name='recall'),
|
| 145 |
+
tf.keras.metrics.AUC(name='auc')]
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
return model
|
src/predict.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import argparse
|
| 4 |
+
import numpy as np
|
| 5 |
+
import tensorflow as tf
|
| 6 |
+
import pandas as pd
|
| 7 |
+
|
| 8 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 9 |
+
|
| 10 |
+
from src.utils import read_binary_file
|
| 11 |
+
|
| 12 |
+
def predict_file(model_path, file_path, max_length=2_000_000): # 2,000,000
|
| 13 |
+
"""
|
| 14 |
+
단일 파일에 대한 예측
|
| 15 |
+
|
| 16 |
+
Args:
|
| 17 |
+
model_path: 저장된 모델 경로
|
| 18 |
+
file_path: 예측할 파일 경로
|
| 19 |
+
max_length: 최대 입력 길이
|
| 20 |
+
|
| 21 |
+
Returns:
|
| 22 |
+
float: 예측 확률 (0에 가까우면 악성코드, 1에 가까우면 정상)
|
| 23 |
+
"""
|
| 24 |
+
# 모델 로드
|
| 25 |
+
model = tf.keras.models.load_model(model_path)
|
| 26 |
+
|
| 27 |
+
# 파일 읽기
|
| 28 |
+
byte_array = read_binary_file(file_path, max_length)
|
| 29 |
+
|
| 30 |
+
# 배치 차원 추가
|
| 31 |
+
input_data = np.expand_dims(byte_array, axis=0)
|
| 32 |
+
|
| 33 |
+
# 예측
|
| 34 |
+
prediction = model.predict(input_data, verbose=0)[0][0]
|
| 35 |
+
|
| 36 |
+
return prediction
|
| 37 |
+
|
| 38 |
+
def predict_batch(model_path, csv_path, output_path=None, max_length=2**20):
|
| 39 |
+
"""
|
| 40 |
+
배치 예측
|
| 41 |
+
|
| 42 |
+
Args:
|
| 43 |
+
model_path: 저장된 모델 경로
|
| 44 |
+
csv_path: 예측할 파일들의 CSV 경로
|
| 45 |
+
output_path: 결과 저장 경로
|
| 46 |
+
max_length: 최대 입력 길이
|
| 47 |
+
"""
|
| 48 |
+
# 모델 로드
|
| 49 |
+
print("모델 로딩 중...")
|
| 50 |
+
model = tf.keras.models.load_model(model_path)
|
| 51 |
+
|
| 52 |
+
# CSV 파일 읽기
|
| 53 |
+
df = pd.read_csv(csv_path)
|
| 54 |
+
|
| 55 |
+
predictions = []
|
| 56 |
+
labels = []
|
| 57 |
+
|
| 58 |
+
print("예측 중...")
|
| 59 |
+
for idx, row in df.iterrows():
|
| 60 |
+
file_path = row['filepath']
|
| 61 |
+
|
| 62 |
+
if os.path.exists(file_path):
|
| 63 |
+
try:
|
| 64 |
+
# 파일 읽기
|
| 65 |
+
byte_array = read_binary_file(file_path, max_length)
|
| 66 |
+
input_data = np.expand_dims(byte_array, axis=0)
|
| 67 |
+
|
| 68 |
+
# 예측
|
| 69 |
+
pred = model.predict(input_data, verbose=0)[0][0]
|
| 70 |
+
predictions.append(pred)
|
| 71 |
+
|
| 72 |
+
# 라벨이 있는 경우
|
| 73 |
+
if 'label' in row:
|
| 74 |
+
labels.append(row['label'])
|
| 75 |
+
|
| 76 |
+
# 결과 출력
|
| 77 |
+
status = "정상" if pred > 0.5 else "악성코드"
|
| 78 |
+
confidence = pred if pred > 0.5 else 1 - pred
|
| 79 |
+
print(f"{file_path}: {status} (신뢰도: {confidence:.4f})")
|
| 80 |
+
|
| 81 |
+
except Exception as e:
|
| 82 |
+
print(f"Error processing {file_path}: {e}")
|
| 83 |
+
predictions.append(-1) # 에러 표시
|
| 84 |
+
else:
|
| 85 |
+
print(f"파일을 찾을 수 없습니다: {file_path}")
|
| 86 |
+
predictions.append(-1)
|
| 87 |
+
|
| 88 |
+
# 결과 저장
|
| 89 |
+
result_df = df.copy()
|
| 90 |
+
result_df['prediction'] = predictions
|
| 91 |
+
result_df['predicted_label'] = (np.array(predictions) > 0.5).astype(int)
|
| 92 |
+
result_df['prediction_text'] = ['정상' if p > 0.5 else '악성코드' if p >= 0 else '에러'
|
| 93 |
+
for p in predictions]
|
| 94 |
+
|
| 95 |
+
if output_path:
|
| 96 |
+
result_df.to_csv(output_path, index=False)
|
| 97 |
+
print(f"결과가 저장되었습니다: {output_path}")
|
| 98 |
+
|
| 99 |
+
# 정확도 계산 (라벨이 있는 경우)
|
| 100 |
+
if labels and len(labels) == len(predictions):
|
| 101 |
+
valid_predictions = [p for p in predictions if p >= 0]
|
| 102 |
+
valid_labels = [labels[i] for i, p in enumerate(predictions) if p >= 0]
|
| 103 |
+
|
| 104 |
+
if valid_predictions:
|
| 105 |
+
pred_binary = (np.array(valid_predictions) > 0.5).astype(int)
|
| 106 |
+
accuracy = np.mean(pred_binary == np.array(valid_labels))
|
| 107 |
+
print(f"\n정확도: {accuracy:.4f}")
|
| 108 |
+
|
| 109 |
+
return result_df
|
| 110 |
+
|
| 111 |
+
def main():
|
| 112 |
+
parser = argparse.ArgumentParser(description='MalConv 모델 예측')
|
| 113 |
+
parser.add_argument('model_path', help='저장된 모델 경로')
|
| 114 |
+
parser.add_argument('--file', help='단일 파일 예측')
|
| 115 |
+
parser.add_argument('--csv', help='배치 예측용 CSV 파일')
|
| 116 |
+
parser.add_argument('--output', help='결과 저장 경로')
|
| 117 |
+
parser.add_argument('--max_length', type=int, default=2**20, help='최대 입력 길이')
|
| 118 |
+
|
| 119 |
+
args = parser.parse_args()
|
| 120 |
+
|
| 121 |
+
if args.file:
|
| 122 |
+
# 단일 파일 예측
|
| 123 |
+
prediction = predict_file(args.model_path, args.file, args.max_length)
|
| 124 |
+
status = "정상" if prediction > 0.5 else "악성코드"
|
| 125 |
+
confidence = prediction if prediction > 0.5 else 1 - prediction
|
| 126 |
+
print(f"파일: {args.file}")
|
| 127 |
+
print(f"예측: {status} (신뢰도: {confidence:.4f})")
|
| 128 |
+
|
| 129 |
+
elif args.csv:
|
| 130 |
+
# 배치 예측
|
| 131 |
+
predict_batch(args.model_path, args.csv, args.output, args.max_length)
|
| 132 |
+
|
| 133 |
+
else:
|
| 134 |
+
print("--file 또는 --csv 옵션을 지정해주세요.")
|
| 135 |
+
|
| 136 |
+
if __name__ == "__main__":
|
| 137 |
+
main()
|
src/train.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import argparse
|
| 4 |
+
import numpy as np
|
| 5 |
+
import tensorflow as tf
|
| 6 |
+
from sklearn.model_selection import train_test_split
|
| 7 |
+
|
| 8 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 9 |
+
|
| 10 |
+
from src.model import create_malconv_model
|
| 11 |
+
from src.utils import (
|
| 12 |
+
configure_gpu_memory,
|
| 13 |
+
plot_training_history,
|
| 14 |
+
evaluate_model,
|
| 15 |
+
get_file_paths_and_labels,
|
| 16 |
+
data_generator,
|
| 17 |
+
read_binary_file
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
def train_malconv(data_source,
|
| 21 |
+
epochs=10,
|
| 22 |
+
batch_size=256,
|
| 23 |
+
max_length=2_000_000,
|
| 24 |
+
validation_split=0.2,
|
| 25 |
+
save_path="models/malconv_model.h5"):
|
| 26 |
+
"""
|
| 27 |
+
MalConv 모델 훈련 (데이터 제너레이터 사용)
|
| 28 |
+
|
| 29 |
+
Args:
|
| 30 |
+
data_source: (malware_dir, benign_dir) 튜플
|
| 31 |
+
epochs: 훈련 에포크 수
|
| 32 |
+
batch_size: 배치 크기
|
| 33 |
+
max_length: 최대 입력 길이 (2MB)
|
| 34 |
+
validation_split: 검증 데이터 비율
|
| 35 |
+
save_path: 모델 저장 경로
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
print("=" * 60)
|
| 39 |
+
print("MalConv 모델 훈련 시작 (데이터 제너레이터 모드)")
|
| 40 |
+
print("=" * 60)
|
| 41 |
+
|
| 42 |
+
# GPU 설정
|
| 43 |
+
configure_gpu_memory()
|
| 44 |
+
|
| 45 |
+
# 데이터 경로 및 레이블 로딩
|
| 46 |
+
if isinstance(data_source, tuple) and len(data_source) == 2:
|
| 47 |
+
malware_dir, benign_dir = data_source
|
| 48 |
+
filepaths, labels = get_file_paths_and_labels(malware_dir, benign_dir)
|
| 49 |
+
else:
|
| 50 |
+
raise ValueError("data_source는 (malware_dir, benign_dir) 튜플이어야 합니다.")
|
| 51 |
+
|
| 52 |
+
# 훈련/검증 분할 (파일 경로 기준)
|
| 53 |
+
filepaths_train, filepaths_val, labels_train, labels_val = train_test_split(
|
| 54 |
+
filepaths, labels, test_size=validation_split, random_state=42, stratify=labels
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
print(f"총 데이터: {len(filepaths)}")
|
| 58 |
+
print(f"훈련 데이터: {len(filepaths_train)}, 검증 데이터: {len(filepaths_val)}")
|
| 59 |
+
|
| 60 |
+
# 데이터 제너레이터 생성
|
| 61 |
+
train_gen = data_generator(filepaths_train, labels_train, batch_size, max_length)
|
| 62 |
+
val_gen = data_generator(filepaths_val, labels_val, batch_size, max_length, shuffle=False) # 검증 시에는 셔플 안함
|
| 63 |
+
|
| 64 |
+
# 모델 생성
|
| 65 |
+
print("MalConv 모델 생성 중...")
|
| 66 |
+
model = create_malconv_model(max_length)
|
| 67 |
+
|
| 68 |
+
# 더미 입력으로 모델 빌드
|
| 69 |
+
dummy_input = np.zeros((1, max_length), dtype=np.uint8)
|
| 70 |
+
_ = model(dummy_input)
|
| 71 |
+
|
| 72 |
+
print("\n=== 모델 아키텍처 ===")
|
| 73 |
+
model.summary()
|
| 74 |
+
print(f"총 파라미터 수: {model.count_params():,}")
|
| 75 |
+
|
| 76 |
+
# 콜백 설정
|
| 77 |
+
callbacks = [
|
| 78 |
+
tf.keras.callbacks.EarlyStopping(
|
| 79 |
+
monitor='val_loss',
|
| 80 |
+
patience=5, # 참을성 증가
|
| 81 |
+
restore_best_weights=True,
|
| 82 |
+
verbose=1
|
| 83 |
+
),
|
| 84 |
+
tf.keras.callbacks.ModelCheckpoint(
|
| 85 |
+
save_path,
|
| 86 |
+
monitor='val_auc',
|
| 87 |
+
save_best_only=True,
|
| 88 |
+
verbose=1,
|
| 89 |
+
mode='max' # AUC는 높을수록 좋음
|
| 90 |
+
)
|
| 91 |
+
]
|
| 92 |
+
|
| 93 |
+
# 훈련
|
| 94 |
+
print(f"\n=== 훈련 시작 ===")
|
| 95 |
+
print(f"배치 크기: {batch_size}")
|
| 96 |
+
print(f"에포크: {epochs}")
|
| 97 |
+
|
| 98 |
+
history = model.fit(
|
| 99 |
+
train_gen,
|
| 100 |
+
steps_per_epoch=len(filepaths_train) // batch_size,
|
| 101 |
+
epochs=epochs,
|
| 102 |
+
validation_data=val_gen,
|
| 103 |
+
validation_steps=len(filepaths_val) // batch_size,
|
| 104 |
+
callbacks=callbacks,
|
| 105 |
+
verbose=1
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
# 평가 (메모리 문제로 검증 데이터의 일부만 사용)
|
| 109 |
+
print("\n=== 최종 평가 ===")
|
| 110 |
+
num_eval_samples = min(len(filepaths_val), 1024) # 평가 샘플 수 제한
|
| 111 |
+
X_eval = np.array([read_binary_file(fp, max_length) for fp in filepaths_val[:num_eval_samples]])
|
| 112 |
+
y_eval = np.array(labels_val[:num_eval_samples])
|
| 113 |
+
|
| 114 |
+
if X_eval.size > 0:
|
| 115 |
+
results = evaluate_model(model, X_eval, y_eval, batch_size=batch_size//2)
|
| 116 |
+
else:
|
| 117 |
+
print("평가할 데이터가 없습니다.")
|
| 118 |
+
results = {}
|
| 119 |
+
|
| 120 |
+
# 시각화
|
| 121 |
+
plot_training_history(history)
|
| 122 |
+
|
| 123 |
+
print(f"\n모델이 저장되었습니다: {save_path}")
|
| 124 |
+
|
| 125 |
+
return model, history, results
|
| 126 |
+
|
| 127 |
+
def main():
|
| 128 |
+
parser = argparse.ArgumentParser(description='MalConv 모델 훈련')
|
| 129 |
+
|
| 130 |
+
# 데이터 소스 옵션
|
| 131 |
+
parser.add_argument('--malware_dir', required=True, help='악성코드 디렉토리')
|
| 132 |
+
parser.add_argument('--benign_dir', required=True, help='정상파일 디렉토리')
|
| 133 |
+
|
| 134 |
+
# 훈련 옵션
|
| 135 |
+
parser.add_argument('--epochs', type=int, default=20, help='에포크 수') # 에포크 증가
|
| 136 |
+
parser.add_argument('--batch_size', type=int, default=64, help='배치 크기') # 배치 크기 조정
|
| 137 |
+
parser.add_argument('--max_length', type=int, default=2_000_000, help='최대 입력 길이')
|
| 138 |
+
parser.add_argument('--save_path', default='models/malconv_model.h5', help='모델 저장 경로')
|
| 139 |
+
|
| 140 |
+
args = parser.parse_args()
|
| 141 |
+
|
| 142 |
+
data_source = (args.malware_dir, args.benign_dir)
|
| 143 |
+
|
| 144 |
+
# 저장 디렉토리 생성
|
| 145 |
+
os.makedirs(os.path.dirname(args.save_path), exist_ok=True)
|
| 146 |
+
|
| 147 |
+
# 모델 훈련
|
| 148 |
+
train_malconv(
|
| 149 |
+
data_source=data_source,
|
| 150 |
+
epochs=args.epochs,
|
| 151 |
+
batch_size=args.batch_size,
|
| 152 |
+
max_length=args.max_length,
|
| 153 |
+
save_path=args.save_path
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
if __name__ == "__main__":
|
| 157 |
+
main()
|
src/tune_model.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import itertools
|
| 4 |
+
import numpy as np
|
| 5 |
+
import tensorflow as tf
|
| 6 |
+
from sklearn.model_selection import train_test_split
|
| 7 |
+
|
| 8 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 9 |
+
|
| 10 |
+
from src.model import MalConv
|
| 11 |
+
from src.utils import preprocess_dataset
|
| 12 |
+
|
| 13 |
+
def hyperparameter_search(csv_path,
|
| 14 |
+
param_grid=None,
|
| 15 |
+
max_length=2**20,
|
| 16 |
+
epochs=5,
|
| 17 |
+
validation_split=0.2):
|
| 18 |
+
"""
|
| 19 |
+
그리드 서치를 통한 하이퍼파라미터 최적화
|
| 20 |
+
|
| 21 |
+
Args:
|
| 22 |
+
csv_path: 훈련 데이터 CSV 경로
|
| 23 |
+
param_grid: 하이퍼파라미터 그리드
|
| 24 |
+
max_length: 최대 입력 길이
|
| 25 |
+
epochs: 훈련 에포크 수
|
| 26 |
+
validation_split: 검증 데이터 비율
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
if param_grid is None:
|
| 30 |
+
param_grid = {
|
| 31 |
+
'embedding_size': [8, 16],
|
| 32 |
+
'num_filters': [64, 128],
|
| 33 |
+
'fc_size': [64, 128],
|
| 34 |
+
'learning_rate': [0.001, 0.0001]
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
print("데이터 로딩 중...")
|
| 38 |
+
X, y = preprocess_dataset(csv_path, max_length)
|
| 39 |
+
X_train, X_val, y_train, y_val = train_test_split(
|
| 40 |
+
X, y, test_size=validation_split, random_state=42, stratify=y
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
# 모든 하이퍼파라미터 조합 생성
|
| 44 |
+
param_names = list(param_grid.keys())
|
| 45 |
+
param_values = list(param_grid.values())
|
| 46 |
+
param_combinations = list(itertools.product(*param_values))
|
| 47 |
+
|
| 48 |
+
best_score = 0
|
| 49 |
+
best_params = None
|
| 50 |
+
results = []
|
| 51 |
+
|
| 52 |
+
print(f"총 {len(param_combinations)}개의 조합을 테스트합니다.")
|
| 53 |
+
|
| 54 |
+
for i, params in enumerate(param_combinations):
|
| 55 |
+
param_dict = dict(zip(param_names, params))
|
| 56 |
+
print(f"\n[{i+1}/{len(param_combinations)}] 테스트 중: {param_dict}")
|
| 57 |
+
|
| 58 |
+
try:
|
| 59 |
+
# 모델 생성
|
| 60 |
+
model = MalConv(
|
| 61 |
+
max_input_length=max_length,
|
| 62 |
+
embedding_size=param_dict['embedding_size'],
|
| 63 |
+
num_filters=param_dict['num_filters'],
|
| 64 |
+
fc_size=param_dict['fc_size']
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
# 컴파일
|
| 68 |
+
model.compile(
|
| 69 |
+
optimizer=tf.keras.optimizers.Adam(
|
| 70 |
+
learning_rate=param_dict['learning_rate']
|
| 71 |
+
),
|
| 72 |
+
loss='binary_crossentropy',
|
| 73 |
+
metrics=['accuracy']
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
# 더미 입력으로 모델 빌드
|
| 77 |
+
dummy_input = np.zeros((1, max_length), dtype=np.uint8)
|
| 78 |
+
_ = model(dummy_input)
|
| 79 |
+
|
| 80 |
+
# 훈련
|
| 81 |
+
history = model.fit(
|
| 82 |
+
X_train, y_train,
|
| 83 |
+
batch_size=16,
|
| 84 |
+
epochs=epochs,
|
| 85 |
+
validation_data=(X_val, y_val),
|
| 86 |
+
verbose=0
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
# 평가
|
| 90 |
+
val_loss, val_acc = model.evaluate(X_val, y_val, verbose=0)
|
| 91 |
+
|
| 92 |
+
result = {
|
| 93 |
+
'params': param_dict,
|
| 94 |
+
'val_accuracy': val_acc,
|
| 95 |
+
'val_loss': val_loss
|
| 96 |
+
}
|
| 97 |
+
results.append(result)
|
| 98 |
+
|
| 99 |
+
print(f"검증 정확도: {val_acc:.4f}")
|
| 100 |
+
|
| 101 |
+
# 최고 성능 업데이트
|
| 102 |
+
if val_acc > best_score:
|
| 103 |
+
best_score = val_acc
|
| 104 |
+
best_params = param_dict
|
| 105 |
+
print(f"새로운 최고 성능! 정확도: {best_score:.4f}")
|
| 106 |
+
|
| 107 |
+
except Exception as e:
|
| 108 |
+
print(f"에러 발생: {e}")
|
| 109 |
+
continue
|
| 110 |
+
|
| 111 |
+
print("\n" + "="*50)
|
| 112 |
+
print("하이퍼파라미터 튜닝 완료")
|
| 113 |
+
print("="*50)
|
| 114 |
+
print(f"최고 성능: {best_score:.4f}")
|
| 115 |
+
print(f"최적 하이퍼파라미터: {best_params}")
|
| 116 |
+
|
| 117 |
+
# 결과 정렬
|
| 118 |
+
results.sort(key=lambda x: x['val_accuracy'], reverse=True)
|
| 119 |
+
|
| 120 |
+
print("\n상위 5개 결과:")
|
| 121 |
+
for i, result in enumerate(results[:5]):
|
| 122 |
+
print(f"{i+1}. 정확도: {result['val_accuracy']:.4f}, "
|
| 123 |
+
f"파라미터: {result['params']}")
|
| 124 |
+
|
| 125 |
+
return best_params, results
|
| 126 |
+
|
| 127 |
+
def main():
|
| 128 |
+
csv_path = "Input/sample_data.csv" # 실제 데이터 경로로 변경
|
| 129 |
+
|
| 130 |
+
# 커스텀 하이퍼파라미터 그리드
|
| 131 |
+
param_grid = {
|
| 132 |
+
'embedding_size': [8, 16],
|
| 133 |
+
'num_filters': [64, 128],
|
| 134 |
+
'fc_size': [64, 128],
|
| 135 |
+
'learning_rate': [0.001, 0.0001]
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
best_params, results = hyperparameter_search(
|
| 139 |
+
csv_path=csv_path,
|
| 140 |
+
param_grid=param_grid,
|
| 141 |
+
epochs=3 # 빠른 테스트를 위해 에포크 수 감소
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
print(f"\n최적 하이퍼파라미터로 모델을 다시 훈련하세요:")
|
| 145 |
+
print(f"python src/train.py {csv_path} --epochs 10")
|
| 146 |
+
|
| 147 |
+
if __name__ == "__main__":
|
| 148 |
+
main()
|
src/utils.py
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import numpy as np
|
| 3 |
+
import pandas as pd
|
| 4 |
+
from sklearn.model_selection import train_test_split
|
| 5 |
+
from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score, balanced_accuracy_score
|
| 6 |
+
import matplotlib.pyplot as plt
|
| 7 |
+
import seaborn as sns
|
| 8 |
+
# utils.py에 누락된 import
|
| 9 |
+
import tensorflow as tf # configure_gpu_memory 함수에서 필요
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def read_binary_file(file_path, max_length=2_000_000):
|
| 13 |
+
"""
|
| 14 |
+
바이너리 파일을 읽어 정수 배열로 변환
|
| 15 |
+
논문 사양: 2MB까지 처리
|
| 16 |
+
"""
|
| 17 |
+
try:
|
| 18 |
+
with open(file_path, 'rb') as f:
|
| 19 |
+
raw_bytes = f.read()
|
| 20 |
+
|
| 21 |
+
# 바이트를 0-255 정수로 변환
|
| 22 |
+
byte_array = np.frombuffer(raw_bytes, dtype=np.uint8)
|
| 23 |
+
|
| 24 |
+
if len(byte_array) > max_length:
|
| 25 |
+
# 긴 파일: 앞 2MB만 사용 (논문 방식)
|
| 26 |
+
return byte_array[:max_length]
|
| 27 |
+
else:
|
| 28 |
+
# 짧은 파일: 0으로 패딩
|
| 29 |
+
padded = np.zeros(max_length, dtype=np.uint8)
|
| 30 |
+
padded[:len(byte_array)] = byte_array
|
| 31 |
+
return padded
|
| 32 |
+
|
| 33 |
+
except Exception as e:
|
| 34 |
+
print(f"파일 읽기 오류 {file_path}: {e}")
|
| 35 |
+
return np.zeros(max_length, dtype=np.uint8)
|
| 36 |
+
|
| 37 |
+
def load_dataset_from_directory(malware_dir, benign_dir, max_length=2_000_000, max_samples_per_class=None):
|
| 38 |
+
"""
|
| 39 |
+
디렉토리에서 직접 바이너리 파일들을 로드
|
| 40 |
+
|
| 41 |
+
Args:
|
| 42 |
+
malware_dir: 악성코드 파일들이 있는 디렉토리
|
| 43 |
+
benign_dir: 정상 파일들이 있는 디렉토리
|
| 44 |
+
max_length: 최대 바이트 길이
|
| 45 |
+
max_samples_per_class: 클래스당 최대 샘플 수
|
| 46 |
+
"""
|
| 47 |
+
X, y = [], []
|
| 48 |
+
|
| 49 |
+
# 악성코드 파일 로드
|
| 50 |
+
if os.path.exists(malware_dir):
|
| 51 |
+
malware_files = [f for f in os.listdir(malware_dir) if os.path.isfile(os.path.join(malware_dir, f))]
|
| 52 |
+
if max_samples_per_class:
|
| 53 |
+
malware_files = malware_files[:max_samples_per_class]
|
| 54 |
+
|
| 55 |
+
print(f"악성코드 파일 로딩 중... ({len(malware_files)}개)")
|
| 56 |
+
for i, filename in enumerate(malware_files):
|
| 57 |
+
file_path = os.path.join(malware_dir, filename)
|
| 58 |
+
byte_array = read_binary_file(file_path, max_length)
|
| 59 |
+
X.append(byte_array)
|
| 60 |
+
y.append(0) # 악성코드 = 0
|
| 61 |
+
|
| 62 |
+
if (i + 1) % 100 == 0:
|
| 63 |
+
print(f" {i + 1}/{len(malware_files)} 처리 완료")
|
| 64 |
+
|
| 65 |
+
# 정상 파일 로드
|
| 66 |
+
if os.path.exists(benign_dir):
|
| 67 |
+
benign_files = [f for f in os.listdir(benign_dir) if os.path.isfile(os.path.join(benign_dir, f))]
|
| 68 |
+
if max_samples_per_class:
|
| 69 |
+
benign_files = benign_files[:max_samples_per_class]
|
| 70 |
+
|
| 71 |
+
print(f"정상 파일 로딩 중... ({len(benign_files)}개)")
|
| 72 |
+
for i, filename in enumerate(benign_files):
|
| 73 |
+
file_path = os.path.join(benign_dir, filename)
|
| 74 |
+
byte_array = read_binary_file(file_path, max_length)
|
| 75 |
+
X.append(byte_array)
|
| 76 |
+
y.append(1) # 정상 = 1
|
| 77 |
+
|
| 78 |
+
if (i + 1) % 100 == 0:
|
| 79 |
+
print(f" {i + 1}/{len(benign_files)} 처리 완료")
|
| 80 |
+
|
| 81 |
+
X = np.array(X)
|
| 82 |
+
y = np.array(y)
|
| 83 |
+
|
| 84 |
+
print(f"\n데이터셋 로딩 완료:")
|
| 85 |
+
print(f" 총 샘플: {len(X)}")
|
| 86 |
+
print(f" 악성코드: {np.sum(y == 0)}")
|
| 87 |
+
print(f" 정상파일: {np.sum(y == 1)}")
|
| 88 |
+
|
| 89 |
+
return X, y
|
| 90 |
+
|
| 91 |
+
def load_dataset_from_csv(csv_path, max_length=2_000_000):
|
| 92 |
+
"""CSV 파일에서 데이터셋 로드"""
|
| 93 |
+
df = pd.read_csv(csv_path)
|
| 94 |
+
|
| 95 |
+
X, y = [], []
|
| 96 |
+
|
| 97 |
+
print("CSV에서 파일 로딩 중...")
|
| 98 |
+
for idx, row in df.iterrows():
|
| 99 |
+
file_path = row['filepath']
|
| 100 |
+
label = row['label']
|
| 101 |
+
|
| 102 |
+
if os.path.exists(file_path):
|
| 103 |
+
byte_array = read_binary_file(file_path, max_length)
|
| 104 |
+
X.append(byte_array)
|
| 105 |
+
y.append(label)
|
| 106 |
+
else:
|
| 107 |
+
print(f"파일을 찾을 수 없습니다: {file_path}")
|
| 108 |
+
|
| 109 |
+
if (idx + 1) % 1000 == 0:
|
| 110 |
+
print(f" {idx + 1} 파일 처리 완료")
|
| 111 |
+
|
| 112 |
+
return np.array(X), np.array(y)
|
| 113 |
+
|
| 114 |
+
def configure_gpu_memory():
|
| 115 |
+
"""GPU 메모리 설정"""
|
| 116 |
+
gpus = tf.config.experimental.list_physical_devices('GPU')
|
| 117 |
+
if gpus:
|
| 118 |
+
try:
|
| 119 |
+
for gpu in gpus:
|
| 120 |
+
tf.config.experimental.set_memory_growth(gpu, True)
|
| 121 |
+
print(f"GPU 설정 완료: {len(gpus)}개 GPU 사용")
|
| 122 |
+
return True
|
| 123 |
+
except RuntimeError as e:
|
| 124 |
+
print(f"GPU 설정 오류: {e}")
|
| 125 |
+
return False
|
| 126 |
+
|
| 127 |
+
def plot_training_history(history):
|
| 128 |
+
"""훈련 히스토리 시각화"""
|
| 129 |
+
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
|
| 130 |
+
|
| 131 |
+
# Loss
|
| 132 |
+
axes[0, 0].plot(history.history['loss'], label='Training Loss')
|
| 133 |
+
if 'val_loss' in history.history:
|
| 134 |
+
axes[0, 0].plot(history.history['val_loss'], label='Validation Loss')
|
| 135 |
+
axes[0, 0].set_title('Model Loss')
|
| 136 |
+
axes[0, 0].set_xlabel('Epoch')
|
| 137 |
+
axes[0, 0].set_ylabel('Loss')
|
| 138 |
+
axes[0, 0].legend()
|
| 139 |
+
axes[0, 0].grid(True)
|
| 140 |
+
|
| 141 |
+
# Accuracy
|
| 142 |
+
axes[0, 1].plot(history.history['accuracy'], label='Training Accuracy')
|
| 143 |
+
if 'val_accuracy' in history.history:
|
| 144 |
+
axes[0, 1].plot(history.history['val_accuracy'], label='Validation Accuracy')
|
| 145 |
+
axes[0, 1].set_title('Model Accuracy')
|
| 146 |
+
axes[0, 1].set_xlabel('Epoch')
|
| 147 |
+
axes[0, 1].set_ylabel('Accuracy')
|
| 148 |
+
axes[0, 1].legend()
|
| 149 |
+
axes[0, 1].grid(True)
|
| 150 |
+
|
| 151 |
+
# AUC
|
| 152 |
+
if 'auc' in history.history:
|
| 153 |
+
axes[1, 0].plot(history.history['auc'], label='Training AUC')
|
| 154 |
+
if 'val_auc' in history.history:
|
| 155 |
+
axes[1, 0].plot(history.history['val_auc'], label='Validation AUC')
|
| 156 |
+
axes[1, 0].set_title('Model AUC')
|
| 157 |
+
axes[1, 0].set_xlabel('Epoch')
|
| 158 |
+
axes[1, 0].set_ylabel('AUC')
|
| 159 |
+
axes[1, 0].legend()
|
| 160 |
+
axes[1, 0].grid(True)
|
| 161 |
+
|
| 162 |
+
# Learning Rate
|
| 163 |
+
if 'lr' in history.history:
|
| 164 |
+
axes[1, 1].plot(history.history['lr'], label='Learning Rate', color='red')
|
| 165 |
+
axes[1, 1].set_title('Learning Rate Schedule')
|
| 166 |
+
axes[1, 1].set_xlabel('Epoch')
|
| 167 |
+
axes[1, 1].set_ylabel('Learning Rate')
|
| 168 |
+
axes[1, 1].set_yscale('log')
|
| 169 |
+
axes[1, 1].legend()
|
| 170 |
+
axes[1, 1].grid(True)
|
| 171 |
+
|
| 172 |
+
plt.tight_layout()
|
| 173 |
+
plt.show()
|
| 174 |
+
|
| 175 |
+
def plot_confusion_matrix(y_true, y_pred, title="Confusion Matrix"):
|
| 176 |
+
"""혼동 행렬 시각화"""
|
| 177 |
+
cm = confusion_matrix(y_true, y_pred)
|
| 178 |
+
|
| 179 |
+
plt.figure(figsize=(8, 6))
|
| 180 |
+
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
|
| 181 |
+
xticklabels=['Malware', 'Benign'],
|
| 182 |
+
yticklabels=['Malware', 'Benign'])
|
| 183 |
+
plt.title(title)
|
| 184 |
+
plt.ylabel('True Label')
|
| 185 |
+
plt.xlabel('Predicted Label')
|
| 186 |
+
plt.show()
|
| 187 |
+
|
| 188 |
+
def evaluate_model(model, X_test, y_test, batch_size=16):
|
| 189 |
+
"""모델 성능 평가"""
|
| 190 |
+
print("모델 평가 중...")
|
| 191 |
+
|
| 192 |
+
# 예측
|
| 193 |
+
y_pred_prob = model.predict(X_test, batch_size=batch_size, verbose=1)
|
| 194 |
+
y_pred = (y_pred_prob > 0.5).astype(int).flatten()
|
| 195 |
+
|
| 196 |
+
# 메트릭 계산
|
| 197 |
+
accuracy = np.mean(y_pred == y_test)
|
| 198 |
+
balanced_acc = balanced_accuracy_score(y_test, y_pred)
|
| 199 |
+
auc_score = roc_auc_score(y_test, y_pred_prob)
|
| 200 |
+
|
| 201 |
+
print(f"\n=== 평가 결과 ===")
|
| 202 |
+
print(f"Accuracy: {accuracy:.4f}")
|
| 203 |
+
print(f"Balanced Accuracy: {balanced_acc:.4f}")
|
| 204 |
+
print(f"AUC Score: {auc_score:.4f}")
|
| 205 |
+
|
| 206 |
+
print(f"\n분류 리포트:")
|
| 207 |
+
print(classification_report(y_test, y_pred, target_names=['Malware', 'Benign']))
|
| 208 |
+
|
| 209 |
+
# 혼동 행렬 시각화
|
| 210 |
+
plot_confusion_matrix(y_test, y_pred, "MalConv Performance")
|
| 211 |
+
|
| 212 |
+
return {
|
| 213 |
+
'accuracy': accuracy,
|
| 214 |
+
'balanced_accuracy': balanced_acc,
|
| 215 |
+
'auc': auc_score,
|
| 216 |
+
'predictions': y_pred_prob
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
def get_file_paths_and_labels(malware_dir, benign_dir, max_samples_per_class=None):
|
| 220 |
+
"""
|
| 221 |
+
디렉토리에서 파일 경로와 레이블 목록을 가져옵니다. (메모리에 파일 로드 안함)
|
| 222 |
+
"""
|
| 223 |
+
filepaths = []
|
| 224 |
+
labels = []
|
| 225 |
+
|
| 226 |
+
# 악성코드 파일 경로
|
| 227 |
+
if os.path.exists(malware_dir):
|
| 228 |
+
malware_files = [os.path.join(malware_dir, f) for f in os.listdir(malware_dir) if os.path.isfile(os.path.join(malware_dir, f))]
|
| 229 |
+
if max_samples_per_class:
|
| 230 |
+
malware_files = malware_files[:max_samples_per_class]
|
| 231 |
+
filepaths.extend(malware_files)
|
| 232 |
+
labels.extend([0] * len(malware_files)) # 악성코드 = 0
|
| 233 |
+
print(f"악성코드 파일 경로 로딩: {len(malware_files)}개")
|
| 234 |
+
|
| 235 |
+
# 정상 파일 경로
|
| 236 |
+
if os.path.exists(benign_dir):
|
| 237 |
+
benign_files = [os.path.join(benign_dir, f) for f in os.listdir(benign_dir) if os.path.isfile(os.path.join(benign_dir, f))]
|
| 238 |
+
if max_samples_per_class:
|
| 239 |
+
benign_files = benign_files[:max_samples_per_class]
|
| 240 |
+
filepaths.extend(benign_files)
|
| 241 |
+
labels.extend([1] * len(benign_files)) # 정상 = 1
|
| 242 |
+
print(f"정상 파일 경로 로딩: {len(benign_files)}개")
|
| 243 |
+
|
| 244 |
+
print(f"\n총 파일 경로: {len(filepaths)}")
|
| 245 |
+
print(f" 악성코드: {labels.count(0)}")
|
| 246 |
+
print(f" 정상파일: {labels.count(1)}")
|
| 247 |
+
|
| 248 |
+
# 데이터 순서 섞기
|
| 249 |
+
indices = np.arange(len(filepaths))
|
| 250 |
+
np.random.shuffle(indices)
|
| 251 |
+
filepaths = np.array(filepaths)[indices].tolist()
|
| 252 |
+
labels = np.array(labels)[indices]
|
| 253 |
+
|
| 254 |
+
return filepaths, labels
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
def data_generator(filepaths, labels, batch_size, max_length=2_000_000, shuffle=True):
|
| 258 |
+
"""
|
| 259 |
+
데이터를 배치 단위로 생성하는 제너레이터
|
| 260 |
+
"""
|
| 261 |
+
num_samples = len(filepaths)
|
| 262 |
+
if num_samples == 0:
|
| 263 |
+
return
|
| 264 |
+
|
| 265 |
+
while True:
|
| 266 |
+
indices = np.arange(num_samples)
|
| 267 |
+
if shuffle:
|
| 268 |
+
np.random.shuffle(indices)
|
| 269 |
+
|
| 270 |
+
for i in range(0, num_samples, batch_size):
|
| 271 |
+
batch_indices = indices[i:i+batch_size]
|
| 272 |
+
|
| 273 |
+
X_batch = []
|
| 274 |
+
y_batch_list = []
|
| 275 |
+
|
| 276 |
+
for j in batch_indices:
|
| 277 |
+
try:
|
| 278 |
+
X_batch.append(read_binary_file(filepaths[j], max_length))
|
| 279 |
+
y_batch_list.append(labels[j])
|
| 280 |
+
except Exception as e:
|
| 281 |
+
print(f"Warning: Skipping file {filepaths[j]} due to error: {e}")
|
| 282 |
+
continue
|
| 283 |
+
|
| 284 |
+
if not X_batch:
|
| 285 |
+
continue
|
| 286 |
+
|
| 287 |
+
yield np.array(X_batch), np.array(y_batch_list)
|