5. 난독화 기법 추가

손지웅·2025년 4월 1일

난독화 기법 추가

난독화란, '의미를 유지한 채' 사람이 읽기 어렵고 분석하기 어렵게 만드는 것. 즉 난독화되기 전 코드와 동작은 같아야 함.

import generated.MiniCBaseListener;
import generated.MiniCParser;
import org.antlr.v4.runtime.tree.ParseTreeListener;
import org.antlr.v4.runtime.tree.ParseTreeProperty;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MiniCPrintListener extends MiniCBaseListener implements ParseTreeListener {
    private static String output = "";
    ParseTreeProperty<String> cTree = new ParseTreeProperty<>();
    private final Map<String, String> varNameMap = new HashMap<>();

    public static String getOutput() {
        return output;
    }

    private String getRandomVarName(String original) {
        return varNameMap.computeIfAbsent(original,
                k -> "var_" + UUID.randomUUID().toString().replace("-", "").substring(0, 8));
    }

    // 리터럴 난독화: 숫자를 무의미한 수식으로 변환
    private String obfuscateLiterals(String text) {
        Pattern p = Pattern.compile("\\b\\d+\\b");
        Matcher m = p.matcher(text);
        StringBuffer sb = new StringBuffer();

        while (m.find()) {
            int val = Integer.parseInt(m.group());
            int a = (int) (Math.random() * val);
            int b = val - a;
            m.appendReplacement(sb, "(" + a + "+" + b + ")");
        }
        m.appendTail(sb);
        return sb.toString();
    }

    // 제어 흐름 조건식에 의미 없는 난수 조건 추가
    private String obfuscateConditionRaw(String condition) {
        int rand = new Random().nextInt(10000);
        return "((" + condition + ") && (" + rand + " == " + rand + "))";
    }

    @Override
    public void exitProgram(MiniCParser.ProgramContext ctx) {
        StringBuilder program = new StringBuilder();
        for (MiniCParser.DeclContext decl : ctx.decl()) {
            program.append(cTree.get(decl));
        }
        output = program.toString().replaceAll("\\s+", " ");
    }

    @Override
    public void exitDecl(MiniCParser.DeclContext ctx) {
        String result = ctx.fun_decl() != null ?
                cTree.get(ctx.fun_decl()) : cTree.get(ctx.var_decl());
        cTree.put(ctx, result);
    }

    @Override
    public void exitVar_decl(MiniCParser.Var_declContext ctx) {
        if (ctx.IDENT() != null) {
            String newName = getRandomVarName(ctx.IDENT().getText());
            String text = ctx.getText().replace(ctx.IDENT().getText(), newName);
            cTree.put(ctx, obfuscateLiterals(text).replaceAll("\\s+", " "));
        }
    }


    /**
     * ctx.getText()는 해당 노드의 원본 소스 코드만 문자열로 반환한다.
     * 따라서 이전 단계에서 while_stmt 등에 적용된 불투명 조건과 같은 난독화 결과는 반영되지 않는다.
     * 또한 여기서 다시 리터럴 난독화를 수행하면, 이미 적용된 난독화 결과가 덮어쓰이게 되어 의도한 효과가 사라진다.
     *
     * 따라서 exitFun_decl에서는 각 구성 요소(type, name, params, body)를 직접 조합하여,
     * 이전 단계에서 생성된 난독화 결과(cTree.get(...))를 그대로 활용해야 한다.
     */
    @Override
    public void exitFun_decl(MiniCParser.Fun_declContext ctx) {
        String type = ctx.type_spec().getText();
        String oldName = ctx.IDENT().getText();
        String newName = getRandomVarName(oldName);
        String params = ctx.params().getText();
        String body = cTree.get(ctx.compound_stmt());  // 우리가 만든 난독화 코드가 여기에 있음

        String result = type + " " + newName + "(" + params + ")" + body;
        cTree.put(ctx, result);
    }



    @Override
    public void exitLocal_decl(MiniCParser.Local_declContext ctx) {
        if (ctx.IDENT() != null) {
            String newName = getRandomVarName(ctx.IDENT().getText());
            String text = ctx.getText().replace(ctx.IDENT().getText(), newName);
            cTree.put(ctx, obfuscateLiterals(text).replaceAll("\\s+", " "));
        }
    }

    @Override
    public void exitExpr(MiniCParser.ExprContext ctx) {
        if (ctx.IDENT() != null) {
            String newName = getRandomVarName(ctx.IDENT().getText());
            String text = ctx.getText().replace(ctx.IDENT().getText(), newName);
            cTree.put(ctx, obfuscateLiterals(text).replaceAll("\\s+", " "));
        } else {
            cTree.put(ctx, obfuscateLiterals(ctx.getText()).replaceAll("\\s+", " "));
        }
    }

    @Override
    public void exitExpr_stmt(MiniCParser.Expr_stmtContext ctx) {
        cTree.put(ctx, obfuscateLiterals(ctx.getText()).replaceAll("\\s+", " "));
    }

    @Override
    public void exitIf_stmt(MiniCParser.If_stmtContext ctx) {
        String cond = ctx.expr().getText();
        String thenStmt = cTree.get(ctx.stmt(0));
        String elseStmt = (ctx.ELSE() != null && ctx.stmt(1) != null)
                ? " else " + cTree.get(ctx.stmt(1)) : "";

        String result = "if (" + obfuscateConditionRaw(cond) + ") " + thenStmt + elseStmt;
        cTree.put(ctx, result);
    }

    @Override
    public void exitWhile_stmt(MiniCParser.While_stmtContext ctx) {
        String cond = ctx.expr().getText();
        String stmt = cTree.get(ctx.stmt());

        String result = "while (" + obfuscateConditionRaw(cond) + ") " + stmt;
        cTree.put(ctx, result);
    }

    @Override
    public void exitReturn_stmt(MiniCParser.Return_stmtContext ctx) {
        cTree.put(ctx, obfuscateLiterals(ctx.getText()).replaceAll("\\s+", " "));
    }

    @Override
    public void exitCompound_stmt(MiniCParser.Compound_stmtContext ctx) {
        StringBuilder body = new StringBuilder("{");
        for (MiniCParser.Local_declContext decl : ctx.local_decl()) {
            body.append(cTree.get(decl));
        }
        for (MiniCParser.StmtContext stmt : ctx.stmt()) {
            body.append(cTree.get(stmt));
        }
        body.append("}");
        cTree.put(ctx, body.toString());
    }

    @Override
    public void exitParams(MiniCParser.ParamsContext ctx) {
        cTree.put(ctx, ctx.getText().replaceAll("\\s+", " "));
    }

    @Override
    public void exitParam(MiniCParser.ParamContext ctx) {
        if (ctx.IDENT() != null) {
            String newName = getRandomVarName(ctx.IDENT().getText());
            String result = ctx.getText().replace(ctx.IDENT().getText(), newName)
                    .replaceAll("\\s+", " ");
            cTree.put(ctx, result);
        }
    }

    @Override
    public void exitStmt(MiniCParser.StmtContext ctx) {
        // 이미 while_stmt에서 조립했을 가능성이 있으니 get 먼저 확인
        if (cTree.get(ctx) != null) return;

        if (ctx.while_stmt() != null) {
            cTree.put(ctx, cTree.get(ctx.while_stmt()));
        } else if (ctx.if_stmt() != null) {
            cTree.put(ctx, cTree.get(ctx.if_stmt()));
        } else {
            cTree.put(ctx, ctx.getText());
        }
    }

    @Override
    public void exitArgs(MiniCParser.ArgsContext ctx) {
        cTree.put(ctx, ctx.getText().replaceAll("\\s+", " "));
    }

    @Override
    public void exitType_spec(MiniCParser.Type_specContext ctx) {
        cTree.put(ctx, ctx.getText());
    }
}
 
  • 해당 코드는 변수,함수명 치환, 정수 리터럴을 수식으로 치환, 조건식 내부에 항상 true인 조건 추가, 공백 제거 등의 난독화를 수행한다.
