Notice
Recent Posts
Recent Comments
Link
«   2026/07   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags more
Archives
Today
Total
관리 메뉴

방망이

[프로젝트]UEFI DXE 바이너리 취약점 분석기 14주차 - P-code Phase2 본문

2026_졸업프로젝트

[프로젝트]UEFI DXE 바이너리 취약점 분석기 14주차 - P-code Phase2

망구입니다 2026. 5. 31. 20:21

[P-code 공부 커리큘럼]

 

Phase2에서 4, 5번에 대한 내용은 팀원들의 블로그를 참고한다.

 

Ghidra P-Code 알아보기 1

P-Code (사실 P-Code는 다들 너무 잘 알고 있을 것이라 생각한다…) P-Code는 Ghidra가 사용하는 IR로, 다양한 프로그래밍 언어들을 통일하기 위한 목적인 컴파일러 레벨의 IR들과 달리 P-Code는 MIPS, x86, ARM

ludence8.github.io

 

UEFI 스터디 15차 - 기드라 p 코드 공부 (sleigh, high VS low pcode.)

sleigh

eatrawlife.github.io

 

15차 전원회의 - p-code operator를! 공부해보자

p-code 오퍼레이터는 ghidra 공식 문서를 참조https://ghidra.re/ghidra_docs/languages/html/pcodedescription.html#cpui_copy P-Code Operation ReferenceFor each possible p-code operation, we give a brief description and provide a table that lists th

img1244.tistory.com


이번 주에 내가 공부한 내용은 위 커리큘럼에서 Phase2에 6~7부분 중 SSA 정적 단일 할당과 데이터 흐름 추적 파트이다. 이 파트에서는 Varnode와 High Variable, 심볼 분류와 분석 소스 판정, Def-Use 체인에 대한 내용을 다룰 예정이다.

 

Varnode와 High Variable

  • Varnode는 P-code에서 모든 값을 표현하는 기본 단위이다. 레지스터든, 메모리든, 상수든간에 전부 (address space, offset, size) 이렇게 세 필드로 표현된다.
  • Raw P-code에서는 Varnode가 단순히 레지스터 위치((register, 0x0, 8)=RAX)를 가리키는 데 그친다. 하지만 High P-code로 올라오면 High Variable이라는 개념이 추가된다.
  • High Variable은 SSA 변환 이후 여러 SSA 버전을 하나의 논리적 변수로 묶은 것.
    예를 들어, 동일한 변수 x가 코드 흐름에서 세번 재정의되면 SSA에서는 x#0, x#1, x#2로 분리되는데, 이 세 개가 모두 같은 High Variable에 속한다.
    Varnode (x#0)  ─┐
    Varnode (x#1)  ─┼─→ HighVariable "x"  (이름, 타입, 심볼 종류 보유)
    Varnode (x#2)  ─┘
    (Ghidra 디컴파일러가 "이 두 SSA 버전은 결국 같은 논리적 변수다"라고 판단)
  • SSA는 분석을 위해 변수를 쪼개고, HighVariable은 사람이 이해할 수 있도록 다시 합쳐주는 역할
  • API로는 varnode.getHigh()로 HighVariable에 접근하고, highVar.getInstances()를 호출하면 해당 HighVariable에 속한 모든 SSA 버전 Varnode 목록(x#0, x#1, x#2)이 반환.
    • Taint 분석에서 변수의 모든 사용 지점을 빠짐없이 추적할 때 이 메서드 사용.

[참조] SSA


심볼 분류와 분석 소스 판정

  • High Variable은 getSymbol() 메서드로 심볼 종류를 구분할 수 있음. 취약점 분석에서 이 분류가 중요한 이유는 Taint의 시작점(Taint source)을 정의할 수 있는 기준이 되기 때문.
    종류 의미 Taint 관련성
    HighParam 함수 선언부의 인자. int func(int a, char* buf)에서 a와 buf Taint source - 외부 입력 진입점
    HighLocal 함수 내부에서 선언된 지역 변수. char buf[64] 같은 것 버퍼 위치 추적
    HighGlobal 함수 밖에 선언된 전역/정적 변수 외부 상태 추적
    HighOther 디컴파일러가 내부적으로 만들어낸 임시 변수. 소스 코드에 대응하는 변수가 없음 대부분 무시
  • Taint source 판정 규칙
    • HighParam → 즉시 Taint source (함수 인자는 외부 호출자가 넘겨준 값이라 신뢰할 수 없기 때문)
    • getDef()를 역추적했을 때 read() / recv() / fgets() 같은 외부 입력 함수의 CALL output Varnode에 닿으면 → Taint  source
      • 이 규칙은 함수 인자가 아닌 로컬 변수가 외부 입력을 받는 경우를 잡기 위한 것
      • ex.
        • void func() {
              char buf[64];                // HighLocal — 인자가 아님
              recv(sock, buf, 64, 0);  // 여기서 buf에 외부 데이터가 들어옴
              strcpy(dst, buf);            // buf가 tainted인지 알아야 함
          }
        • 위와 같은 코드의 경우 buf가 HighParam이 아니기 때문에 규칙 1에서 잡히지 않음. 그래서 getDef()를 따라 역추적.
        • strcpy가 위험한 이유 : strcpy(dst, src)는 src 문자열을 dst 버퍼에 복사하는데, src의 길이를 전혀 확인하지 않음 → 공격자가 64바이트보다 긴 데이터를 보내면 Buffer Overflow
  • Java코드로는 아래와 같이 판정한다.
HighVariable hv = varnodeAST.getHigh();
if (hv != null && hv.getSymbol() != null) {
    String kind = hv.getSymbol().getClass().getSimpleName();
    if (kind.equals("HighParam")) {
        // Taint source로 등록
    }
}

Def-Use 체인 슬라이싱

  • SSA의 핵심 장점은 각 변수가 딱 한 번만 정의된다는 것. 이 덕분에 어떤 Varnode든 그것을 만든 op와 그것을 사용하는 op를 바로 추적할 수 있음.
  • 역방향 슬라이싱 - getDef()
    • Varnode를 만든 op를 거슬러 올라갑니다. Taint가 어디서 시작됐는지 출처를 사용할 때 사용.
    • strcpy의 src 인자
           ← INT_ADD (버퍼 + 오프셋)
                ← LOAD (메모리에서 읽기)
                     ← CALL recv() ← [SOURCE] 외부 입력
  • 순방향 슬라이싱 - getDescendant()
    • Varnode가 이후 어디서 사용되는지 흐름을 따라감. Taint가 어디까지 전파되는지, 어떤 sink에 도달하는지 추적할 때 사용.
// 역방향
PcodeOp defOp = varnodeAST.getDef();

// 순방향
Iterator<PcodeOp> uses = varnodeAST.getDescendants();
while (uses.hasNext()) {
    PcodeOp use = uses.next();
}
  • MULTIEQUAL 처리
    • 조건 분기가 있으면 합류점에서 MULTIEQUAL op가 생김. Taint 분석에서 이 노드를 처리하지 않으면 false negative가 발생하게 됨.
    • 따라서 MULTIEQUAL을 처리하는 방법은 MULTIEQUAL의 입력 중 하나라도 tainted이면 출력도 tainted.
if (op.getMnemonic().equals("MULTIEQUAL")) {
    for (Varnode inp : op.getInputs()) {
        if (tainted.contains(inp)) {
            tainted.add(op.getOutput()); // 전파
        }
    }
}