서론
소프트웨어를 분석할 때는 일반적으로 큰 구조를 먼저 관찰하지만 이것만으로 소프트웨어의 실제 동작을 알기 어려울 때가 많다.
그럴 때는 실제로 프로그램을 실행해보며 동작을 관찰해볼 수 있다.
그리고 더욱 자세한 분석이 필요할 때는 다시 여러 수단을 동원하여 특정 부분을 세밀하게 관찰한다.
리버스 엔지니어링에서는 소프트웨어를 분석하기 위해 사용하는 분석 방법들을 크게 정적 분석(Static Analysis)과 동적 분석(Dynamic Analysis)으로 구분한다.
정적 분석은 외적인 관찰만을 통해 정보를 알아내는 것을 의미하며, 동적 분석은 실행을 통해 동작을 분석하는 것을 의미한다.
우리는 대개 정적인 방법과 동적인 방법을 적절히 혼용하여야 대상을 효과적으로 분석할 수 있다.
동적인 방법만을 고수하면 큰 구조를 놓칠 수 있고, 정적인 방법만을 고수하면 실행을 통해 알 수 있는 부분을 분석하느라 시간을 허비할 수 있다.
따라서 우리는 정적 분석과 동적 분석 중 한 방법만을 고수하는 것이 아니라 상황에 따라 적절한 방법을 선택해야 한다.
어떤 상황에 어떤 방법을 선택할 것이냐가 리버싱 실력을 좌우하는 중요한 요소이다.
정적 분석
정적 분석(Static Analysis) : 프로그램을 실행시키지 않고 분석하는 방법
정적 분석의 장점
정적 분석을 사용하면 프로그램의 전체 구조를 파악하기 쉽다.
정적 분석 도구들은 프로그램의 여러 정보를 살펴볼 수 있도록 도움을 주는데, 프로그램이 어떤 함수로 구성됐고 함수들은 서로 어떤 호출 관계를 갖는지, 어떤 API를 사용하고 어떤 문자열을 포함하는지 등을 종합적으로 살펴볼 수 있다.
분석자는 이 정보들을 바탕으로 프로그램을 큰 관점에서 이해할 수 있다.
또한, 분석 환경의 제약에서도 비교적 자유롭다.
안드로이드의 apk 파일은 별도의 소프트웨어를 사용하지 않는 한 윈도우 시스템에서 실행할 수 없다.
실행을 전제로 하는 동적 분석을 윈도우 환경에서 apk를 대상으로 하기는 다소 번거롭지만 정적 분석은 프로그램을 실행하지 않아도 되므로 분석을 지원하는 적절한 도구만 갖춘다면 시도할 수 있다.
마지막으로 바이러스와 같은 악성 프로그램의 위협으로부터 안전합니다.
만약 바이러스를 동적 분석할 경우, 바이러스를 실제로 실행해야 하므로 자신의 컴퓨터가 감염될 우려가 있다.
반면 정적 분석은 프로그램을 실행하지 않고 분석하므로 이런 걱정을 하지 않아도 된다.
정적 분석의 단점
정적 분석은 프로그램에 난독화(Obfuscation)가 적용되면 분석이 매우 어려워진다.
최근에는 많은 개발자가 자신의 소프트웨어를 리버스 엔지니어링으로부터 보호하기 위해 난독화 기법을 적용한다.
난독화가 적용되면 프로그램의 코드가 심하게 변형돼서 이를 읽고, 실행 흐름을 파악하기가 어려워진다.
이를 해제하기 위한 여러 연구가 진행되고 있지만, 여전히 많은 상용 난독화 서비스들을 무력화하는 방법은 알려지지 않았다.
그리고 정적 분석만으로는 다양한 동적 요소를 고려하기 어렵다.
프로그램은 실행중에 영향을 주고 받는 여러 함수로 구성된다.
A라는 함수가 B라는 함수에 들어가는 인자를 결정할 수도 있고, A와 B가 서로 같은 전역변수를 공유할 수도 있다.
따라서 어떤 함수가 특정 시점에 정확히 어떤 인자와 어떤 전역 변수를 가지고 실행될지는 정적으로 알기 어렵다.
이런 문제는 프로그램의 실행 흐름이 복잡할수록 더욱 심각해진다.
정적 분석의 예시
대표적인 정적 분석 도구 중 하나인 IDA를 이용하여 HelloWorld.exe라는 프로그램을 열면 아래와 같은 모습을 볼 수 있다.

이미지의 가운데 부분에서 프로그램의 어셈블리 코드를 살펴볼 수 있다.
이미지의 우측에 위치하는 코드는 디컴파일(Decompile)된 코드이다.
이들은 기계어를 사람이 이해하기 쉬운 언어로 나타내어 분석을 더욱 쉽게 할 수 있게 해준다.