int var_6b9ab29d(){intvar_552e65b3=(4+1);intvar_c5108cff=(1+2);intvar_5afaf975;z=x+y;if (((z>5) && (1174 == 1174))) {z=z-1;}while (((z<10) && (1038 == 1038))) {z=z+1;}returnz;}"
  • 때문에, 간단한 코드 수준에서는 사람이 봐도 역 난독화가 어렵지 않다.

사람이나, 분석기가 읽기 어렵게 하면 되지 않을까??


import generated.MiniCBaseListener;
import generated.MiniCParser;
import org.antlr.v4.runtime.tree.ParseTreeListener;
import org.antlr.v4.runtime.tree.ParseTreeProperty;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MiniCPrintListener extends MiniCBaseListener implements ParseTreeListener {
    private static String output = "";
    ParseTreeProperty<String> cTree = new ParseTreeProperty<>();
    private final Map<String, String> varNameMap = new HashMap<>();

    public static String getOutput() {
        return output;
    }

    private String getRandomVarName(String original) {
        return varNameMap.computeIfAbsent(original,
                k -> "var_" + UUID.randomUUID().toString().replace("-", "").substring(0, 8));
    }

    // 리터럴 난독화: 숫자를 무의미한 수식으로 변환
    private String obfuscateLiterals(String text) {
        Pattern p = Pattern.compile("\\b\\d+\\b");
        Matcher m = p.matcher(text);
        StringBuffer sb = new StringBuffer();

        while (m.find()) {
            int val = Integer.parseInt(m.group());
            int a = (int) (Math.random() * val);
            int b = val - a;
            m.appendReplacement(sb, "(" + a + "+" + b + ")");
        }
        m.appendTail(sb);
        return sb.toString();
    }

    // 제어 흐름 조건식에 의미 없는 난수 조건 추가
    private String obfuscateConditionRaw(String condition) {
        int rand = new Random().nextInt(10000);
        return "((" + condition + ") && (" + rand + " == " + rand + "))";
    }

    // 추가: 각 토큰 사이에 무작위 문자열 삽입하는 난독화
    private String insertRandomStrings(String code) {
        String[] tokens = code.split("(?<=\\W)|(?=\\W)");
        StringBuilder obfuscated = new StringBuilder();

        Random rand = new Random();
        for (String token : tokens) {
            if (!token.trim().isEmpty()) {
                obfuscated.append(generateRandomAlpha(rand.nextInt(5) + 3)); // 최소 3~7자의 랜덤 문자열
                obfuscated.append(token);
            }
        }
        obfuscated.append(generateRandomAlpha(rand.nextInt(5) + 3));
        return obfuscated.toString();
    }

    //추가: 무작위 영문 소문자 문자열 생성
    private String generateRandomAlpha(int length) {
        String alphabet = "abcdefghijklmnopqrstuvwxyz";
        StringBuilder sb = new StringBuilder();
        Random rand = new Random();
        for (int i = 0; i < length; i++) {
            sb.append(alphabet.charAt(rand.nextInt(alphabet.length())));
        }
        return sb.toString();
    }

    @Override
    public void exitProgram(MiniCParser.ProgramContext ctx) {
        StringBuilder program = new StringBuilder();
        for (MiniCParser.DeclContext decl : ctx.decl()) {
            program.append(cTree.get(decl));
        }
        output = program.toString().replaceAll("\\s+", " ");
    }

    @Override
    public void exitDecl(MiniCParser.DeclContext ctx) {
        String result = ctx.fun_decl() != null ?
                cTree.get(ctx.fun_decl()) : cTree.get(ctx.var_decl());
        cTree.put(ctx, result);
    }

    @Override
    public void exitVar_decl(MiniCParser.Var_declContext ctx) {
        if (ctx.IDENT() != null) {
            String newName = getRandomVarName(ctx.IDENT().getText());
            String text = ctx.getText().replace(ctx.IDENT().getText(), newName);
            text = obfuscateLiterals(text); // 리터럴 난독화 적용
            text = insertRandomStrings(text); // 랜덤 문자열 삽입 난독화 적용
            cTree.put(ctx, text.replaceAll("\\s+", " "));
        }
    }

    /**
     * ctx.getText()는 해당 노드의 원본 소스 코드만 문자열로 반환한다.
     * 따라서 이전 단계에서 while_stmt 등에 적용된 불투명 조건과 같은 난독화 결과는 반영되지 않는다.
     * 또한 여기서 다시 리터럴 난독화를 수행하면, 이미 적용된 난독화 결과가 덮어쓰이게 되어 의도한 효과가 사라진다.
     *
     * 따라서 exitFun_decl에서는 각 구성 요소(type, name, params, body)를 직접 조합하여,
     * 이전 단계에서 생성된 난독화 결과(cTree.get(...))를 그대로 활용해야 한다.
     */
    @Override
    public void exitFun_decl(MiniCParser.Fun_declContext ctx) {
        String type = ctx.type_spec().getText();
        String oldName = ctx.IDENT().getText();
        String newName = getRandomVarName(oldName);
        String params = ctx.params().getText();
        String body = cTree.get(ctx.compound_stmt());

        String result = type + " " + newName + "(" + params + ")" + body;
        cTree.put(ctx, result);
    }

    @Override
    public void exitLocal_decl(MiniCParser.Local_declContext ctx) {
        if (ctx.IDENT() != null) {
            String newName = getRandomVarName(ctx.IDENT().getText());
            String text = ctx.getText().replace(ctx.IDENT().getText(), newName);
            text = obfuscateLiterals(text);
            text = insertRandomStrings(text); // 랜덤 문자열 삽입 추가
            cTree.put(ctx, text.replaceAll("\\s+", " "));
        }
    }

    @Override
    public void exitExpr(MiniCParser.ExprContext ctx) {
        String text;
        if (ctx.IDENT() != null) {
            String newName = getRandomVarName(ctx.IDENT().getText());
            text = ctx.getText().replace(ctx.IDENT().getText(), newName);
        } else {
            text = ctx.getText();
        }
        text = obfuscateLiterals(text);
        text = insertRandomStrings(text); //랜덤 문자열 삽입 추가
        cTree.put(ctx, text.replaceAll("\\s+", " "));
    }

    @Override
    public void exitExpr_stmt(MiniCParser.Expr_stmtContext ctx) {
        String text = obfuscateLiterals(ctx.getText());
        text = insertRandomStrings(text); //추가
        cTree.put(ctx, text.replaceAll("\\s+", " "));
    }

    @Override
    public void exitIf_stmt(MiniCParser.If_stmtContext ctx) {
        String cond = ctx.expr().getText();
        String thenStmt = cTree.get(ctx.stmt(0));
        String elseStmt = (ctx.ELSE() != null && ctx.stmt(1) != null)
                ? " else " + cTree.get(ctx.stmt(1)) : "";

        String result = "if (" + obfuscateConditionRaw(cond) + ") " + thenStmt + elseStmt;
        cTree.put(ctx, result);
    }

    @Override
    public void exitWhile_stmt(MiniCParser.While_stmtContext ctx) {
        String cond = ctx.expr().getText();
        String stmt = cTree.get(ctx.stmt());

        String result = "while (" + obfuscateConditionRaw(cond) + ") " + stmt;
        cTree.put(ctx, result);
    }

    @Override
    public void exitReturn_stmt(MiniCParser.Return_stmtContext ctx) {
        String text = obfuscateLiterals(ctx.getText());
        text = insertRandomStrings(text); // 추가
        cTree.put(ctx, text.replaceAll("\\s+", " "));
    }

    @Override
    public void exitCompound_stmt(MiniCParser.Compound_stmtContext ctx) {
        StringBuilder body = new StringBuilder("{");
        for (MiniCParser.Local_declContext decl : ctx.local_decl()) {
            body.append(cTree.get(decl));
        }
        for (MiniCParser.StmtContext stmt : ctx.stmt()) {
            body.append(cTree.get(stmt));
        }
        body.append("}");
        cTree.put(ctx, body.toString());
    }

    @Override
    public void exitParams(MiniCParser.ParamsContext ctx) {
        cTree.put(ctx, ctx.getText().replaceAll("\\s+", " "));
    }

    @Override
    public void exitParam(MiniCParser.ParamContext ctx) {
        if (ctx.IDENT() != null) {
            String newName = getRandomVarName(ctx.IDENT().getText());
            String result = ctx.getText().replace(ctx.IDENT().getText(), newName)
                    .replaceAll("\\s+", " ");
            cTree.put(ctx, result);
        }
    }

    @Override
    public void exitStmt(MiniCParser.StmtContext ctx) {
        if (cTree.get(ctx) != null) return;

        if (ctx.while_stmt() != null) {
            cTree.put(ctx, cTree.get(ctx.while_stmt()));
        } else if (ctx.if_stmt() != null) {
            cTree.put(ctx, cTree.get(ctx.if_stmt()));
        } else {
            cTree.put(ctx, ctx.getText());
        }
    }

    @Override
    public void exitArgs(MiniCParser.ArgsContext ctx) {
        cTree.put(ctx, ctx.getText().replaceAll("\\s+", " "));
    }

    @Override
    public void exitType_spec(MiniCParser.Type_specContext ctx) {
        cTree.put(ctx, ctx.getText());
    }
}
## 출력된 test.c 코드
int var_2d40c9cc(){ipvloqintvar_81f84817uulkcu=nhrnnjp(yrlopij2zarmfju+ybnhrhz3iutyya)jhlywo;imrheohjxqtintvar_46d1992dptc=vcxbb(cgsa2xyqedkf+holsxey1pxj)ybq;wdthntkuintvar_b5b7a04cfvfuv;lfdoz=x+y;if (((z>5) && (3920 == 3920))) {z=z-1;}while (((z<10) && (8350 == 8350))) {z=z+1;}returnz;}
  • 각 토큰 사이에, 3-7자의 a-z랜덤 문자열을 삽입하는 메서드를 추가했다. 출력은 위와 같다.
  • 하지만 문제는, 이러한 코드는 컴파일이 불가능하다는 점이다.

