남기면 좋잖아

1부 기초 리버싱 (1) 본문

Penetration testing/리버싱

1부 기초 리버싱 (1)

Beautiful Hugo 2017. 7. 21. 07:35
반응형

- Contents -



1장 리버싱 스토리

2장 Hello World! 리버싱

3장 리틀 엔디언 표기법

4장 IA-32 Register 기본 설명

5장 스택






안녕하세요. 오늘은 대망의 "리버싱"이라는 공부의 첫걸음을 하게 되는 날입니다...

기대되기도하고 무섭기도하고 떨리기도 하네요.

앞서 언급했듯이 "리버싱의 핵심원리" 라는 책을 바탕으로 리버싱의 기초를 닦을 예정인데요.


분량이 어마어마하군요 ㄷㄷ

자 그럼 바로 들어가도록 하겠습니다.





1장 리버싱 스토리




먼저 리버스 엔지니어링(Reverse Engineering, RE)이라고 하면 물건이나 기계장치 혹은 시스템 등의 구조, 기능, 동작 등을 분석하여 그 원리를 이해하며 단점을 보완하고 새로운 아이디어를 추가하는 일련의 작업이라고 할 수 있습니다.


리버스 코드 엔지니어링(Reverse Code Engineering, RCE)은 소프트웨어 분야의 리버스 엔지니어링이라고 생각하면 됩니다. 소프트웨어를 리버싱 관점에서 상세하게 분석한다는 뜻이라고 합니다. 

정확한 용어의 통일이 되지않아서 여러가지 용어들이 같이 사용되고 있기 때문에 RCE, RE, 역공학, 리버싱 등의 용어를 섞어서 사용되는데 이 책에서도 RCE, RE, 리버싱이라고 표현한다고 합니다.



1.1 리버싱 분석방법


이런 리버싱에도 분석방법이 크게 두가지로 나뉘는데요. 정적 분석과 동적 분석이 있습니다.


  • 정적 분석

파일의 겉모습을 관찰하여 분석하는 방법입니다. 정적 분석 단계에서는 파일을 실행하지 않습니다.

파일의 종류(EXE, DLL, DOC, ZIP 등), 크기, 헤더(PE) 정보, Import/Export API, 내부 문자열, 실행 압축 여부, 등록정보, 디버깅 정보, 디지털 인증서 등의 다양한 내용을 확인하는 것입니다.

또한 디스 어셈블러를 이용해서 내부 코드와 그 구조를 확인하는 것도 정적 분석의 범주에 들어갈 수 있습니다.

이러한 방법으로 얻어낸 다양한 정보는 이후 소개될 동적 분석 방법에 좋은 참고 자료로 활용 됩니다.


  • 동적 분석
파일을 직접 실행시켜서 그 행위를 분석하고, 디버깅을 통하여 코드 흐름과 메모리 상태 등을 자세히 살펴보는 방법입니다. 

파일, 레지스트리, 네트워크 등을 관찰하면서 프로그램의 행위를 분석합니다. 또한 디버거를 이용하여 프로그램 내부 구조와 동작원리를 분석할 수 있습니다.


보통 저자의 경우 정적 분석으로 정보를 수집하면서 해당 프로그램의 구조와 동작원리에 대해 예측을 하고, 이후 수행될 동적 분석 방법에 많은 아이디어를 제공한다고 합니다. 

이처럼 두가지 방법을 잘 활용하면 프로그램을 리버싱할 때 시간을 많이 단축시킬 수 있고, 더 효과적인 분석이 가능합니다.


리버싱에서 취급하는 대상은 보통 실행 파일인 경우가 많습니다. 소스코드 없이 실행파일의 바이너리 자체를 분석합니다.
그렇다면 소스코드와 바이너리 코드사이의 관계를 살펴 보는 것이 리버싱을 이해하는데 도움이 될 것입니다.
예를 들어 C++ 소스코드가 빌드 과정을 거치면 어떤 식으로 변경되는지 보자면

1. 개발도구 (Visual C++)에서 소스코드(helloworld.cpp)를 빌드하면 helloworld.exe 실행 파일이 생성됩니다.

2. 생성된 실행 파일은 컴퓨터가 이해할 수 있는 2진수(Binary) 형식으로 되어있습니다. 그러나 아무리 리버싱 전문가라도 0과 1로만 구성되어 있는 binary 파일을 직접 보고 의미를 해석하는 것은 매우 힘든 일입니다.

 따라서 HexEditor를 이용하여 2진수를 16진수 형식으로 변환 시키곤 하는데, 자릿수가 줄어들면서 훨씬 보기에 수월하기 때문입니다.

3. 사실 16진수(Hex) 코드는 사람에게 직관적인 형태는 아닙니다. 따라서 좀 더 사람이 이해하기 쉬운 어셈블리 코드 형태로 보기 위해 helloworld.exe 실행파일을 디버거를 이용해 열어보면 디스 어셈블과정을 거쳐서 어셈블리 코드로 변환하여 보여줍니다.


일반적인 리버싱 과정에서는 위와 같은 어셈블리 코드를 분석하곤 합니다.



