본문 바로가기

정보보안

[문제해결] Reversing.kr Easy Keygen 해결과정

 

 

 

 

 

 

 

 

 

일단 힌트가 될 만한것들이 있는지 문자열과 함수부분을 잘 살펴보자.

 

Correct라는 문자열이 있다.

 

401113 함수로 가면 될것같다.

 

입력받는 부분들을 찾아보자

 

문법구조상 이름을 입력받는 부분은 이곳일 것이다.

 

esp+143C+var_12C에 Name을 입력받는다.

 

repne scasb라는 함수가 보인다. 구글링 결과 scasb는 EAX에 있는 값이랑 (EDI가 가리키는 곳의 공간에 담겨있는 값)을 1바이트 단위로 비교하는 것이고, repne는 값이 같은곳이 나올때까지 비교하는 것이다. 그리고 한번 비교할때마다 ECX가 1씩 줄어드는데, 0이 되면 작동을 멈춘다.

 

edi가 가리키고 있는곳은.. esp+0x144-0x12C 부분이다. 이 부분은 esp가 4 더해지기 전 사용자로부터 입력을 받았던 곳이다.(앞에서 나온 10,20,30저장하기 동작은 esp의 위치가 바뀌었기 때문에 의미가 없어졌다..)

 

repne scasb함수를 통해 ECX 레지스터에 FFFFFFFF-(글자수+1)이 저장되었다. scasb는 비교 결과 상관없이 비교를 한번 수행하는 순간 ECX를 1만큼 올리기 때문에 여기에서는 글자수+1만큼 ECX에서 차감된 것이다.

 

이 패턴은 나중에도 자주 나온다고 하니 꼭 기억해 둬야겠다.

 

.text:00401070                 not     ecx

.text:00401072                 dec     ecx
.text:00401073                 test    ecx, ecx
.text:00401075                 jle     short loc_4010B6

 

not 연산을 통해 ecx에 글자길이+1 값이 저장되어 있다.(과정이 궁금하면 계산기로 직접 해보면 된다.)

 

dec ecx를 통해 1만큼 더해졌던 값을 빼고, ecx가 0이 아닌지, 즉 글자길이가 0이 아닌지 검사하는 모습이다.

0이 아니라 가정하고 넘어가보자

 

.text:00401077                 cmp     esi, 3
.text:0040107A                 jl      short loc_40107E
.text:0040107C                 xor     esi, esi

 

esi는 xor연산을 통해 0이 되었으므로 jl문에 들어갈 수 없다. 패스

 

.text:0040107E loc_40107E:                             ; CODE XREF: sub_401000+7A↑j
.text:0040107E                 movsx   ecx, [esp+esi+13Ch+var_130]
.text:00401083                 movsx   edx, [esp+ebp+13Ch+var_12C]
.text:00401088                 xor     ecx, edx
.text:0040108A                 lea     eax, [esp+13Ch+var_C8]
.text:0040108E                 push    ecx
.text:0040108F                 push    eax
.text:00401090                 lea     ecx, [esp+144h+var_C8]
.text:00401094                 push    offset aS02x    ; "%s%02X"
.text:00401099                 push    ecx
.text:0040109A                 call    sub_401150

 

직접 계산하기 어려우니 디버거의 도움을 받아보자..

esp+esi+13Ch+var_130는 0x10,0x20,0x30,...이 저장되어있는 곳을 가리키고

esp+ebp+13Ch+var_12C는 Name변수를 가리킨다.

 

ecx = 0x10과 [Name의 첫 글자]의 xor 결과가 들어가고,

401150함수가 호출되기 전 &esp+144h+var_C8->"%s%02X"->&esp+13Ch+var_C8->xor결과가 위에서부터 스택에 쌓인다.

 

무슨 함수인지를 유추해야하는데..(이게 가장 힘들다) sprintf 함수와 모양이 가장 비슷하다. 두번째 인자에 서식 지정자가 들어간것까지 따져보면 말이다.

 

