본문 바로가기
Study/Reversing

키젠문제 풀이

by Melpin 2015. 5. 18.

키젠 문제란 원본 문제파일은 수정하지 않은 상태에서 분석만을 통해서 키 값들을 계산하거나 조합하는 문제이다.

시리얼 키 문자열이 그대로 메모리상에 있어서 풀리는 문제도 있지만 이번에는 Key를 생성해내도록 하는것이 목표이다

키젠 문제를 풀기위해 어셈블리어를 꼼꼼하게 알아볼 필요가 있다.


먼저 함수를 선언할때, if문, for문이 어셈블리코드에서 어떤 형태를 보이는지 알아보자

fuc(1, 2) 라고 함수를 선언하게 된다면 두번째 파라미터를 넣고 그다음 첫번째 파라미터값을 넣은 뒤 call을 하게된다.

push 2

push 1

call 004017E0

두번째 값부터 넣는 이유는 메모리구조가 스택 형식이기 때문이다.

스택은 LIFO(Last In First Out)구조로 나중에 들어온 값이 먼저 나가기 때문이다.


일반적인 메모리 구조는 그림과 같다.

프로그램이 실행되기 위해서는 실행에 필요한 모든 것들이 메모리에 적재되어야만 한다.

텍스트영역 : 컴파일된 프로그램 소스가 기계어의 형태로 위치

데이터영역 : 프로그램에서 초기화된 데이터들이 위치

힙영역 : 동적으로 할당된 변수들이 위치

스택영역 : 지역변수 Command Line arguments 위치



Intel CPU에서는 EBP, ESP, SFP, EIP 등을 이용해 스택을 구현한다.

EBP : 현재까지 실행된 곳의 주소를 저장

ESP : 스택 포인터

SFP : 스택의 top을 가르킴

EIP : 다음으로 실행할 명령어의 주소를 가르킴


콜링컨벤션

스택을 이용하여 파라미터를 전달할 때 스택에 파라미터를 어떠한 순서로 넣을 것이며 또한 전달된 파라미터를 어느 곳에서 해제할 것인가를 결정하는 방식.

1. __cdcel

C 또는 C++ 프로그램에서 파라미터 전달시 디폴트로 사용

파라미터 전달은 오른쪽에서 왼쪽 방향으로 스택에 저장

파라미터 해제는 프로시저를 호출한 것으로 책임짐

프로시저 : 특정 작업을 수행하는  블록으로, 매개변수를 받을수도 있고 반복적으로 사용할 수도 있다.


2.__stdcall

Windows API 프로시저에서 사용

파라미터 전달은 __cdcel과 동일

파라미터 해제는 프로시저 복귀 전에 이루어짐

장점

① 함수 독립성이 뛰어남

② 프로시저를 부르기 전에 스택에 파라미터를 쌓아놓고 그 프로시저를 부르기만 하면 그 함수가 리턴된 후에는 그 프로시저의 스택 포인터가 이전 상태로 복원되었으므로 복귀된 후에 호출한 프로시저에 대하여 신경쓸 필요가 없음

③ __cdecl 방식의 콜링컨벤션에 비해 코드 크기가 줄어든다.

④ 스택을 해제하는 코드가 호출한 프로시저 안에 있으므로 이 프로시저가 여러 곳에서 호출된다 할지라도 스택 해제하는 코드는 프로시저 내에 하나만 존재함


3.__fastcall

처음 두 개까지의 파라미터는 스택을 사용하지 않고 ECX, EDX 레지스터를 사용함

그 이상의 파라미터에 대해서만 오른쪽에서 왼쪽으로 스택에 저장

스택 제거는 __stdcall과 동일


if조건문

if(result == 1)이라는 식은 다음과 같이 변경된다.

cmp dword ptr [ebp - 4], 1

jne 00401234

>> jne = jump if not equal >> ZF = 0

cmp로 비교를 한 후에 점프 구문으로 원하는지점으로 이동한다.


for반복문

for(int i = 1; i <= param; i++) 라는 식은 다음과 같다


mov dword ptr [ebs - 8] , 1 // i 변수

jmp 004017FF

mov eax, dword ptr [ebp - 1]

add eax, 1

mov dword ptr [ebp - 8], eax

mov ecx, dword ptr [ebp - 8] // ecx로 카운팅을 한다.

cmp ecx, dword ptr [ebo + 8] // 종료인지 체크

jg 00401812

>> jg = jump if (signed) greater >> ZF = 0 and SF = 0


반복을 하기위해 I 변수 값이 변경되고 반복하면서 카운팅을 하는 변수가 반복 횟수를 초과했는지를 cmp로 검사해서 반복 구문을 빠져나오게 된다.


Babylon keygenme 풀이

프로그램을 실행시켜보면 다음과 같은 화면을볼 수 있다.

이름을 입력하고

그다음 시리얼키를 입력하면 성공의 유무를 보여준다.

Mauvais mot de passe 라고 나오는대

프랑스어 이다. 번역기를 통해 번역해 보면

잘못된 패스워드 라는 뜻임을 알 수 있다.



Serch for > All referenced text strings 를 선택하여 문자열을 추출해보면

Mauvais mot de passe! 잘못된 패스워드 라는 문장과

