맨위로

이 글은 코드엔진(http://codeengn.com/) 에서 제공하는 리버스 엔지니어링 관련 다양한 문제들을 풀어보면서 리버스 엔지니어링을 공부하는 목적으로 작성되었습니다. 이글을 작성하는 필자도 리버스 엔지니어링에 흥미를 가지고 공부하는 입장이므로 잘못된 내용이 있을수도 있습니다. 잘못된 내용이 있다면 댓글이나 방명록에 알려주세요 :)

Challenges : Basic 14 


Author : BENGALY

Korean : 

Name이 CodeEngn 일때 Serial을 구하시오 

(이 문제는 정답이 여러개 나올 수 있는 문제이며 5개의 숫자로 되어있는 정답을 찾아야함, bruteforce 필요) 

Ex) 11111 

English : 

Find the Serial when the Name of CodeEngn 

(This problem has several answers, and the answer should be a 5 digit number. Brute forcing is required.) 

Ex) 11111 

문제를 보아하니 시리얼값을 생성하는 알고리즘을 찾아서 분석하는게 문제를 푸는데 큰 열쇠가 될거같습니다. 문제를 통해서 얻을수 있는 약간의 정보는 이름에 따라서 시리얼이 바뀐다는것을 알수 있습니다. 

프로그램을 실행시켜보니 아래 그림1과 같은 형태를 가지고 있는 전형적인 인증프로그램입니다. 

<그림 1. 프로그램 실행>

특별히 살펴볼만한 것들은 없으니 바로 코드분석을 들어가보도록 하겠습니다. 먼저 분석하기전에 패킹의 여부를 확인해보도록 하겠습니다. PEiD를 통해서 확인한결과 UPX로 패킹되어있다는 것을 확인할수 있습니다. 

<그림 2. PEiD>

