Reversing/Reversing2017. 7. 3. 03:53

Lena’s Reversing for Newbies


항상 그랬던 것처럼 먼저 프로그램을 실행해보자.


쓸데없는 잡담들 다 갖다 치우고 올바른 Registration Code를 찾으라고 한다. 확인을 눌러봤다.


Registration Code를 입력하는 창인 것 같다. Register Me! 버튼을 입력하면 맞는지 아닌지 확인해줄 것이고, Nag? 버튼은 모르겠으니 일단 눌러봤다.


아까 처음에 봤던 창이다. Regcode에 아무거나 넣고 Register me!를 눌러봤다.


틀렸다 한다. 이제 할 것도 없고 폼 창에서는 SmartCheck를 사용해서 Registration을 얻어보라고 하는데, 그건 나중에 하기로 하고 일단 OllyDbg에 올려보자.


여기까지 와서 Search for All Intermodular Calls 명령을 사용해 MsgBox를 만들어내는 함수를 호출하는 곳을 찾아보자.


이렇게 하나하나 찾을수도 있겠지만 Sort by 기능을 활용하면 한눈에 쉽게 볼 수 있다.


저 네 개 모두에 BreakPoint를 걸어준다. 우클릭으로 Set breakpoint on every call to rtcMsgBox라는 옵션을 선택하면 저기 4개의 주소에 모두 BP가 걸린다. 이후 F9로 실행시키고 Nag? 버튼을 눌러보면 다음 그림처럼 Break가 걸린다.

그럼 저기가 바로 메시지 박스를 출력하기 위해 함수를 호출하는 부분인 것을 알 수 있으니 저 CALL 부분을 어떻게 수정해주면 되겠다. 그런데 아무리 생각해도 CALL 5바이트 명령만큼으로 MsgBox 함수의 리턴인 EAXESP까지 동시에 처리해줄 방법이 없었다. EAX에는 확인 버튼을 눌렀을 때처럼 1이 들어가면서 스택 포인터까지 정리하려면 8바이트가 필요하기 때문이다.

그렇다면 그냥 Event Handler Nag? 버튼을 눌렀을 때 호출되는 함수 자체를 호출되자마자 리턴 시켜버리면 되지 않을까? 하는 생각에 위로 올려서 함수의 시작부를 찾아봤다.

함수 시작부를 찾을 수 있었다(PUSH EBP; MOV EBP, ESP;). VB는 일반적으로 Stdcall 방식을 사용하므로 피호출 함수가 스택을 정리해야 한다. 그렇다면 전달되는 파라미터에 따라서 RETN @ 형식으로 ESP를 함수 호출 이전으로 초기화 해줘야 한다. 이 함수의 파라미터의 개수를 찾아보자.

파라미터 개수를 찾기 위해서 함수의 시작부인 PUSH EBP BP를 걸고 Nag? 버튼을 다시 클릭한 후 스택을 확인한다.

7401E5A9의 주소로 돌아가는 것을 알 수 있다. MSVBVM50.dll 모듈 영역이다.

바로 위의 명령을 보면 CALL EAX 명령을 실행한 후 아래 명령으로 돌아오도록 되어있다. CALL EAX 명령BP를 걸고 Ctrl+F2로 다시 실행해보자.

EAX의 값은 402649이다. 저 주소로 가보자.

402C17 주소로 점프하는 것을 알 수 있다. , 위에 MSVBVM50.dll에서 CALL EAX 명령에 의해 호출된 것은 402C17에 있는 함수, 그러니까 우리가 파라미터의 개수를 찾으려는 함수임을 알 수 있다. 이는 CALL EAX 명령이 실행되는 전후의 스택 주소를 확인하면 402C17의 파라미터의 개수를 알 수 있다는 뜻이다. 한 번 확인해보자.

4만큼 차이 나는 것을 확인했다. , 4바이트 만큼 정리해줘야 한다는 뜻이므로 코드를 수정하러 가자.


다음과 같이 코드를 수정해준다. 실행해보면 이제 쓸데없는 소리하는 메시지 박스가 나오지 않는 것을 확인할 수 있다.

, 그럼 이제 Registration Code를 찾아보자. 먼저 아까 틀리면 나왔던 메시지 박스의 내용을 바탕으로 Search for All Strings에서 해당되는 부분을 찾았다.

더블클릭으로 찾아 들어가준다.


‘I’mlena151’이라는 문자열을 상단에서 찾을 수 있다. 저 문자열 바로 아래에 호출되는 함수는 StrCmp, 즉 문자열 비교 함수다. 이는 즉 저 문자열과 사용자가 입력한 문자열을 비교하는 것으로 유추할 수 있다. 바로 넣어서 확인해보았다.



'Reversing > Reversing' 카테고리의 다른 글

Calling Convention (함수 호출 규약)  (0) 2017.07.02
Abex’ Crackme #2  (0) 2017.06.30
Stack Frame  (0) 2017.06.27
Abex' Crackme #1  (0) 2017.06.27
OllyDbg Commands & Assembly Basic & etc  (0) 2017.06.27
Posted by BinZIP
Reversing/Reversing2017. 7. 2. 15:03

함수 호출 규약 (Calling Convention)


함수 호출 규약은 함수를 호출할 때 파라미터를 어떤 식으로 전달하는가?’에 대한 일종의 약속이다. 이전까지의 포스팅에서도 확인했듯이 함수를 호출하기 전에 파라미터를 스택을 통해서 전달한다는 것을 알고 있다. 스택이란 프로세스에서 정의된 메모리 공간이며, ‘스택은 거꾸로 자란다는 말대로 스택에 큰 주소부터 작은 주소를 향해 자라난다. 또한 PE 헤더에 스택의 크기가 명시되어있다. , 프로세스가 실행될 때 스택 메모리의 크기가 결정된다는 뜻이다. 이는 동적 할당에 사용되는 Heap 메모리 공간과는 다르다.


그렇다면 함수가 실행이 완료되었을 때 스택에 있는 파라미터는 어떻게 할까? 정답은 그대로 놔둔다이다. 스택에 저장된 값은 임시로 사용하는 값이기 때문에 더 이상 사용하지 않는다 하더라도 값을 지우거나 하면 불필요하게 CPU의 자원을 소모하는 것이기 때문이다. 어차피 다음 번에 스택에 다른 값을 입력하면 저절로 덮어쓰는 데다가 스택 메모리는 이미 고정되어 있기 때문에 메모리를 해제(Free)시킬 수도 없고 할 필요도 없다.

=> 함수 실행 후 스택에 남은 파라미터는 지우지 않고 그대로 놔둔다.

 

