| 개발자 책꽂이 |
여름나기 책 2선: 리버싱과 문제 해결 대작전 |
 |


2007년 8월에 OS와 최적화 관점에서 여름나기 책 2선을 실은 적이 있다. 시스템 성능을 끌어내고 운영체제 제작에 필요한 운영체제 지식과 어셈블리어를 주로 다룬 두 책은 어렵긴 하지만 소프트웨어 개발 과정 기초를 닦기에 좋은 정보를 제공했다고 생각한다. 이번에는 역시 무더운 여름 더위를 날려버리기 위해 다 만들어진 소프트웨어를 대상으로 동작 원리와 문제점을 파악하는 방법을 설명하는 책 두 권을 소개하겠다.
1번 타자
리버싱: 리버스 엔지니어링 비밀을 파헤치다
엘다드 에일람 지음, 윤근용 옮김, 에이콘출판사 2009년 출간
오픈 소스 세계에서는 원시 코드 차원에서 남이 만든 소프트웨어를 분석하는 경우가 흔하다. 원저자 의도에 맞춰 오픈 소스 제품을 제대로 이용하거나 필요에 따라 수정해 사용하거나 동작 원리와 이론을 파악하려는 목적으로 분석 작업을 수행한다. 하지만 원시 코드가 없는 상용 소프트웨어는 어떻게 해야 하나? 이런 경우에는 이진 실행 파일을 대상으로 리버싱 기법을 사용해 소프트웨어를 해부한 다음에 설계와 구현 내용을 거꾸로 추론해내야 한다.
하지만 이런 작업은 결코 쉽지 않다. 고급 프로그래밍 언어로 되어 있는 원본 원시 코드는 컴파일러와 어셈블리어를 거쳐 이진 파일 형태로 변환되며, 이 과정에서 상당히 유용한 정보가 많이 제거된다. 가까스로 함수 이름이 남았더라도 내부 변수 이름이 사라지며, 구조체에 대한 정보도 메모리에 위치한 오프셋으로 바뀐다.
최적화를 높이기 위해 원래 프로그램의 명령 순서를 바꾸기도 하고, 필요 없는 부분을 제거하기도 하고, 인라인 중복으로 불필요한 코드가 끼어들기도 한다. 제한된 레지스터 개수 때문에 프로그램이 무척 길어지기도 하고 정렬을 맞추려고 불필요한 메모리를 끼워 넣기도 한다. 설상가상으로 리버싱을 회피하려는 목적으로 코드 난독화기(obfuscator)를 사용해 그나마 실낱처럼 남아있던 심볼 정보와 흐름까지도 엉망진창으로 뒤섞어 버린다.
이 책은 이런 어려운 상황에서 최대한 원본 원시 코드에 가깝도록 코드를 역으로 복원하는 방법을 차근차근 다룬다. 목차를 보면 1부에서 리버스 엔지니어링 기초 설명, 로우레벨 소프트웨어 설명, 마이크로소프트 윈도우 운영체제 기초 설명, 리버싱 도구 설명이 나오며, 2부에서 리버싱 실전, 파일 포맷 분석 기초, 프로그램 이진 실행 파일 검사 방법, 악성 코드 리버싱 기법이 나오며, 3부에서 저작권 침해와 불법 복사 방지 방법, 안티 리버싱 기술, 보호 기술 파괴, 4부에서 닷넷 리버싱, 디컴파일이 나온다. 나쁜 마음을 먹은 크래커가 이 책 내용을 적극적으로 활용해 나쁜 짓(?)을 하지 못하도록 3부 내용이 조금 자체 검열을 거쳐 순화된 측면이 아쉽긴 하지만 전반적으로 리버싱 감을 기르기에 충분하다는 생각이다.
물론 다른 부분도 도움이 많이 되지만 이 책에서 단연 2부 5장 ‘리버싱 실전’이 돋보인다. 어셈블리 코드만 보고 C 프로그래밍 언어의 원래 모습을 추론해 나가는 데 필요한 모든 증거 자료를 단서로 삼는 방법을 보고 있으면 추리 소설 한 편이 연상되기 마련이다. 어셈블리어로 윈도우 API를 호출하는 패턴, 어셈블리어로 C 구조체와 배열을 다루는 패턴, 어셈블리어로 C 반복문과 제어문을 다루는 패턴, 어셈블리어로 함수를 호출하고 콜백을 부르는 패턴, 심지어 고급 자료 구조를 어셈블리어로 표현한 코드 뭉치를 역으로 추측하는 패턴 등은 컴파일러가 원시 코드를 해석해 어셈블리어로 바꾸는 과정을 바둑 복기하듯이 거꾸로 돌리는 놀라운 경험을 하게 한다. 거의 원본 원시 코드에 가깝도록 리버싱한 결과를 보고 화들짝 놀라지 마시라.
이 책의 또 다른 특성은 충실한 부록이다. 단순한 부가 설명이나 따분한 자료 모음을 넘어 부록 자체만으로 충실한 리버싱 관련 정보를 제공한다. C에서 어셈블리어로 만들어지는 패턴을 소개하는 코드 구조 해석, 컴파일된 연산을 일목요연하게 정리해 리버싱을 손쉽게 도와주는 컴파일된 연산의 이해, 스택, 링크드 리스트, 클래스 구조를 분석하는 과정에서 도움을 주는 프로그램 데이터 해석이 차례로 나오므로 본문을 다 읽고 책을 덮지 말고 부록도 빠뜨리지 말고 읽어보기 바란다.
비록 이 책이 윈도우 운영체제와 IA-32 어셈블리어에 밀접한 내용으로 설명을 전개하지만, 윈도우가 아닌 다른 운영체제와 다른 아키텍처를 지원하는 어셈블리어를 사용하는 경우에도 충분히 본문 내용을 활용할 수 있으리라고 본다.
이 책을 읽고 나면 CPU 아키텍처, 컴파일러 최적화 정도, 코드 난독화기, 복잡한 알고리즘, 안티 리버싱 기법, 리버싱 도구 성능은 난공불락의 요새를 만들지 못하며 단지 리버싱 작업 시간을 늘일 뿐이라는 사실을 이해할 것이다. 쉽게 말해 리버싱 작업을 수행하는 개발자 역량이 리버싱 범위를 제한할 뿐이다.
2번 타자
리눅스 문제 분석과 해결
마크 윌딩 외 지음, 박재호/이해영 옮김, 에이콘출판사 2006년 출간
앞서 오픈 소스 세계에는 원시 코드가 존재한다고 말을 했다. 그렇다면 소프트웨어 문제 분석과 해결 과정에서 원시 코드를 살펴보면 되니까 큰 어려움이 없으리라는 생각이 들지도 모르겠다. 하지만 현실은 조금 다르다. 정적 검사 도구로 온갖 문제점을 다 잡아내고 난 다음에도 실행 시점에 희한한 문제가 튀어나오기 마련이다. 즉, 이진 실행 파일 형태로 완성된 소프트웨어에 대해 문제를 분석하고 해결책을 찾아야 할 필요가 있다. 이럴 경우 원시 코드는 전체 그림을 파악하기 위한 하나의 단서에 불과하다.
앞서 리버싱과 마찬가지로 이런 작업은 쉽지 않다. 실행 환경이라는 변수가 개입하며, 원시 코드가 아니라 이진 코드의 동작 특성을 동적으로 파악해야 하는 어려움이 있기 때문이다. 하이젠버그와 같이 버그를 추적하려고 마음먹으면 어김없이 사라지는 현상 앞에서 코드를 새로 만들고 싶다는 욕망이 불끈 솟아오른 경험은 개발자라면 누구나 한번씩 겪었을 법하다. 리버싱은 상대편의 논리를 모르는 상황에서 논리를 재구성하는 작업이라면 문제 분석과 해결은 어느 정도 논리도 알고 원시 코드도 확보한 상황에서 이진 실행 파일을 분석하는 작업이라고 볼 수 있다.
이 책은 리눅스 커널, 디바이스 드라이버, 단순 컴파일러와 디버그 옵션, 시스템 호출과 라이브러리 설명을 벗어나 본격적으로 문제를 분석하고 해결하는 방법을 설명한다. 목차를 보면 1장 문제 해결에 필요한 조사 과정, 2장 strace와 시스템 호출 추적, 3장 커널 내부를 들여다보는 /proc 파일 시스템 소개, 4장 컴파일러 다루기, 5장 프로그램 수행 과정에서 가장 중요한 자료 구조인 스택, 6장 디버거인 gdb를 십분 활용하기, 7장 리눅스 세상의 BsoD(공포의 푸른 화면)인 비정상 종료와 정지, 8장 커널 디버거인 kdb 활용하기, 9장 리눅스 파일 형식인 ELF를 다룬다.
이 책은 바이너리 자료를 많이 다루며 디버깅에 필요한 리눅스 내부 동작 원리를 외부에서 관찰하므로 커널 서적과는 또 다른 고난도를 자랑한다. 평소에 일반 (시스템) 프로그래머에게 숨겨져 있는 다양한 비밀이 등장하므로 커널과 프로그래밍 실행 환경의 내부 구조가 궁금했던 호사가의 호기심을 충분히 채워주리라 믿는다.
이 책에서는 특히 5장 스택이 가장 돋보인다. 현재 PC CPU 아키텍처로 가장 널리 쓰이는 x86과 x86-64 아키텍처에서 프로그램이 실행될 때 사용되는 스택 구조를 아주 자세하고 쉽게 설명한다. ABI(Application Binary Interface) 규칙을 그림과 역어셈블 결과로 설명하는 부분은 C나 C++에서 함수 호출이 어떻게 일어나는지 확실히 이해하게 도와준다. 게다가 디버거로 가공되지 않은 스택 정보를 확인하는 방법과 직접 프로그램으로 스택을 역추적 하는 방법까지 소개하므로 단순히 스택 역추적 결과만 보여주는 기존 리눅스 프로그래밍 서적에서 진일보했다고 생각한다.
gdb에 대한 설명도 뛰어나다. 예제를 위한 예제가 아니라 상황을 잘 드러내는 예제를 직접 추적하면서 실제 활용 기법을 설명하므로 실전에 도움을 준다. 특히 디버그 심볼이 없는 프로그램에서 구조체를 보는 방법, C++에서 전역 생성자와 소멸자를 다루는 방법, 어셈블리어를 활용한 고급 gdb 활용 기법은 신경 바짝 써서 봐두어야 한다. 기초적인 명령어부터 고급 활용법까지 모두 나오므로 간단한 gdb 튜토리얼로 활용해도 좋겠다.
이 책은 리버싱에 필요한 정보도 다수 제공한다. 앞서 언급한 스택 분석 기법과 gdb를 사용해 디버그 심볼이 없는 경우 대응 방안은 물론이고 strace를 사용해 원시 코드가 없는 상황에서 시스템 호출을 점검하는 방법과 리눅스 파일 형식인 ELF 분석 방법을 활용하면, 앞서 소개한 ‘리버싱: 리버스 엔지니어링 비밀을 파헤치다’에 나온 기초 지식과 결합해 리눅스 쪽에서도 충분한 리버싱 작업을 수행할 수 있으리라 믿는다.
정보 한 가지
마침 이 서평을 쓰고 있을 때 터보테크라는 국내 보안 회사에서 ‘제1회 해킹 And 리버스 엔지니어링 대회’를 열었다(기간은 2009년 8월 28일까지). 난독화기로 리버싱이 어렵게 되어 있는 이진 실행 프로그램을 분석해 키워드를 찾아내는 과제를 걸고 최우수상에 상금 2000만 원을 걸었다고 하니 리버싱에 관심 있는 독자들은 한번 도전해보면 어떨까?
이 문서 북마킹 하기
|