Home > 졸업프로젝트 > Ghidra P-Code 알아보기 1

Ghidra P-Code 알아보기 1
Ghidra P-Code

P-Code

(사실 P-Code는 다들 너무 잘 알고 있을 것이라 생각한다…)
P-Code는 Ghidra가 사용하는 IR로, 다양한 프로그래밍 언어들을 통일하기 위한 목적인 컴파일러 레벨의 IR들과 달리 P-Code는 MIPS, x86, ARM 등의 CPU 아키텍쳐 단의 통일을 위해 만들어진 IR로 볼 수 있다.
매우 간단한 예시로, 기존 어셈블리어로 표현되던

ADD RAX, RBX

와 같은 연산을 내부적으로

variable_1 = RAX + RBX
RAX = variable_1

과 같은 느낌의 연산으로 변경되어 표현된다고 볼 수 있다.

Varnode? PcodeOp?

Varnode와 PcodeOp를 프로젝트를 하는 동안 한번씩은 봤을 것이라고 생각한다. 쉽게 생각해 Varnode는 값, PcodeOp는 연산자라고 생각하면 된다.

Varnode

Varnode는 레지스터든, 메모리든, 상수든 모든 데이터들의 위치를 추상화한 튜플이다. 튜플의 형태는
(주소 공간, 오프셋, 사이즈)
의 형태로 구성된다. 주소 공간은 register, ram, const, unique 등이 존재한다.

  • register : 아키텍쳐의 레지스터
  • ram : 메모리
  • const : 상수값
  • unique : 임시 변수

unique는 P-Code의 전용 임시 변수로, 위 덧셈 예제처럼 어셈블리 코드를 원자적인 연산으로 쪼개는 과정에서 중간 연산 결과가 필요할 수 있는데, 이를 실제 레지스터나 메모리 등에 쓴다면 side-effect가 발생할 수 있으므로 분석기만 볼 수 있는 별도 주소 공간 정도로 이해하면 좋을 것 같다.
사이즈는 해당 Varnode가 몇바이트짜리인지 정도로 보면 된다.
오프셋은 바이트 단위로, ram이라면 메모리의 절대 주소를, const라면 상수값 그 자체를, unique라면 임시변수 내 바이트 오프셋을 의미한다. 그리고 register 역시 레지스터 공간의 바이트 오프셋을 담는다고 하는데, 이는 Ghidra가 레지스터 역시 메모리처럼 주소 공간으로 지정하기 위해 하나의 가상 주소 공간으로 모델링하기 때문이다. Ghidra는 레지스터들을 묶은 가상 레지스터 배열을 하나 만들고, 그 안에서 이 레지스터는 이 오프셋에서부터 몇바이트야!라는 매핑을 부여하게 된다.
제목
실제로 Ghidra 내에서 스크립트를 돌려보면 각 레지스터가 어떻게 매핑되어있는지 확인할 수 있다.
(TMI. 이 형태는 High P-Code, Low P-Code 모두 사용하는 형태. 하지만 SSA 형태로 변환해주는건 High P-Code로 변환되는 과정에서 SSA 정의 한번에 별개 Varnode 객체가 생성되어 객체가 자신을 정의한 OP 및 사용하는 곳을 들고다닌다. 그러므로 getDef()나 getDescendants() 등은 High P-Code에서만 의미있다. 자세한건 뒤에서…)

PcodeOp

PcodeOp는 P-Code 연산 하나하나의 원자적 의미 연산들로써,
output = OPCODE(inputs)
의 형태로 표현된다. 이때 outputㅇㄴ 0~1개의 Varnode(분기 연산은 0개), input으로는 0개 이상의 Varnode가 들어온다.
opcode 리스트는 뒤에서 잘 설명해줄 것이다.