
Ghidra를 위한 바이너리 코드 커버리지 시각화 플러그인
Dragon Dance는 Ghidra 플러그인으로, 바이너리 코드 커버리지 데이터를 시각화하고 조작할 수 있게 해줍니다. 여러 커버리지 소스에서 커버리지 데이터를 가져올 수 있습니다. 현재 플러그인은 Dynamorio와 Intel Pin 바이너리 계측 도구를 지원합니다. Dynamorio는 "drcov"라는 자체 커버리지 수집 모듈을 가지고 있습니다. Intel Pin은 내장 커버리지 수집 모듈을 제공하지 않습니다. 이 부재 상황을 처리하기 위해 Intel Pin용 자체 커버리지 수집 모듈을 작성해야 했습니다. 그래서 ddph(Dragon Dance Pin Helper)라는 Intel Pin용 커버리지 수집 모듈을 작성했습니다. 이를 사용할 수 있습니다. ddph의 소스 코드는 이 링크에서 볼 수 있습니다. 직접 컴파일하기 귀찮다면 Windows, macOS, Linux용으로 제공된 컴파일된 바이너리를 사용할 수 있습니다.

Dragon Dance는 동일한 세션에서 여러 커버리지 데이터를 가져와 사용할 수 있습니다. (멀티 세션도 지원하지만 현재 GUI에서는 사용할 수 없습니다). 그리고 그들 사이를 전환하거나 교차, 차이, 고유 또는 합 연산을 서로 빠르게 적용할 수 있습니다.
Dragon Dance를 통해 실행된 명령어의 강도를 볼 수 있습니다. 따라서 어떤 명령어가 얼마나 자주 실행되었는지 힌트를 얻을 수 있습니다. 또한 함수 그래프 창에서 커버리지 시각화를 볼 수 있습니다.

Dragon Dance는 자체 스크립팅 시스템도 지원합니다.

커버리지 데이터를 유연하게 다룰 수 있게 해줍니다. 데이터를 로드, 삭제, 표시, 교차, 차이, 고유, 합 연산을 수행할 수 있습니다. 다음 섹션에는 스크립팅 시스템과 API가 포함됩니다. 스크립트를 실행하려면 Alt + Enter 키를 누르세요.
내장 함수는 스크립팅 시스템에 인터페이스를 제공하기 위한 내부 커버리지 연산의 구현입니다. 내장 함수는 커버리지 객체 변수를 반환하거나 아무 것도 반환하지 않을 수 있습니다. 내장 함수는 별칭을 가질 수 있습니다.
이들은 매개변수로 내장 인수를 받습니다. 매개변수는 가변 길이일 수 있습니다.
내장 인수는 다양한 값 유형을 보유하는 참조입니다. 내장 인수는 왼쪽에서 오른쪽 순서로 전달됩니다. 내장 인수는 다음 값 유형을 보유할 수 있습니다:
변수는 커버리지 객체만 보유하는 역할을 합니다. 내장 함수에 의해 로드될 수 있습니다. 내장 함수에 매개변수(내장 인수)로 전달될 수 있습니다.
두 가지 유형의 커버리지 객체가 있습니다.
물리적 커버리지 객체와 논리적 커버리지 객체
물리적 커버리지 객체는 커버리지 파일에서 직접 로드된 커버리지 객체를 가리킵니다. 이들은 GUI의 커버리지 테이블에 표시되며 GUI 작업을 통해 상호 작용할 수 있습니다.
논리적 커버리지 객체는 내장 함수에서 처리되어 결과로 반환된 커버리지 객체를 가리킵니다. 이들은 GUI에 표시되지 않지만 소멸될 때까지 변수에 존재할 수 있습니다.
커버리지 객체는 두 유형 모두에 대해 변수 객체에 의해 자동으로 유지 관리됩니다. 예를 들어;``` cov1 = load("firstcoverage.out") cov2 = load("secondcov.out")
cov1 = diff(cov1,cov2)
이 예제에서 cov1과 cov2는 변수입니다. 그리고 두 변수 모두 물리적 커버리지 객체를 가지고 있습니다. diff 내장 함수는 두 변수를 가져와서 반환 값을 cov1 변수에 설정합니다. 이 덮어쓰기 연산은 결과 커버리지 객체를 변수에 설정하지만, 해당 객체가 물리적 커버리지 객체이기 때문에 커버리지 객체를 삭제하지는 않습니다. 해당 커버리지 데이터는 세션과 GUI 테이블에 계속 남아 있습니다.
이전 예제를 이렇게 생각해 봅시다;```
cov1 = load("first.out")
cov2 = load("second.out")
cov3 = load("third.out")
rvar = sum(cov1,cov2,cov3)
rvar = diff(rvar, cov2)
In this example three physical coverage variable goes into sum operation and the sum operation returns logical result coverage object. Then the diff operation takes a logical and a physical variable in it and overwrites the variable named rvar.
이 예제에서는 세 개의 물리적 커버리지 변수가 sum 연산에 들어가고, sum 연산은 논리적 결과 커버리지 객체를 반환합니다. 그런 다음 diff 연산은 논리적 변수와 물리적 변수를 입력으로 받아 rvar라는 변수를 덮어씁니다.
In this case, the result will be set to the rvar and it's previous coverage value destroyed immediately. Because this was a logical object and should be deleted to prevent object leakage. If you want to destroy a variable that it contained a physical coverage object, you have to call discard built-in to do. All built-ins will be detailed below.
이 경우 결과는 rvar에 설정되고 이전 커버리지 값은 즉시 제거됩니다. 이는 논리적 객체였기 때문에 객체 누출을 방지하기 위해 삭제되어야 하기 때문입니다. 만약 물리적 커버리지 객체를 포함한 변수를 삭제하려면 내장 함수 discard를 호출해야 합니다. 모든 내장 함수는 아래에서 자세히 설명합니다.
You can write complex scripts using nested built-in calls, you can write something like so:
중첩된 내장 함수 호출을 사용하여 복잡한 스크립트를 작성할 수 있습니다. 예를 들어 다음과 같이 작성할 수 있습니다.
cres = diff(intersect(a, load("another.log"), c, d), sum(e,f) )
you don't have to write the logic line by line.
로직을 한 줄씩 작성할 필요가 없습니다.
다음 API 문서와 그 동작은 최종 버전에 도달할 때까지 변경될 수 있습니다.
clear()
| Property | Description |
|---|---|
| Return Value | None |
| Minimum Parameter Count | 0 |
| Maximum Parameter Count | 0 |
cwd( String : workingDirectory )
| Property | Description |
|---|
diff( Variable : var1, var2, ..... varN )
| Property | Description |
|---|---|
| Return Value | Variable |
| Minimum Parameter Count | 2 |
| Maximum Parameter Count | Unlimited |
discard( Variable : var1, var2, ..... varN )
| Property | Description |
|---|---|
| Return Value | None |
| Minimum Parameter Count | 1 |
| Maximum Parameter Count |
distinct( Variable : var1, var2, ..... varN )
| Property | Description |
|---|---|
| Return Value | Variable |
| Minimum Parameter Count | 2 |
| Maximum Parameter Count | Unlimited |
goto( Integer : offset )
| Property | Description |
|---|---|
| Return Value | None |
| Minimum Parameter Count | 1 |
| Maximum Parameter Count | 1 |
import( String : filePathOrCoverageName )
| Property | Description |
|---|
intersect( Variable : var1, var2, ..... varN )
| Property | Description |
|---|---|
| Return Value | Variable |
| Minimum Parameter Count | 2 |
| Maximum Parameter Count | Unlimited |
show( Variable : var )
| Property | Description |
|---|---|
| Return Value | None |
| Minimum Parameter Count | 1 |
| Maximum Parameter Count |
sum( Variable : var1, var2, ..... varN )
| Property | Description |
|---|---|
| Return Value | Variable |
| Minimum Parameter Count | 1 |
| Maximum Parameter Count | Unlimited |
Fix Ups
Dragon Dance는 커버리지 데이터 파일을 가져오는 동안 ghidra에서 잘못 분석된 상황을 수정하려고 시도할 수 있습니다. 일부 바이너리에서 ghidra는 컴파일러의 예기치 않은 코드 생성으로 인해 함수의 명령어를 디컴파일하지 않습니다. Dragon Dance는 로드된 이미지와 커버리지 데이터의 무결성을 확인합니다. 두 값이 서로 유효하고 주소가 실행 가능한 섹션에 속하지만 명령어 디컴파일이 부족한 경우, 플러그인이 수정을 요청합니다. 그런 다음 원시 섹션을 디컴파일하여 수정을 시도합니다.

향후 플러그인 버전에서는 이미지에 대한 더 많은 수정 또는 해결 방법이 포함될 수 있습니다.
설치는 매우 쉽습니다.
Ghidra를 시작합니다.
"File" 메뉴를 클릭한 다음 "Install Extensions.."를 선택합니다.
창의 오른쪽 상단에 있는 녹색 더하기 아이콘을 클릭합니다.
플러그인 zip 패키지를 선택하고 Ok를 선택합니다.
목록에서 dragondance를 선택합니다.
Ok를 클릭하고 ghidra를 다시 시작합니다.
플러그인 설치 후 Ghidra에 바이너리를 처음 로드할 때, Ghidra는 새로 설치된 플러그인을 구성할지 묻습니다.
Yes 버튼을 클릭하면 DragonDance 플러그인이 즉시 나타납니다.
No 버튼을 클릭하면 수동으로 활성화해야 합니다.
수동으로 활성화하려면,
"File" 메뉴를 클릭한 다음 디스어셈블리 창(CodeBrowser)에서 Configure를 선택합니다.
Configure Tool 창의 오른쪽 상단 모서리에 있는 작은 플러그 아이콘을 클릭합니다.
플러그인 목록에서 DragonDance 항목을 찾아 체크박스를 활성화한 다음 Ok를 클릭합니다.
Dragon Dance 창이 나타납니다.
활성화 후에는 Window 메뉴에서 Dragon Dance 항목을 볼 수 있습니다.
앞서 설명한 대로 Dragon Dance는 Dynamorio와 Intel Pin에서 커버리지 데이터를 가져올 수 있습니다. (현재로서는). 실제로 이들은 일반적인 바이너리 계측 도구입니다. 커버리지 데이터를 수집하려면 적절한 모듈을 사용해야 합니다. Dynamorio에는 drcov라는 자체 커버리지 모듈이 있습니다. 해당 내장 모듈을 사용하여 커버리지를 수집할 수 있습니다.
Using Dynamorio
Dynamorio에서 커버리지 데이터를 수집하려면 다음 명령을 사용할 수 있습니다.
drrun -t drcov -logdir [COVERAGE_OUTPUT_DIRECTORY_PATH] -- [EXECUTABLE_PATH_TO_EXAMINE] [EXECUTABLE_ARGUMENTS]
출력은 drcov.[EXECUTABLE_NAME].[ID].proc.log 형식으로 지정된 디렉터리에 배치됩니다.
Using Intel Pin
앞서 언급했듯이 Intel Pin은 내장 커버리지 수집 모듈을 제공하지 않습니다. 사용자 지정 pin 모듈을 사용해야 합니다. 다행히 Pin에서 커버리지를 수집하기 위해 직접 제작했습니다. 따라서 필요에 따라 확장하거나 추가 기능, 옵션을 추가할 수 있다는 장점이 있습니다.
최신 버전에서도 작동할 수 있지만, Intel PIN 3.7만 지원됩니다. 이 버전은 Intel PIN 페이지에서 즉시 제공되지 않으므로 직접 다운로드 링크는 다음과 같습니다.
ddph의 소스는 여기에서 확인할 수 있습니다. Windows, macOS 및 Linux용 바이너리를 공유하겠습니다. 또는 빌드 셸 스크립트를 사용하여 직접 바이너리를 빌드할 수 있습니다.
Intel Pin에서 커버리지 데이터를 수집하려면 다음 명령을 사용하십시오.
pin -t ddph.[so,dylib,dll] [ddph options] -- [EXECUTABLE_PATH_TO_EXAMINE] [EXECUTABLE_ARGUMENTS]
ddph에는 수집을 위한 몇 가지 옵션이 있습니다.
-o: 이 옵션으로 커버리지 출력 파일 이름을 지정할 수 있습니다. (기본값: ddph.out)
-l: 작업 로그 파일 이름을 지정할 수 있습니다. 이 옵션에 "no"를 전달하면 ddph는 로깅 작업을 수행하지 않습니다. (기본값: ddph.log)
-p: 캡처 상세 수준입니다. 이 옵션은 reduced 또는 high가 될 수 있습니다. high 수준은 전체 명령어를 하나씩 캡처하고 사전 처리 실행 블록을 구축합니다. 이렇게 하면 더 집중적인 커버리지 출력을 제공하지만 reduced보다 느립니다. reduced 수준은 pin의 트레이스 블록을 사용하므로 high 수준보다 훨씬 빠릅니다. 그러나 커버리지 출력 수준에 큰 차이를 초래하지는 않습니다. 특정 작업을 수행하지 않으려면 reduced 수준을 사용하는 것이 좋습니다. (기본값: reduced)
For macOS users: macOS 10.11(OS X El Capitan)부터 운영 체제에는 System Integrity Protection SIP라는 보안 계층이 함께 제공됩니다. 이는 루트 권한으로 실행 중이더라도 다른 프로세스에 주입이나 수정을 시도하는 사용자 모드 프로세스를 방지합니다.
이 방지를 극복하려면 비활성화해야 합니다. 이를 위해 아래 단계를 따르십시오.
macOS를 다시 시작합니다.
부팅 과정에서 Command + R 키를 누르고 계속 누릅니다.
OS가 결국 복구 모드로 진입합니다.
Utilities 섹션에서 터미널을 엽니다.
csrutil status를 입력하고 Enter를 누릅니다. SIP가 활성화되어 있어야 합니다.
csrutil disable을 입력하고 Enter를 누릅니다.
csrutil status를 다시 입력하여 비활성화되었는지 확인합니다. 그런 다음 OS를 다시 시작하고 정상적으로 부팅합니다. 이제 바이너리 계측 도구를 사용할 준비가 되었습니다.



First, download ghidra (latest version, currently 9.1.2) and dragondance
먼저 ghidra(최신 버전, 현재 9.1.2)와 dragondance를 다운로드하십시오.``` $ wget https://ghidra-sre.org/ghidra_9.1.2_PUBLIC_20200212.zip $ wget https://github.com/0ffffffffh/dragondance/archive/master.zip $ unzip ghidra_9.1.2_PUBLIC_20200212.zip $ unzip master.zip
다음으로 gradle과 jdk를 설치하십시오.```
$ sudo apt install openjdk-11-jdk
$ wget https://services.gradle.org/distributions/gradle-5.2.1-bin.zip
$ sudo unzip -d /opt/gradle gradle-5.2.1-bin.zip
새 프로필 파일 생성``` $ sudo vi /etc/profile.d/gradle.sh
그리고 다음을 추가하여 이후 모든 로그인 시 gradle을 PATH에 추가하십시오.```
export GRADLE_HOME=/opt/gradle/gradle-5.2.1
export PATH=${GRADLE_HOME}/bin:${PATH}
로그아웃하지 않고 즉시 수행하려면``` $ source /etc/profile.d/gradle.sh
이제 dragondance를 빌드할 수 있습니다.```
$ cd dragondance-master/
$ gradle -PGHIDRA_INSTALL_DIR=/home/ubuntu/ghidra_9.1.2_PUBLIC
> Task :buildExtension
Created ghidra_9.1.2_PUBLIC_20200506_dragondance-master.zip in /home/ubuntu/dragondance-master/dist
BUILD SUCCESSFUL in 29s
5 actionable tasks: 5 executed
ubuntu@ubuntu:~/dragondance-master$
ghidra를 다운로드한 경로를 조정해야 할 수도 있습니다. 이제 빌드된 확장 프로그램을 dragondance-master/dist에서 찾을 수 있습니다.
명령 프롬프트 스타일의 줄 단위 스크립트 실행.
컨텍스트 변경 인식
함수(루틴) 기반 커버리지 시각화
실행 흐름 인식
스크립팅에 더 많은 내장 함수 추가
더 빠른 저장 및 로드를 위한 자체 커버리지 데이터베이스 형식, 세션의 최신 변경 사항 유지.
의사 코드 페인팅. (Ghidra는 이에 대한 API를 제공하지 않습니다. 따라서 Ghidra의 소스 코드를 연구하여 이를 달성할 방법이나 해결책을 찾아야 합니다.)
UI 개선
Oğuz Kartal (@0ffffffffh)
| Description |
| 이 내장 함수는 시각화 중인 커버리지를 지우고 활성 커버리지를 null로 설정합니다. |
| Aliases | None |
| Return Value | None |
| Minimum Parameter Count | 1 |
| Maximum Parameter Count | 1 |
| Description | 주어진 경로로 현재 작업 디렉터리를 설정합니다. cwd 이후에 절대 경로 없이 호출하는 모든 import는 활성 작업 디렉터리에서 커버리지 파일을 검색합니다. |
| Aliases | None |
| Description | 주어진 가변 길이 Variables에 차집합 연산을 적용합니다. 결과 커버리지 변수를 반환합니다. |
| Aliases | None |
| Unlimited |
| Description | 물리적 또는 논리적 변수를 삭제합니다. 먼저 커버리지 객체를 삭제한 후 변수 목록에서 변수 이름을 등록 해제합니다. 이 호출 이후에 주어진 모든 변수는 정의되지 않습니다. |
| Aliases | del |
| Description | 주어진 가변 길이 variables에 distinct (xor) 연산을 적용합니다. 결과 커버리지 변수를 반환합니다. |
| Aliases | xor |
| Description |
| 주어진 오프셋으로 현재 주소 선택을 위치시킵니다. 실제 주소 값은 이미지 베이스 값에 오프셋 값을 더하여 계산됩니다. |
| Aliases | None |
| Return Value | Variable |
| Minimum Parameter Count | 1 |
| Maximum Parameter Count | 1 |
| Description | 물리적 커버리지 파일에서 커버리지 데이터를 가져옵니다. 상대 경로 또는 절대 경로를 받습니다. 또는 이전에 물리적으로 로드된 커버리지 이름을 받습니다. 주어진 경로가 절대 경로이면 import는 해당 경로에서 직접 로드합니다. 그렇지 않으면 현재 작업 디렉터리 아래에서 로드합니다. 두 경우 모두 import는 경로를 사용하여 커버리지 데이터가 이미 로드되었는지 확인합니다. 이미 로드된 경우 캐시된 커버리지 변수를 반환합니다. 또는 주어진 값이 물리적 커버리지의 이름인 경우 세션에서 커버리지 맵을 조회하고 존재하면 커버리지 객체를 반환합니다. |
| Aliases | get, load |
| Description | 주어진 가변 길이 variables에 교집합 연산을 적용합니다. 결과 커버리지 변수를 반환합니다. |
| Aliases | and |
| 1 |
| Description | 주어진 커버리지 변수를 시각화합니다. 활성 시각화된 커버리지 객체가 있고 그것이 논리적이면, 함수는 이전 커버리지 객체를 즉시 삭제하고 주어진 객체를 표시합니다. |
| Aliases | None |
| Description | 주어진 가변 길이 variables에 합집합 연산을 적용합니다. 결과 커버리지 변수를 반환합니다. |
| Aliases | or, union |