Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
# Each line is a file pattern followed by one or more owners.

# These owners will be the default owners for everything in the repo.
* @gerardofn @winden-g @escipion
* @gerardofn @escipion @guspascual

13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# VirusTotal Plugin for IDA Pro

This is the official VirusTotal plugin for Hex-Rays IDA Pro, version **1.06**. It seamlessly integrates VirusTotal's powerful analysis capabilities directly into your reverse engineering workflow.
This is the official VirusTotal plugin for Hex-Rays IDA Pro. It seamlessly integrates VirusTotal's powerful analysis capabilities directly into your reverse engineering workflow.

The plugin offers two core functionalities:
1. **Code Similarity Search**: Perform advanced searches for code, bytes, and strings across VirusTotal's massive dataset directly from IDA's disassembly and strings views.
Expand Down Expand Up @@ -116,11 +116,12 @@ While other architectures may work, they have not been officially tested. Raw by
Check IDA Pro's output window for any message that may need your attention.

## Changelog
- v1.06 : Updated plugin metadata to support HCLI Plugin Manager ecosystem
- v1.05 : Fixes crash when Code Insight returns an invalid response
- v1.07 : Improved error handling, now CodeInsight works with other CPU architectures identified by IDA Pro.
- v1.06 : Updated plugin metadata to support HCLI Plugin Manager ecosystem.
- v1.05 : Fixes crash when Code Insight returns an invalid response.
- v1.04 : Fixes issue that left IDA hanging while a query was being performed.
- v1.03 : BUG fixed (wrongly showing an invalid api key msg)
- v1.03 : BUG fixed (wrongly showing an invalid api key msg).
- v1.02 : Added support for IDA Pro 9.2
- v1.00 : Added Code Insight panel
- v1.00 : Added Code Insight panel.
- v0.11 : Added support for IDA Pro 8.x
- v0.10 : Initial release
- v0.10 : Initial release.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.06
1.07
2 changes: 1 addition & 1 deletion ida-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"plugin": {
"name": "vt-ida-plugin",
"entryPoint": "plugin/vt.py",
"version": "1.0.6",
"version": "1.0.7",
"idaVersions": ">=8",
"description": "Integrates VirusTotal's powerful analysis capabilities directly into your reverse engineering workflow.",
"license": "Apache 2.0",
Expand Down
8 changes: 6 additions & 2 deletions plugin/virustotal/ci_notebook.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2025 Google Inc. All Rights Reserved.
# Copyright 2025 Google LLC. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
Expand Down Expand Up @@ -90,7 +90,11 @@ def encode_response(summary, description):
"summary": summary,
"description": description
}
response_str = json.dumps(response)
try:
response_str = json.dumps(response)
except TypeError:
logging.error('[VT Plugin] ERROR encoding CI response (not serializable).')
response_str = "{}"
encoded_response = base64.b64encode(response_str.encode('utf-8'))
return encoded_response.decode('ascii')

Expand Down
19 changes: 11 additions & 8 deletions plugin/virustotal/codeinsight.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2025 Google Inc. All Rights Reserved.
# Copyright 2025 Google LLC. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
Expand All @@ -14,6 +14,7 @@
__author__ = 'gerardofn@virustotal.com'

import logging
import binascii
import requests
from virustotal import config
from virustotal.vt_ida.disassembler import Disassembler
Expand Down Expand Up @@ -133,7 +134,7 @@ def _process_output(self, response):

try:
decoded_str = base64.urlsafe_b64decode(answer)
except:
except (binascii.Error, ValueError):
logging.debug('[VT Plugin] ERROR decoding Code Insight response: %s', response)
return None

Expand Down Expand Up @@ -195,7 +196,7 @@ def run(self):

try:
response = requests.post(f'{API_URL}/{endpoint}', json = {'data': payload}, headers=headers_apiv3)
except:
except requests.RequestException:
logging.debug('[VT Plugin] ERROR: unable to connect to Code Insight')
self._error_msg = 'ERROR: unable to connect to Code Insight'
return
Expand Down Expand Up @@ -375,10 +376,9 @@ def askCI(self, *args, **kwargs):

if json_str:
try:
return_msg = json.loads(json_str)
except:
logging.debug('[CodeInsight] Error processing the returned json file.')
return return_msg
return json.loads(json_str)
except json.JSONDecodeError:
logging.debug('[CodeInsight] Error processing the returned json file.')
else:
self.error_msg = ci.get_error_msg()

