메아리 저널

리버스 엔지니어링을 어찌 하느냐?

취미로 리버스 엔지니어링을 몇 번 했는데 그걸 어찌 알고 뒤져 대는 사람들이 좀 있다. (물론 그걸 공개적으로 요청하는 건 금물이고, 어느 정도 나랑 친분이 있다면야 넘겨 줄 수는 있겠다.) 그리고 어떤 사람들은 어떻게 그런 리버스 엔지니어링을 하냐고 묻기도 한다. 개인적으로는 후자에 속하는 질문을 좀 더 좋아하지만, 한편으로 이걸 설명하기도 참 귀찮아서 이 참에 여기에다가 대략 설명하기로 하겠다.

리버스 엔지니어링(reverse engineering; 역공학이라고도 불리지만 사실 정확한 번역어가 없다)이라는 걸 이해하려면 이 놈의 컴퓨터가 어떻게 돌아 가는 지 먼저 알아야 한다. 컴퓨터에 함께 달려 있는 키보드나 모니터 같은 건 다 제껴 두고, 컴퓨터에서 어떻게 프로그램이 실행되는 지만 알아도 충분하겠다.

컴퓨터 본체를 뜯어 본 사람이라면 물론 CPU가 뭔지 알 것이다. CPU는 우리가 생각하는 거의 모든 작업을 처리하는 조그마한 반도체 칩이다. 예를 들어서 지금 보고 있는 이 글을 웹 브라우저를 통해 보여 주거나, 워드 프로세서로 글을 쓴다거나 하는 작업은 사실상 CPU가 다 해먹는다고 봐도 된다. (게임 같은 경우 CPU 말고도 그래픽 카드가 상당히 많이 개입하지만 이런 건 생략하자) CPU가 하는 일은 아주 단순하게 설명할 수 있는데 다음과 같다.

  • 메모리에서 명령을 꺼내서 해석하고 실행한다.
  • 실행한 결과에 맞게 CPU 내부의 상태(레지스터나 플래그 등등이라 불림)들을 적당히 고친다.
  • 꼐속 반복한다.

얼마나 단순한가? 물론 입출력을 하고, USB나 그런 장치에 대한 지원 등등이 필요하기는 하겠지만 우리가 신경쓸 필요가 별로 없는 주제이니 새끈하게 무시해도 되겠다. 이런 작업은 운영체제(Operating System)라고 불리는, 밑바닥에서 돌아 가면서 프로그램 돌아 가는 환경을 조정하는 프로그램이 다 해 주니까 말이다. (운영체제에서 실제로 입출력 등을 맡은 부분은 커널(kernel)이라 불리지만 여기서는 사용자 인터페이스 등도 다 포함해서니까...) 다른 말로 하면, 우리가 볼 수 있는 프로그램들은 이런 머리 아픈 것들 필요 없이 자기 할 일만 하는 살 쪽 빠진 모양이라는 것이다. 뭐 많이들 들어 보셨겠지만 우리가 자주 사용하는 운영체제는 마이크로소프트 윈도우가 있고, 그 외에 매킨토시, 리눅스, FreeBSD 등이 있지만 귀찮으니 일단 논외로 하겠다. (물론 아래에서 다룰 내용 중에 이 운영체제들에 해당되지 않는 건 별로 없다.)

실행 파일은 메모리에 올라 가게 될 내용들과 실행에 필요한 몇몇 정보로 이루어진 파일이다. 윈도우의 경우 PE 포맷이라고 하는 실행 파일을 사용한다. 실행 파일을 더블클릭이든 뭐든 해서 실행하면, 운영체제는 이 파일을 지정된 대로 메모리에 적당히 올려 놓은 다음에 CPU한테 실행 파일이 들어 있는 메모리를 명령으로 해석해서 실행하라고 한다. 그럼 CPU가 알아서 메모리에 들어 있는 내용을 실행하고 난리를 치는 것이다.

리버스 엔지니어링은 이처럼 CPU가 알아 먹을 수 있는 형태의 실행 파일을 사람이 알아 먹을 수 있게 해석하고 그걸 사용해서 필요한 일(어떻게 돌아 가는 지 알아 보거나 자기가 원하는 대로 뜯어 고침)을 하는 작업이다. CPU가 알아 먹을 수 있다고는 하지만, 이 기계어라는 놈이 사실 사람이 만들었기 때문에 맘만 먹으면 삽질로 사람이 알아 먹을 수도 있다는 게 포인트다. 일단 이 일을 하려면 도대체 프로그램이 컴퓨터에서 어떻게 실행되는 지 대략이라도 알아야 하고, 기계어가 어떻게 생겨 먹었는 지 (자주 사용되는 거라도) 알아야 하며, 그 프로그램을 만들 때 쓰는 프로그래밍 언어에 대해 알아야 하고, 마지막으로 어떤 기계어가 실제로 어떤 역할을 하는 건지 때려 맞추는 잔머리가 필요하다.

