공유 블로그

Oxyry Python Obfuscator(https://pyob.oxyry.com/)코드 난독화 사이트가 없어져서 그냥 제가 만들었습니다.

왜 없애는지 어차피 이렇게 개발자들이 다시 만들텐데 정말 귀찮게 됐었네요.

 

필요하신분 아래 코드 또는 첨부 파일 다운받으셔서 난독화_GUI.py 사용하시면 됩니다.

 

첨부파일

9.코드 암호화.zip
0.01MB


 2025/08/30 

9.코드 암호화.zip
0.04MB

 

 

코드

더보기

 코드_난독화_도구.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
코드 난독화 변환 도구
다양한 난독화 기법을 사용하여 Python 코드를 난독화합니다.
"""

import os
import re
import random
import string
import base64
import zlib
import argparse


class CodeObfuscator:
    """코드 난독화 클래스"""
    
    def __init__(self):
        self.variable_mapping = {}
        self.function_mapping = {}
        self.class_mapping = {}
        self.string_mapping = {}
        self.comment_removed = False
        
    def generate_random_name(self, length: int = 8) -> str:
        """랜덤 변수명 생성"""
        chars = string.ascii_letters + '_'
        return ''.join(random.choice(chars) for _ in range(length))
    
    def remove_comments(self, code: str) -> str:
        """주석 제거"""
        # 한 줄 주석 제거
        lines = code.split('\n')
        cleaned_lines = []
        
        for line in lines:
            # 문자열 내의 #은 보존
            in_string = False
            string_char = None
            cleaned_line = ""
            
            for i, char in enumerate(line):
                if char in ['"', "'"] and (i == 0 or line[i-1] != '\\'):
                    if not in_string:
                        in_string = True
                        string_char = char
                    elif string_char == char:
                        in_string = False
                        string_char = None
                
                if char == '#' and not in_string:
                    break
                cleaned_line += char
            
            if cleaned_line.strip():
                cleaned_lines.append(cleaned_line)
        
        return '\n'.join(cleaned_lines)
    
    def remove_docstrings(self, code: str) -> str:
        """docstring 제거"""
        # 삼중 따옴표 docstring 제거
        code = re.sub(r'""".*?"""', '', code, flags=re.DOTALL)
        code = re.sub(r"'''.*?'''", '', code, flags=re.DOTALL)
        return code
    
    def obfuscate_strings(self, code: str) -> str:
        """문자열 난독화"""
        def replace_string(match):
            original_string = match.group(1)
            if len(original_string) > 3:  # 짧은 문자열은 건너뛰기
                # base64로 인코딩
                encoded = base64.b64encode(original_string.encode()).decode()
                return f'base64.b64decode("{encoded}").decode()'
            return match.group(0)
        
        # 문자열을 base64로 인코딩
        code = re.sub(r'"([^"]*)"', replace_string, code)
        code = re.sub(r"'([^']*)'", replace_string, code)
        
        return code
    
    def obfuscate_variables(self, code: str) -> str:
        """변수명 난독화"""
        # 변수명 패턴 찾기
        variable_pattern = r'\b([a-zA-Z_][a-zA-Z0-9_]*)\s*='
        
        def replace_variable(match):
            var_name = match.group(1)
            if var_name not in ['self', 'cls', '__init__', '__main__', 'if', 'for', 'while', 'def', 'class', 'import', 'from', 'as', 'in', 'is', 'not', 'and', 'or', 'True', 'False', 'None']:
                if var_name not in self.variable_mapping:
                    self.variable_mapping[var_name] = self.generate_random_name()
                return f'{self.variable_mapping[var_name]} ='
            return match.group(0)
        
        code = re.sub(variable_pattern, replace_variable, code)
        
        # 변수 사용 부분도 변경
        for original, obfuscated in self.variable_mapping.items():
            code = re.sub(r'\b' + re.escape(original) + r'\b', obfuscated, code)
        
        return code
    
    def obfuscate_functions(self, code: str) -> str:
        """함수명 난독화"""
        # 함수 정의 패턴
        function_pattern = r'def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\('
        
        def replace_function(match):
            func_name = match.group(1)
            if not func_name.startswith('__'):
                if func_name not in self.function_mapping:
                    self.function_mapping[func_name] = self.generate_random_name()
                return f'def {self.function_mapping[func_name]}('
            return match.group(0)
        
        code = re.sub(function_pattern, replace_function, code)
        
        # 함수 호출 부분도 변경
        for original, obfuscated in self.function_mapping.items():
            code = re.sub(r'\b' + re.escape(original) + r'\s*\(', f'{obfuscated}(', code)
        
        return code
    
    def add_junk_code(self, code: str) -> str:
        """쓰레기 코드 추가"""
        junk_lines = [
            f'{self.generate_random_name()} = {random.randint(1, 100)}',
            f'{self.generate_random_name()} = "{self.generate_random_name()}"',
            f'if False: {self.generate_random_name()} = {random.randint(1, 100)}',
            f'while False: {self.generate_random_name()} = {random.randint(1, 100)}',
            f'for {self.generate_random_name()} in []: pass'
        ]
        
        lines = code.split('\n')
        new_lines = []
        
        for line in lines:
            new_lines.append(line)
            if random.random() < 0.1:  # 10% 확률로 쓰레기 코드 추가
                new_lines.append(random.choice(junk_lines))
        
        return '\n'.join(new_lines)
    
    def encode_code(self, code: str) -> str:
        """코드를 base64로 인코딩하여 실행 코드로 변환"""
        compressed = zlib.compress(code.encode('utf-8'))
        encoded = base64.b64encode(compressed).decode()
        
        decoder_code = f'''
import base64
import zlib
exec(zlib.decompress(base64.b64decode("{encoded}")).decode('utf-8'))
'''
        return decoder_code
    
    def obfuscate(self, code: str, level: int = 1, encode: bool = False) -> str:
        """코드 난독화 메인 함수"""
        print("난독화 시작...")
        
        # 1단계: 기본 정리
        if level >= 1:
            print("1단계: 주석 및 docstring 제거...")
            code = self.remove_comments(code)
            code = self.remove_docstrings(code)
        
        # 2단계: 변수명 난독화
        if level >= 2:
            print("2단계: 변수명 난독화...")
            code = self.obfuscate_variables(code)
        
        # 3단계: 함수명 난독화
        if level >= 3:
            print("3단계: 함수명 난독화...")
            code = self.obfuscate_functions(code)
        
        # 4단계: 문자열 난독화
        if level >= 4:
            print("4단계: 문자열 난독화...")
            code = self.obfuscate_strings(code)
        
        # 5단계: 쓰레기 코드 추가
        if level >= 5:
            print("5단계: 쓰레기 코드 추가...")
            code = self.add_junk_code(code)
        
        # 최종 인코딩
        if encode:
            print("최종 인코딩...")
            code = self.encode_code(code)
        
        print("난독화 완료!")
        return code


def read_file(file_path: str) -> str:
    """파일 읽기"""
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            return f.read()
    except Exception as e:
        print(f"파일 읽기 오류: {e}")
        return ""


def write_file(file_path: str, content: str) -> bool:
    """파일 쓰기"""
    try:
        os.makedirs(os.path.dirname(file_path), exist_ok=True)
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(content)
        return True
    except Exception as e:
        print(f"파일 쓰기 오류: {e}")
        return False


def main():
    parser = argparse.ArgumentParser(description='Python 코드 난독화 도구')
    parser.add_argument('input_file', help='입력 파일 경로')
    parser.add_argument('-o', '--output', help='출력 파일 경로')
    parser.add_argument('-l', '--level', type=int, default=3, choices=[1, 2, 3, 4, 5], 
                       help='난독화 레벨 (1-5, 기본값: 3)')
    parser.add_argument('-e', '--encode', action='store_true', 
                       help='최종 코드를 base64로 인코딩')
    parser.add_argument('--no-comments', action='store_true', 
                       help='주석 제거')
    
    args = parser.parse_args()
    
    # 파일 읽기
    code = read_file(args.input_file)
    if not code:
        return
    
    # 난독화 실행
    obfuscator = CodeObfuscator()
    obfuscated_code = obfuscator.obfuscate(code, level=args.level, encode=args.encode)
    
    # 출력 파일 경로 설정
    if args.output:
        output_path = args.output
    else:
        base_name = os.path.splitext(args.input_file)[0]
        output_path = f"{base_name}_obfuscated.py"
    
    # 파일 저장
    if write_file(output_path, obfuscated_code):
        print(f"난독화된 코드가 저장되었습니다: {output_path}")
        
        # 원본과 난독화된 코드 크기 비교
        original_size = len(code)
        obfuscated_size = len(obfuscated_code)
        print(f"원본 크기: {original_size} 문자")
        print(f"난독화 크기: {obfuscated_size} 문자")
        print(f"크기 변화: {((obfuscated_size - original_size) / original_size * 100):.1f}%")


if __name__ == "__main__":
    main()

 

난독화_GUI.py   

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
코드 난독화 GUI 도구
사용자 친화적인 인터페이스로 코드를 난독화합니다.
"""

import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import os
import threading
from 코드_난독화_도구 import CodeObfuscator, read_file, write_file


class ObfuscatorGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("코드 난독화 도구")
        self.root.geometry("800x600")
        self.root.resizable(True, True)
        
        # 변수 초기화
        self.input_file_path = tk.StringVar()
        self.output_file_path = tk.StringVar()
        self.obfuscation_level = tk.IntVar(value=3)
        self.encode_code = tk.BooleanVar(value=False)
        self.remove_comments = tk.BooleanVar(value=True)
        
        self.setup_ui()
        
    def setup_ui(self):
        # 메인 프레임
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 그리드 가중치 설정
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=1)
        main_frame.rowconfigure(3, weight=1)
        
        # 파일 선택 섹션
        ttk.Label(main_frame, text="입력 파일:").grid(row=0, column=0, sticky=tk.W, pady=5)
        ttk.Entry(main_frame, textvariable=self.input_file_path, width=50).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=5, pady=5)
        ttk.Button(main_frame, text="찾아보기", command=self.browse_input_file).grid(row=0, column=2, padx=5, pady=5)
        
        ttk.Label(main_frame, text="출력 파일:").grid(row=1, column=0, sticky=tk.W, pady=5)
        ttk.Entry(main_frame, textvariable=self.output_file_path, width=50).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=5, pady=5)
        ttk.Button(main_frame, text="찾아보기", command=self.browse_output_file).grid(row=1, column=2, padx=5, pady=5)
        
        # 옵션 섹션
        options_frame = ttk.LabelFrame(main_frame, text="난독화 옵션", padding="10")
        options_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
        options_frame.columnconfigure(1, weight=1)
        
        # 난독화 레벨
        ttk.Label(options_frame, text="난독화 레벨:").grid(row=0, column=0, sticky=tk.W, pady=5)
        level_frame = ttk.Frame(options_frame)
        level_frame.grid(row=0, column=1, sticky=tk.W, pady=5)
        
        levels = [
            ("1 - 기본 (주석 제거)", 1),
            ("2 - 변수명 난독화", 2),
            ("3 - 함수명 난독화", 3),
            ("4 - 문자열 난독화", 4),
            ("5 - 최고 (쓰레기 코드 추가)", 5)
        ]
        
        for i, (text, value) in enumerate(levels):
            ttk.Radiobutton(level_frame, text=text, variable=self.obfuscation_level, 
                           value=value).grid(row=0, column=i, padx=10)
        
        # 추가 옵션
        ttk.Checkbutton(options_frame, text="코드 인코딩 (base64)", 
                       variable=self.encode_code).grid(row=1, column=0, columnspan=2, sticky=tk.W, pady=5)
        ttk.Checkbutton(options_frame, text="주석 제거", 
                       variable=self.remove_comments).grid(row=2, column=0, columnspan=2, sticky=tk.W, pady=5)
        
        # 버튼 섹션
        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=3, column=0, columnspan=3, pady=10)
        
        ttk.Button(button_frame, text="난독화 실행", command=self.run_obfuscation).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="미리보기", command=self.preview_obfuscation).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="초기화", command=self.reset_form).pack(side=tk.LEFT, padx=5)
        
        # 결과 표시 섹션
        result_frame = ttk.LabelFrame(main_frame, text="결과", padding="10")
        result_frame.grid(row=4, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=10)
        result_frame.columnconfigure(0, weight=1)
        result_frame.rowconfigure(0, weight=1)
        
        # 노트북 (탭) 생성
        self.notebook = ttk.Notebook(result_frame)
        self.notebook.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 원본 코드 탭
        original_frame = ttk.Frame(self.notebook)
        self.notebook.add(original_frame, text="원본 코드")
        self.original_text = scrolledtext.ScrolledText(original_frame, wrap=tk.WORD, height=15)
        self.original_text.pack(fill=tk.BOTH, expand=True)
        
        # 난독화된 코드 탭
        obfuscated_frame = ttk.Frame(self.notebook)
        self.notebook.add(obfuscated_frame, text="난독화된 코드")
        self.obfuscated_text = scrolledtext.ScrolledText(obfuscated_frame, wrap=tk.WORD, height=15)
        self.obfuscated_text.pack(fill=tk.BOTH, expand=True)
        
        # 통계 탭
        stats_frame = ttk.Frame(self.notebook)
        self.notebook.add(stats_frame, text="통계")
        self.stats_text = scrolledtext.ScrolledText(stats_frame, wrap=tk.WORD, height=15)
        self.stats_text.pack(fill=tk.BOTH, expand=True)
        
        # 진행 상황 표시
        self.progress_var = tk.StringVar(value="준비됨")
        ttk.Label(main_frame, textvariable=self.progress_var).grid(row=5, column=0, columnspan=3, pady=5)
        
    def browse_input_file(self):
        filename = filedialog.askopenfilename(
            title="입력 파일 선택",
            filetypes=[("Python files", "*.py"), ("All files", "*.*")]
        )
        if filename:
            self.input_file_path.set(filename)
            self.load_original_code()
            
            # 자동으로 출력 파일명 설정
            base_name = os.path.splitext(filename)[0]
            self.output_file_path.set(f"{base_name}_obfuscated.py")
    
    def browse_output_file(self):
        filename = filedialog.asksaveasfilename(
            title="출력 파일 선택",
            defaultextension=".py",
            filetypes=[("Python files", "*.py"), ("All files", "*.*")]
        )
        if filename:
            self.output_file_path.set(filename)
    
    def load_original_code(self):
        file_path = self.input_file_path.get()
        if file_path and os.path.exists(file_path):
            try:
                code = read_file(file_path)
                self.original_text.delete(1.0, tk.END)
                self.original_text.insert(1.0, code)
                self.progress_var.set("원본 코드 로드됨")
            except Exception as e:
                messagebox.showerror("오류", f"파일 읽기 오류: {e}")
    
    def run_obfuscation(self):
        if not self.input_file_path.get():
            messagebox.showwarning("경고", "입력 파일을 선택해주세요.")
            return
        
        if not self.output_file_path.get():
            messagebox.showwarning("경고", "출력 파일을 선택해주세요.")
            return
        
        # 별도 스레드에서 실행
        thread = threading.Thread(target=self._run_obfuscation_thread)
        thread.daemon = True
        thread.start()
    
    def _run_obfuscation_thread(self):
        try:
            self.progress_var.set("난독화 진행 중...")
            self.root.update()
            
            # 원본 코드 읽기
            original_code = read_file(self.input_file_path.get())
            if not original_code:
                messagebox.showerror("오류", "입력 파일을 읽을 수 없습니다.")
                return
            
            # 난독화 실행
            obfuscator = CodeObfuscator()
            obfuscated_code = obfuscator.obfuscate(
                original_code, 
                level=self.obfuscation_level.get(), 
                encode=self.encode_code.get()
            )
            
            # 파일 저장
            if write_file(self.output_file_path.get(), obfuscated_code):
                # 결과 표시
                self.obfuscated_text.delete(1.0, tk.END)
                self.obfuscated_text.insert(1.0, obfuscated_code)
                
                # 통계 계산
                original_size = len(original_code)
                obfuscated_size = len(obfuscated_code)
                size_change = ((obfuscated_size - original_size) / original_size * 100)
                
                stats = f"""난독화 완료!

파일 정보:
- 입력 파일: {self.input_file_path.get()}
- 출력 파일: {self.output_file_path.get()}

크기 정보:
- 원본 크기: {original_size:,} 문자
- 난독화 크기: {obfuscated_size:,} 문자
- 크기 변화: {size_change:+.1f}%

난독화 옵션:
- 레벨: {self.obfuscation_level.get()}
- 인코딩: {'예' if self.encode_code.get() else '아니오'}
- 주석 제거: {'예' if self.remove_comments.get() else '아니오'}

난독화된 파일이 성공적으로 저장되었습니다!
"""
                
                self.stats_text.delete(1.0, tk.END)
                self.stats_text.insert(1.0, stats)
                
                self.notebook.select(2)  # 통계 탭으로 이동
                self.progress_var.set("난독화 완료!")
                
                messagebox.showinfo("완료", "코드 난독화가 완료되었습니다!")
            else:
                messagebox.showerror("오류", "파일 저장에 실패했습니다.")
                
        except Exception as e:
            messagebox.showerror("오류", f"난독화 중 오류가 발생했습니다: {e}")
            self.progress_var.set("오류 발생")
    
    def preview_obfuscation(self):
        if not self.input_file_path.get():
            messagebox.showwarning("경고", "입력 파일을 선택해주세요.")
            return
        
        try:
            self.progress_var.set("미리보기 생성 중...")
            self.root.update()
            
            # 원본 코드 읽기
            original_code = read_file(self.input_file_path.get())
            if not original_code:
                messagebox.showerror("오류", "입력 파일을 읽을 수 없습니다.")
                return
            
            # 난독화 실행 (미리보기용)
            obfuscator = CodeObfuscator()
            obfuscated_code = obfuscator.obfuscate(
                original_code, 
                level=self.obfuscation_level.get(), 
                encode=self.encode_code.get()
            )
            
            # 결과 표시
            self.obfuscated_text.delete(1.0, tk.END)
            self.obfuscated_text.insert(1.0, obfuscated_code)
            
            # 통계 계산
            original_size = len(original_code)
            obfuscated_size = len(obfuscated_code)
            size_change = ((obfuscated_size - original_size) / original_size * 100)
            
            stats = f"""미리보기 통계:

크기 정보:
- 원본 크기: {original_size:,} 문자
- 난독화 크기: {obfuscated_size:,} 문자
- 크기 변화: {size_change:+.1f}%

난독화 옵션:
- 레벨: {self.obfuscation_level.get()}
- 인코딩: {'예' if self.encode_code.get() else '아니오'}
- 주석 제거: {'예' if self.remove_comments.get() else '아니오'}
"""
            
            self.stats_text.delete(1.0, tk.END)
            self.stats_text.insert(1.0, stats)
            
            self.notebook.select(1)  # 난독화된 코드 탭으로 이동
            self.progress_var.set("미리보기 완료")
            
        except Exception as e:
            messagebox.showerror("오류", f"미리보기 생성 중 오류가 발생했습니다: {e}")
            self.progress_var.set("오류 발생")
    
    def reset_form(self):
        self.input_file_path.set("")
        self.output_file_path.set("")
        self.obfuscation_level.set(3)
        self.encode_code.set(False)
        self.remove_comments.set(True)
        
        self.original_text.delete(1.0, tk.END)
        self.obfuscated_text.delete(1.0, tk.END)
        self.stats_text.delete(1.0, tk.END)
        
        self.progress_var.set("준비됨")


def main():
    root = tk.Tk()
    app = ObfuscatorGUI(root)
    root.mainloop()


if __name__ == "__main__":
    main()

  

 


 2025/08/30 

내용이 길어 첨부파일로만 올립니다.

 

 

주의사항

0.난독화_GUI.py의 레벨5를 사용하면서 코드 인코딩, 주석 암호화 또는 인코딩 암호화를 사용전 난독화 실행을 한번 실행후 결과를 확인해서 사용해보고 잘될때 코드 인코딩, 주석 암호화 또는 인코딩 암호화를 사용하시기 바랍니다.

공유하기

facebook twitter kakaoTalk naver band Copy URL

후원하기