6.14 筆記法實例 (commands.py)

import os
import getopt

from viper.common.out import *
from viper.common.colors import bold, cyan, white
from viper.core.session import __session__
from viper.core.plugins import __modules__
from viper.core.database import Database
from viper.core.storage import store_sample, get_sample_path

class Commands(object):

    def __init__(self):
        # Open connection to the database.
        self.db = Database()

        # Map commands to their related functions.
        self.commands = dict(
            help=dict(obj=self.cmd_help, description="Show this help message"),
            open=dict(obj=self.cmd_open, description="Open a file"),
            close=dict(obj=self.cmd_close, description="Close the current session"),
            info=dict(obj=self.cmd_info, description="Show information on the opened file"),
            clear=dict(obj=self.cmd_clear, description="Clear the console"),
            store=dict(obj=self.cmd_store, description="Store the opened file to the local repository"),
            delete=dict(obj=self.cmd_delete, description="Delete the opened file"),
            find=dict(obj=self.cmd_find, description="Find a file"),
        )

    ##
    # CLEAR
    #
    # This command simply clears the shell.
    def cmd_clear(self, *args):
        os.system('clear')

    ##
    # HELP
    #
    # This command simply prints the help message.
    # It lists both embedded commands and loaded modules.
    def cmd_help(self, *args):
        print(bold("Commands:"))

        rows = []
        for command_name, command_item in self.commands.items():
            rows.append([command_name, command_item['description']])

        print(table(['Command', 'Description'], rows))       
        print("")
        print(bold("Modules:"))

        rows = []
        for module_name, module_item in __modules__.items():
            rows.append([module_name, module_item['description']])

        print(table(['Command', 'Description'], rows))

    ##
    # OPEN
    #
    # This command is used to open a session on a given file.
    # It either can be an external file path, or a SHA256 hash of a file which
    # has been previously imported and stored.
    # While the session is active, every operation and module executed will be
    # run against the file specified.
    def cmd_open(self, *args):
        def usage():
            print("usage: open [-h] [-f] target")

        def help():
            usage()
            print("")
            print("Options:")
            print("\t--help (-h)\tShow this help message")
            print("\t--file (-f)\tThe target is a file")
            print("")

        try:
            opts, argv = getopt.getopt(args, 'hf', ['help', 'file'])
        except getopt.GetoptError as e:
            print(e)
            usage()
            return

        is_file = False

        for opt, value in opts:
            if opt in ('-h', '--help'):
                help()
                return
            elif opt in ('-f', '--file'):
                is_file = True

        if len(argv) == 0:
            usage()
            return
        else:
            target = argv[0]

        if is_file:
            target = os.path.expanduser(target)

            if not os.path.exists(target) or not os.path.isfile(target):
                print_error("File not found")
                return

            __session__.set(target)
        else:
            target = argv[0].strip().lower()
            path = get_sample_path(target)
            if path:
                __session__.set(path)

    ##
    # CLOSE
    #
    # This command resets the open session.
    # After that, all handles to the opened file should be closed and the
    # shell should be restored to the default prompt.
    def cmd_close(self, *args):
        __session__.clear()

    ##
    # INFO
    #
    # This command returns information on the open session. It returns details
    # on the file (e.g. hashes) and other information that might available from
    # the database.
    def cmd_info(self, *args):
        if __session__.is_set():
            print(table(
                ['Key', 'Value'],
                [
                    ('Name', __session__.file.name),
                    ('Path', __session__.file.path),
                    ('Size', __session__.file.size),
                    ('Type', __session__.file.type),
                    ('MD5', __session__.file.md5),
                    ('SHA1', __session__.file.sha1),
                    ('SHA256', __session__.file.sha256),
                    ('SHA512', __session__.file.sha512),
                    ('SSdeep', __session__.file.ssdeep),
                    ('CRC32', __session__.file.crc32)
                ]
            ))

    ##
    # STORE
    #
    # This command stores the opened file in the local repository and tries
    # to store details in the database.
    def cmd_store(self, *args):
        # TODO: Add tags argument.
        if __session__.is_set():
            # Store file to the local repository.
            new_path = store_sample(__session__.file)
            # Add file to the database.
            status = self.db.add(__session__.file)

            print_success("Stored to: {0}".format(new_path))

            # Open session to the new file.
            self.cmd_open(*[__session__.file.sha256])

    ##
    # DELETE
    #
    # This commands deletes the currenlty opened file (only if it's stored in
    # the local repository) and removes the details from the database
    def cmd_delete(self, *args):
        if __session__.is_set():
            while True:
                choice = raw_input("Are you sure you want to delete this binary? Can't be reverted! [y/n] ")
                if choice == 'y':
                    break
                elif choice == 'n':
                    return

            rows = self.db.find('sha256', __session__.file.sha256)
            if rows:
                malware_id = rows[0].id
                if self.db.delete(malware_id):
                    print_success("File deleted")
                else:
                    print_error("Unable to delete file")

            os.remove(get_sample_path(__session__.file.sha256))
            __session__.clear()

    ##
    # FIND
    #
    # This command is used to search for files in the database.
    def cmd_find(self, *args):
        if len(args) == 0:
            print_error("Invalid search term")
            return

        key = args[0]
        try:
            value = args[1]
        except IndexError:
            value = None

        items = self.db.find(key, value)
        if not items:
            return

        rows = []
        for item in items:
            rows.append([item.name, item.type, item.size, item.sha256])

        print(table(['Name', 'Type', 'Size', 'SHA256'], rows))