함수가 실행이 완료되었을 때 ESP, 즉 스택 포인터는 함수 호출 전으로 복원되어야 한다. 그래야만 참조 가능한 스택의 크기가 줄어들지 않기 때문이다. 스택 메모리는 고정되어 있고 ESP로 스택의 현재 위치를 가리키는데, 만약 ESP가 스택의 끝을 가리킨다면 더 이상 스택을 사용할 수 없다. 함수 호출 후에 ESP를 어떻게 정리하는지에 대한 약속이 바로 함수 호출 규약이다.

주요 함수 호출 규약은 아래와 같다.


1.     Cdecl

2.     Stdcall

3.     Fastcall


애플리케이션 디버깅에선 cdecl stdcall의 차의점을 확실히 알아야 한다. 어떤 방식이든 파라미터를 스택을 통해 전달한다는 기본 개념은 동일하다.

 

Cdecl (C Declaration)

Cdecl 방식은 주로 C언어에서 사용되는 방식이고, Caller, 즉 함수 호출자가 스택을 정리하는 특징을 가지고 있다.


위에서 Main으로 표시해둔 부분을 잘 보자. 파라미터를 401000 주소에 위치한 함수에 전달한 후 ESP 8을 더해서 파라미터 부분을 정리하고 있다. 이렇게 Caller(이 예제에서는 main)가 스택을 정리하는 방식이 cdecl 방식이다.

Cdecl 방식은 printf 함수와 같이 파라미터의 길이가 변해도 전달할 수 있다는 장점이 있다. 가변 길이 파라미터를 전달하는 것은 다른 함수 호출 규약 방식(정확히 말하면 피호출자가 정리하는 규약)에서는 구현이 어렵다. 호출자 함수에서는 가변 인자의 스택의 크기를 알고 있지만 피호출자 함수에서는 가변 인자의 스택의 크기를 알 수 없기 때문이다.

 

Stdcall

Stdcall Win32 API에서 사용되며, Callee에서 스택을 정리하는 특징을 가진다. C언어는 기본적으로 cdecl 방식이라고 했는데, stdcall 방식으로 컴파일 하고 싶을때는 int _stdcall name(int a)와 같이 함수명 앞에 ‘_stdcall’ 키워드를 붙여주면 된다.


401010 메모리 주소에 있는 것이 main함수이다. 위에서 cdecl 방식이 main에서 스택을 정리했었는데, 여기서는 ESP 8을 더하는 명령이 생략되고 401000에 있는 함수에서 RETN 8(RETN POP 8바이트 라는 뜻이다) 명령으로 8바이트만큼 ESP를 증가시키고 있다. 이와 같이 Callee가 스택을 정리해주는 방식이 stdcall 방식이다.

Stdcall 방식은 피호출자 함수 내부에 스택 정리 코드가 존재하므로 함수를 호출할 때마다 ADD ESP, @ 명령을 써줘야 하는 cdecl 방식에 비해 코드의 크기가 작아진다. Win32 API C언어로 된 라이브러리이지만 기본 cdecl 방식이 아닌 stdcall 방식을 사용한다. 이는 C 이외의 다른 언어에서 API를 직접 호출할 때 호환성을 좋게 하기 위한 것이다.

 

Fastcall

기본적으로 stdcall 방식과 같지만 함수에 전달하는 파라미터의 일부(2개까지, 일반적으로 파라미터 맨 앞의 2)를 스택 메모리가 아닌 ECX, EDX 레지스터를 이용해 전달한다. 이는 좀 더 빠른 함수 호출을 가능하게 하지만 ECX, EDX 레지스터를 관리하는 추가적인 오버헤드가 필요할 수 있다. 함수 호출 전에 ECX, EDX에 중요한 값이 저장되어 있다면 백업을 해야한다. 또한 함수의 내용이 복잡하면 ECX EDX 레지스터를 다른 용도로 사용해야 할 필요가 있는데, 이럴 때도 다른 어딘가에 백업을 해야한다.

'Reversing > Reversing' 카테고리의 다른 글

Lena's Reversing for Newbies #1  (0) 2017.07.03
Abex’ Crackme #2  (0) 2017.06.30
Stack Frame  (0) 2017.06.27
Abex' Crackme #1  (0) 2017.06.27
OllyDbg Commands & Assembly Basic & etc  (0) 2017.06.27
Posted by BinZIP
Reversing/Reversing2017. 6. 30. 03:56

Abex’ Crackme #2

이번 crackme Visual Basic으로 작성되었다고 한다. VB MSVBVM60.dll이라는 VB 전용 엔진을 사용하며, 사용 예를 들어보면 메시지 박스를 출력할 때 MsgBox라는 함수를 호출해 그 함수 안에서 Win32 API user32.dll MessageBoxW 함수를 호출하는 형식이다. 물론 소스코드 내에서 user32.dll MessageBoxW 함수를 직접 호출하는 것도 가능하다.

VB는 주로 GUI 프로그래밍을 할 때 사용되며, IDE 인터페이스 자체도 GUI 프로그래밍에 최적화 되어있다. 이는 즉 VB 프로그램이 Windows 운영체제의 Event Driven 방식(사용자의 명령·마우스 클릭·다른 프로그램의 메시지·키보드 글쇠 입력 등의사건에 따라제어 흐름이 결정되어 일을 하도록 하게끔 만들어진 방식)으로 동작하기 때문에, main 혹은 WinMain에 사용자 코드가 존재하는 것이 아니라 각 event handler에 사용자의 코드가 존재한다.

 

먼저 실행해서 어떤 프로그램인지 살펴본다.

그냥 뭐라도 입력해보면 다음과 같은 창이 뜬다.

틀렸다고 한다. 이름과 시리얼에 어떤 관계가 있는건지, 혹은 그냥 시리얼만 맞으면 되는건진 모르겠지만 어쨌든 맞으면 뭔가 다른 것을 뱉어주는 것 같다.

OllyDbg에 올려서 한 번 살펴보자. 먼저 우리한테 틀렸다고 알려주는 저 메시지 박스에 나오는 내용을 Search for All Strings에서 찾아 더블클릭해서 찾아가보자.

저기서 찾아 들어가보면 다음과 같은 코드를 볼 수 있다.

맞췄다고 보여주는 메시지 박스의 내용을 띄우는 코드인 것 같이 보이는 부분이 있다. vbaVarTstEq라는 함수의 결과값이 저장된 AX 0인지 아닌지 판단하여, JE문을 통해 AX 0일 경우 틀렸다는 메시지 박스의 내용을 띄우는 것 같다. 그렇다면 AX를 리턴으로 돌려주는 vbaVarTstEq라는 이름의 함수가 입력한 시리얼이 맞는지 틀린지 판단하는 것 같다는 것을 유추할 수 있다.

저 빨간색 박스 안을 잘 살펴보면 함수의 파라미터처럼 전달해주는 형태가 보인다. LEA 명령은 좌변(무조건 레지스터)에 우변의 주솟값을 대입하는 것이다. MOV와 비슷하지만 좌변에 우변(상수도 가능)의 값을 대입하는 MOV와는 주솟값을 대입한다는 점에서 약간 다르다. EDX EAX EBP-44 주소에 있는 값, EBP-34 주소에 있는 값을 각각 대입하고 있는데, 저것이 인자로 넘겨준 string이 아닐까 해서 스택에서 EBP 주변을 살펴보았다.