1.2 패치와 크랙


프로그램의 파일 혹은 실행중인 프로세스 메모리의 내용을 변경하는  작업을 패치(patch) 라고 합니다. 크랙(crack)은 패치와 같은 개념이지만 특별히 그 의도가 비합법적이고 비도덕적인 경우를 따로 구분하여 말합니다.

일반적인 패치의 예는 Microsoft의 Windows 업데이트를 들 수 있습니다. 프로그램의 취약점 수정과 기능 개선이 목적이죠.

반면 크랙은 주로 저작권을 침해하는 행위(불법 복제/사용 등)에 주로 사용됩니다.

그러므로 리버싱을 공부하는 사람은 자신이 익히는 기술이 양날의 칼이 될 수 있음을 명심하고 악용하지 않는 자세로 높은 윤리 의식이 필요하다고 볼 수 있습니다.






2장 Hello World! 리버싱




아마도 모든 개발자가 처음 만들어보는 프로그램은 Hello Wolrd 일 것입니다. 더 할 나위 없이 간결한 소스코드를 볼때면 처음으로 프로그래밍 언어를 공부할 때의 감동과 흥분이 떠오르겠죠.


여기서 Hello World.exe 파일을 리버싱 예제는 여러가지 방법이 있는데요. 이 저자의 사이트에서 exe 파일을 다운로드받아서 바로 올리디버거에 올릴 수 있고, 아니면 본인이 직접 cpp 파일을 만들어 visual C++로 exe 파일을 생성하여 실행하셔도 됩니다.


저같은 경우는 cpp 파일을 다운로드 받아서 오랜만에 visual studio를 사용할 겸 exe 파일을 생성하여 해보았습니다.


HellowWorld 프로젝트를 Visual C++로 열어 봅시다.