UPX 는 이전 문제에서 많이 언패킹 해본 방식이기 때문에 자동화툴을 이용해서 언패킹해줍니다. (UPX 언패킹 방법 http://stih.tistory.com/63 참조) 

<그림 3. UPX 언패킹>

이제 언패킹도 했으니 본격적으로 코드를 분석해보도록 하겠습니다. 

<그림 4. ollydbg>

어셈블리어로 작성되어서 그런지 코드가 깔끔한게 눈에 들어옵니다. 많은 양은 아니지만 하나하나 분석하기에는 너무 비효율적이므로 시리얼관련 핵심 코드부분으로 이동할 필요가 있습니다. 핵심 부분을 찾기위해서는 어떤 식으로 시리얼을 생성할것인가 예상해보면 쉽게 찾을수 있습니다.

일단 이 프로그램의 특징 중하나가 이름값을 입력받고 시리얼값을 생성하는점이니 시리얼값이 어느 한 주소에 저장되있지 않을것입니다. (Search For 로 확인) 

아마도 사용자 입력값을 가져와서 사용하는 방식이라는것을 예상 할수 있습니다. 이런 방식이라면 입력값을 가져오는 함수를 사용했을것입니다. 이 함수들에 BP를 설정하고 실행시켜보면 핵심 부분을 찾을수 있을것입니다.

일단 함수들을 확인해보도록 하겠습니다. Search For -> All intermodular calls 를 통해서 쉽게 확인할수 있습니다. 확인 결과 아래 그림 5 와 같이 GetDlgItem 관련 함수들이 있는것을 확인할수 있습니다.

<그림 5. Search For -> All intermodular calls 을 이용한 BP 설정>

그림 5와 같이 GetDlgItem 관련 함수들에 BP를 설정해주었으면 이제 프로그램을 실행시켜 시리얼 관련 부분으로 이동해보겠습니다.

처음으로 아래의 그림 6 과 같이 004010D4 에서 멈추는 것을 확인할수 있습니다. 하지만 프로그램은 아직 미완성적인 형태(입력 창, 버튼 …) 인것을 보니 사용자 입력값을 받아오기 위한 부분은 아닌거 같습니다. 

<그림 6. BP 설정 후 실행>

계속 실행을 해서 3번째 BP (00401032) 까지 실행해도 위의 그림 6과 같은 모습이니 더 진행해보겠습니다. F9를 통해서 진행해보니 아래의 그림 7과 같이 00401161 에서 멈추며 입력창이 나타나는것을 확인할수 있습니다. 값을 입력하고 계속 진행시키면 핵심 부분으로 이동할수 있을것입니다.

<그림 7. 00401161 >

name 창에 codeengn serial 창에 임의의 값 (12341234) 을 입력한후 Check 버튼을 눌러보겠습니다. 아래 그림과 같이 004012BD 에서 멈추는것을 확인할수 있습니다.

<그림 8. 004012BD>

코드들을 조금 분석해보면 아래와 같습니다.

 004012B1

 PUSH 40

 Count

 004012B3

 PUSH 403038

 Buffer (사용자가 입력한 name 값 403038에 저장)

 004012B8

 PUSH 6A

 Control ID

 004012BA

 PUSH DWORD PTR SS:[EBP+8]

 hWnd

 004012BD

 CALL 004013CA

 GetDlgItemTextA 함수 호출 (name 부분)

 004012C2

 CMP EAX,0

 EAX (글자수) 와 0 비교

 004012C5

 JE SHORT 004012DF

 EAX 값이 0 이면 004012DF 주소로 이동

 004012C7

 PUSH 40

 Count

 004012C9

 PUSH 403138

 Buffer (사용자가 입력한 serial 값 403138에 저장)

 004012CE

 PUSH 6B

 Control ID

 004012D0

 PUSH DWORD PTR SS:[EBP+8]

 hWnd

 004012D3

 CALL 004013CA

 GetDlgItemTextA 함수 호출

 004012D8

 CMP EAX,0

 EAX 값과 0 비교

 004012DB

 JE SHORT 004012DF

 EAX 값이 0 이면 004012DF 주소로 이동

 004012DD

 JMP SHORT 004012F6

 004012F6 으로 이동

 004012DF

 PUSH 0

 Style

 004012E1

 PUSH 403462

 Title

 004012E6

 PUSH 403000

 Text 

 004012EB

 PUSH 0

 hOwner

 004012ED

 CALL 004013EE

 MessageBoxA 함수 (입력 값이 없으면 출력하는 메시지)

코드들을 살펴보니 핵심부분에 가까워진것을 볼수 있습니다. 하지만 아직은 핵심 알고리즘을 발견하지 못했으니 더진행해보도록 하겠습니다. 진행을 조금만해보면 아래 그림9 와 같이 핵심알고리즘을 확인할수 있습니다.

<그림 9. 시리얼 생성 알고리즘>

간단하게 알고리즘을 표현하면 아래와 같습니다.

 name 값의 길이 구함  ->  길이 만큼 알고리즘 반복  ->  알고리즘 결과값 ESI 에 저장  ->  serial 값 16진수로 변환 EAX 에 저장  ->  EAX 와 ESI 비교 -> 분기

대충 어떤식으로 실행되는지 확인했으니 이제 하나하나 분석해보도록 하겠습니다. 

 004012F6

 PUSH 403038

 name 값 (codeengn)

 004012F8

 CALL 00401430

 lstrlen 함수 호출 길이 값 EAX에 저장

 00401300

 XOR ESI,ESI

 ESI 0 으로 초기화

 00401302

 MOV ECX,EAX

 ECX 에 EAX 값 저장 

 00401304

 MOV EAX,1

 EAX 값에 1 저장

 00401309

 MOV EDX,DWORD PTR DS:[403038]

 EDX 에 첫번째 부터 4번쨰 문자열이 저장

 0040130F

 MOV DL,BYTE PTR DS:[EAX+403037]

 DL 에 첫번째 문자를 저장

 00401315

 AND EDX,0FF

 EDX 에 0FF 를 AND 시켜서 EDX 에 DL 많이 남도록 함

 0040131B

 MOV EBX,EDX

 EDX 값을 EBX에 저장

 0040131D

 IMUL EBX,EDX

 EBX 값과 EDX 값을 곱한다. 즉 제곱 값을 EBX 에 저장

 00401320

 ADD ESI,EBX

 ESI 값에 EBX (제곱 값) 를 더한다.

 00401322

 MOV EBX,EDX

 EDX 값을 EBX에 저장한다. 첫글자를 EBX에 저장한다.

 00401324

 SAR EBX,1

 쉬프트 연산을 진행한다.  (S : 쉬프트 A : 산술 R : 우측)

 00401326

 ADD ESI,EBX

 ESI 값에 EBX 값을 더한다. (제곱값 + 첫글자 쉬프트 연산값)

 00401328

 SUB ESI,EDX

 ESI 값에서 다시 EDX 값 (첫글자) 을 뺴준다.

 0040132A

 INC EAX

 EAX 값에 1을 증가시킨다. 반복문 카운팅 용도

 0040132B

 DEC ECX

 ECX 값에 1을 감소시킨다. 반복문 카운팅 용도

 0040132C

 JNZ SHORT 00401309

 ECX 값이 0이면  (문자의 길이 만큼 반복하면) 점프

 0040132E

 PUSH ESI

 ESI 값을 PUSH

 0040132F

 PUSH 403138

 입력한 Serial 값을 PUSH

 00401334

 CALL 00401383

 401383 (16진수로 변환하는 코드) 호출 

 00401339

 POP ESI

 ESI 값을 POP

 0040133A

 CMP EAX,ESI

 EAX 와 ESI 비교

 0040133C

 JNZ SHORT 00401353

 같으면 분기

위와 같은 과정을 통해서 결과적으로 아래와 같은 계산이 반복된다고 보시면됩니다.

빠른 계산을 위해서 분기전에 BP를 설정하고 ESI 값을 확인해보면 아래와 같이 시리얼 값을 확인할수 있습니다.

<그림 10. ESI 값>

위의 그림10을 보시면 ESI 값을 확인할수 있습니다. 이값을 10진수로 변환한후 입력해주면 아래의 그림11 과같이 인증에 성공한모습을 확인할수 있습니다.

<그림 11. 인증 성공>

참고로 네임값에서 대문자 소문자도 구분하셔야 됩니다.




Posted by STIH

댓글을 달아 주세요