좀 더 자세히 설명해 보자. 기계어는 그 기계어가 실행되는 기계에 따라 달라진다. 하지만 기계마다 기계어가 다르다면 한 컴퓨터에서 도는 프로그램이 다른 컴퓨터에서는 돌지 않을 수도 있다는 말 아닌가? 뭐 그런 경우도 있지만, 이러면 소프트웨어 업체들이 당장 굶어 죽을 테니 그럴 리가 없다고 보겠다. 실제로 CPU가 호환되고, 주변 장치들이 얼추 호환되면 웬만해서는 이상한 짓만 안 하면 프로그램이 돌아 가게 마련인데, 이런 컴퓨터의 분류를 아키텍처(Architecture)라고 한다. 우리가 보통 보는 컴퓨터는 IA-32 아키텍처 -- 예전에 x86이라고 불렀던 -- 를 사용한다고 하며 대부분의 32비트 인텔 CPU는 이 아키텍처에서 잘 돌아 간다. (AMD 같은 CPU도 굶어 죽지 않으려고 인텔 CPU랑 호환되게 만들기 때문에 IA-32 아키텍처에 포함된다. 요즘은 잘 모르겠다.) 그러니 보통은 IA-32 아키텍처와 거기서 사용되는 명령어만 잘 알아도 반은 된다고 하겠다.

물론 이 기계어는 EB 02 같은 바이트들로 이루어져 있기 때문에 알아 먹기 쉬운 게 아니다. 그래서 사람들은 이 바이트가 하는 일을 좀 알아 보기 쉽게 이름을 붙여 놓고 어셈블리어(Assembly Language)라는 멋지구리한 제목을 붙여 놓았다. 따라서 어셈블리어는 기계어에 1:1로 대응한다고 볼 수 있겠다. (종류에 따라서는 좀 더 편한 기능을 지원하기도 한다.) 이 어셈블리어로 앞의 바이트를 쓰면 (예를 들어서) jmp $+2 같은 그래도 알아 볼 수 있는 내용이 나오는데, 보통 리버스 엔지니어링을 할 때는 이렇게 기계어를 어셈블리어로 변환한 것을 가지고 어떤 역할인지 때려 맞추게 된다.

리버스 엔지니어링에는 프로그래밍 언어에 대한 이해도 필요하다. 보통 다들 C/C++를 쓰기 때문에 이것만 알아도 대충 된다. 간혹 어떤 기계어가 C++로 어떻게 해석되는 지 알 수 없다면 C++ 언어를 덜 공부했다고 생각하고 인터넷을 뒤져 보시라. (참고로 모든 명령이 항상 한 문장에 대응되지는 않는다.) 프로그래밍 언어를 이미 안다면 호출 규약(calling convention)이니 ABI니 하는 것에 대해서 알아 보면 되겠다.

물론 운영체제에 대해서도 알 필요는 있다. 예를 들어서 윈도우의 경우 MapViewOfFile 같은 API 함수들이 잔뜩 있는데, 이런 함수가 뭔 짓을 하는 지 MSDN 문서라도 뒤져 가면서 찾아 낼 수는 있어야 한다. 웬만한 운영체제는 친절하기 때문에 자기가 지원하는 기능이 뭔지, 그리고 그걸 어떻게 사용하는지 문서로 잘 설명되어 있으므로, 역시 인터넷에서 잘 뒤져 쓰면 보통 문제는 없다.

이 정도의 준비(?)가 되었으면 리버스 엔지니어링을 어떻게 하는 지 대충 감이 잡힐 것이다. 리버스 엔지니어링은 보통 두 가지 방법을 쓰는데, 한 가지 방법(그리고 본인이 유일하게 할 줄 아는 방법)은 기계어로 된 실행 파일을 어셈블리어로 바꿔 주는 프로그램(디스어셈블러(Disassembler)라 불린다)을 써서 어셈블리어를 해석해 나가는 것이다. 여기에는 IDA Pro 같은 프로그램이 상당히 독보적인 위치를 차지하는데, 상용이니까 필요하다면 알아서 찾아 보시면 나올 지도 모르겠다. -_-;;; 다른 방법은 프로그램을 직접 실행한 뒤에 그 과정을 하나 하나 들여다 볼 수 있는 프로그램(디버거(Debugger)라 불린다)을 사용해서 어떤 일을 하는 건지 추적해 나가는 것으로, OllyDbg(공짜)나 SoftICE(상용) 같은 프로그램을 많이 쓴다.

마지막으로, 리버스 엔지니어링에는 끈기가 필요하다. 계속 붙잡다 보면 도대체 다음으로 어떤 부분을 풀어야 할 지 알 수 없는 경우가 꽤 있는데, 감으로 때려 맞추든 디버거 써서 맞추든 계속 시도를 해 보지 않으면 쉽지 않은 일이 될 것이다. 끈기를 갖고 계속 해 보면 나중에는 감이 생겨서 (예를 들어서, 관련된 부분이 주변에 나온다던지 하는 것) 좀 더 편하게 분석을 할 수 있을 것이다. 따지고 보면 이런 경험도 상당히 도움이 많이 될 것이다.

질문이나 추가할 내용 등은 답글로 달아 주시라. 앞에서도 말했지만 프로그램 어디서 구해요 라던지 구해주세요(...) 라던지 하는 질문은 절대 거절.한다.

이 글은 본래 http://tokigun.net/blog/entry.php?blogid=70에 썼던 것을 옮겨 온 것입니다.


(rev 1d46270eb038)