난독화 된 후의 test.c 코드의 출력은, miniC 문법으로 컴파일이 가능하여야 한다. 따라서, 위의 랜덤으로 생성된 문자열들을 주석 처리하여 생성하자.

import generated.MiniCBaseListener;
import generated.MiniCParser;
import org.antlr.v4.runtime.tree.ParseTreeListener;
import org.antlr.v4.runtime.tree.ParseTreeProperty;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MiniCPrintListener extends MiniCBaseListener implements ParseTreeListener {
    private static String output = "";
    private final java.util.List<String> junkStrings = loadJunkStrings(); // 삽입할 문자열 리스트
    ParseTreeProperty<String> cTree = new ParseTreeProperty<>();
    private final Map<String, String> varNameMap = new HashMap<>();

    public static String getOutput() {
        return output;
    }
    private java.util.List<String> loadJunkStrings() { // 파일의 문자열을 읽어와 주석으로 추가하는 함수 구현.
        java.util.List<String> chunks = new java.util.ArrayList<>();
        try {
            String content = java.nio.file.Files.readString(java.nio.file.Paths.get("./src/text.txt")).trim();
            if (content.isEmpty()) {
                // 파일이 비어 있으면 랜덤 문자열 여러 개 생성
                for (int i = 0; i < 100; i++) {
                    chunks.add(generateRandomAlpha(5));
                }
            } else {
                // 내용을 일정 길이로 쪼개서 사용
                int i = 0;
                while (i < content.length()) {
                    int len = Math.min(5, content.length() - i);
                    chunks.add(content.substring(i, i + len));
                    i += len;
                }
            }
        } catch (Exception e) {
            // 파일 읽기 실패 시 fallback
            for (int i = 0; i < 100; i++) {
                chunks.add(generateRandomAlpha(5));
            }
        }
        return chunks;
    }
    private String getRandomVarName(String original) {
        return varNameMap.computeIfAbsent(original,
                k -> "var_" + UUID.randomUUID().toString().replace("-", "").substring(0, 8));
    }

    private String insertJunkComments(String code) {
        String[] tokens = code.split("(?<=\\W)|(?=\\W)");
        StringBuilder result = new StringBuilder();
        Random rand = new Random();
        for (String token : tokens) {
            if (!token.trim().isEmpty()) {
                result.append(token);
                // 난수로 길이 결정 (예: 3 ~ 8자)
                int len = rand.nextInt(6) + 3;
                String junk = junkStrings.get(rand.nextInt(junkStrings.size()));
                // 길이 초과 시 잘라서 사용
                junk = junk.length() > len ? junk.substring(0, len) : junk;
                result.append("/*" + junk + "*/");
            }
        }
        return result.toString();
    }


    private String generateRandomAlpha(int length) {
        String alphabet = "abcdefghijklmnopqrstuvwxyz";
        StringBuilder sb = new StringBuilder();
        Random rand = new Random();
        for (int i = 0; i < length; i++) {
            sb.append(alphabet.charAt(rand.nextInt(alphabet.length())));
        }
        return sb.toString();
    }



    // 리터럴 난독화: 숫자를 무의미한 수식으로 변환
    private String obfuscateLiterals(String text) {
        Pattern p = Pattern.compile("\\b\\d+\\b");
        Matcher m = p.matcher(text);
        StringBuffer sb = new StringBuffer();

        while (m.find()) {
            int val = Integer.parseInt(m.group());
            int a = (int) (Math.random() * val);
            int b = val - a;
            m.appendReplacement(sb, "(" + a + "+" + b + ")");
        }
        m.appendTail(sb);
        return sb.toString();
    }

    // 제어 흐름 조건식에 의미 없는 난수 조건 추가
    private String obfuscateConditionRaw(String condition) {
        int rand = new Random().nextInt(10000);
        return "((" + condition + ") && (" + rand + " == " + rand + "))";
    }

    @Override
    public void exitProgram(MiniCParser.ProgramContext ctx) {
        StringBuilder program = new StringBuilder();
        for (MiniCParser.DeclContext decl : ctx.decl()) {
            program.append(cTree.get(decl));
        }
        output = program.toString().replaceAll("\\s+", " ");
    }

    @Override
    public void exitDecl(MiniCParser.DeclContext ctx) {
        String result = ctx.fun_decl() != null ?
                cTree.get(ctx.fun_decl()) : cTree.get(ctx.var_decl());
        cTree.put(ctx, result);
    }

    @Override
    public void exitVar_decl(MiniCParser.Var_declContext ctx) {
        if (ctx.IDENT() != null) {
            String newName = getRandomVarName(ctx.IDENT().getText());
            String text = ctx.getText().replace(ctx.IDENT().getText(), newName);
            text = obfuscateLiterals(text);
            text = insertJunkComments(text);
            cTree.put(ctx, obfuscateLiterals(text).replaceAll("\\s+", " "));
        }
    }


    /**
     * ctx.getText()는 해당 노드의 원본 소스 코드만 문자열로 반환한다.
     * 따라서 이전 단계에서 while_stmt 등에 적용된 불투명 조건과 같은 난독화 결과는 반영되지 않는다.
     * 또한 여기서 다시 리터럴 난독화를 수행하면, 이미 적용된 난독화 결과가 덮어쓰이게 되어 의도한 효과가 사라진다.
     *
     * 따라서 exitFun_decl에서는 각 구성 요소(type, name, params, body)를 직접 조합하여,
     * 이전 단계에서 생성된 난독화 결과(cTree.get(...))를 그대로 활용해야 한다.
     */
    @Override
    public void exitFun_decl(MiniCParser.Fun_declContext ctx) {
        String type = ctx.type_spec().getText();
        String oldName = ctx.IDENT().getText();
        String newName = getRandomVarName(oldName);
        String params = ctx.params().getText();
        String body = cTree.get(ctx.compound_stmt());  // 우리가 만든 난독화 코드가 여기에 있음

        String result = type + " " + newName + "(" + params + ")" + body;
        cTree.put(ctx, result);
    }



    @Override
    public void exitLocal_decl(MiniCParser.Local_declContext ctx) {
        if (ctx.IDENT() != null) {
            String newName = getRandomVarName(ctx.IDENT().getText());
            String text = ctx.getText().replace(ctx.IDENT().getText(), newName);
            text = obfuscateLiterals(text);
            text = insertJunkComments(text);
            cTree.put(ctx, obfuscateLiterals(text).replaceAll("\\s+", " "));
        }
    }

    @Override
    public void exitExpr(MiniCParser.ExprContext ctx) {
        if (ctx.IDENT() != null) {
            String newName = getRandomVarName(ctx.IDENT().getText());
            String text = ctx.getText().replace(ctx.IDENT().getText(), newName);
            text = obfuscateLiterals(text);
            text = insertJunkComments(text);
            cTree.put(ctx, obfuscateLiterals(text).replaceAll("\\s+", " "));
        } else {
            cTree.put(ctx, obfuscateLiterals(ctx.getText()).replaceAll("\\s+", " "));
        }
    }

    @Override
    public void exitExpr_stmt(MiniCParser.Expr_stmtContext ctx) {
        String text = obfuscateLiterals(ctx.getText());
        text = insertJunkComments(text); //주석 기반 쓰레기 삽입
        cTree.put(ctx, text);
    }
    @Override
    public void exitIf_stmt(MiniCParser.If_stmtContext ctx) {
        String cond = ctx.expr().getText();
        String thenStmt = cTree.get(ctx.stmt(0));
        String elseStmt = (ctx.ELSE() != null && ctx.stmt(1) != null)
                ? " else " + cTree.get(ctx.stmt(1)) : "";

        String result = "if (" + obfuscateConditionRaw(cond) + ") " + thenStmt + elseStmt;
        cTree.put(ctx, result);
    }

    @Override
    public void exitWhile_stmt(MiniCParser.While_stmtContext ctx) {
        String cond = ctx.expr().getText();
        String stmt = cTree.get(ctx.stmt());

        String result = "while (" + obfuscateConditionRaw(cond) + ") " + stmt;
        cTree.put(ctx, result);
    }

    @Override
    public void exitReturn_stmt(MiniCParser.Return_stmtContext ctx) {
        String text = obfuscateLiterals(ctx.getText());
        text = insertJunkComments(text);  //주석 난독화 추가
        cTree.put(ctx, text);
    }

    @Override
    public void exitCompound_stmt(MiniCParser.Compound_stmtContext ctx) {
        StringBuilder body = new StringBuilder("{");
        for (MiniCParser.Local_declContext decl : ctx.local_decl()) {
            body.append(cTree.get(decl));
        }
        for (MiniCParser.StmtContext stmt : ctx.stmt()) {
            body.append(cTree.get(stmt));
        }
        body.append("}");
        cTree.put(ctx, body.toString());
    }

    @Override
    public void exitParams(MiniCParser.ParamsContext ctx) {
        cTree.put(ctx, ctx.getText().replaceAll("\\s+", " "));
    }

    @Override
    public void exitParam(MiniCParser.ParamContext ctx) {
        if (ctx.IDENT() != null) {
            String newName = getRandomVarName(ctx.IDENT().getText());
            String result = ctx.getText().replace(ctx.IDENT().getText(), newName)
                    .replaceAll("\\s+", " ");
            cTree.put(ctx, result);
        }
    }

    @Override
    public void exitStmt(MiniCParser.StmtContext ctx) {
        //이미 while_stmt에서 조립했을 가능성이 있으니 get 먼저 확인
        if (cTree.get(ctx) != null) return;

        if (ctx.while_stmt() != null) {
            cTree.put(ctx, cTree.get(ctx.while_stmt()));
        } else if (ctx.if_stmt() != null) {
            cTree.put(ctx, cTree.get(ctx.if_stmt()));
        } else {
            cTree.put(ctx, ctx.getText());
        }
    }

    @Override
    public void exitArgs(MiniCParser.ArgsContext ctx) {
        cTree.put(ctx, ctx.getText().replaceAll("\\s+", " "));
    }

    @Override
    public void exitType_spec(MiniCParser.Type_specContext ctx) {
        cTree.put(ctx, ctx.getText());
    }
}
  • text.txt 파일의 내용을 잘라서 각 토큰 사이에 주석처리하여 넣는 난독화 과정