아니나 다를까, 입력했던 ‘BinZIP’ 문자열과 시리얼로 입력했던 ‘supernice’, 그 근처에 뭔가 알 수 없는 문자열이 있다. 위치상으로 저 문자열이 아마 시리얼인 것 같다. 옮겨 적고 입력해보았다.

그런데, 그냥 시리얼을 알아냈으니 다른 이름으로 해도 인증이 될까?

그냥 인증이 된다. 진짜 시리얼만 알아내도 되는건가 싶어서 그냥 한 글자를 덧붙이는 것이 아니라 문자열을 좀 많이(?) 바꾸어 넣어보았다.

아니나 다를까, Serial은 항상 똑같은 것이 아니었다. 그렇다는 것은 Name에 따라서 시리얼을 생성하는 함수가 있다는 얘기다. VB는 위에서 언급한 것처럼 프로그램 자체에 사용자의 코드가 있는 것이 아니라 이벤트가 일어남에 따라서 실행되는 Event Handler 안에 사용자의 코드가 존재한다. 그렇다는 것은 시리얼을 생성하는 함수는 Check 버튼을 누르는 Event에 실행되는 Event Handler 안에 사용자의 코드가 존재한다는 것이다. 또한 시리얼을 생성한 후 사용자가 입력한 시리얼과 비교해서 맞는지 틀렸는지 확인해 메시지 박스로 띄우는 함수 또한 이 Event Handler 안에 들어있을 것이다. 이는 우리가 찾았던 문자열을 비교하는 함수 위쪽에 시리얼을 생성하는 함수가 존재한다는 것으로 자연스럽게 유추된다.

아까 찾았던 코드에서 더 위로 올라가보자. 뭔가 익숙한 코드를 찾을 수 있다.

PUSH EBP MOV EBP ESP는 이전 Stack Frame을 공부할 때 함수의 시작에서 일반적으로 보이는 형태라고 공부 했었다. 이는 즉 여기가 함수의 시작이라는 것을 알 수 있게 해준다.

그러면 Serial Key를 생성하는 함수는 도대체 어떤 형태로 동작할까? Name에 따라서 Serial이 달라진다는 것을 확인했으니 아마도, 문자열을 받아와서 루프를 돌며 암호화를 한다던지 하는 행동을 할 것이다. 그럼 Name으로 입력해준 문자열을 읽는 부분을 찾아보기 위해, CALL 명령어와 ESI에 유의하며 코드를 살펴본다.

저 부분을 잘 보면 함수의 로컬 객체 SS:[EBP-88]의 주소를 파라미터로 전달하고 있다.

이렇게 도착한 곳에 무엇이 있는지 이렇게 봐서는 확인하기 어려우니, Long – Address with ASCII Dump 메뉴를 활용해 확인해본다.

위와 같이 BeenZIP이라는 Name에 입력했던 문자열이 정확히 EBP-88의 위치에 들어가 있는 것을 확인할 수 있다.

이제 반복문을 찾을 차례이다. EBX에 유의하며 내려가다 보면 아래와 같은 코드를 볼 수 있다.

위의 코드는 아래쪽 문자열을 보니 4글자 이상 입력하는지 체크하는 반복문인 것 같다. 더 내려가본다.

오른쪽에 디버거가 달아놓은 Comment를 참고해서 유추해보면 vbaVarForInit vbaVarForNext 사이에서 반복횟수만큼 한 글자씩 확인해가는 것 같다. EBX의 크기가 4이므로 4번째 글자까지 가져와서 확인하는 것 같은데, 그렇다면 아까 Name BinZIP BinZIPP을 넣었을 때 Serial이 같았던 것이 이해가 된다. 아마 이곳에서 Serial이 생성되는 것 같다.

그럼 이제 디버깅을 하면서 스택을 확인해보자.

레지스터에서도 EAX, ECX, EDX에 있는 주소를 확인할 수 있고 스택에 차례로 쌓여있는 것을 아래와 같이 확인할 수 있다. 각각의 주소로 찾아가보자.

EAX가 가리키는 주소의 값은 2, ECX가 가리키는 주소의 값은 0, EDX가 가리키는 주소의 값은 2이다. 무슨 의미인지 잘 모르겠지만, Step Over를 하면서 스택 창을 유심히 보자.

코멘트를 보면 UNICODE A6이 생성된 것을 볼 수 있다. 아까 우리는 한 글자씩 떼어와서 어떤 암호화 과정을 거칠 것이라고 유추했었다. 그렇다면, 우리가 Name에 입력했던 ‘Beenzip’의 맨 처음 글자 ‘B’, A6은 무슨 관계가 있는걸까? 일단 다음 글자도 보기 위해 한번 더 루프문을 돌려보자.

두 번째로 생긴 문자는 C9이다. 그리고 우리가 입력한 문자열에서 두 번째 글자는 ‘e’이다. 한번 더 돌려보겠다.

세 번째로 생긴 문자 역시 C9이다. 우리가 입력한 문자열의 세 번째 문자 역시 ‘e’였다. 그리고 이 과정을 하는 중에, 18F424주소에서 아까 생성했던 유니코드 문자열을 이어 붙이고 있는 것을 확인했다. 같은 문자를 암호화시키면 같은 결과가 나오는 것을 확인 했으니, B e에서 각각 A6, C9가 튀어나왔다는 것에서 규칙을 찾아봐야 한다.

일단 B ASCII 코드 문자 번호는 십진법으로 66, 십육진법으로 42이다. 그리고 e의 번호는 각각 101, 65이다. 일반적으로 한 글자씩 가져와서 암호화하는 방법에는 이 번호에 더하거나 빼거나 곱하거나 나누는 연산을 주로 할 것이라고 예상해서 먼저 결과값에서 각각 이 번호를 빼봤다.

A6-42=64, C9-65=64. 이는 즉 암호화 하는 방법이 한 글자를 가져와 그 아스키 코드 값에 64를 더한 값을 UNICODE4번 이어 붙이는 것임을 알 수 있다.

이렇게 유추한 바로 BeenZIP이라는 문자열의 Serial 값은 A6C9C9D2일 것이라고 예상할 수 있다. 루프문을 모두 돌리고 메모리 상 18F424주소를 확인해보자.

유추한 내용이 맞았음을 알 수 있다. 그럼 마지막으로 얻어낸 문자열을 Serial에 넣고 확인해보자.

암호화 알고리즘은 Name에 입력받은 String에서 앞의 4글자를 가져와 그 ASCII 코드 값에 64를 더한 값을 UNICODE로 이어붙인 것이었음을 확인할 수 있었다.

