#malwareとコールバックサーバの繋がりの可視化
##これは
networkxの練習のためmalwareとコールバックサーバの繋がりの可視化について語った記事です。
##環境
Kali Linux
python 2.2.17
##大まかな実装の流れ
1.引数の設定
2.suffixの整理
3.hostname取得の関数
4.targetディレクトリの走査
5.ネットワーク作成
##必要なライブラリを読み込み
import pefile
import sys
import argparse
import os
import pprint
import networkx
import re
from networkx.drawing.nx_agraph import write_dot
import collections
from networkx.algorithms import bipartite
##コマンドライン引数の設定
args = argparse.ArgumentParser()
args.add_argument("target")
args.add_argument("filename")
args.add_argument("malware_pro")
args.add_argument("hostname_pro")
args = args.parse_args()
network = networkx.Graph()
suffixの整理
suffixes = map(lambda string: string.strip(), open("suffixes.txt"))
suffixes = set(suffixes)
最後2行でmapとラムダ式を使いながらsuffixes.txtのsuffixを整理。suffixes.txtは自分で作ってもいいがめんどい。今回はMalware Data Scienceからお借りする。また、target引数に渡すマルウェアサンプルも同じく拝借。まずはsuffixes.txt内の改行コードが邪魔なのでstrip()。python3だとmap関数の返り値がmap関数のオブジェクトになるので注意。
def get_hostnames(string):
tmp = re.findall(r'(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}', string)
hostnames = filter(lambda hostname: hostname.split(".")[-1].lower() in suffixes, tmp)
return hostnames
正規表現でドメインを特定し、"."で区切る。1番最後の要素を小文字にし、filterとラムダ式を使ってsuffixesと一致するかチェック。ここもpython3だと...
for root,dirs,files in os.walk(args.target):
for file in files:
try:
pe = pefile.PE(os.path.join(root, file))
except pefile.PEFormatError:
continue
f_path = os.path.join(root, file)
contents = os.popen("strings '{0}'".format(f_path)).read()
hostnames = get_hostnames(contents)
if len(hostnames):
network.add_node(file,label=file ,color='blue', penwidth=3,bipartite=0)
for hostname in hostnames:
network.add_node(hostname,label=hostname,color='purple', penwidth=10,bipartite=1)
network.add_edge(hostname, file ,penwidth=2)
if hostnames:
print "Extracted hostname from:", file
pprint.pprint(hostname)
walkで目的のディレクトリを走査しfor文でルートディレクトリ、サブディレクトリ、ファイルパスを取得しtryでpefile可動化をチェック。違ったら次のループへ。f_pathにpeファイルのフルパスを格納し、印字化可能文字列を引数としてget_hostnames関数に渡す。
ホスト名を取得できたら、contentsalwareとホストのネットワークをそれぞれ作成。次にhost名が見つかればそのファイルパスを2部ネットワークの片方に、ホスト名自体を全て2部ネットワークのもう片方に登録。最後のif文で実行時にコマンドライン上にホスト名が取得できたファイルパスを表示
write_dot(network, args.filename)
codes= set(n for n,d in network.nodes(data=True) if d['bipartite']==0)
hostname = set(network)-codes
1行目でネットワークをfilenameに書き出す。先ほどbipartite=0としたマルウェア側をcodeに格納。network.nodes(data=True)とすることで、ノード名とノードのもつ属性の辞書からなるタプルが返ってくる。同じくhostnameにホスト名をいれる。
codes = bipartite.projected_graph(network, codes)
hostname = bipartite.projected_graph(network, hostname)
マルウェアとホストそれぞれについて射影を作成する。射影とはここでは2部ネットワークを簡素化したもので、例えばマルウェア(codes)の場合、ホスト名が共通しているマルウェア同士を結ぶ。
write_dot(codes ,args.malware_pro)
write_dot(hostname ,args.hostname_pro)
作成した射影をそれぞれファイルに書き出す。
##fdpで可視化
fdp filename.dot -T png -o filename.png -Goverlap=false
fdp malware_pro.dot -T png -o malwre_pro.png -Goverlap=false
fdp hostname_pro.dot -T png -o hostname_pro.png -Goverlap=false
fdpとは力指向に基づいてネットワークを可視化してくれるツールです。他にもsfdpなど他のツールもありますが、今回は割愛。実行してみる。
filename.png
malware_pro.pnf
hostname_pro.png
という画像ファイルでできる。
eog filename.png
できた^^
##ちなみに力指向って?
ネットワークをレイアウトする際に問題なのがエッジの長さです。ノードの重みが同じならエッジの長さも同じであることが望ましい。しかしノードの数が4つ以上になると絶対にノードの長さを全て同じにすることはできないですよね?したがって、この歪みを最小にしようと考えます。そこで出てくるのが力指向アルゴリズム。エッジをバネに見立ててシミュレートするとエッジが自動的にノード間の距離をできるだけ均一にしようとします。バネ偉大ですね。では。