Reversing.kr의 Challenge의 Easy Crack 문제를 풀어보았다.

우선, 실행 화면을 살펴보면, 아래와 같이 무언가를 입력하도록 새로운 윈도우 창이 생성된다.


[그림1. exe 파일 실행화면]


무언가 제대로 된 값을 넣어야됨을 알 수가 있다.

우선 소스를 상상해보면 값을 입력하였고, 그 입력 값과 Key 값을 비교할 것이라는 흐름이 떠오를 수 있다.

그럼 비교를 위해서 할 수 있는 흐름은? 단순 string 문자열 끼리 비교할 수도 있고.. (이런 흐름이라면 때땡큐)

아니면 입력한 값과 Key 값을 1byte 씩 값 비교할 수도 있을 것이라 생각할 수 있다.


한 번 OllyDbg로 살펴보자. 

처음부터 흐름을 A -> B -> C -> D ..... 로 끌고갈 실력도 안되거니와 복잡하기 때문에 Referenced String을 확인하여 우리가 체크해야할 Routine 영역으로 바로 넘어가도록 한다.


[그림2. Reference String 확인 결과]


위 [그림2]와 같이 왠지 느낌이 확 오는 문자열들을 확인해 볼 수 있다. 여기 중에 Congratulation !!을 클릭하여 해당 위치로 한 번 가보자.


[그림3. Contratulation 문자열을 따라 해당 영역으로 이동 후, 해당 영역의 시작 지점에 Break Point Set]


위와 같이 Break Point를 걸어 놓고, F9를 통해 Running을 하면, Key 입력받는 창이 생기며, 그 Key 창에 임의의 값을 입력 하고 Enter를 누르면 우리가 Break Point 설정한 위 지점에서 멈추게 된다.


그리고 천천히 밑으로 내리다보면 (F8로) GetDlgItemTextA라는 API를 만날 수 있다. GetDlgItmeTextA를 통해 메시지 박스에서 입력한 값을 API 인자에 포함된 Buffer 주소 값에 Input 할 수 있다.


[그림4. GetDlgItemTextA API를 이용하여 MessageBox 입력 값을 받아온다.]



[그림5. GetDlgItemTextA를 통해 얻어온 값]


이후 이 값의 2번째 위치한 o와 'a' (61h)를 비교하게 된다. 이를 통해 2번째 위치한 문자열은 왠지 'a' 일 것 같은 느낌이 팍 든다. 그리고 입력 값의 2번째 위치한 문자가 'a'와 다르다면 'Incorrect Password'를 띄우는 루틴을 태우도록 JNZ 분기가 들어가 있다. (왜 첫번 째 문자열 먼저 비교하지 않을까 의심해볼 필요가 있다.) 그리고 그 밑에 5y 문자열을 집어넣고 call 401150을 호출하는데 그냥 추측컨데 왠지 a 뒤에 오는 문자열은 5y가 되어야할 것 같은 느낌이다.


[그림6. 입력 값의 2번째 문자열이 a 인지 체크 이후, JNZ 분기, a일 경우 5y 비교]


한번 step into (F7) 로 401150 함수 안을 들여다 보자.


[그림7. 401150 함수 안, a와 비교 이후 5y 문자열 비교]


이후의 문자열은 R3versing 문자열을 비교하는 루틴이다. R3versing 문자열 비교를 위해 1byte 씩 이동해가면서 1byte 비교를 하고 있으며, CMP BYTE PTR SS:[ESP+4], 45를 보면 45h는 E를 의미하며 ESP+4는 입력 값의 첫 번째 byte를 의미한다. 제일 처음에 2번째 byte를 a로 비교하면서 첫번째 바이트 비교를 왜 먼저 안했는지 의문이 든다고 했는데, 뒤에서 첫번째 byte를 비교함을 확인할 수 있다.


[그림8. 5y 문자열 비교 이후에 R3versing 비교]


이렇게 비교해본 문자열을 종합해보면 Ea5yR3versing 이라는 문자열을 확인할 수가 있다.




# REPE는 ECX 값 만큼 SCAS (EAX와 SCAS 이후 나오는 주소 값과 비교), STOS (EAX/AH/AL을 STOS 이후 

   나오는 주소 값에 저장) 할 수  있는데 같을 경우, 그 비교를 멈추고 나온다.


# REPNE는 ECX 값 만큼 비교 및 저장하고, 같지 않을 경우, 비교를 멈추고 나온다.



