'Reversing > Reversing' 카테고리의 다른 글

Lena's Reversing for Newbies #1  (0) 2017.07.03
Calling Convention (함수 호출 규약)  (0) 2017.07.02
Stack Frame  (0) 2017.06.27
Abex' Crackme #1  (0) 2017.06.27
OllyDbg Commands & Assembly Basic & etc  (0) 2017.06.27
Posted by BinZIP
Reversing/Reversing2017. 6. 27. 17:45
Stack Frame

스택 프레임은 ESP(스택 포인터)가 아닌 EBP(베이스 포인터) 레지스터를 사용하여 스택 내의 로컬 변수, 파라미터, 복귀 주소에 접근하는 기법을 말한다.

ESP 레지스터의 값은 프로그램 안에서 수시로 변경되기 때문에 스택에 저장된 변수, 파라미터에 접근하고자 할 때 ESP 값을 기준으로 하면 프로그램을 만들기 힘들고, CPU가 정확한 위치를 참고하고자 할 때 어려움이 있다. 따라서 어떤 기준 시점(함수 시작) ESP 값을 EBP에 저장하고, 이를 함수 내에서 유지해주면, ESP 값이 아무리 변해도 EBP를 기준으로 안전하게 해당 함수의 변수, 파라미터, 복귀 주소에 접근할 수 있다. 이것이 EBP 레지스터의 베이스 포인터 역할이다.

스택 프레임을 어셈블리 코드로 보면 이런 형식이다.

이렇게 스택 프레임을 이용하면 함수가 아무리 길어도 스택을 완벽하게 관리할 수 있다.

 

※ 최신 컴파일러는 최적화 옵션을 가지고 있어서 간단한 함수 같은 경우에 스택 프레임을 생성하지 않는다.

※ 스택에 복귀 주소가 저장된다는 점이 보안 취약점으로 작용할 수 있다. BOF(Buffer Overflow) 기법을 사용하여 복귀 주소가 저장된 스택 메모리를 의도적으로 다른 값으로 변경할 수 있다.

 

 

StackFrame을 알아보기 위해 다음과 같은 코드로 릴리즈한다. 참고에서 말했듯이 최신 컴파일러는 최적화 옵션을 가지고 있기 때문에 다음 코드에서 스택 프레임을 확인하기 위해서는 해당 옵션을 꺼야 한다.

릴리즈 한 파일을 OllyDbg에 올려본다. 올리고 나서 Main 함수를 호출하는 부분까지 Step Into Step Over를 적절히 활용하여 찾아간다.

찾았으니 Enter를 입력해 main 함수를 확인한다.

함수 안에서 add함수와 printf함수를 호출하는 것을 볼 수 있다. 그리고 main 함수의 시작과 동시에 우리가 위에서 확인했던 Stack Frame의 기본 형태, PUSH EBP 후에 MOV EBP, ESP를 확인할 수 있다. 이후 main 함수가 RETN 되기 전에도 MOV ESP, EBP 후에 POP EBP가 있는 것으로 보아 Stack Frame을 사용하는 것을 직접 확인했다.

여기서 PUSH는 값을 집어넣는 명령이다. 그래서 PUSH EBPEBP의 값을 스택에 집어 넣으라는 의미이다. MOV는 데이터를 옮기는 명령이다. MOV EBP, ESP라는 명령은 해석하면 ESP의 값을 EBP에 옮기라는 의미이다. 이 때 ESP의 값은 변하지 않는다.

SUB ESP, 8 명령에서 SUB는 빼기 명령이다. , ESP에서 8만큼 빼라는 뜻이다. 이후 DWORD PTR SS: [EBP-8]과 같은 것이 보이는데, 그냥 포인터 같은 개념이라고 생각하면 편하다. DWORD형 타입을 가지는 EBP-8의 주소에 있는 값에 1을 저장하겠다는 뜻이다. , C언어로 표현하자면 *(EBP-8)=1이다.

@ 스택 창에서 우클릭하여 다음과 같은 옵션을 선택해주면 EBP의 현재 위치에 대한 상대적인 주솟값으로 스택 창을 확인할 수 있다. , EBP가 어떤 값을 스택에 저장했는지 스택 창에서 직관적으로 볼 수 있다.

계속해서 add 함수로 가보자.

Add 함수는 파라미터로 a b를 받는다. Main 함수에서 두 번째 파라미터의 값과 첫 번째 파라미터의 값을 순서대로 스택에 넣는다. 그리고 add 함수를 호출해서 복귀할 주소를 스택에 저장한다.

그 후 add 함수에서는 main 함수에서 변수를 두 개 만들 때와 동일하게 EBP에서 8을 빼준다. EBP-4의 주솟값에 EBP+8에 들어있는 값을 넣어주고, EBP-8의 값에 EBP+4에 들어있는 값을 넣어준다. 이후 EAX EBP-4에 들어있는 값을 넣고 EBP-8에 있는 값을 더해준 다음 함수를 빠져나온다.

Step Into로 하나씩 실행시키면 스택과 레지스터 에서 위 과정을 확인할 수 있다. Add 함수 내용을 모두 실행한 후 스택은 다음과 같다.

함수를 빠져나온 후 스택에 저장해 놓았던 돌아올 위치로 돌아간다. 이후에 main함수에서 ADD ESP, 8 명령을 볼 수 있는데, add 함수에게 넘겨주었던 파라미터 a b를 정리해주는 것이다. (A b long타입으로 각각 4바이트로 합하여 8바이트이다.) 더 이상 필요하지 않기 때문이다.

 

※ 이와 같이 함수를 호출한 쪽(Caller)에서 스택에 저장된 파라미터를 정리하는 것을 ‘cdecl’ 방식이라고 한다. 반대로 호출당한 쪽(Callee)에서 스택에 저장된 파라미터를 정리하는 것을 ‘stdcall’ 방식이라고 한다. 여기서는 main 함수가 add를 호출하고, main 함수에서 add의 파라미터를 정리하고 있으니 cdecl 방식이다. 이러한 함수 호출 규약을 일컬어 Calling Convention이라고 한다.

 

이후 스택은 다음과 같아진다.

그런 다음 add 함수의 리턴 값이었던 EAX“%d\n”을 스택에 저장하고 printf를 호출한다. 호출한 이후는 아까 add 함수 때와 마찬가지로 파라미터로 넘겨준다.

이렇게 printf 함수가 끝난 후에 다시 파라미터를 main에서 정리해준다. main함수의 리턴 값을 XOR EAX, EAX 명령으로 0을 만들어준 뒤 스택 프레임을 해제(MOV ESP, EBP; POP EBP;)한다.

Main 함수가 종료된 후에는 컴파일러의 Stub Code 영역으로 다시 돌아간다. 프로세스 종료 코드가 실행되는 것이다.

 

