졸업프로젝트

결과 json에는 어떤 것들이 들어가야 빼 먹지 않고 잘 들어갔다고 소문이 날까?

img1244 2026. 2. 26. 20:10

일단 취약점 탐색 스크립트는 무슨 내용을 담아야되는 가에 대해서 고민을 해봤다.

{
  "script_name": "string",
  "binary_name": "string",
  "vulnerability_found": boolean, 
  "timestamp": "ISO 8601 format",
  "locations": [
    {
      "function_name": "string",
      "pcode_address": "string"
    }
  ],
  "info" : "string"
}


// info에는 위에 해당하지 않는 내용(수정방안 || 특징점 etc)

 

필드 타입 설명 비고
script_name string 취약점을 찾는 기드라 스크립트의 이름  
binary_name string 분석 대상 드라이버/바이너리의 이름  
vulnerability_found boolean 이 스크립트가 취약점을 발견했는지 여부  
timestamp ISO 8601 분석을 실행한 날짜/시간 (UTC 기준)  
function_name string 취약점이 위치한 함수의 이름 코드가 속한 범위가 어디인지 체크용
pcode_adrress string 기드라 p-code 메모리 주소(16진수) 정확한 취약점 지점 체크용

위험도(critical,high...)나 CVSS등의 위험도는 별도의 계산기가 있을 정도로 계산이 복잡해서 스크립트에 추가하기에는 무리가 있을 것이라 판단하여, 팀과 의견을 나눠야 할 것 같아서 우선적으로 포함시키지 않았다(저런 탐지 스크립트가 탐지한 이후 점수를 매기는 건 사람이 하는 게 더 정확할 것 같기도 하고...)

 

일단 저런 형태의 json을 뱉어낼 수 있게끔 팀원이 작성했던 코드를 개조해보자  https://ludence8.github.io/%EC%A1%B8%EC%97%85%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8/2026-02-12-UEFI_6.html 참조

 

Before

@Override
public void run() throws Exception {
    println("=== SMM Indirect Call 검증기 (Safety Check 탐지) 시작 ===");

    InstructionIterator instructions = currentProgram.getListing().getInstructions(true);
    int suspiciousCount = 0;
    int highRiskCount = 0;

    while (instructions.hasNext()) {
        if (monitor.isCancelled()) break;
        Instruction instr = instructions.next();

        // 1. CALL 명령어 찾기
        if (!instr.getMnemonicString().equalsIgnoreCase("CALL")) continue;

        // 2. 오퍼랜드 분석 (RAX, RBX 등 레지스터 호출인지?)
        String opString = instr.getDefaultOperandRepresentation(0);

        // "0x..." 고정 주소 호출이나 "[...]" 메모리 참조는 일단 패스 (복잡하니까)
        // 오직 "CALL RAX", "CALL R12" 같은 순수 레지스터 호출만 집중 타격!
        boolean isRegisterCall = !opString.contains("[") && !opString.startsWith("0x");

        if (isRegisterCall) {
            // 호출에 사용된 레지스터 가져오기 (예: RAX)
            Register callReg = instr.getRegister(0);

            if (callReg != null) {
                //핵심: 뒤로 15줄 검색해서 CMP나 TEST가 있는지 확인!
                boolean isChecked = hasSafetyCheck(instr, callReg, 15);

                if (!isChecked) {
                    println(String.format("🚨 [초위험/No-Check] 주소: %s | 코드: %s | (검증 로직 발견 못함)",
                            instr.getAddress(), instr.toString()));
                    highRiskCount++;
                } else {
                    println(String.format("✅ [안전 추정] 주소: %s | 코드: %s | (검증 로직 있음)",
                            instr.getAddress(), instr.toString()));
                }
                suspiciousCount++;
            }
        }
    }

    println("==========================================");
    println("분석 완료: 총 " + suspiciousCount + "개 중 " + highRiskCount + "개가 '검증 없는 위험 호출'로 보입니다.");
}

// 뒤로 걸어가면서 검사 로직(CMP, TEST) 찾는 함수
private boolean hasSafetyCheck(Instruction startInstr, Register targetReg, int maxSteps) {
    Instruction current = startInstr.getPrevious(); // 바로 윗줄부터 시작
    int steps = 0;

    while (current != null && steps < maxSteps) {
        String mnemonic = current.getMnemonicString();

        // 1. 비교(CMP)나 테스트(TEST) 명령어를 찾음
        if (mnemonic.equalsIgnoreCase("CMP") || mnemonic.equalsIgnoreCase("TEST")) {
            // 2. 그 명령어가 우리가 의심하는 레지스터(targetReg)를 쓰는지 확인
            Object[] opObjects = current.getOpObjects(0); // 첫 번째 오퍼랜드
            for (Object op : opObjects) {
                if (op instanceof Register && ((Register) op).equals(targetReg)) {
                    return true; // 안전장치 발견!
                }
            }
        }

        // 만약 레지스터 값이 여기서 덮어씌워졌다면(MOV RAX, ...), 그 위는 볼 필요 없음 (추적 끊김)
        if (mnemonic.equalsIgnoreCase("MOV") || mnemonic.equalsIgnoreCase("LEA")) {
            Object[] resultObjects = current.getResultObjects();
            for (Object res : resultObjects) {
                if (res instanceof Register && ((Register) res).equals(targetReg)) {
                    return false; // 값을 막 대입하고 바로 호출함 -> 위험!
                }
            }
        }

        current = current.getPrevious(); // 한 줄 더 위로
        steps++;
    }
    return false; // 끝까지 검사 로직 못 찾음
}

 

