일단 취약점 탐색 스크립트는 무슨 내용을 담아야되는 가에 대해서 고민을 해봤다.
{
"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)란이 필요할 듯(해결법에 관한 내용도 스크립트에서 뱉어줄 때를 대비해서)
'졸업프로젝트' 카테고리의 다른 글
| 끝이 보인다!끝이 보인다!끝이 보인다!끝이 보인다!(수정)아... 아직이네 쓰읍 (0) | 2026.03.19 |
|---|---|
| 이제 depex hunter를 자동화 해보자 (1) | 2026.03.15 |
| 취약점이 반영된 검증용 드라이버... 을! 만들어 봤읍니다. (0) | 2026.02.25 |
| ghidra headless 사용 (0) | 2026.02.13 |
| 사전공부 6 (0) | 2026.02.06 |