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