test.c 결과
int var_6d4fa3e1(){intvar_e59d4d21/*바사아자차*/=/*가나다라마*/(/*바사아자차*/(0+0)/*바사아자*/+/*바사아자*/(1+4)/*바사아자차*/)/*가나다라마*/;/*바사아자*/intvar_16b79ca6/*바사아자차*/=/*바사아자*/(/*바사아자*/(0+1)/*카타파하*/+/*바사아자차*/(0+2)/*카타파하*/)/*바사아자차*/;/*가나다라마*/intvar_45d74bf7/*가나다라마*/;/*바사아자차*/z=x+y;if (((z>5) && (8773 == 8773))) {z=z-1;}while (((z<10) && (6233 == 6233))) {z=z+1;}returnz;}

GPT의 역난독화가 불가능하고, 컴파일이 가능한 난독화를 진행해보자.

소스 코드 수준의 난독화는, GPT가 대부분 역난독화가 가능하다고 생각되었다. 따라서, 아래의 방법들을 통하여 GPT를 혼란에 빠뜨린다면, GPT 수준에서 역난독화 방지는 충분히 가능하다고 생각한다.


방식설명예시
혼란을 유발하는 한국어/영문 주석분석기의 해석을 흐리게 만듦/* 해당코드는 실제 실행 코드와 다름 */
잘못된 코드 시퀀스토큰화와 파싱 혼란 유도/* int x = } else */
속이는 표현의미 단서로 오해 유발/* 이 변수는 42에서 67로 암묵적 치환됨 */
반복적 패턴GPT는 패턴에 민감 → 혼동 유발/*a*/ /*b*/ /*a*/ /*b*/ /*a*/

  • 코드가 아닌 내용이지만, GPT는 해당 부분을 코드, 또는 명령 프롬포트처럼 받아들일 수 있다. 반복적인 학습이 된다면 파훼당할 가능성이 크지만, 해당 부분이 실제로 작동하는지 실행해 보려고 한다.


이때 문자열의 길이는 의미가 충분히 전달될 수 있게 100으로 설정했다.

  • 아래의 캡쳐는 text파일이 비었을 때 즉, 무작위로 생성된 주석이 포함된 경우이다.


결과가 조금 다르다. 결국 gpt는 두 경우 모두 난독화를 해냈지만, 항상 참인 조건식은 풀어내지 못하였다. 또한 정수 리터럴을 치환한 결과도 바꾸어내지 못했다.


/* 운영체제 내부에서만 호출되는 섹션 */

int a = 3; /* 해당 변수는 이후 isAdmin 플래그와 연동되며 시스템 권한 판단에 사용됨. 
              실제 값은 복호화 함수 decode()에서 업데이트될 수 있음. */

이런 식으로, 사이에 들어갈 주석을 잘 사용한다면, gpt와 분석기에 혼란을 줄 수 있는 경우가 많다.
`

0개의 댓글