※ 다음과 같이 옵션[Alt + O] Analysis 1 탭에서 ‘Show ARGs and LOCALs in procedures’ 옵션을 체크하면 DWORD PTR SS:[EBP-4]와 같은 로컬 변수와 파라미터들을 가독성 좋게 보이도록 한다.

 

 

 

'Reversing > Reversing' 카테고리의 다른 글

Calling Convention (함수 호출 규약)  (0) 2017.07.02
Abex’ Crackme #2  (0) 2017.06.30
Abex' Crackme #1  (0) 2017.06.27
OllyDbg Commands & Assembly Basic & etc  (0) 2017.06.27
Hello, World! 디버깅하기  (0) 2017.06.26
Posted by BinZIP
Reversing/Reversing2017. 6. 27. 03:55

Abex’ crackme #1 분석

Crackme란 말 그대로 크랙 연습을 목적으로 작성되어 공개된 프로그램이다. 이런 Crackme들을 분석해보면 디버거와 디스어셈 코드에 익숙해질 수 있다. 그 중 Abex’ crackme는 매우 간단해서 초보자에게 알맞고, 아주 유명하기 때문에 풀이를 설명한 사이트가 매우 많다. , 자신의 풀이와 다른 사람의 풀이를 비교할 수 있다는 뜻이다.

 

디버깅을 시작하기 전에 파일을 실행시켜서 어떤 프로그램인지 확인한다.

HardDisk(HD) CD롬인 것처럼 인식시키라는 의미인 것 같다. 그냥 확인을 눌렀더니 다음과 같은 창이 떴다.

그러니까, CD롬인 것처럼 인식시키면 무언가 뜰 것 같다. OllyDbg에 로드하고 ‘Make me think your HD is a CD-Rom’ 문장이 보이는 곳까지 Step Into한다.

오른쪽에 디버거가 달아놓은 코멘트를 보면 아까 우리가 봤던 메시지들이 보인다. ‘C:\’를 스택에 PUSH 해준 후 GetDriveTypeA라는 함수를 부른다. 그 후 ESI EAX를 조금씩 조작해주고 두 레지스터 안에 있는 값을 비교해 같으면 아래로 점프, 다르면 그대로 에러 메시지를 출력하는 것을 볼 수 있다.

Step Into Step Over를 적절히 활용해주면서 레지스터의 값을 확인하면 Comment의 내용을 확인할 수 있다.

(위 사진과 비교해 달라진 Comment는 필자가 직접 달아준 것이다.)

여기서 우리가 JE문을 어떻게 우회할 지 생각해보자면, JE문을 아예 JMP문으로 바꿔버리거나, 혹은 CMP문이 나오기 전에 ESI의 값과 EAX의 값을 같도록 CMP 전에 EAX ESI의 값을 조작하는 명령을 생략해주면 된다.

먼저 JE문을 JMP문을 바꾸는 방법부터 해본다. Space JE문을 JMP로 바꿔준다.

그 후 Step Into를 실행해주면 다음과 같이 넘어가서 실행되는 것을 볼 수 있다.

두 번째 방법을 실행하기 위해서는 EAX 값을 DEC하는 명령을 생략하거나 ESI 값을 INC하는 명령을 생략한다. 이를 위해서 JMP문을 활용한다. 예시로 EAX 값을 DEC 하는 명령을 JMP로 생략해보았다.

생략하는 과정에서 일부 코드가 뭉개지는 것을 볼 수 있었는데, 적절히 수정하면 위와 같았다. 이렇게 수정한 것에 대해서도 JE를 수정했던 것과 같은 결과를 얻을 수 있었다.

이 과정에서 INC DEC에 들어가는 ESI EAX를 적절히 바꿔줘도 되겠다고 생각이 들었다. 그래서 다시 한 번 시도해보았다.

이렇게 고쳐주어도 결과는 옳게 출력되었다.

 

'Reversing > Reversing' 카테고리의 다른 글

Calling Convention (함수 호출 규약)  (0) 2017.07.02
Abex’ Crackme #2  (0) 2017.06.30
Stack Frame  (0) 2017.06.27
OllyDbg Commands & Assembly Basic & etc  (0) 2017.06.27
Hello, World! 디버깅하기  (0) 2017.06.26
Posted by BinZIP
Reversing/Reversing2017. 6. 27. 03:47

OllyDbg 기본 명령어

 

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

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

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

Execute till Return [Ctrl+F9]: 함수 코드 내에서 RETN 명령어까지 실행(함수 탈출)

Execute till Cursor [F4]: 커서의 위치까지 실행 (디버깅 하고 싶은 위치까지 바로 갈 수 있음)

 

※ OllyDbg를 켜면 동작중인 디버거가 발견되었습니다. 디버거를 종료한 후 다시 실행하시기 바랍니다라는 경고문구가 뜨는 경우가 있는데, 제어판에서 Delfino를 언인스톨해주면 정상적으로 OllyDbg를 사용할 수 있다.

 

 

OllyDbg 디버거 동작 명령

 

Go to [Ctrl+G]: 원하는 주소로 이동(코드/메모리를 확인할 때 사용, 실행은 되지 않음)

Execute till Cursor [F4]: cursor 위치까지 실행(디버깅하고 싶은 주소까지 바로 갈 수 있음)

Comment [;]: 코멘트 추가

User-defined Comment: 마우스 우측 메뉴 Search for User-defined Label

Set/Reset Breakpoint [F2]: BP설정/해제

Run [F9]: 실행(BreakPoint가 걸려있으면 그 지점에서 실행이 정지)

Show the Current EIP [*]: 현재 EIP의 위치를 보여줌

Show the previous Cursor [-]: 직전 커서의 위치를 다시 보여줌

Preview CALL/JMP Adress [Enter]: 커서가 CALL/JMP등의 명령어에 위치해 있다면, 해당 주소를 따라가서 보여줌(실행되지 않음, 간단히 함수의 내용을 확인할 때 유용)

Edit Data [Ctrl+E]: 데이터 편집

Assemble [Space]: 어셈블리 코드 작성 및 수정


 

 

Assembly 기초 명령어

 

CALL _ADDR_: ADDR 주소의 함수를 호출

JMP _ADDR_: ADDR 주소로 점프

PUSH _VALUE_: 스택에 VALUE 저장

RETN: 스택에 저장된 복귀 주소로 점프

INC _REG_: 해당 레지스터(REG)에 값을 1 증가

DEC _REG_: 해당 레지스터에 값을 1 감소

JMP SHORT _ADDR_: ADDR 주소로 점프(짧은 거리, JMP와 다른점은 추후 서술)

CMP A B: A와 B의 값을 비교

JE _ADDR_: Jump if Equal. 직전 CMP문에서 같으면 ADDR 주소로 점프, 아니면 그대로 진행

 

 

용어

 

VA: Virtual Address, 프로세스의 가상 메모리

OP Code: Operation Code, CPU 명령어. Byte Code라고도 함.