Yeah, c'es bon. 라는 맞았을때 출력되는 문장이보인다.

이 문장을 더블클릭해 그 위치로 가보자



C언어로 제작된 문제이기 때문에 C언어에서 사용되는 함수명들이 보인다.

scanf, printf, strlen, system 등.



좀더 위로 스크롤 해보면 Name과 Serial 을 입력받는 곳이 보인다.

scanf %s 를 통해서 name을 받고 strlen으로 길이를 검사해 3 < nom < 15 일때만 jmp한다는 것을 볼 수 있다.

만약 아니라면 system으로 pause 명령을 실행하게 된다.



그다음 시리얼 값을 입력받은 부분부터 보면 


004014E0   MOV EAX,DWORD PTR SS:[EBP-18]

[EBP - 18] 에는 입력한 이름 test의 길이 4가 있고 그것을 EAX에 저장


004014E3   MOV EDX,EAX

EAX값을 EDX에도 저장


004014E5   LEA EAX,DWORD PTR DS:[EDX+EDX]

[EDX + EDX]는 EDX의 값을 곱하기 2를 하여 8이 된 값의 주소를 EAX 에 저장


004014E8   CMP DWORD PTR SS:[EBP-4],EAX

EAX와 [EBP - 4]를 비교해 for문을 언재까지 반복할지 결정한다.


004014EB   JL SHORT Babylon_.004014F0

위 비교 결과를 가지고 반복문에 들어갈것인지 아닌지를 결정


004014ED   JMP SHORT Babylon_.00401520

반복문이 완료되면 반복 구문 밖으로 점프해 반복을 종료한다.


004014EF   NOP

아무일도 안함.


004014F0   LEA EAX,DWORD PTR SS:[EBP-260]

[EBP - 260]의 주소를 EAX에 저장.


004014F6   MOV EDX,DWORD PTR SS:[EBP-4]

EDX = 0


004014F9   LEA ECX,DWORD PTR SS:[EBP-120]

이름의 주소를 ECX에 저장


004014FF   MOV EBX,DWORD PTR SS:[EBP-8]

EBX = 0


00401502   MOV CL,BYTE PTR DS:[EBX+ECX]

이름의 첫 번째 값의 아스키 코드를 CL에 저장, test의 t가 먼저 저장


00401505   MOV BYTE PTR DS:[EDX+EAX],CL

CL의 값을 [EDX + EAX]에 저장


00401508   MOV EAX,DWORD PTR SS:[EBP-4]

EAX = 0


0040150B   INC EAX

EAX = 1


0040150C   LEA EDX,DWORD PTR SS:[EBP-260]

[EBP - 260]의 주소를 EDX에 저장


00401512   MOV BYTE PTR DS:[EAX+EDX],20

20을  [EAX + EDX]에 넣는다.

16진수 20 > 0x20  = 10진수 32이고

32는 아스키 코드표에서 보면 SP 공백을 나타낸다.


00401516   INC DWORD PTR SS:[EBP-8]

[EBP - 8]의 값을 증가


00401519   ADD DWORD PTR SS:[EBP-4],2

[EBP - 4] 에 값 2를 더함


0040151D   JMP SHORT Babylon_.004014E0

다시 반복문을 진행하기위해 윗부분으로 점프. 


위의 부분을 통해 입력받은 이름의 길이를 판단한다.



첫번째 반복문 부분을 보면.

CMP DWORD PTR SS: [EBP - 4], EAX

JB SHORT Babylon_.00401543

JMP SHORT Babylon_.00401570

부분으로 반복횟수를 넘어가면 이 반복문을 넘어가도록 설정이 되어있다.

그리고 [EBP - 160] 에 "-[#]]=}&&&+(=$*,,)&.*/+++[][;/..?" 값이 들어간다.

[EBP - 160] 의 값을 증가시킨다. 그럼 그 결과로 다음과 같은 문자열이 된다.

".\$^^>~''',)>%+--*'/+0,,,\^\<0//?"


두번째 반복문은 아까 입력한 "t e s t " 와 [EBP - 160] 에 있는 문자열을 XOR 한뒤

다시 [EBP - 160] 에 넣는다.


세번째 반복문은 [EBP - 360] 에 [EBP - 160] 을 뒤집어서 넣는다

그리고 1, 2, 3 번째 문자열을 1?/ 로 변경한다.

[EBP - 360] 에 들어간 문자열은 다음과 같다.

"1?/0<\^\,,,0+/'*--+%>),''-~A|Z"


네번째 반복문은 [EBP - 460] 에 [EBP - 360]의 값과 [EBP - 160]값을 번갈아가며 넣는다.


다섯번째 반복문은 [EBP - 460]의 문자열중 Z보다 크거나 1F 보다 작으면 모두 6으로 바뀐다.


그리고 입력한 길이만큼 [EBP - 460] 만큼 잘라주면 된다.


t e s t 를 입력하면 시리얼 값은 1Z66이 나오게 된다.




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

nag 제거, PE 헤더  (0) 2015.05.21
KeyFile 체크 문제 풀이, 바이너리 수정  (0) 2015.05.19
Unpack, Back To User mode  (0) 2015.05.18
어셈블리 명령어  (0) 2015.05.15
CPU레지스터와 데이터타입  (0) 2015.05.15

댓글