해석을 해보면 첫번째 인자에 에 xor 연산결과가 2자리 16진수 문자열 형태로 저장된다. &esp+13Ch+var_C8에는 아무것도 없기 때문에 %s 자리에는 아무것도 전달되지 않을것이다.

 

디버거 확인 결과

AAAAA를 입력했을때 ecx가 가리키고 있던 19F36C 자리에 xor 연산 결과인 51이 잘 저장된것을 알 수 있다.

 

lea     edi, [esp+13Ch+var_12C]
or      ecx, 0FFFFFFFFh

 

edi에 Name 주소가 저장되고

ecx를 하위 4바이트만 남긴다.(sprintf 출력 장소)

 

xor     eax, eax
inc     esi
repne scasb
not     ecx
dec     ecx
cmp     ebp, ecx

 

이부분은 앞에서 한번 본 부분이다.

ecx에는 Name의 글자수가 저장될 것이고, ebp에는 현재까지 반복한 횟수가 저장되어있다.

그리고 esi를 잘 보면 0,1,2,0,1,2,.. 순서대로 저장되어있다.

 

xor 연산을 할때 가져오는 값이 esp+[0x10 주소] 였으니까.. 0x10, 0x20, 0x30을 돌아가면서 불러오는 것이었다.

그리고 ebp+[var_12C 주소]에서는 문자열들을 처음부터 하나씩 불러오는 것이다.

 

이 코드 영역의 역할을 정리해보자

 

for(Name의 글자 수만큼){

var_C8 문자열 = 기존 var_C8 + xor( {0x10,0x20,0x30}[esi] , Name[ebp]} 

}

 

이 var_C8이 어디에서 사용되나 살펴보자

 

loc_4010B6:
mov     ecx, 19h
xor     eax, eax
lea     edi, [esp+13Ch+var_12C]
push    offset aInputSerial ; "Input Serial: "
rep stosd
call    sub_4011B9
add     esp, 4
lea     edx, [esp+13Ch+var_12C]
push    edx
push    offset aS       ; "%s"
call    sub_4011A2

 

사용자로부터 입력받은 Serial을 원래 Name이 저장되었던 곳에 저장되는것을 볼 수 있다.

 

lea     esi, [esp+13Ch+var_C8]
lea     eax, [esp+13Ch+var_12C]

 

esi에는 연산결과, eax에는 serial이 저장되어 있음을 알아두자

 

이쪽 부분은 영역별로 따로따로 메모를 해놨다.

"입력한 시리얼과 연산 결과를 비교해서 맞으면 Correct, 틀리면 Wrong이 출력된다"는 기능을 하고있다.

 

이로써 Correct가 나오는 조건에 대해서는 분석이 모두 끝났다.

 

테스트를 한번 해보자

 

AAAA를 입력했을때 시리얼 넘버가 예상했던대로 나오나 보자

 

 

예상한 시리얼 넘버의 저장 형태

 

예상대로 브레이크포인트가 Correct쪽에서 걸렸다.

 

이제 본격적으로 문제를 해결해아한다.

 

Find the Name when the Serial is 5B134977135E7D13이라는데..

 

거꾸로 저 Serial 넘버가 나오는 Name를 찾아야 한다는 이야기이다.

 

일일이 계산하기 귀찮을 수 있으니 무작위 대입법을 통해서 이름을 찾아보자

 

a = [0x10, 0x20, 0x30]
chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
serial = '5B134977135E7D13'
name = ''
for i in range(len(serial)//2):
    for c in range(len(chars)):
        if(a[i%3] ^ ord(chars[c]) == int(serial[2*i:2*i+2], 16)):
            name += chars[c]
            break;
print(name)

 

지금까지 나온 정보를 바탕으로 짠 파이썬 코드이다.

 

결과값으로 나온 문자열을 Auth에 넣어보자

 

성공!

 

와우.. Easy라면서.. 시간을 이틀이나 잡아먹었다..

알고보니 이 사이트 난이도가 살짝 어려운 편이라고 한다.

하지만 어려울수록 배워가는건 많아지는법

 

거의 스스로 한 문제를 해결해서 보람 있었던것 같다.