악성 행위를 하는 파일을 분석하다 보면, GetTempPath() API 등을 사용하여 PC의 Temp 폴더에 *.tmp 형태의 파일을 생성하여 이용하는 경우가 종종 있다. 


이 이유는 일부 백신에서 EXE 파일과 DLL 파일을 대상으로 검사하는 알고리즘을 회피하기 위한 목적이라고 생각해볼 수 있다고 한다. (출처 : http://havu.tistory.com/30)




오늘 새로 알게 된 지식은 PDF (Portable Document Format)의 구조와 그 구조 중에서 어떻게 악성 Stream을 찾는지에 대한 내용을 알게 되었다. 해당 내용은 항상 멀리서 따라가고자 하는 최원혁 대표님의 교육 영상을 통해 습득 했으며, 이번 주말에 PDF에 대해 조금 더 알아보고자 한다.


오늘은 Part1에 대한 동영상 완강을 목표로 하였고, 그 목표를 달성하였다.

강의 내용은 아래와 같다.




PDF 구조는 큰 그림으로 보았을 때 아래와 같이 생겼다고 한다.



Header에는 뭐 PDF 버전이 뭔지 이런게 담겨져 있고, Body는 각종 Object들이 담겨져 있다고 한다.

Cross Reference Table은 Body에 담겨져 있는 간접 Object들에 대한 위치 정보와 사용 유/무에 대한 내용이 담겨져 있다고 하며, Trailer는 Body에서 사용 중인 Root Object (문서의 시작은 Root Object 로부터) 의 정보와 Cross Reference Table의 시작 위치를 담고 있다고 한다.


따라서 우선 시 봐야할 것은 Trailer라고 한다. Trailer를 통해 Root Object가 무엇인지 확인하고, Cross Reference Table을 통해 사용되는 Object의 수와 시작 위치를 확보한 뒤에 Root Object 부터 차근히 Flow를 따라 따라 내려가면 된다.


Object들 중에 Stream을 포함하고 있는 것들이 존재하는데, 아마도 이 Stream에 악성 행위를 위한 자바 스크립트가 포함되어 있지 않나 싶다. 사용자 모르게 악성 자바 스크립트가 실행되는 것이다. 


그래서 대표님은 PDF 자체에 대한 분석 자체보다 이 Stream 값이 담고 있는 자바 스크립트를 분석해내는 것이 더욱 중요하다고 말씀하신 것 같다. 자바 스크립트 또한 난독화되어 쉽게 복호화 하기는 어렵기 때문이다.


# PDF 파일의 Body : 문서의 실질적인 내용을 담고 있는 간접 오브젝트들로 구성

                             이러한 오브젝트들은 문서의 내용, 폰트나 페이지, 이미지 등과 같은 요소를 나타낸다.



두 번째 강의는 아래와 같다.



두 번째 강의에서는 Stream에 Java Script외 Flash 파일 .swf 파일을 추가적으로 감싸고 있는 상황에 대해서 설명하고 있다. Stream을 가지고 있는 Object에서 swf 파일의 내용을 가지고 있다. 하지만 실제로 해당 소스를 정확히 판별할 수 없어 디컴파일이 필요함을 느끼고, showmycode.com을 이용하여 디컴파일한 소스를 다운로드 받는다. 그 중에 HeapSpray, 즉 쉘코드를 확인하고 그 쉘코드를 분석해보면, 최초 PDF 파일의 Header 체크 등을 통해 결국엔 PDF 내 다른 Object의 지점을 실행해서 svchost.exe 악성 파일을 생성하도록 하는 것이 포인트였다.



메시지 구동 구조


윈도우 운영체제에서 실행되는 대부분의 응용프로그램은 메시지 구동 구조 (Message - Driven Architecture)로 동작한다. 여기서 메시지는 운영체제가 프로그램의 외부 또는 내부에 변화가 발생했음을 해당 프로그램에 알리기 위한 개념이다.


일반적으로 도스용 프로그램이나 윈도우의 콘솔 응용 프로그램은 프로그래머가 정한 순서대로 실행된다. 따라서 순차적인 방식에 의존해야 하므로 알고리즘과 논리의 흐름에 중점을 두고 프로그래밍 한다.


하지만 대부분의 윈도우 응용 프로그램은 순차적으로 실행되지 않고 어떤 메시지를 받는가에 따라 코드의 실행 순서가 달라진다.



출처 : 한빛출판사 윈도우 프로그래밍 기초

           [출처 : 한빛출판사 윈도우 프로그래밍 기초 sample.pdf]


외부에서 메시지를 발생시키는 이벤트(Event)가 발생하면 운영체제가 관리하는 시스템 메시지 큐 (Message Queue)에 정보가 저장된다. 각각의 응용프로그램은 운영체제로부터 독립적인 메시지 큐를 할당받으며 운영체제는 시스템 메시지 큐에 저장된 메시지를 적절한 응용프로그램 메시지 큐에 보낸다. 응용프로그램은 자신의 메시지 큐를 감시하다가 메시지가 발생해서 큐에 들어오면 하나씩 꺼내서 처리하고 메시지가 없을 때는 대기한다.



메시지 핸들러 집합


윈도우 운영체제의 특징에서 살펴본 것처럼 윈도우 응용프로그램은 메시지 구동 구조로 동작한다. 메시지 구동 구조에서는 받는 메시지에 따라 실행 순서가 달라지며 해당 메시지에 어떻게 반응하는가에 따라 동작이 달라진다. 메시지를 받았을 때 동작을 결정하는 코드를 편의상 메시지 핸들러 (Message Handler)라 부르자. 프로그래머는 키보드 메시지 핸들러, 마우스 메시지 핸들러, 메뉴 메시지 핸들러 같은 다양한 메시지 핸들러를 작성하게 된다.


메시지 핸들러의 집합을 윈도우 프로시저(Window Procedure)라 부른다. 윈도우 운영체제는 프로그램이 처리하지 않은 메시지를 자동으로 처리할 수 있도록 운영체제 차원의 메시지 핸들러를 제공한다.



참고 URL : http://maxtrain.egloos.com/2775961




아래 내용 출처는 http://adolys.tistory.com/entry/PeekMessage-GetMessage-TranslateMessage-DispatchMessage 입니다.


윈도우즈 프로그램에서 메시지 (Message) 를 처리하는 부분을 메시지 루프라고 하며

메시지 루프 (Message Loop) 에서 하는 일은 메시지를 꺼내고 필요한 경우 약간 형태를 바꾼 후

응용프로그램으로 전달하는 것 뿐이다. 이 과정은 WM_QUIT 메시지가 전달될 때까지, 

즉 프로그램이 종료될 때까지 반복된다. 


결국 메시지 루프 (Message Loop) 가 하는 일이란 메시지 큐에서 메시지를 꺼내

메시지 처리 함수로 보내주는 것 뿐이다.


BOOL GetMessage




이 함수는 시스템이 유지하는 메시지 큐에서 메시지를 읽어들인다. 읽어들인 메시지는 첫번째 인수가 지정하는 MSG 구조체에 저장된다. 이 함수는 읽어들인 메시지가 프로그램을 종료하라는 WM_QUIT일 경우 False를 리턴하며 그 외의 메시지이면 True를 리턴한다.


따라서 WM_QUIT 메시지가 읽혀질 때까지 즉, 프로그램이 종료될 때까지 전제 while 루프가 계속 실행된다. 나머지 세 개의 인수는 읽어들일 메시지의 범위를 지정하는데 잘 사용되지 않으므로 일단 무시하기로 한다.




BOOL TranslateMessage



TranslateMessage는 키보드 입력 메시지를 가공하여 프로그램에서 쉽게 쓸 수 있도록 해준다고 한다. Windows는 키보드의 어떤 키가 눌러졌다거나 떨어졌을 때 키보드 메시지를 발생시키는데 이 함수는 키보드의 눌림 (WM_KEYDOWN) 과 떨어짐 (WM_KEYUP) 이 연속적으로 발생할 때 문자가 입력되었다는 메시지 (WM_CHAR) 를 만드는 역할을 한다. 예를 들어 A키를 누른 후 다시 A키를 떼면 A문자가 입력되었다는 메시지를 만들어낸다.



LONG DispatchMessage



시스템 메시지 큐에서 꺼낸 메시지를 프로그램의 메시지 처리 함수로 전달

이 함수에 의해 메시지가 프로그램으로 전달되며 프로그램에서는 전달된 메시지를 점검하여 다음 동작을 결정



BOOL PeekMessage



GetMessage함수와 마찬가지로 메시지 큐에서 메시지를 읽는다. 메시지의 범위를 줄 수 있는 기능도 GetMessage와 동일하다. 그러나 이 함수는 GetMessage와는 달리 읽은 메시지를 무조건 제거하지 않으며 큐가 비어있을 경우 대기하지 않고 곧바로 리턴한다는 점이 다르다. 따라서 이 함수는 메시지를 읽지 않고 단순히 메시지가 있는지 확인만 할 수 있으며 이런 특성은 백그라운드 작업에 적절하다.


GetMessage 함수는 메시지 큐가 비어 있을 경우 무한 대기를 하기 때문에 백그라운드 작업을 할 수 없지만

PeekMessage 함수를 사용하면 즉시 리턴하며 리턴 값으로 메시지의 유무를 알려주므로 0을 리턴할 때 백그라운드 작업을 수행할 수 있다. 


- hWnd : 메시지를 받을 윈도우이며 이 윈도우로 보내지는 메시지를 조사한다. 이 윈도우는 반드시 같은 스레드에 소속된 윈도우여야 하며 다른 스레드로 보내지는 메시지는 메시지는 조사할 수 없다. 이 인수가 NULL이며 이 함수를 호출한 스레드로 전달된 모든 메시지를 조사한다.


- wMsgFilterMin : 조사할 메시지의 최소 값

- wMsgFilterMax : 조사할 메시지의 최대 값


- 위 wMsgFilterMin과 wMsgFilterMax 두 인수를 사용하면 일정한 범위에 속한 메시지만 조사할 수 있는데 이를 메시지 필터링이라고 한다. 예를 들어, 키보드 관련 메시지만 조사하고 싶으면 WM_KEYFIRST, WM_KEYLAST로 범위를 지정할 수 있다. 이 두 인수가 모두 0이면 메시지 필터링을 하지 않으며 모든 메시지를 조사한다.


- wRemoveMsg : 조사한 메시지를 처리할 방법을 지정하는 플래그의 조합


- Flag 설명

  1) PM_NOREMOVE - 메시지를 읽은 후 큐에서 메시지를 제거하지 않는다.

  2) PM_REMOVE - 메시지를 읽은 후 큐에서 메시지를 제거한다.

  3) PM_NOYIELD - 다른 스레드로 제어를 양보하지 않는다.

  4) PM_QS_INPUT - 디폴트로 이 함수는 모든 메시지를 다 처리하는데 이하의 플래그들을 지정하면 

                              특정 메시지들만 처리하도록 할 수 있다. 이 플래그들은 98 이상, 2000이상에서 적용된다.

                              마우스나 키보드 등의 입력 메시지만 처리한다.

  5) PM_SQ_PAINT - 그리기 메시지만 처리

  6) PM_QS_POSTMESSAGE - 타이머나 핫키 메시지를 포함하여 붙여지는 메시지만 처리

  7) PM_SQ_SENDMESSAGE - 보내지는 메시지만 처리