(참고로 이 책의 예제 파일과 소스코드는 www.reversecore.comhttp://blog.insightbook.co.kr/ 에서 받을 수 있습니다.)





소스는 이렇게 되어있네요.

그리고 빌드하면 실행파일(test.exe)이 생성됩니다.

이 과정은 결국 사람이 이해하기 쉬운 C언어 소스코드(helloworld.cpp)를 기계가 이해하기 쉬운 기계어(helloworld.exe) 로 변환하는 것입니다.

이러한 기계어는 사람이 알아보기 어렵기 때문에 좀 더 편하게 보기 위해서 디버거 유틸리티를 사용합니다.


디버거에 탑재된 디스어셈블러 모듈은 이 기계어를 어셈블리 언어로 번역해서 보여주게 되죠.



2.1 helloworld.exe 디버깅


HelloWorld.exe 실행 파일을 디버깅 하여 어셈블리 언어로 변환된 main() 함수를 찾아보겠습니다. 

이 과정을 통해서 기본적인 디버거의 사용법과 어셈블리 명령어에 대해 공부합니다.







위 그림이 앞으로 여러분이 계속 사용하게될 OllyDbg 의 실행 화면입니다.

디버깅을 시작하기전 메인 화면 구성에 대해 간단히 설명하겠습니다.


  • Code Window : 기본적으로 disassembly code를 표시하여 각종 comment, label을 보여주며, 코드를 분석하여 loop, jump 위치 등의 정보를 표시합니다.


  • Register Window : CPU register 값을 실시간으로 표시하며 특정 register 들은 수정도 가능합니다.


  • Dump Window : 프로세스에서 원하는 memory 주소 위치를 Hex와 ASCII/유니코드 값으로 표시하고 수정도 가능합니다.


  • Stack Window : ESP register 가 가리키는 프로세스 stack memory를 실시간으로 표시하고 수정도 가능합니다.


2.2 EP


디버거가 멈춘 곳은 EP(EntryPoint) 코드로, HellowWorld.exe의 실행 시작 주소(A811A1)입니다.

일단 EP 코드에서 눈에 띄는 건 CALL 명령과 그 밑의 JMP 명령입니다.




위 두줄의 어셈블리 코드는 그 의미가 매우 명확합니다.


"A8284C 주소의 함수를 호출(CALL) 한 후 A8104F 주소로 점프(JMP) 하라"



이렇게 해석이 됩니다. 계속 디버깅을 진행해봅니다.

목표는 우리가 작성한 main() 함수에서 MessageBox() 함수 호출을 확인 하는것입니다.




2.3 A8284C 함수 따라가기


본격적인 디버깅을 하기위해서 올리디버거의 기본 명령어 사용법을 알아보겠습니다.


Restart : Ctrl + F2 - 다시 처음부터 디버깅 시작 ( 디버깅을 당하는 프로세스를 종료하고 재실행 )


Step Into : F7 - 하나의 OP code 실행 ( CALL 명령을 만나면, 그 함수 코드 내부로 따라 들어감 )


Step Over : F8 - 하나의 OP code 실행 ( CALL 명령을 만나면, 따라 들어가지 않고 그냥 함수 자체를 실행 )


Execute till Retrun : Ctrl + F9 - 함수 코드 내에서 RETN 명령어까지 실행 ( 함수 탈출 목적 )


EP 코드의 A811A1 주소에서  F7 명령어를 사용하여 A8284C 함수 안으로 따라갈 수 있습니다.



굉장히 복잡해보이는 어셈블리 코드가 나타났습니다. 저도 그렇지만 앞으로 계속 진행해 나가다 보면 차츰 눈에 익숙해지고 자연스럽게 의미를 이해할 수 있을 겁니다.

위 그림의 가장 오른쪽 부분은 OllyDbg에서 보여주는 주석(comment) 입니다.

빨간색 글씨로 된 부분은 코드에서 호출되는 API 함수 이름입니다. 이 주석 부분에서 호출되는 API 함수 이름들만 살펴보기 바랍니다.

원본 소스코드에서 사용된 적없는 API들이 호출되고 있습니다. 이곳은 분명 우리가 찾는 main() 함수가 아니겠습니다.

사실 이곳은 Visual C++ 에서 프로그램 실행을 위해서 추가시킨 Visual C++ Stub Code 입니다. 지금은 신경 쓰지말고 우리의 목표인 main()을 찾아 계속 진행해보겠습니다.


저의 경우에는 함수 끝부분에 RETN 명령어가 있습니다. RETN은 함수의 끝에서 사용되며 이 함수가 호출된 원래 주소쪽으로 되돌아갑니다.

A8284C 주소에 있는 RETN 명령어까지 Ctlr+F9 명령을 이용하여 한 방에 가봅니다. 그리고 TETN 명령어를 실행[F7/F8] 하면, A811A6 주소로 오게 됩니다. (C언어에서 함수를 호출하고 리턴하면, 그 다음 명령어로 오는 것과 마찬가지 입니다.)


2.4 A8104F 함수 따라가기


아까처럼 A811A6 주소의 JMP 00A8104F 명령을 실행해서 A8104F 주소로 갑니다.






또 상당히 복작해 보이는 코드가 나타났습니다. 이 코드 역시 Visual C++ stub code 입니다.

이 코드를 따라가다 보면 우리의 목표인 main() 함수가 나타납니다.



2.5 main() 함수 찾기


위 그림의 A8104F 주소부터 함수 호출 명령어를 하나하나 따라가다 보면 우리가 원하는 main() 함수를 만날 수 있는데, 이를 직접 실습해보겠습니다.


앞서 소개한 4가지 명령어 (Restart, Step Into, Step Over, Execute till Retrun) 만 가지고 main() 함수를 찾아보겠습니다.


F7 명령으로 한 줄씩 내려오다보면 CALL HelloWor.00A82658 함수 호출 명령어를 만나는데, F7 명령으로 내부로 들어가봅니다.




보다시피 A82658 함수는 main() 함수라고 보기 어렵습니다.

왜냐하면 MessageBox() API 호출 코드가 보이지 않기 때문입니다. Ctrl+F9 명령어를 사용하면 한번에 RETN 명령어 위치까지 디버깅이 진행 됩니다.


여기서 F7을 써서 함수를 탈출하여 A8105B 주소로 돌아갑니다.


이와같이 F7 명령으로 디버깅을 해나가면서 함수 호출을 만나면 따라 들어가고 코드를 보고 main() 함수인지 확인합니다.

아니라면 Ctrl+F9 로 탈출한 후 계속 같은 방식으로 디버깅을 진행하면 됩니다. 


그럼 도중에 아래과 같은 코드를 만나게됩니다.


CALL DWORD PTR DS:[<&KERNEL32.GetCommandLineA>]


이 명령어는 Win32 API 호출코드입니다. 지금 단계에서는 따라들어갈 필요가 없으니 F8로 넘어가세요.

(내부 반복문이 존재해서 탈출할때까지 시간이 좀 걸립니다.)


별 무리없이 진행해왔다면, 아래와 같은 코드를 볼 수있을겁니다.


CALL HelloWor.00A81000



내부로 따라들어가게되면





 드디어 MessageBoxW() API를 호출하는 코드가 나타나는데,  그 API 파라미터가 www.reversecore.com과 Hello World! 문자열입니다.

따라서 A81000 함수가 바로 main() 함수 입니다.


혹시 main() 함수를 찾지 못해도 괜찮습니다. 디버거를 처음 사용하면서 대략적인 디버깅의 감을 잡았다면, 그것으로 충분합니다.


그리고 다음에 추가적인 디버거 명령어를 배우면 훨씬 쉽게 디버깅할 수 있습니다.






오늘은 여기까지하고 다음에 HelloWorld! 리버싱을 이어서 나머지 목차를 마치도록 하겠습니다.


저도 오늘 하면서 리버싱이란 대충은 이런것이구나 라는걸 알게됬는데요. 역시 만만치 않았습니다 ㅠㅠ

아직까진 너무도 생소하고 막막하지만 계속 공부하면서 이겨내야겠죠.



그럼 저는 이만 여기까지하고 다음에 뵙도록 하겠습니다.

반응형

'Penetration testing > 리버싱' 카테고리의 다른 글

리버싱 공부를 시작하기전..  (0) 2017.07.21
Comments