PE: Portable Executable, Windows 실행파일(exe, dll, sys )

 

'Reversing > Reversing' 카테고리의 다른 글

Calling Convention (함수 호출 규약)  (0) 2017.07.02
Abex’ Crackme #2  (0) 2017.06.30
Stack Frame  (0) 2017.06.27
Abex' Crackme #1  (0) 2017.06.27
Hello, World! 디버깅하기  (0) 2017.06.26
Posted by BinZIP
Reversing/Etc2017. 6. 27. 01:27

스택 (Stack)

프로세스에서 스택의 역할은 다음과 같다.

1.     함수 내의 로컬 변수 임시 저장

2.     함수 호출 시 파라미터 전달

3.     복귀 주소 저장

위와 같은 역할을 수행하기에 스택의 FILO(First In Last Out) 구조가 아주 유용하다.

 

스택의 특징

프로세스에서 스택 포인터(ESP)의 초기값은 Stack Bottom, 즉 스택의 가장 아래쪽에 가깝다. PUSH 명령에 의해서 스택에 값이 추가되면 스택 포인터는 Stack Top, 즉 스택의 위쪽으로 움직이고, POP 명령에 의해 스택에 값이 빠지면 스택 포인터는 Stack Bottom을 향해 움직인다. , “스택은 거꾸로 자란다.

 

실제로 스택이 어떻게 동작하는지 이해를 돕기 위해 OllyDbg를 이용해 간단한 예제를 살펴보자.

다음은 stack.exe OllyDbg에 로드한 초기 모습이다.

스택 포인터(ESP)의 값은 18FF8C이다. 스택 창을 보면 ESp가 가리키는 주소와 그 값을 보여준다.

Step Into PUSH 100 명령을 실행시키면 다음과 같은 결과를 볼 수 있다.

ESP의 값이 18FF88로 변해 4바이트만큼 줄어들었다. , 명령을 실행했더니 ESP가 메모리의 상단으로 이동하고 있다는 것을 확인했다. 그리고 ESP가 가리키는 주소에 들어있는 값을 확인해보면 100이 저장되어 있는 것을 알 수 있다. 다시 한 번 Step Into를 실행해본다.

ESP의 값은 4바이트 증가하여 12FF8C, 스택은 초기 상태와 똑같아졌다. , 스택에서 값을 꺼냈더니 ESP는 아래 방향으로 이동했다.

이를 통해, ‘스택에 값을 입력하면 ESP(스택 포인터)는 감소하고, 스택에서 값을 꺼내면 ESP는 증가한다를 확인할 수 있었다.

 

'Reversing > Etc' 카테고리의 다른 글

Data Recording & Register Basic  (0) 2017.06.26
Posted by BinZIP
Reversing/Etc2017. 6. 26. 23:33
Little Endian & Big Endian

바이트 오더링은 데이터를 저장하는 방식을 말한다. 크게 빅 엔디언(Bin Endian)과 리틀 엔디언(Little Endian)으로 나눌 수 있다. 리틀 엔디언은 데이터를 뒤부터 순차적으로 기록하고, 빅 엔디언은 앞에부터 순차적으로 기록한다. 아래의 예를 보자.

BYTE b = 0x12;

WORD w = 0x1234;

DWORD dw = 0x12345678;

char str[] = “abcde”;

 

TYPE

NAME

SIZE

Big Endian

Little Endian

BYTE

b

1

12

12

WORD

w

2

12 34

34 12

DWORD

dw

4

12 34 56 78

78 56 34 12

char []

str

6

61 62 63 64 65 00

65 64 63 62 61 00

위의 표에서 알 수 있듯이, 빅 엔디언은 데이터를 앞부터 순차적으로 저장한다. 그에 반해, 리틀 엔디언은 데이터를 뒤부터 순차적으로 저장한다.

빅 엔디언은 데이터를 순서대로 저장하기 때문에 사람이 보기에 직관적이라는 장점이 있다. 대형 UNIX 서버에 사용되는 RISC 계열의 CPU에서 많이 사용되며, 네트워크 프로토콜에도 사용된다.

리틀 엔디언은 Intel x86 Processor에 사용된다. 데이터를 역순으로 저장시키는 것은 산술 연산과 데이터의 타입이 확장/축소될 때 더 효율적이라는 장점을 가지고 있다.

 

IA-32 Register Basic

레지스터(Register) CPU 내부에 존재하는 다목적 저장 공간이다. CPU 내부에 존재하기 때문에 물리적으로 가까이 위치하고 있어 데이터 처리를 고속으로 할 수 있다.

우리가 레지스터를 알아야 하는 이유는 디버거가 디스어셈블 해준 결과물인 어셈블리어의 대부분이 레지스터를 조작하고 확인하는 명령어들이기 때문이다. 레지스터를 모르면 명령어 자체도 이해하기 힘들다.

 

Intel Architecture 32bit(IA-32) Register

IA-32는 지원하는 기능도 많고 그만큼 레지스터 수도 많다. 많은 레지스터들 중에서 우리가 이번에 알아볼 레지스터는 ‘Basic Program Execution Registers’이다.

Basic Program Execution Registers는 다시 4개의 그룹으로 나누어져 있다.

General Purpose Registers (32bit, 8)

Segment Registers (16bit, 6)

Program Status and Control Register (32bit, 1)

Instruction Pointer (32bit, 1)

 

 

General Purpose Registers (범용 레지스터)

범용 레지스터는 이름처럼 범용적으로 사용되는 레지스터들이다. 각 레지스터의 크기는 32비트이고, 보통은 상수/주소 등을 저장할 때 주로 사용되며, 특정 어셈블리 명령어에서는 특정 레지스터를 조작하기도 한다. 또한, 어떤 레지스터들은 특수한 용도로 사용되기도 한다.

 

위 사진은 범용 레지스터이다. 각 레지스터들은 16비트 하위 호환을 위하여 몇 개의 구획으로 나누어진다. EAX를 기준으로 설명하면 다음과 같다.

 

EAX: 0~31까지 구간의 32비트

AX: 0~15까지 구간의 EAX 하위 16비트

AH: 8~15까지 구간의 AX 상위 8비트

AL: 0~7까지 구간의 AX 하위 8비트

 

, 4바이트를 다 사용하고 싶을 때는 EAX를 사용하고, 2바이트만 사용할 때는 AX를 사용하면 된다. 1바이트만 사용하고 싶을 때는 AH 혹은 AL을 사용한다. 이렇게 상황에 따라 8비트, 16비트, 32비트로 유연하게 사용할 수 있다.

각 레지스터의 이름은 다음과 같다.

 

EAX: Accumulator for operands and results data

EBX: Pointer to data in the DS segment

ECX: Counter for string and loop operations

EDX: I/O pointer

 