프로그램을 구성하는 여러 함수와 프로그램과 관련된 각종 정보도 살펴볼 수 있다.
이외에도 IDA는 문자열이나 함수를 어디에서 사용하는지를 보여주는 상호 참조(Cross Reference)기능이나 함수의 실행 흐름을 보기 쉽게 해주는 제어 흐름 그래프(Control Flow Graph) 등을 통해 분석가가 프로그램을 쉽게 이해할 수 있도록 돕는다.
동적 분석
동적 분석(Dynamic Analysis) : 프로그램을 실행시키면서 분석하는 방법
동적 분석의 장점
동적 분석을 활용하면 코드를 자세히 분석해보지 않고도 프로그램의 개략적인 동작을 파악할 수 있다.
대개의 프로그램은 많은 함수로 구성되어 있으며, 각각의 함수들이 서로 복잡하게 영향을 주고받아 정적 분석만으로 프로그램을 완전히 이해하기는 일반적으로 매우 어렵다.
동적 분석은 어떤 입력에 대한 개별 함수 또는 프로그램의 출력을 빠르게 확인할 수 있으므로, 이 출력값들을 기반으로 동작을 추론해 볼 수 있다.
쉬운 예로, md5라는 복잡한 알고리즘을 수행하는 함수가 있을 때, 정적으로만 분석하여 이 함수를 파악하는 것은 어려울 수 있다.
그러나 동적 분석으로는 어떤 입력 값을 넣고 결과 값이 md5 알고리즘의 결과 값과 같은지 비교하여 해당 함수가 md5함수인지를 쉽게 판단할 수 있다.
동적 분석의 단점
동적 분석의 단점은 분석 환경을 구축하기 어려울 수 있다는 것이다.
동적 분석은 프로그램을 실행하면서 분석하는 것이므로, 프로그램을 실행하지 못하면 동적 분석을 진행할 수 없다.
그래서 다른 환경의 프로그램을 동적 분석할 때에는 가상 머신을 구축하거나 프로그램을 실행할 수 있는 장치를 구매해야 하는데, 이 과정이 대상에 따라 매우 번거롭고 어려울 수 있다.
또한, 앞서 정적 분석에서 소개한 '난독화'처럼 동적 분석에 대해서도 이를 어렵게 하는 여러 기법이 개발되어 있다.
그 중 대표적인 것이 동적 분석의 일종인 디버깅을 방해하는 안티 디버깅(Anti Debugging)입니다.
if (is_debugging()) // 디버깅인지 확인
exit(-1); // 프로그램 종료
Func();
단순한 안티 디버깅의 예로, 아래의 코드처럼 자신이 디버깅 당하고 있는지 검사하고, 디버깅 중이면 프로그램을 강제로 종료시키는 방법이 있다.
동적 분석의 예시
윈도우의 대표적인 동적 분석 도구로는 디버거 중 하나인 x64dbg가 있다.
디버거는 프로그램의 버그를 찾아내고 제거하기 위해 사용되는 도구인데, 이를 이용하면 실행 중인 어셈블리 코드, CPU의 레지스터(Register) 상태, 메모리(Memory)와 스택(Stack)의 값을 확인하며 분석을 진행할 수 있다.
//helloworld.c
#include <stdio.h>
int main()
{
int n = 0x31337;
printf("Hello World 0x%x\n", n);
return 0;
}
다음의 코드를 컴파일한 HelloWorld.exe를 x64dbg로 동적 분석해보았다.
왼쪽 위가 어셈블리, 왼쪽 아래가 메모리, 오른쪽 위가 레지스터, 오른쪽 아래가 스택의 정보를 보여준다.

현재 코드는 0x31337이라는 상숫값을 스택에 저장하고 있다. 소스 코드의 int n = 0x31337에 대응된다.

1의 코드를 실행한 직후, 이미지 오른쪽 아래를 보면 스택에 0x31337이 저장된 것을 확인할 수 있다.

printf함수를 호출한다.
이미지 오른쪽 위의 레지스터를 보면, rcx에 Hello World 0x%x\n문자열이, rdx에 0x31337이 저장된 것을 확인할 수 있다.
소스 코드를 참조하면, printf 함수의 인자랑 관련이 있다는 것을 알 수 있다.

3의 코드를 실행한 직후, 프로그램을 보면 Hello World 0x31337이 출력된 것을 확인할 수 있다.

이 예시처럼 동적 분석은 프로그램을 실제로 실행하며 시스템의 변화를 관찰하는 것이 특징이다.
함수를 실행할 때, 어떤 인자가 함수로 전달되며, 그 때의 메모리 상태는 어떠한지 등을 직관적으로 확인할 수 있다.