Expand Down Expand Up @@ -463,7 +463,10 @@ def askCI(self, *args, **kwargs):
self.encoded_src = ci.get_encoded_src()

if json_str:
return json.loads(json_str)
try:
return json.loads(json_str)
except json.JSONDecodeError:
logging.debug('[CodeInsight] Error processing the returned json file.')
else:
self.error_msg = ci.get_error_msg()

Expand Down
2 changes: 1 addition & 1 deletion plugin/virustotal/vt_ida/disassembler.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2020 Google Inc. All Rights Reserved.
# Copyright 2019 Google LLC. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
Expand Down
132 changes: 78 additions & 54 deletions plugin/virustotal/vt_ida/plugin_loader.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2025 Google Inc. All Rights Reserved.
# Copyright 2019 Google LLC. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
Expand All @@ -22,6 +22,7 @@
import logging
import os
import requests
import pathlib
import threading
from virustotal import config
from virustotal import vtgrep
Expand All @@ -37,7 +38,7 @@
except ImportError:
import configparser

VT_IDA_PLUGIN_VERSION = '1.06'
VT_IDA_PLUGIN_VERSION = '1.07'
widget_panel = VTPanel()

if config.DEBUG:
Expand All @@ -60,18 +61,24 @@ def calculate_hash(input_file):
"""Return hash if the file hash has been properly calculated."""

file_hash = None

try:
path_obj = pathlib.Path(input_file)
except TypeError:
logging.debug('[VT Plugin] Invalid path format: %s', input_file)
path_obj = None

if os.path.isfile(input_file):
if path_obj and path_obj.is_file():
hash_f = hashlib.sha256()
logging.debug('[VT Plugin] Input file available.')
with open(input_file, 'rb') as file_r:
try:
try:
with path_obj.open('rb') as file_r:
for file_buffer in iter(lambda: file_r.read(8192), b''):
hash_f.update(file_buffer)
file_hash = hash_f.hexdigest()
logging.debug('[VT Plugin] Input file hash been calculated.')
except:
logging.debug('[VT Plugin] Can\'t load the input file.')
file_hash = hash_f.hexdigest()
logging.debug('[VT Plugin] Input file hash been calculated.')
except OSError:
logging.debug('[VT Plugin] Can\'t load the input file.')
else:
logging.debug('[VT Plugin] Input file not available.')
tmp_hash = idautils.GetInputFileMD5()
Expand Down Expand Up @@ -462,7 +469,7 @@ def check_file_missing_in_VT(self):
logging.debug('[VT Plugin] Checking hash: %s', self.file_hash)
try:
response = requests.get(url, headers=headers)
except:
except requests.RequestException:
logging.error('[VT Plugin] Unable to connect to VirusTotal.com')
return False

Expand Down Expand Up @@ -496,8 +503,9 @@ def upload_file_to_VT(self):

try:
response = requests.post(url, files=files, headers=headers)
except:
except requests.RequestException:
logging.error('[VT Plugin] Unable to connect to VirusTotal.com')
return

if response.ok:
logging.debug('[VT Plugin] Uploaded successfully.')
Expand Down Expand Up @@ -548,7 +556,7 @@ def read_config(self):
else:
self.auto_upload = False
return True
except:
except configparser.Error:
logging.error('[VT Plugin] Error reading the user config file.')
return False

Expand All @@ -564,7 +572,7 @@ def write_config(self):
parser.set('General', 'auto_upload', str(self.auto_upload))
parser.write(config_file)
config_file.close()
except:
except (OSError, configparser.Error):
logging.error('[VT Plugin] Error while creating the user config file.')
return False
return True
Expand Down Expand Up @@ -601,7 +609,7 @@ def check_version(self):

try:
response = requests.get(url, headers=headers)
except:
except requests.RequestException:
logging.error('[VT Plugin] Unable to check for updates.')
return False

Expand Down Expand Up @@ -676,6 +684,13 @@ class VTplugin(idaapi.plugin_t):
vtpanel = None
vtsetup = None

def _safe_register_action(self, action_cls, label):
"""Helper to safely register an action, logging any failures without crashing."""
try:
action_cls.register(self, label)
except Exception:
logging.exception('[VT Plugin] Failed to register action: %s', label)

def init(self):
"""Set up menu hooks and implements search methods."""

Expand Down Expand Up @@ -716,48 +731,57 @@ def init(self):
arch_info = idaapi.get_inf_structure()
proc_name = get_procname(arch_info)