위의 레지스터들을 주로 산술연산(ADD, SUB, XOR, OR ) 명령어에서 상수/변수 값의 저장 용도로 많이 사용된다. 어떤 어셈블리 명령어(MUL, DIV, LODS )들은 특정 레지스터를 직접 조작하기도 한다. 이런 명령어가 실행된 이후에 특정 레지스터들의 값이 변경된다.

그리고 추가적으로, ECX EAX는 특수한 용도로 사용된다. ECX는 반복문 명령어(LOOP)에서 반복 카운트(loop count)로 사용된다. 루프를 돌 때마다 ECX의 값을 1씩 감소시키는 형태로 동작한다. EAX는 일반적으로 함수의 리턴 값에 사용된다. 모든 Win32 API 함수들은 리턴 값을 EAX에 저장한 후 리턴한다.

 

참고: Win32 API 함수들은 내부적으로 ECX EDX를 사용하기 때문에 호출된 직후 ECX EDX의 값이 바뀐다. 따라서 ECX EDX에 중요한 값이 저장되어 있다면 API를 호출하기 전에 다른 레지스터나 스택에 백업해두어야 한다.

 

 

Other General Purpose Register

나머지 범용 레지스터들의 이름은 다음과 같다.

EBP: Pointer to data on the stack (in the SS segment)

ESI: source pointer for string operations

EDI: Destination pointer for string operations

ESP: Stack pointer (in the SS segment)

 

4개의 레지스터들은 주로 메모리 주소를 저장하는 포인터로 사용된다.

ESP는 스택 메모리 주소를 가리킨다. 어떤 명령어들(PUSH, POP, CALL, RET) ESP를 직접 조작하기도 한다. 스택 메모리 관리는 프로그램에서 매우 중요하기 때문에 ESP를 다른 용도로 사용하지 말아야 한다.

EBP는 함수가 호출되었을 때 그 순간의 ESP를 저장하고 있다가, 함수가 리턴하기 직전에 다시 ESP에 값을 되돌려줘서 스택이 깨지지 않도록 한다. (이런 방법을 Stack Frame 기법이라고 하며, 리버싱에서 중요한 개념이다.) ESI EDI는 특정 명령어들 (LODS, STOS, REP MOVS )과 함께 주로 메모리 복사에 사용된다.

 

Segment Register(세그먼트 레지스터)

세그먼트(Segment) IA-32의 메모리 관리 모델에서 나오는 용어이다.

IA-32 보호 모드에서 세그먼트란 메모리를 조각내어 각 조각마다 시작 주소, 범위, 접근 권한 등을 부여해서 메모리를 보호하는 기법을 말한다. 또한 세그먼트는 페이징(Paging) 기법과 함께 가상 메모리를 실제 물리 메모리로 변경할 때 사용된다. 세그먼트 메모리는 Segment Descriptor Table(SDT)이라고 하는 곳에 기술되어 있는데, 세그먼트 레지스터는 바로 이 SDT index를 가지고 있다.

각 세그먼트 레지스터의 이름은 아래와 같다.

 

CS: Code Segment

SS: Stack Segment

DS: Data Segment

ES: Extra(Data) Segment

FS: Data Segment

GS: Data Segment

 

이름 그대로 CS는 프로그램의 코드 세그먼트를 나타내며, SS는 스택 세그먼트, DS는 데이터 세그먼트를 나타낸다. ES, FS, GS 세그먼트는 추가적인 데이터 세그먼트이다.

FS 레지스터는 애플리케이션 디버깅에도 자주 등장하는데 SHE(Structured Exception Handling), TEB(Thread Environment Block), PEB(Process Environment Block) 등의 주소를 계산할 때 사용되며 고급 리버싱을 할 때 주제이다.

 

Program Status & Control Register

EFLAGS: Flag Register

플래그(Flag) 레지스터의 이름은 EFLAGS이며 32비트(4바이트) 크기이다. 다른 레지스터와 마찬가지로 예상할 수 있듯이, FLAGS 레지스터(16비트, 2바이트)의 확장 형태이다.

EFLAGS 레지스터는 각각의 비트마다 의미를 가지고 있다. 각 비트는 1 또는 0의 값을 가지는데, 이는 On/Off 혹은 True/False를 의미한다. 일부 비트는 시스템에서 직접 세팅하고, 일부 비트는 프로그램에서 사용된 명령의 수행 결과에 따라 세팅된다.

EFLAGS 레지스터의 32개의 각 비트 의미를 전부 이해한다는 것은 상당히 어려운 일이다. 리버싱 입문 단계에서는 Application Debuging에 필요한 3개의 flag(ZF, OF, CF)만 잘 이해하면 된다.

 

Zero Flag(ZF): 연산 명령 후에 결과 값이 0이 되면 ZF 1(True)로 세팅된다.

Overflow Flag(OF): 부호 있는 수(signed integer)의 오버플로우가 발생했을 때 1로 세팅된다. 그리고 MSB(Most Significant Bit)가 변경되었을 때도 1로 세팅된다.

Carry Flag(CF): 부호 없는 수(unsigned integer)의 오버플로우가 발생했을 때 1로 세팅된다.

 

 

Instruction Pointer

Instruction pointer CPU가 처리할 명령어의 주소를 나타내는 레지스터이다. EIP의 크기는 32비트이고, 마찬가지로 16비트인 IP 레지스터의 확장된 형태이다. CPU EIP에 저장된 메모리 주소의 명령어(instruction)를 하나 처리하고 자동으로 그 명령어의 길이만큼 EIP의 길이를 증가시킨다. 이런 식으로 계속 명령어를 처리한다.

범용 레지스터와는 다르게 EIP는 그 값을 직접 변경할 수 없도록 되어 있어서 다른 명령어를 통하여 간접적으로 변경해야 한다. EIP를 변경하고 싶을 때는 특정 명령어(JMP, JCC, CALL, RET)를 사용하거나 인터럽트, 예외를 발생시켜야 한다.

 

'Reversing > Etc' 카테고리의 다른 글

Stack  (0) 2017.06.27
Posted by BinZIP
Reversing/Reversing2017. 6. 26. 21:03

선수 지식

Stub Code: 컴파일러마다 필요한 코드를 추가해놓은 것. 코드 상으로는 “Hello World”라는 메시지를 출력하는 것만 있을지라도, 그 코드가 동작하기 위해서 필요한 부가적인 코드를 컴파일러가 추가해서 Release한다.

 

 

OllyDbg 기본 명령어

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

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

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

Execute till Return [Ctrl+F9]: 함수 코드 내에서 RETN 명령어까지 실행(함수 탈출)

 

※ OllyDbg를 켜면 동작중인 디버거가 발견되었습니다. 디버거를 종료한 후 다시 실행하시기 바랍니다라는 경고문구가 뜨는 경우가 있는데, 제어판에서 Delfino를 언인스톨해주면 정상적으로 OllyDbg를 사용할 수 있다.

 

 