After

import ghidra.app.script.GhidraScript;
import ghidra.program.model.listing.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.address.Address;
import java.time.format.DateTimeFormatter;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.io.File;
import java.io.PrintWriter;
import java.io.IOException;

public class SmmVulnerabilityScanner extends GhidraScript {

    @Override
    public void run() throws Exception {
        List<String> locationEntries = new ArrayList<>();
        InstructionIterator instructions = currentProgram.getListing().getInstructions(true);

        while (instructions.hasNext()) {
            if (monitor.isCancelled()) break;
            Instruction instr = instructions.next();

            if (!instr.getMnemonicString().equalsIgnoreCase("CALL")) continue;

            String opString = instr.getDefaultOperandRepresentation(0);
            boolean isRegisterCall = !opString.contains("[") && !opString.startsWith("0x");

            if (isRegisterCall) {
                Register callReg = instr.getRegister(0);
                if (callReg != null) {
                    boolean isChecked = hasSafetyCheck(instr, callReg, 15);

                    if (!isChecked) {
                        String funcName = getFunctionName(instr.getAddress());
                        String addrStr = instr.getAddress().toString();
                        
                        String locationJson = String.format(
                            "    {\n      \"function_name\": \"%s\",\n      \"pcode_address\": \"0x%s\"\n    }",
                            funcName, addrStr
                        );
                        locationEntries.add(locationJson);
                    }
                }
            }
        }

        // JSON 문자열 생성 및 파일 저장
        String finalJson = buildJsonString(locationEntries);
        saveJsonToFile(finalJson);
    }

    private String getFunctionName(Address addr) {
        Function func = currentProgram.getFunctionManager().getFunctionContaining(addr);
        return (func != null) ? func.getName() : "Unknown";
    }

    private String buildJsonString(List<String> locations) {
        String timestamp = ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT);
        boolean found = !locations.isEmpty();
        
        StringBuilder json = new StringBuilder();
        json.append("{\n");
        json.append(String.format("  \"script_name\": \"%s\",\n", "SMM_Indirect_Call_Scanner"));
        json.append(String.format("  \"binary_name\": \"%s\",\n", currentProgram.getName()));
        json.append(String.format("  \"vulnerability_found\": %b,\n", found));
        json.append(String.format("  \"timestamp\": \"%s\",\n", timestamp));
        json.append("  \"locations\": [\n");
        json.append(String.join(",\n", locations));
        json.append("\n  ]\n");
        json.append("}");
        
        return json.toString();
    }

    private void saveJsonToFile(String jsonContent) {
        String dirPath = "D:\\project\\project script_result";
        String fileName = "simple_tamji_result.json";
        
        File directory = new File(dirPath);
        if (!directory.exists()) {
            directory.mkdirs(); // 폴더가 없으면 생성
        }

        File file = new File(directory, fileName);
        try (PrintWriter out = new PrintWriter(file)) {
            out.println(jsonContent);
            println("✅ 분석 결과가 저장되었습니다: " + file.getAbsolutePath());
        } catch (IOException e) {
            printerr("❌ 파일 저장 중 오류 발생: " + e.getMessage());
        }
    }

    private boolean hasSafetyCheck(Instruction startInstr, Register targetReg, int maxSteps) {
        Instruction current = startInstr.getPrevious();
        int steps = 0;

        while (current != null && steps < maxSteps) {
            String mnemonic = current.getMnemonicString();
            if (mnemonic.equalsIgnoreCase("CMP") || mnemonic.equalsIgnoreCase("TEST")) {
                for (Object op : current.getOpObjects(0)) {
                    if (op instanceof Register && ((Register) op).equals(targetReg)) return true;
                }
            }
            if (mnemonic.equalsIgnoreCase("MOV") || mnemonic.equalsIgnoreCase("LEA")) {
                for (Object res : current.getResultObjects()) {
                    if (res instanceof Register && ((Register) res).equals(targetReg)) return false;
                }
            }
            current = current.getPrevious();
            steps++;
        }
        return false;
    }
}

 


일단은 큰 형태는 잡아놓았고, 추후 스크립트가 더 완성이 되는 데로 추가할 예정

++comment(string)란이 필요할 듯(해결법에 관한 내용도 스크립트에서 뱉어줄 때를 대비해서)