실제 메시지 처리는 별도의 메시지 처리 함수 (WndProc) 에서 수행한다. 

메시지는 시스템의 변화에 대한 정보이며 MSG라는 구조체에 보관된다. 


MSG 구조체는 다음과 같이 정의되어 있다.


typedef struct tagMSG{

HWND         hwnd;

UNIT           message;

WPARAM    wParam;

LPARAM     lParam;

DWORD       time;

POINT         pt;

} MSG;


- hwnd : 메시지를 받을 윈도우 핸들

- message : 어떤 종류의 메시지인가를 나타내며, 중요하게 봐야할 목록

- wParam : 전달된 메시지에 대한 부가적인 정보를 가진다. 어떤 의미를 가지는가는 메시지 별로 다르다.

- lParam : 전달된 메시지에 대한 부가적인 정보를 가진다. 어떤 의미를 가지는가는 메시지 별로 다르다.

- time : 메시지가 발생한 시간

- pt : 메시지가 발생했을 때의 마우스 위치


message 멤버를 읽음으로써 메시지의 종류를 파악하며 message 값에 따라 프로그램의 반응이 달라진다.

GetMessage 함수는 읽은 메시지를 MSG 형의 구조체에 대입해 주며 이 구조체는 DispatchMessage 함수에 의해 응용프로그램의 메시지 처리 함수 (WndProc) 로 전달된다. 


메시지는 실제로 하나의 정수 값으로 표현되는데

메시지의 종류가 많아 windows.h를 참고 하여 확인토록 한다. 

WM_ 접두어로 시작한다.


WM_QUIT : 프로그램 끝낼 때 발생 메시지

WM_LBUTTONDOWN : 마우스의 좌측 버튼을 누를 경우 발생

WM_CHAR : 키보드로부터 문자가 입력될 때 발생

WM_PAINT : 화면을 다시 그려야할 필요가 있을 때 발생

WM_DESTROY : 윈도우가 메모이에서 파괴될 때 발생

WM_CREATE : 윈도우가 처음 만들어질 때 발생





 













+ Recent posts