此程式主要功能為定義 viper 系統指令。

有 1 類別 Commands
及 8 類別函式
    cmd_clear, 
    cmd_close, 
    cmd_delete, 
    cmd_find, 
    cmd_help, 
    cmd_info, 
    cmd_open, 
    cmd_store 

類別 instance 初始化時,做兩件事
1. 用 self.db = Database() 開啟與 db 的連線

2. 定義 self.commands
self.commands 是用兩層 巢狀字典組成
字典第一層 key 值是系統指令, value 是第二層字典。
第二層字典內有兩個 item。
    第一個 item 是 obj,對應到類別函式,
    第二個 item 是 description,即描述指令功能之文字

因此 console.py 只要呼叫 self.cmd.commands[第一層(指令)][第二層(obj)]()
就可執行 viper 系統函式。

接下來逐一看類別函式。

1. cmd_clear()
此函式用 os.system('clear') 清除 shell 上訊息。

2. cmd_help()
此函式會印出 help 訊息,即系統指令與模組指令。
首先印出 Commands
並用迴圈 將 commands 中 items 抓出來,

所以 command_name 就是 key (指令), command_item 就是 value(obj 與 description)。
隨後將 key 與 value 塞進 list
再將此 list 塞進 rows (也是個 list)

接著用 prettytable 的 table 將系統指令以表格印出。

同樣的方法運用至將模組指令與描述以 table 印出。
(plugins.py 中, line 30 將模組指令以與系統指令相同之巢狀字典方式,組成)


3. cmd_open
此函式首先接收 *args,不定參數,也就是可以接參數,但參數數量不限。
函式中,定義了兩個函式 usage() 與 help()
usage() 印出 open 使用方法簡單說明
help() 中印出方法簡單說明與參數詳細說明

接者用 getopt 開始 parse 參數
getopt 回傳兩 item。
1. opts
2. argv

opts 是個 list of (option, value)
argv 是其他沒被定義到的參數值 list

用 try 來 parse 參數
若執行失敗,則 raise getopt.GetoptError 
接著再印出 usage()
再 return,結束 cmd_open

接下來設定 is_file變數,預設是 False

再來用迴圈處理 opts
主要將 option 拿出來,看到底是 -h 還是 -f
若是 -h,則印出 help()後直接 return 結束 cmd_open
若是 -f 則將 is_file 改為 True

接著變檢查 argv,也就是沒被定義的參數 list 長度是否為零
如果是零,則表示 -f 後面沒有接檔案路徑。
所以印出 usage()後,接 return 結束 cmd_open
若 argv 長度非零,則將 list 中第一個 item 抓為檔案位置,也就是 target。

接著,檢查 is_file flag 是否為 True,
若是,則用 os.path.expanduser() 處理 target 路徑,主要功能是將 ~ replace 成 $HOME。
接著用 os.path.exists() 檢查路徑是否存在,以及使用 os.path.isfile()檢查是否為檔案。
若不存在或不是檔案,則印出 File not found 錯誤訊息

若存在,則用 __session__.set() 開啟 session。

若 is_file flag 為 False (此種作法就是直接 open file)
則用 .strip().lower() 方法將 argv[0] 先以空白為區隔丟進 list,再將 list 裡面字串通通轉為小寫。
接著用 get_sample_path,取得檔案路徑
若路徑 (path) 不是空的
則用 __session__.set() 開啟 session。

4. cmd_close()
負責關掉 __session__

5. cmd_info()
負責印出現有 session 所分析檔案之基本資訊。
使用 prettytable 以表格漂亮印出相關資訊。

6. cmd_store()
將 session打開的檔案存在 local repository
將 檔案資訊存在 database。

首先用 __session__.is_set() 檢查 session 是否開啟。
接著用 store_sample(儲存檔案)後 ,將檔案路徑傳給 new_path 變數
再用 seld.db.add(__session__.file) 將檔案資訊存到 db 中

最後印出檔案儲存路徑,並重新開啟檔案 session。

7. cmd_delete()
首先檢查 session 是否開啟。
若開啟,則用一 while 迴圈,
再次確認使用者是否真的要刪除 current open file。
若是 則 break 迴圈,若否則用 return 結束 cmd_delete。

若是,則用 self.db.find('sha256', __session__.file.sha256) 找出檔案,並將結果 assign 給 rows
rows 的 type 是 list。
(Return the results represented by this Query as a list.)
參考文獻:http://docs.sqlalchemy.org/en/latest/orm/query.html?highlight=query

接著,若 rows 不是空,則用 rows[0].id 將 malware_id 找出來。
其中 rows[0] type 是 class instance。
並用 self.db.delete(malware.id) 刪除資料庫中該檔案資訊。
若失敗,則印出 Unable to delete file。

為何 rows[0] 一定會是 current opened file 呢?
原因在於 database.py 中 line 41 ~ line 49
針對 md5, crc32, sha1, sha256, sha512 欄位,做 uniq。

最後用 os.remove() 將 local repository 中檔案刪除
並關掉 session。

8. cmd_find()
搜尋資料庫中檔案資訊

首先檢查參數長度是否為零,若是,則表示沒給參數。
接著開始 parse 參數,key 表示 sha256, value 表示 hash value。
接著用 self.db.find(key, value) 將資料撈出來,倒給 items。
若無資料,就用 return 結束 cmd_find。
若有,則用 prettytable 印出檔案相關資訊。

Last updated

Was this helpful?