nuitka –mingw64 –windows-disable-console Antivirus flagging Python patcher application as virus despite benign behavior

So for some reason, avast and AVG, is flagging my patcher as virus

Avast
Win64:TrojanX-gen [Trj]
AVG
Win64:TrojanX-gen [Trj]

Command is:

nuitka --mingw64 --windows-disable-console 

App code is

import hashlib
import json
import os
import time
from pathlib import Path
from stat import S_ISDIR, S_ISREG
import sys
import logging
import subprocess
import traceback

import paramiko
import psutil
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QTextEdit, QLabel, QDesktopWidget, 
    QGraphicsDropShadowEffect
from PyQt5.QtCore import QThread, pyqtSignal, Qt, QTimer, QPropertyAnimation, QEasingCurve
from PyQt5.QtGui import QMovie, QLinearGradient, QColor, QPalette, QBrush
import ctypes

# Set up logging configuration

enable_logging = False

if enable_logging:
    logging.basicConfig(filename='patcher.log', level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
else:
    logging.getLogger().addHandler(logging.NullHandler())

def get_project_root() -> str:
    return str(Path(__file__).parent)


class FileHashUtility:
    def __init__(self, sftp_instance=None):
        self.sftp_instance = sftp_instance
        self.excluded_extensions = ['.rar']
        self.excluded_files = ['.gitignore', 'generate_hash.py', "hash_values.json"]

    def calculate_file_hash(self, filepath, local=False):
        hasher = hashlib.md5()
        if local:
            with open(filepath, 'rb') as file:
                for chunk in iter(lambda: file.read(4096), b''):
                    hasher.update(chunk)
        else:
            with self.sftp_instance.open(filepath, 'rb') as file:
                for chunk in iter(lambda: file.read(4096), b''):
                    hasher.update(chunk)
        return hasher.hexdigest()

    def generate_hash_values(self, directory):
        hash_values = {}
        for root, dirs, files in os.walk(directory):
            for filename in files:
                filepath = os.path.join(root, filename)
                if self.should_ignore(filepath):
                    continue
                hash_value = self.calculate_file_hash(filepath, local=True)
                hash_values[filepath.replace(directory, '')[1:].replace('\', '/')] = hash_value
        return hash_values

    def should_ignore(self, filepath):
        filename = os.path.basename(filepath)
        if filename in self.excluded_files:
            return True
        _, ext = os.path.splitext(filename)
        if ext.lower() in self.excluded_extensions:
            return True
        return False

    def save_to_json(self, hash_values, filename, save=False):
        if save:
            with open(filename, 'w') as json_file:
                json.dump(hash_values, json_file, indent=4)

class DownloaderThread(QThread):
    status_updated = pyqtSignal(str)
    download_finished = pyqtSignal(bool, float)  # Signal to indicate download finished and the elapsed time

    def __init__(self, sftp_client, local_directory, remote_directory):
        super().__init__()
        self.sftp_client = sftp_client
        self.local_directory = local_directory
        self.remote_directory = remote_directory

    def run(self):
        start_time = time.time()
        self.sftp_client.get_r_portable(self.remote_directory, self.local_directory, self.status_updated.emit)
        elapsed_time = time.time() - start_time
        self.download_finished.emit(True, elapsed_time)


class DownloaderUI(QWidget):
    def __init__(self, sftp_client, remote_targer_dir):
        super().__init__()
        self.sftp_client = sftp_client
        self.local_directory = os.getcwd()  # Set the local directory to the current working directory
        self.exe_path = os.getcwd() + '\metin2client.exe'
        print(self.exe_path)
        self.init_ui()
        self.remote_targer_dir = remote_targer_dir

    def init_ui(self):
        # Set window properties
        self.setWindowTitle("SFTP Downloader")
        self.setGeometry(0, 0, 800, 600)  # Set initial window size

        # Create layout
        layout = QVBoxLayout(self)
        layout.setContentsMargins(20, 20, 20, 20)

        # Create gradient background
        self.setAutoFillBackground(True)
        palette = self.palette()
        gradient = QLinearGradient(0, 0, 0, self.height())
        gradient.setColorAt(0.0, QColor(255, 165, 0))  # Orange
        gradient.setColorAt(1.0, QColor(128, 0, 128))  # Purple
        palette.setBrush(QPalette.Window, QBrush(gradient))
        self.setPalette(palette)

        # Add title label
        title_label = QLabel("M2 - Patcher", self)
        title_label.setAlignment(Qt.AlignCenter)
        # Styling the label with a rounded, friendly font and cool color
        title_label.setStyleSheet('QLabel { font-family: "Segoe UI", sans-serif; font-size: 20pt; font-weight: bold; color: white; text-decoration-color: rgba(255, 255, 255, 0.5); border-radius: 15px; padding: 10px; box-shadow: 0 0 10px rgba(0, 0, 20, 0.5); }')
        title_label.resize(202, 202)
        # Creating and setting the shadow effect
        shadow_effect = QGraphicsDropShadowEffect()
        shadow_effect.setOffset(5, 5)
        shadow_effect.setBlurRadius(20)
        shadow_effect.setColor(QColor(0, 0, 20, 127))  # Semi-transparent dark shadow
        title_label.setGraphicsEffect(shadow_effect)

        layout.addWidget(title_label, alignment=Qt.AlignCenter)

        # Add preview window with styles
        self.preview_window = QTextEdit(self)
        self.preview_window.setReadOnly(True)
        self.preview_window.setStyleSheet('QTextEdit { border: 0.3px solid black; border-radius: 20px; padding: 20px; background-color: rgba(255, 255, 255, 0.95); font-family: "Segoe UI", sans-serif; font-size: 9pt; color: #333; margin-top: 20px; }')

        self.preview_window.setGeometry(50, 50, 500, 300)

        # Creating and setting the shadow effect
        shadow_effect = QGraphicsDropShadowEffect()
        shadow_effect.setOffset(5, 5)
        shadow_effect.setBlurRadius(15)
        self.preview_window.setGraphicsEffect(shadow_effect)

        self.preview_window.setFixedSize(int(self.width() * 0.8), int(self.height() * 0.8))
        self.preview_window.setAlignment(Qt.AlignCenter)

        # Center the preview_window horizontally within the layout
        layout.addWidget(self.preview_window, alignment=Qt.AlignCenter)

        # Add loading animation
        self.loading_label = QLabel(self)
        self.loading_label.setAlignment(Qt.AlignCenter)
        self.loading_animation = QMovie("loading.gif")  # Replace "loading.gif" with your loading animation file
        self.loading_label.setMovie(self.loading_animation)
        layout.addWidget(self.loading_label)
        self.loading_label.hide()

        # Add start button
        self.start_button = QPushButton("Start Download", self)
        self.start_button.clicked.connect(self.start_download)

        self.start_button.setStyleSheet('QPushButton { background-color: #4b4453; color: white; border: 1.5px solid rgba(255, 255, 255, 1); border-radius: 17px; padding: 15px 30px; font-family: "Segoe UI", sans-serif; font-size: 10pt; font-weight: bold; height: 20px; box-shadow: 10px 10px 15px rgba(17, 14, 16, 0.8), inset 0 -3px 3px rgba(0,0,0,0.3); transition: background-color 0.5s ease-in-out, color 0.5s ease-in-out, transform 0.5s ease-in-out, box-shadow 0.5s ease-in-out; } QPushButton:hover { background-color: white; color: #4CAF50; transform: translateY(-2px); box-shadow: 10px 10px 15px rgba(17, 14, 16, 0.5); } QPushButton:pressed { box-shadow: inset 0 -3px 3px rgba(0,0,0,0.5); transform: translateY(0); }')
        shadow_effect = QGraphicsDropShadowEffect()
        shadow_effect.setOffset(5, 5)  # Similar to CSS box-shadow offset
        shadow_effect.setBlurRadius(15)  # Similar to CSS box-shadow blur radius
        shadow_effect.setColor(QColor(17, 14, 16, 204))  # Similar to CSS box-shadow color, semi-transparent
        self.start_button.setGraphicsEffect(shadow_effect)

        layout.addWidget(self.start_button, alignment=Qt.AlignCenter)

    def center_window(self):
        screen = QDesktopWidget().screenGeometry()
        size = self.geometry()
        self.move((screen.width() - size.width()) // 2, (screen.height() - size.height()) // 2)

    def start_download(self):
        try:
            self.preview_window.clear()
            if "metin2_client" in os.getcwd():
                self.preview_window.append(
                    "<span style='font-size: 14pt;'>Starting download...</span><br>")

                self.start_button.setEnabled(False)
                self.loading_label.show()
                self.loading_animation.start()

                self.downloader_thread = DownloaderThread(self.sftp_client, self.local_directory, self.remote_targer_dir)
                self.downloader_thread.status_updated.connect(self.update_preview)
                self.downloader_thread.download_finished.connect(self.download_finished)
                self.downloader_thread.start()
            else:
                self.preview_window.append(
                    "<span style='font-size: 16pt; color: red; font-weight: bold; font-family: Arial, "
                    "sans-serif;'>Please add the patcher in 'metin2_client' directory</span><br> "
                )
                self.preview_window.append(
                    "<span style='font-size: 12pt; color: red; font-weight: bold; font-family: Arial, "
                    "sans-serif;'>Proceeding to close patcher in 2 seconds</span><br> "
                )
                QTimer.singleShot(3000, self.close)


        except Exception as e:
            error_msg = f"Error occurred: {str(e)}"
            traceback_str = traceback.format_exc()
            self.status_updated.emit(error_msg)
            self.download_finished.emit(False, 0)
            logging.info("Error occurred during download")


    def update_preview(self, status):
        self.preview_window.append(status)

    def download_finished(self, success, elapsed_time):
        self.loading_animation.stop()
        self.loading_label.hide()
        self.start_button.setEnabled(False)

        if success:
            self.preview_window.append(f"Download completed in {elapsed_time:.2f} seconds.")
            # Use QTimer.singleShot for UI update
            QTimer.singleShot(4000, self.launch_exe)
        else:
            QTimer.singleShot(1000, lambda: self.preview_window.append("Download failed."))

    def launch_exe(self):
        if self.exe_path:
            self.preview_window.append("Launched metin2")
            os.startfile(self.exe_path)
            self.preview_window.append(":MT2 client launched, preparing to exit...")
            QTimer.singleShot(1000, self.close)



class M2_SFTP_Client:
    def __init__(self, hostname, username, password, port=22):
        self.hostname = hostname
        self.username = username
        self.password = password
        self.port = port
        self.sftp_instance = None
        self.transport = None

    def connect(self):
        try:
            # Create a transport object
            self.transport = paramiko.Transport((self.hostname, self.port))
            self.transport.connect(username=self.username, password=self.password)

            # Create an SFTP client
            self.sftp_instance = paramiko.SFTPClient.from_transport(self.transport)

            logging.info("Connected to SFTP server successfully.")
        except paramiko.AuthenticationException:
            error_msg = "Authentication failed. Please check your credentials."
            logging.error(error_msg)
            print(error_msg)
        except paramiko.SSHException as ssh_exception:
            error_msg = f"SSH exception occurred"
            logging.error(error_msg)
            print(error_msg)
        except Exception as e:
            error_msg = f"An error occurred during server connection"
            logging.error(error_msg)
            print(error_msg)

    def get_list_of_files_in_current_dir(self):
        if self.sftp_instance:
            current_dir = self.sftp_instance.listdir()
            logging.info(f"Current working directory files list: {current_dir}")
            return current_dir
        else:
            error_msg = "SFTP connection not established. Please connect first."
            logging.error(error_msg)
            print(error_msg)
            return []

    def change_working_dir(self, directory):
        if self.sftp_instance:
            self.sftp_instance.chdir(directory)
        else:
            error_msg = "SFTP connection not established. Please connect first."
            logging.error(error_msg)
            print(error_msg)

    def get_current_directory(self):
        if self.sftp_instance:
            current_dir = self.sftp_instance.getcwd()
            logging.info(f"Current working directory: {current_dir}")
        else:
            error_msg = "SFTP connection not established. Please connect first."
            logging.error(error_msg)
            print(error_msg)

    def get_r_portable2(self, remotedir, localdir, status_callback):
        hash_file_path = 'hash_values.json'

        try:
            # print("hash files is", hash_file_path)
            # print(self.sftp_instance.getcwd())
            # print(hash_file_path)
            print('cur w dir', self.sftp_instance.getcwd())
            with self.sftp_instance.open(hash_file_path, 'r') as hash_file:
                remote_hashes = json.load(hash_file)
                # print("remote_hashesn", json.dumps(remote_hashes, indent=4))
        except FileNotFoundError:
            status_callback("hash_values.json not found on the server. Unable to compare hashes.")
            return
        except Exception as e:
            status_callback(f"Error occurred while reading hash_values.json: {e}")
            return
        print(remotedir)
        for entry in self.sftp_instance.listdir_attr(remotedir):
            print(entry)
            remotepath = remotedir + "/" + entry.filename
            localpath = os.path.join(localdir, entry.filename)
            mode = entry.st_mode
            if S_ISDIR(mode):
                try:
                    os.mkdir(localpath)
                except OSError:
                    pass
                self.get_r_portable(remotepath, localpath, status_callback)
            elif S_ISREG(mode):

                print('filename', localpath.replace(get_project_root(), '').replace('\', '/'))
                print("projhect r oot", get_project_root())
                print("getcwd", os.getcwd())
                remote_hash = remote_hashes.get(localpath.replace(os.getcwd(), "").replace('\', '/'))

                if remote_hash:
                    # Check if the local file exists
                    if os.path.exists(localpath):
                        # Calculate the hash of the local file
                        local_hash = self.calculate_file_hash(localpath, local=True)
                        status_callback(f"Checking file {localpath}")

                        # Compare the hashes
                        if remote_hash == local_hash:
                            status_callback(f"File {localpath} is up to date. Skipping download.")
                        else:
                            status_callback(f"File {localpath} has a different hash. Downloading.")
                            self.sftp_instance.get(remotepath, localpath)
                    else:
                        status_callback(f"File {localpath} does not exist locally. Downloading.")
                        self.sftp_instance.get(remotepath, localpath)
                else:
                    status_callback(f"No hash found for {entry.filename} in hash_values.json. Deleting.")
                    if "patcher-source" not in os.getcwd():
                        if os.path.exists(localpath):
                            os.remove(localpath)
                            pass

    def get_r_portable3(self, remotedir, localdir, status_callback):
        try:
            if not self.sftp_instance:
                status_callback("SFTP connection not established. Please connect first.")
                return

            hash_file_path = 'hash_values.json'
            try:
                with self.sftp_instance.open(hash_file_path, 'r') as hash_file:
                    remote_hashes = json.load(hash_file)
            except FileNotFoundError:
                status_callback("hash_values.json not found on the server.")
                return
            except Exception as e:
                status_callback(f"Error occurred while reading hash_values.json: {e}")
                return

            # Removing the path prefix from remote hash keys
            remote_hashes = {key.replace('/home/ftp_user/', ''): value for key, value in remote_hashes.items()}

            # Generate local hashes
            utility = FileHashUtility()
            directory_path = os.getcwd()
            local_hashes = utility.generate_hash_values(directory_path)

            # Removing cwd prefix from local hash keys
            local_hashes = {key.replace(os.getcwd() + '/', ''): value for key, value in local_hashes.items()}

            # Download or delete files based on hash comparison
            for local_file, local_hash in local_hashes.items():
                remote_hash = remote_hashes.get(local_file)
                if remote_hash:
                    if remote_hash != local_hash:
                        remote_file_path = os.path.join('/home/ftp_user', local_file)
                        status_callback(f"Updating local file {local_file}.")
                        self.sftp_instance.get(remote_file_path, os.path.join(os.getcwd(), local_file))
                else:
                    status_callback(f"Removing local file {local_file} not present on server.")
                    try:
                        # os.remove(os.path.join(os.getcwd(), local_file))
                        pass
                    except Exception as ex:
                        pass

            # Remove local files that are not in the remote hashes
            for local_file in set(local_hashes.keys()) - set(remote_hashes.keys()):
                status_callback(f"Removing outdated local file {local_file}.")
                try:
                    pass
                    # os.remove(os.path.join(os.getcwd(), local_file))
                except Exception:
                    pass
        except Exception as e:
            status_callback(str(e))
            time.sleep(102300)

    def delete_unwanted_file(self, path, callback):
        exceptions = ['m2-patcher.exe']
        try:
            if 'm2-patcher.exe' not in path:
                callback(f"Removing unnecesary file {path}")
                os.remove(path)
        except Exception as e:
            pass

    def get_r_portable(self, remotedir, localdir, status_callback, remove=True):
        status_callback(f"Checking local files, please wait")

        if not self.sftp_instance:
            status_callback("SFTP connection not established. Please connect first.")
            return

        # Load remote hashes from the server
        hash_file_path = 'hash_values.json'
        try:
            with self.sftp_instance.open(hash_file_path, 'r') as hash_file:
                remote_hashes = json.load(hash_file)
        except FileNotFoundError:
            status_callback("hash_values.json not found on the server.")
            return
        except Exception as e:
            status_callback(f"Error occurred while reading hash_values.json: {e}")
            return

        utility = FileHashUtility()
        local_directory_path = os.getcwd()
        local_hashes = utility.generate_hash_values(local_directory_path)

        # local_hashes = {key.replace(os.getcwd() + '/', '').replace('\', ''): value for key, value in local_hashes.items()}

        local_files = local_hashes.keys()

        for remote_file, remote_hash in remote_hashes.items():
            local_file_to_download = os.path.join(localdir, remote_file)

            local_directory_path = os.path.dirname(local_file_to_download)

            if not os.path.exists(local_directory_path):
                os.makedirs(local_directory_path)
                print(f"Created directory: {local_directory_path}")

            if remote_file not in local_files:
                print(remote_file)
                print(local_files)
                self.sftp_instance.get(remote_file, local_file_to_download)
                status_callback(f"- Downloading {local_file_to_download}")
            else:
                if remote_hash != local_hashes[remote_file]:
                    try:
                        status_callback(f"- Removing outdated file {local_file_to_download}")
                        if remove:
                            os.remove(local_file_to_download)
                    except Exception:
                        pass
                    status_callback(f"- Replacing outdated file {local_file_to_download}")
                    self.sftp_instance.get(remote_file, local_file_to_download)
                else:
                    status_callback(f"- OK {local_file_to_download}")

        if remove:
            [self.delete_unwanted_file(os.path.join(localdir, key), status_callback) for key in local_hashes.keys() if key not in remote_hashes.keys()]



    def calculate_file_hash(self, filepath, local=False):
        hasher = hashlib.md5()
        if local:
            with open(filepath, 'rb') as file:
                for chunk in iter(lambda: file.read(4096), b''):
                    hasher.update(chunk)
        else:
            with self.sftp_instance.open(filepath, 'rb') as file:
                for chunk in iter(lambda: file.read(4096), b''):
                    hasher.update(chunk)
        return hasher.hexdigest()

    def disconnect(self):
        if self.sftp_instance:
            self.sftp_instance.close()
            if self.transport:
                self.transport.close()
                logging.info("Disconnected from SFTP server.")
        else:
            logging.error("SFTP connection not established.")

def status_callback(text):
    print(text)

if __name__ == "__main__":
    if 0 == 0:
        try:
            app = QApplication(sys.argv)

            sftp_client = M2_SFTP_Client("62.171.164.192", "ftp_user", "]]9-82hD$pAMVI94D3D=TdCd@5I$D?]")
            sftp_client.connect()
            sftp_client.change_working_dir("/client")
            print(sftp_client.get_list_of_files_in_current_dir())

            downloader_ui = DownloaderUI(sftp_client, '.')
            downloader_ui.show()

            sys.exit(app.exec_())
        except Exception as e:
            pass

    if 0 == 1:
        sftp_client = M2_SFTP_Client("xc", "t", "rq")
        sftp_client.connect()
        sftp_client.change_working_dir("/client")

        sftp_client.get_r_portable('', os.getcwd(), status_callback=status_callback, remove=False)

    if 0 == 1:
        print("Started generating hashes")
        if os.path.exists("hash_values.json"):
            os.remove("hash_values.json")
        print("Removed hash_values.json")

        utility = FileHashUtility()
        directory_path = os.getcwd()
        hash_values = utility.generate_hash_values(directory_path)

        json_filename = "hash_values.json"
        utility.save_to_json(hash_values, json_filename)

        print(f"Hash values saved to {json_filename}.")

I’ve developed a Python patcher application for updating files from a remote server. However, I’m encountering an issue where antivirus software, specifically Avast and AVG, flag the executable generated by the application as a virus.
Problem:

The patcher application downloads files from a remote server, updates local files, and launches an executable. While the application doesn’t directly interact with the operating system or other programs in a malicious manner, it’s still being flagged by antivirus software.

Disable the antivirus or use pyinstaller. A prompt similar to what you presented would be:

pip install pyinstaller

pyinstaller –onefile –noconsole NameHere.py

New contributor

Remove is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật