안녕하세요 씨앤텍 시스템즈 황순호 연구원입니다.
이번 포스트는 GHIDRA를 소개하고 해당 소프트웨어를 다운로드하여 간단하게 사용까지 해보도록 하겠습니다.
먼저 리버스 엔지니어링은 실행 바이너리 파일을 이용해 역으로 분석하여 소스 코드를 도출하는 기법입니다. 그리고 GHIDRA는 미국의 국가안보국(NSA)에서 제작한 리버스 엔지니어링 툴입니다. 2017년 WikiLeaks에 의해 폭로된 CIA의 기밀문서 Vault 7에서 처음 GHIDRA의 존재가 알려졌으며 이후 2019년 싱가포르에서 개최된 RSA 컨퍼런스에서 공식적으로 공개되었습니다. 현재 NSA 공식 Github에서 해당 툴을 오픈소스로 공개하여 누구나 무료로 사용이 가능합니다.
GHIDRA가 제공하는 기능으로는 디컴파일, 디스어셈블리, 프로젝트 공유, 디버깅 등이 있습니다.
기존 툴인 IDA PRO에 익숙한 사람들에겐 다소 불편할 수 있고 부족한 기능도 있겠지만 IDA PRO 라이선스에 상당한 액수의 금액을 지불하지 않아도 된다는 점과 오픈소스의 장점인 커뮤니티를 통해 빠르게 개선되어 간다는 점은 GHIDRA를 사용하기에 충분한 이유가 될 수 있다고 봅니다.
1. GHIDRA 사용 환경
운영체제 :
- Microsoft Windows 7 or 10 (64-bit)
- Linux (64-bit, CentOS 7 is preferred)
- macOS (OS X) 10.8.3+ (Mountain Lion or later)
JDK 버전 :
- Java 11 64-bit Runtime and Development Kit (JDK)
GHIDRA는 위 운영체제에서 사용 가능하도록 개발되었으며 32bit는 지원하지 않습니다. 또한 GHIDRA는 JAVA로 개발되어 JDK를 추가로 설치해야 실행이 가능합니다.
2. JDK 설치
Windows 10 기준으로 진행하도록 하겠습니다.GHIDRA 실행에 필요한 JDK는 오라클 공식 페이지에서 다운로드하여 설치할 수 있습니다.
설치가 끝나면 환경변수도 함께 추가가 됩니다만 모종의 이유로 추가가 되지 않았다면 GHIDRA도 실행이 되지 않으므로
제어판 > 시스템의 환경 변수 편집 > 환경변수 > 시스템 변수 편집으로 이동하여 확인하도록 합니다.
추가되지 않았다면 설치 경로에 맞게 추가하도록 합니다.
3. GHIDRA 다운로드
GHIDRA는 공식 NSA GitHub에서 무료로 다운이 가능합니다.
현재 최신 버전은 10.0.4 입니다. ghidra_10.0.4_PUBLIC_20210928을 다운로드하고 압축을 풀도록 합니다.
압축을 푼 후 다음 파일들과 디렉토리들을 확인할 수 있습니다. GHIDRA는 별도의 설치 과정이 없으며 windows 환경에서는 ghidraRun.bat 파일을 실행하면 GUI 환경의 GHIDRA를 사용할 수 있습니다. Linux 또는 macOS에서는 ghidraRun을 실행하도록 합니다.
4. GIHDRA 실행
실행 후 화면입니다. 먼저 프로젝트를 생성하겠습니다. 상단바에서 FIle > New Project 클릭합니다.
GHIDRA의 기능 중 하나인 프로젝트 공유입니다. Non-Shared Project로 체크하겠습니다.
마지막 설정입니다. 프로젝트를 저장할 디렉토리와 이름을 설정합니다. 프로젝트 이름은 Test로 정하고 Finish를 누르겠습니다.
상단바 File > Import File 을 클릭하면 실행 가능한 파일을 가져올 수 있습니다.
아무 프로그램이나 프로젝트에 import하여 인터페이스를 확인해보겠습니다.
OK 버튼 클릭 후 앞서 만든 Test 프로젝트 아래 import 한 파일을 확인할 수 있습니다. 더블클릭을 해봅니다.
analyze를 진행하겠냐고 창이 뜨는데 Yes를 누르고 진행합니다.
중앙에 어셈블리어를 비롯해서 Program Trees, Symbol Tree 등 여러 가지 화면이 보입니다만 Symbol Tree만 잠시 살펴보도록 하겠습니다. Symbol Tree에서는 프로그램에서 사용하는 함수나 참조하는 DLL 등을 확인할 수 있고 디컴파일하여 우측에서 Pseudo 코드로 확인이 가능합니다.
호출된 함수에 마우스를 가져다 놓으면 어디에서 참조하고 있는지 확인할 수 있습니다.
5. GHIDRA 테스트
C로 만들어진 간단한 코드입니다.
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[] = "apple";
char str2[24] = "";
while (1) {
printf("Insert Password!\n");
scanf_s("%s", str2,sizeof(str2));
if (strcmp(str1, str2) == 0) {
printf("Success\n");
}
else {
printf("Fail\n");
}
}
return 0;
}
※ scanf_s는 MSVC에서 제공하는 함수로 GCC로 컴파일 시 오류 발생합니다.
apple을 입력하면 Success가 출력되고 그 외에는 Fail을 출력하도록 하였습니다.
실행파일 출력화면만 봤을 땐 어떤 값을 입력해야 할지 알 수 없습니다.
입력해야 할 값을 알기 위해 GHIDRA에서 import 하고 analyze 합니다. 그리고 Symbol Tree 필터에서 Main 함수를 검색해보겠습니다.
검색이 되지 않습니다.
함수중에서 entry 이름을 찾아서 Pseudo 코드를 확인하겠습니다. entry 함수는 프로그램이 실행되는 동안 가장 먼저 호출되는 함수입니다.
entry내에서도 여러 함수들이 호출됩니다만 main 함수와는 상관없는 함수들입니다. 예를 들어 Microosft 문서에 의하면 _security_init_cookie 함수는 전역 보안 쿠키로 버퍼 보안 검사를 통해 컴파일된 코드와 예외 처리를 사용하는 코드에서 버퍼 오버런을 방지한다고 합니다.
쭉 찾아보면 main함수를 발견할 수 있습니다. 해당 함수로 이동하여 코드를 봤을 때 작성한 C 코드의 main함수와 일치하는 듯합니다.
Pseudo 코드 상 local_28과 local_24에 하드코딩되어있는 부분을 보고 답을 유추할 수 있습니다.
헥사코드인 0x6c707061을 평문으로 변환하면 lppa 0x65는 e로 elppa라는 값을 알 수 있습니다.
apple이 아닌 elppa로 저장된 것은 현재 환경이 인텔 프로세서를 사용하기 때문입니다. 인텔 프로세서는 리틀 엔디안 방식으로 상위 바이트 값을 높은 주소에 저장합니다. 빅 엔디안 방식을 사용하는 다른 프로세서를 사용할 경우엔 apple로 저장하게 되겠죠.
더 확실하게 파악하자면 Pseudo코드 상에서 uVar5 == 0 비교를 통해 출력 결과가 Fail 또는 Success로 값이 달라지게 되는데요, uVar5은 LAB_00401107이 실행되냐에 따라 정해집니다. 해당 프러시저가 실행되면 1과 OR 연산을 하므로 절대 0이 될 수 없으며 그대로 Fail이 출력하게 되는 것이죠.
해당 부분을 더블클릭하여 어셈블리에서도 확인이 가능합니다. apple의 크기가 5바이트인 관계로 두 번에 걸쳐서 비교를 하게 되는데
004010e7 8a 11 MOV DL,byte ptr [ECX]=>local_28
004010e9 3a 10 CMP DL,byte ptr [EAX]=>local_20
004010eb 75 1a JNZ LAB_00401107
---
004010f1 8a 51 01 MOV DL,byte ptr [ECX + local_28+0x1]
004010f4 3a 50 01 CMP DL,byte ptr [EAX + local_20[1]]
004010f7 75 0e JNZ LAB_00401107
CMP 명령어에서 피연산자끼리 값이 다를 경우 결과는 1 ZF(Zero Flag)는 0으로 세트 되며 그 밑의 JNZ는 ZF가 0일 경우에 실행되는 명령어이므로 LAB_00401107로 넘어가게 되면서 Fail을 출력하게 됩니다. 결국 CMP에서 비교하는 피연산자 local_28 주소와 1번지만큼 더한 주소에 저장된 값이나 local_20 주소와 그다음 번지 주소에 저장된 값 중 하나가 알고자 하는 문자열이라고 볼 수 있습니다. 이런 흐름을 추적하면서 local_28에 도달하게 되면서 apple이라는 답을 도출할 수 있습니다.
GHIDRA 소개부터 간단히 사용 방법까지 작성했습니다.
감사합니다