import ghidra.app.script.GhidraScript; import ghidra.app.decompiler.*; import ghidra.program.model.address.AddressIterator; import ghidra.program.model.symbol.*; import ghidra.program.model.listing.*; import ghidra.program.model.pcode.*; import ghidra.program.model.address.Address; import java.util.Iterator; import java.util.HashSet; import java.util.Set; public class SmmCalloutHunter extends GhidraScript { // 디컴파일러 private DecompInterface decomp; // 이미 방문한 함수 주소를 저장하여 무한 루프 방지 private Set
visitedFunctions = new HashSet<>(); // Def-Use Chain에서 찾은 gBS 주소를 저장할 변수 private Address foundGbsAddr = null; @Override protected void run() throws Exception { println("=================================================="); println("Starting SMM Callout using gBS..."); println("=================================================="); // 디컴파일러 초기화 decomp = new DecompInterface(); decomp.openProgram(currentProgram); // ================================================== // [Phase 1] Def-Use: gBS 전역 변수 위치 찾기 // ================================================== println("gBS 전역 변수 탐색 시작!"); // 진입점 찾기 AddressIterator entryPoints = currentProgram.getSymbolTable().getExternalEntryPointIterator(); if (entryPoints.hasNext()) { // 보통 UEFI는 _ModuleEntryPoint이 진입점이므로, 첫 번째 엔트리 포인트를 사용 Function entryFunc = getFunctionAt(entryPoints.next()); // System Table 포인터는 entry 함수의 두번쨰 인자. Varnode systemTableNode = getParameterVarnode(entryFunc, 1); if (systemTableNode != null) { visitedFunctions.add(entryFunc.getEntryPoint()); // SystemTable 포인터를 통해 gBS 추적 boolean found = trackSystemTable(entryFunc, systemTableNode, 0); if (!found) { println("gBS 추적 실패. 스크립트를 종료합니다."); return; } } else { println("Entry Point 파라미터 분석 실패."); return; } } else { println("Entry Point를 찾을 수 없습니다."); return; } println("타겟 gBS 주소: " + foundGbsAddr); println("--------------------------------------------------"); // ================================================== // [Phase 2] Use-Def: SMM Callout 취약점 스캔 // ================================================== println("\nSMM Callout 취약점 스캔 시작!"); println("--------------------------------------------------"); scanForSmmCallouts(); println("\n모든 분석이 완료되었습니다."); } // Def-Use Chain을 타고 gBS 전역 변수의 위치를 찾는 함수들 private boolean trackSystemTable(Function func, Varnode node, int depth) { if (node == null || depth > 10) { return false; } // 이 노드를 사용하는 PcodeOp를 순회 Iterator uses = node.getDescendants(); while (uses.hasNext()) { PcodeOp op = uses.next(); int opcode = op.getOpcode(); // PTRADD : 포인터 값에 정수 오프셋을 더해 새 메모리 주소 계산 // INT_ADD : PTRADD 말고 단순 정수 덧셈으로 계산하는 경우도 있을 수 있다 // PTRSUB : + 0x60이 아니라 -0x60으로 접근하는 경우도 있을 수 있다 if (opcode == PcodeOp.PTRADD || opcode == PcodeOp.INT_ADD || opcode == PcodeOp.PTRSUB) { Varnode offsetNode = op.getInput(1); if (offsetNode != null && offsetNode.isConstant() && offsetNode.getOffset() == 0x60) { println("[+0x60 Catch!] 함수: " + func.getName()); if (checkIfStoredToGlobal(op.getOutput(), 0)) { return true; } } } // COPY나 CAST로 포인터가 한 번 더 꼬여있으면 더 깊게 파고들기 else if (opcode == PcodeOp.COPY || opcode == PcodeOp.CAST) { if (trackSystemTable(func, op.getOutput(), depth)) { return true; } } // CALL로 다른 함수에 매개변수로 넘어가는 경우도 추적 (recursive call) else if (opcode == PcodeOp.CALL) { Address targetAddr = op.getInput(0).getAddress(); Function targetFunc = getFunctionAt(targetAddr); // 만약 이미 방문 한 곳이라면 패스 if (targetFunc != null && !visitedFunctions.contains(targetAddr)) { int paramIndex = -1; for (int i = 1; i < op.getNumInputs(); i++) { if (op.getInput(i) == node) { paramIndex = i - 1; break; } } if (paramIndex != -1) { visitedFunctions.add(targetAddr); Varnode nextNode = getParameterVarnode(targetFunc, paramIndex); if (trackSystemTable(targetFunc, nextNode, depth + 1)) return true; } } } } return false; } // 전역 변수로 들어가는지 추적하는 함수 private boolean checkIfStoredToGlobal(Varnode ptrNode, int depth) { if (ptrNode == null || depth > 5) { return false; } Iterator uses = ptrNode.getDescendants(); while (uses.hasNext()) { PcodeOp op = uses.next(); int opcode = op.getOpcode(); // CAST 나 COPY로 한 번 더 꼬여있을 수 있으니 계속 추적 if (opcode == PcodeOp.CAST || opcode == PcodeOp.COPY) { if (checkIfStoredToGlobal(op.getOutput(), depth + 1)) { return true; } } // LOAD를 통해 메모리에서 들어오는 과정 역시 추적 else if (opcode == PcodeOp.LOAD) { if (trackValueToMemory(op.getOutput(), 0)) { return true; } } } return false; } // LOAD 추적 함수 private boolean trackValueToMemory(Varnode node, int depth) { if (node == null || depth > 5) { return false; } Iterator uses = node.getDescendants(); while (uses.hasNext()) { PcodeOp op = uses.next(); int opcode = op.getOpcode(); if (opcode == PcodeOp.STORE) { Varnode destNode = op.getInput(1); if (destNode != null && destNode.isConstant()) { foundGbsAddr = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(destNode.getOffset()); println("gBS 전역 변수 발견: " + foundGbsAddr); try { // createLabel을 통해 시각화 createLabel(foundGbsAddr, "gBS_Global_Variable", true); } catch (Exception e) {} return true; } } else if (opcode == PcodeOp.COPY || opcode == PcodeOp.CAST || opcode == PcodeOp.MULTIEQUAL || opcode == PcodeOp.INDIRECT) { Varnode destNode = op.getOutput(); if (destNode != null) { if (destNode.getAddress().isMemoryAddress()) { foundGbsAddr = destNode.getAddress(); println("gBS 전역 변수 발견: " + foundGbsAddr); try { createLabel(foundGbsAddr, "gBS_Global_Variable", true); } catch (Exception e) {} return true; } else { if (trackValueToMemory(destNode, depth + 1)) return true; } } } } return false; } // ------------------------------------------------------- // [Phase 2 Logic] 역방향 추적 (Use-Def) - SMM Callout 탐지 // ------------------------------------------------------- private void scanForSmmCallouts() { FunctionIterator funcs = currentProgram.getFunctionManager().getFunctions(true); int vulnCount = 0; while (funcs.hasNext()) { Function func = funcs.next(); // 함수별로 디컴파일 수행 DecompileResults results = decomp.decompileFunction(func, 30, monitor); HighFunction highFunc = results.getHighFunction(); if (highFunc == null) continue; Iterator ops = highFunc.getPcodeOps(); while (ops.hasNext()) { PcodeOp op = ops.next(); // 간접 호출 발견 // 직접 호출은 절대 좌표를 통해 하드코딩해야 하기 때문에, 레지스터나 계산된 주소로 호출하는 간접 호출. if (op.getOpcode() == PcodeOp.CALLIND) { Varnode targetFuncPtr = op.getInput(0); // 호출하려는 주소값(RAX 등) // 역추적 결과가 gBS라면? if (isTaintedByGBS(targetFuncPtr, 0)) { println("\n[SMM Callout 취약점 의심부 발견!]"); println("함수: " + func.getName()); println("주소: " + op.getSeqnum().getTarget()); println("원인: gBS 전역 변수를 참조하여 외부 함수를 호출함!"); vulnCount++; } } } } if (vulnCount == 0) { println("\nSafe : gBS를 사용하는 Callout 패턴이 발견되지 않았습니다."); } else { println("\n총 " + vulnCount + "개의 취약점 의심 지점이 발견되었습니다."); } } // Taint Analysis를 통해 거꾸로 추적하는 함수 private boolean isTaintedByGBS(Varnode node, int depth) { if (node == null || depth > 10) { return false; } // 역추적을 해 연산자 가져오기 PcodeOp defOp = node.getDef(); if (defOp == null) { return false; } int opcode = defOp.getOpcode(); // 메모리에서 읽어온거면 주소 확인 if (opcode == PcodeOp.LOAD) { Varnode addrNode = defOp.getInput(1); // 읽어온 메모리 주소 // 주소가 상수라면 그대로 비교 if (addrNode != null && addrNode.isConstant()) { Address sourceAddr = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(addrNode.getOffset()); // 동일하다면 gBS에서 유래한 주소가 맞으므로 오염된 것으로 간주 if (sourceAddr.equals(foundGbsAddr)) { return true; } } // 계산이 된 경우라면 그 주소가 gBS에서 유래했는지 계속 추적 else { return isTaintedByGBS(addrNode, depth + 1); } } // 단순 복사 및 이동일 경우 else if (opcode == PcodeOp.COPY || opcode == PcodeOp.CAST || opcode == PcodeOp.INT_ADD || opcode == PcodeOp.PTRADD || opcode == PcodeOp.MULTIEQUAL || opcode == PcodeOp.INDIRECT) { // 입력값들 중 하나라도 gBS에서 왔다면 오염된 것으로 간주 for (Varnode input : defOp.getInputs()) { if (isTaintedByGBS(input, depth + 1)) { return true; } } } return false; } // 유틸리티 함수 private Varnode getParameterVarnode(Function func, int paramIndex) { DecompileResults results = decomp.decompileFunction(func, 30, monitor); HighFunction highFunc = results.getHighFunction(); if (highFunc == null) { return null; } LocalSymbolMap lsm = highFunc.getLocalSymbolMap(); if (paramIndex >= lsm.getNumParams()) { return null; } HighSymbol paramSym = lsm.getParamSymbol(paramIndex); if (paramSym != null && paramSym.getHighVariable() != null) { return paramSym.getHighVariable().getRepresentative(); } return null; } }