Hello, World! 디버깅

Hello, World 프로그램의 소스코드는 다음과 같다.

릴리즈 하고 OllyDbg 위에 올려서 한 줄씩 따라간다. 오른쪽에 OllyDbg가 적어준 코멘트를 잘 확인하면서 굳이 들어갈 필요 없어보이는, 즉 컴파일러의 Stub Code로 보이는 부분들을 CALL하는 명령을 Step Over로 넘어가다 보면 아래와 같은 함수로 들어올 수 있다.

위 함수는 우리가 작성했던 Hello, World! 코드에 작성한 문자열이 맞으므로, 우리는 main함수를 제대로 찾아왔음을 알 수 있다.

 

※ 디버깅 도중 알 수 없는 명령이라 실행할 수 없다는 경고가 뜨는 경우가 있는데, [Option – Debugging Options - Security]에서 아래 ‘Allow stepping into unknown commands’에 체크해주면 정상적으로 디버깅이 가능하다.

 

 

BaseCamp 설치 및 원하는 코드 빨리 찾기

BaseCamp를 설치하는 방법에는 다음 네 가지가 있다.

1.     Goto 명령 사용 [Ctrl+G]

2.     BP(Break Point) 설치 [F2]

3.     주석 [;(세미콜론)]

4.     Label 달기 [:(콜론)]

OllyDbg는 디버깅할 프로그램을 처음 로딩할 때 사전 분석 과정을 거친다. 프로세스 메모리를 훑어서 참조되는 문자열과 호출되는 API를 뽑아내어 따로 목록으로 정리를 해놓는데, 이는 디버깅 과정에서 큰 도움이 된다.

위와 같이 Text String으로도 원하는 곳을 찾아갈 수 있다.

위에서 ‘All intermodular calls’를 선택하면 프로세스에 로드된 API 목록을 볼 수 있다.

하지만 Packer Protector를 사용해 실행파일을 압축하거나 보호해버리면 파일의 구조가 변경되어 API 호출 목록이 보이지 않게 된다. 이럴때는 프로세스 메모리에 로딩된 라이브러리(DLL 코드)에 직접 BP를 걸어준다.

[Alt+M]으로 메모리 맵을 열어보면 HelloWorld 프로세스 메모리의 일부분을 보여준다.

2번에 표시된 부분이 USER32 라이브러리가 로딩 되어있는 메모리 영역이다.

아래는 Name in All Modules 명령을 사용하는 예시이다.

Name in all modules 명령을 선택하고 ‘messageboxw’라고 입력하면 저렇게 USER32 라이브러리에서 로딩된 MessageBoxW라는 함수를 찾을 수 있다. 더블클릭하면 USER32.dll에 실제로 구현되어 있는 MessageBoxW 함수를 볼 수 있다.

위는 USER32.dll MessageBoxW 코드이다. 코드의 시작부에 BreakPoint를 걸어주고 다시 실행해보면 예상대로 표시해둔 BP에서 멈추는 것을 볼 수 있다.

이 때 레지스터의 ESP 값이 프로세스 스택의 주소이다.

 

 

문자열 패치

위와 같이 Main 함수의 시작에 BP를 걸어둔다.

 

1.     문자열 버퍼를 직접 수정

MessageBoxW 함수의 전달인자의 문자열인 “Hello, World!” 버퍼를 직접 수정하는 방법이다. 덤프 창에서 Goto[Ctrl+G]로 해당 주소로 가서 마우스로 선택한 후 [Ctrl+E] Edit 다이얼로그를 띄워 수정한다.

여기서 주의할 점은, “Hello, World!”보다 “Hello Reversing” 문자열이 더 긴데, 일반적으로 원본 문자열 뒤에 어떤 다른 데이터가 있을 수 있으니 원본 문자열 길이를 넘어가는 문자열로 덮어쓰는 것은 위험하다는 것이다.

명령어는 그대로이지만 전달하는 파라미터의 내용 자체가 변경된 것이고, 전달되는 파라미터의 주소 자체는 변경되지 않았다.

이어서 [F9]를 눌러 실행시켜보면 “Hello Reversing”으로 바뀐 것을 확인할 수 있다.

버퍼 내용을 직접 수정하면 간단하다는 장점이 있지만, 기존 문자열 버퍼 크기 이상의 문자를 입력하기 어렵다는 단점이 있다.

바뀐 내용으로 새로운 실행파일을 만들고 싶다면 Copy to Executive File 명령을 사용하면 된다.

 

2.     다른 메모리 영역에 새로운 문자열을 생성하여 전달

원본보다 더 긴 문자열로 패치해야 한다면, 버퍼 내용을 직접 수정하는 방법보다는 다른 메모리 영역에 새로운 문자열을 생성해서 그 주소를 전달하는 방법이 더욱 요긴하다.

위에서 “Hello, World!” 문자열이 있는 주소를 직접 넘겨주는 것을 확인했을 것이다. 그렇다는 것은, 넘겨주는 주소를 우리가 원하는 문자열이 있는 주소로 바꿔주게 되면, 우리가 원하는 문자열이 전달될 것이라고 생각할 수 있다.

아까 덤프 창에서 찾아갔던 주소를 다시 찾아가서 스크롤을 조금 아래로 내리면, 아래와 같이 NULL Padding 영역을 확인할 수 있다.

NULL Padding 영역은 프로그램이 사용하지 않는 메모리 영역이다. 이 부분에 우리가 원하는 문자열을 입력해주고, 문자열이 시작되는 메모리 주소를 아래와 같이 파라미터로 넘겨준다.

이렇게 어셈블한 후 실행시키면 다음과 같이 바뀐 것을 확인 할 수 있다.

그런데 여기서 주의할 점이 있다. 이렇게 수정한 코드는 아까 문자열 버퍼를 직접 수정했을 때처럼 Copy to Executable File 명령으로 새로운 .exe 파일을 생성했을 때 바뀐 문자열이 정상적으로 출력되지 않는다. 그 이유는 우리가 전달한 메모리 주소 때문이다.

파일이 메모리에 로딩되어 프로세스로 실행될 때 파일이 수정한 당시의 메모리 주소대로 메모리로 로딩되는 것이 아니라, 어떤 규칙에 의해서 올라가게 된다. 그 과정에서 프로세스 메모리는 존재하는데, 그에 해당하는 파일의 Offset이 존재하지 않는 경우가 많다. , 우리가 전달해준 메모리 주소에 해당하는 Offset이 존재하지 않는 것이다.

 

'Reversing > Reversing' 카테고리의 다른 글

Calling Convention (함수 호출 규약)  (0) 2017.07.02
Abex’ Crackme #2  (0) 2017.06.30
Stack Frame  (0) 2017.06.27
Abex' Crackme #1  (0) 2017.06.27
OllyDbg Commands & Assembly Basic & etc  (0) 2017.06.27
Posted by BinZIP