try:
logging.debug('[VT Plugin] Processor detected by IDA: %s', proc_name)
if (proc_name in self.SEARCH_STRICT_SUPPORTED) | (proc_name in self.SEARCH_CODE_SUPPORTED):
VTGrepWildcards.register(self, 'Search for similar code')
VTGrepWildCardsFunction.register(self, 'Search for similar functions')
if len(config.API_KEY) > 0:
CodeInsightASM.register(self, 'Ask Code Insight')
CodeInsightDecompiled.register(self, 'Ask Code Insight')

### Register menu entry
current_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '.'))
file_icon = os.path.join(current_path,
'ui',
'resources',
'vt_icon.png')
vticon_data = open(file_icon, 'rb').read()
vtmenu = idaapi.load_custom_icon(data=vticon_data)
action_desc = idaapi.action_desc_t(
'my:vtpanel',
'VirusTotal',
MenuVTPanel(),
'',
'Show VirusTotal panel with information about the current file',
vtmenu)

idaapi.register_action(action_desc)
idaapi.attach_action_to_menu(
'View/Open subviews/',
'my:vtpanel',
idaapi.SETMENU_APP)

if proc_name in self.SEARCH_STRICT_SUPPORTED:
VTGrepWildCardsStrict.register(self, 'Search for similar code (strict)')
logging.debug('[VT Plugin] Processor detected by IDA: %s', proc_name)

else:
logging.info('\n - Processor detected: %s', get_procname(arch_info))
logging.info(' - Searching for similar code is not available.')
if len(config.API_KEY) > 0:
self._safe_register_action(CodeInsightASM, 'Ask Code Insight')
self._safe_register_action(CodeInsightDecompiled, 'Ask Code Insight')

### Register VirusTotal menu entry
vticon_data = None
current_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '.'))
file_icon = os.path.join(current_path,
'ui',
'resources',
'vt_icon.png')
try:
vticon_data = open(file_icon, 'rb').read()
except OSError:
logging.error('[VT Plugin] Failed to load icon file: %s', file_icon)

if vticon_data:
try:
vtmenu = idaapi.load_custom_icon(data=vticon_data)
action_desc = idaapi.action_desc_t(
'my:vtpanel',
'VirusTotal',
MenuVTPanel(),
'',
'Show VirusTotal panel with information about the current file',
vtmenu)

idaapi.register_action(action_desc)
idaapi.attach_action_to_menu(
'View/Open subviews/',
'my:vtpanel',
idaapi.SETMENU_APP)
except Exception:
logging.exception('[VT Plugin] Failed to register VirusTotal menu icon/action.')

if (proc_name in self.SEARCH_STRICT_SUPPORTED) | (proc_name in self.SEARCH_CODE_SUPPORTED):
self._safe_register_action(VTGrepWildcards, 'Search for similar code')
self._safe_register_action(VTGrepWildCardsFunction, 'Search for similar functions')

VTGrepBytes.register(self, 'Search for bytes')
VTGrepStrings.register(self, 'Search for string')
except:
logging.error('[VT Plugin] Unable to register popups actions.')
if proc_name in self.SEARCH_STRICT_SUPPORTED:
self._safe_register_action(VTGrepWildCardsStrict, 'Search for similar code (strict)')

else:
logging.info(' - Processor detected: %s', proc_name)
logging.info(' - Searching for similar code is not available.')

self._safe_register_action(VTGrepBytes, 'Search for bytes')
self._safe_register_action(VTGrepStrings, 'Search for string')

else:
logging.info('[VT Plugin] Plugin disabled, restart IDA to proceed. ')
ida_kernwin.warning('Plugin disabled, restart IDA to proceed.')
Expand Down
1 change: 1 addition & 0 deletions plugin/virustotal/vt_ida/ui/qt5logo.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2025 Google LLC. All Rights Reserved.

# Resource object code
#
Expand Down
1 change: 1 addition & 0 deletions plugin/virustotal/vt_ida/ui/qt5panel.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2025 Google LLC. All Rights Reserved.

# Form implementation generated from reading ui file 'vtpanel.ui'
#
Expand Down
1 change: 1 addition & 0 deletions plugin/virustotal/vt_ida/ui/qt6logo.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2025 Google LLC. All Rights Reserved.

# Resource object code
#
Expand Down
1 change: 1 addition & 0 deletions plugin/virustotal/vt_ida/ui/qt6panel.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Copyright 2025 Google LLC. All Rights Reserved.
# Form implementation generated from reading ui file 'vtpanel.ui'
#
# Created by: PyQt6 UI code generator 6.9.1
Expand Down
Loading