본문으로 바로가기

파일의 IT 블로그

  1. Home
  2. 보안 강좌/Reversing.kr
  3. [Reversing.Kr] Easy Crack 풀이

[Reversing.Kr] Easy Crack 풀이

· 댓글개 · KRFile

 

안녕하세요 파일입니다! 맨날 CodeEngn 문제만 풀이하다가 이번에는 좀 새로운걸 해보고 싶어서 Reversing.kr 사이트의 첫 번째 문제인 Easy Crack을 풀이해보겠습니다.

 

실행

오늘도 어김없이 프로그램 비밀번호 찾기입니다.

 

이 값이 뭔지 알아봐야겠네요..!

 

다운로드

파일의 모든 출처는 http://reversing.kr/에 있습니다.

 

풀이

기본적으로 Reversing.kr의 문제는 첫 문제부터 쫌 어렵습니다 -.-

코드엔진이나 Abex CrackMe 같은 입문 문제보다도 좀 더 까다롭고 짜증납니다.

기본적으로 PE32(32비트 실행 파일) 이므로 x86 어셈블리어로 분석할겁니다.

우선은 x86(32비트) 어셈블리어를 조금 숙지해 오셔야 이 문제를 풀이할 수 있습니다.

따로 패킹은 되어 있지 않으므로 바로 IDA로 열어줍시다.

 

열면 똑똑한 IDA가 메인 함수를 바로 찾아줍니다.. 만 일단은 저 비밀번호 성공 여부를 검사하는 코드를 찾아야 합니다.

 

Strings 탭에서 Congratulation!! 이라는 성공 메시지가 보이네요. 저기로 가봅시다.

 

성공 분기를 보면 알겠지만 정답 문자열을 한번에 비교하는게 아니라 굉장히 복잡하게 비교하고 있습니다.

 

일반적으로 문자열 비교코드를 작성하면 우리가 입력한 문자열과, 정답 문자열을 한번에 비교하는 어셈블리 코드가 작성되어 답을 금방 찾을 수 있습니다.

 

여기선 조금 분석을 해보면 알겠지만 저희가 입력한 값과 문자 글자를 부분 부분 비교를 하게 되는데 아마 리버싱 문제용으로 일부러 코드 작성자가 코드를 꼬아놓은듯 싶습니다.

 

IDA의 디컴파일 기능(F5)을 활용하면 비교하는 IF문 분기가 총 4개임을 알 수 있습니다.

이미지 출처 : https://thrillfighter.tistory.com/265

||은 OR 구문으로,  피연산자의 값이 하나라도 1이면 참을 반환하는 성질을 가지고 있어서, IF문 안에서 하나라도 1(True) 가 되버리면 바로 실패 메시지로 분기합니다. 

 

디컴파일로 코드를 봐도 그렇게 이번건 잘 이해가 되지 않네요.

직접 어셈블리 코드로 분석해보도록 하겠습니다.

 

일단 제일 첫번째 jnz 부분에서 참이 되어버리면 바로 실패 메시지로 분기합니다. [IDA에서는 JMP 계열 명령어가 참이면 초록색 쪽으로 뛰고, 거짓이면 빨간색 부분으로 뛴다고 시작적으로 보여줍니다.]

 

저기를 일단은 넘어야 하는데요. 우리가 원하는건 실패메시지가 아닌 저 빨간색 부분, 성공 메세지로 향하는 방향으로 뛰는 것 입니다.

 

jmp 계열 명령어는 cmp나 test 명령어 실행 후 여기서 비교한 값을 기준으로 점프를 뛰게 되는데요. 즉 우리가 봐야 할 것은

cmp     [esp+68h+var_63], 61h ; 'a'

이 명령어 부분입니다.

cmp로 메모리 값에 저장된 부분과 61이 같은지 체크하고, 같지 않으면 바로 실패 메시지로 분기하게 됩니다. (==jump not zero)

 

61h는 숫자 61이라고 보시면 되는데 정확히는 16진수 61입니다. 10진수로는 97인데 ASCII 코드로 알파벳 'a' 를 의미합니다. 메모리에 [esp+68h+var_63] 부분에 저장된 내용이 'a' 라는 부분과 비교하는 코드라고 보면 되겠네요!

 

그러면 [esp+68h+var_63] 라는 부분에 뭐가 들어있는지 확인해야 정확히 코드를 이해할 수 있겠네요.

원래라면 디버깅은 올리디버거로 하지만 이번엔 분기가 많아서 특별히 IDA로 직접 디버깅 해보겠습니다.

=> IDA에서 디버깅 하는 법

 

현재 디버깅 모드로 들어왔고 아까 cmp 명령어에 Break Point(F2) 를 건 상태입니다.

프로그램 이 실행되었고 입력어로 "abcdefghi" 를 입력했습니다.

 

이제 프로그램 내부에서 확인 버튼을 누르면 저 비밀번호가 맞는지 확인하는 코드가 실행됩니다.

아까전에 찾은 cmp 명령어 부분이 바로 그 부분이지요.

BP를 걸어놨으니깐 저기서 멈춥니다 ^^

 

이제  [esp+68h+var_63] 는 esp+68h+var_63 라는 메모리(RAM) 주소에서 값을 읽어오는 명령어인데,  esp+68h+var_63 위치에 무슨 값이 있는지 확인해보겠습니다.

 

마우스를 올려보면 esp+68h+var_63 는 0019F719 라는걸 알 수 있습니다.

 

Hex View에서는 프로그램이 사용중인 메모리(RAM)의 상태를 볼 수 있는데요.

여기 Hex View를 한번 클릭해서 포커스를 맞추고 G를 누릅니다.

 

Jump to Address 창이 나오면 0019F719를 치고 이동합니다.

 

확인해보면 0019F719는 우리가 입력한 비밀번호 중 2번째 문자에 대한 값이 저장되어 있네요.

 

정리해보면 cmp [esp+68h+var_63], 61h ; 'a' 라는 명령어는 cmp [0019F719], 97 을 실행시키는 것과 같으며, 이것은 우리가 입력한 비밀번호 중 2번째 문자가 'a' 라는 문자와 같은지 1차적으로 체크하는 코드입니다.

 

이 상태에서 F8(Step Over) 을 이용해 코드를 cmp 명령어를 실행시키면 초록색 방향이 깜빡이며 jnz가 참이라서 실패 메세지로 분기한다고 IDA에서 시각적으로 표시를 해주게 됩니다.

그야 그럴법한게 저희가 입력한 문자에서 2번째 문자는 'a'가 아닌 'b'이기 때문이죠...

 

일단 디버깅을 껐다가 다시 실행한 후 입력값을 "bacdefghi" 로 수정합니다.

 

입력값을 수정하고 F8을 해 확인해보니 이제 2번째 입력값이 a가 되어 성공 메세지 쪽으로 분기하며 깜빡거리는걸 확인해보실 수 있습니다. 

 

그러면 이제 여기까지 왔는데요. 여기서도 보시면 또 test와 jnz 명령어가 있습니다.

여기서 또 조건이 만족되지 않으면 실패 메세지로 넘어갑니다.

 

코드를 보시면 알겠지만 반가운 C언어 함수 strncmp 가 보임을 알 수 있습니다.

 

여기서 주요한 코드는 2부분입니다.

strncmp 호출과, test eax, eax로 eax가 0임을 체크하는 부분.

기본적으로 함수의 반환값이 eax 레지스터에 저장되므로, strncmp 를 통해서 두 문자열을 비교, 같다면 리턴값이 0이 되어 eax값에 0이 저장되고 성공 메세지로 분기하게 되겠죠. (jump 명령어가 jnz임.)

 

그래서 strncmp 에서 비교하는 부분을 잘 봐야합니다.

 

F5를 눌러서 디컴파일 해보면 strncmp 를 호출하는 부분은 strncmp(&Str1, Str2, 2u) 와 같은 형태로 복구됩니다.

Str2는 "5y" 가 저장되어 있다고 IDA에서 분석합니다. 또 2u는 unsinged int로 2를 말하는데 그냥 10진수 2로 이해하면 됩니다. (16진수 2이긴 한데 이건 10진수 2나 똑같음)

 

즉 strncmp(&Str1, "5y", 2) 를 호출해서, Str1 주소 위치에서 앞 2글자가 5y이면 성공 메세지로 분기하는 코드가 되겠습니다.

 

 

IDA가 Str1이라고 분석한 부분은 저 ecx 레지스터 입니다. ecx에 문자열의 시작주소가 저장되어 있습니다.

그러면 저 ecx 레지스터엔 어떤 주소값이 들어가 있을까요? 확인해봅시다.

F8을 눌러서 우선 저 위치까지 실행시킵니다.

 

ecx 레지스터 쪽에 마우스를 올려보면 ecx의 값은 0019F71A 입니다.

 

Hex View에서 찾아보면 0019F71A 주소가 가리키는 위치의 메모리 값은 우리가 입력한 값의 3번째 글자의 주소입니다.

 

즉 strncmp(&Str1, "5y", 2) 의 의미는 strncmp(0019F71A, "5y", 2) 와 같고, 한마디로 우리가 입력한 3번째 글짜 위치부터 2개의 글자가 5y인지 체크하는 코드입니다.

 

즉 지금까지 추정 가능한 비밀번호는 "?a5y?????..."  가 되겠습니다.

첫 번째 코드는 2번쨰 글자가 a인지, 지금 보고 계신 2번째 코드는 3번째, 4번째 글자가 5y인지 체크하는 코드입니다.

 

다시 디버깅을 끄고 다시 시작한 후 입력값을 "ba5y???????" 로 수정하겠습니다.

 

이제 또 성공 메세지로 한 걸음 다가갔습니다.

위에 push, push, mov, lea 후 바로 다음 라인 의미없는 jmp가 이어진 후 mov 명령어 3개와 또 cmp 명령어가 보입니다.

 

사실 여기서부턴 코드 그래프가 상당히 복잡하여 정확히 분석이 어렵습니다.

F8로 위부터 일단은 쭈우욱 실행해보겠습니다.

 

 

그냥 실행하면 얄짤 없이 실패 메세지로 이동합니다.

실패 / 성공에 영향을 주는 주요한 분기점은 어디일까요?

 

자세히 코드를 보니깐 방금전에 본 jnz 위치에서 cmp로 dl과 bl 이 같지 않으면 얄짤 없이 실패 메세지로 이동하네요.

dl 과 bl은 어떻게 세팅된거지..? 위를 좀 보겠습니다.

 

위 4줄의 코드는 의식의 흐름대로 넘겼는데 이것도 결국 한줄 한줄씩 봐야겠네요..

 

push    ebx
push    esi

일단은 push ebx 와 push esi를 통해 스택 메모리에 ebx값과 esi 값을 PUSH 하고 있습니다.

 

이 ebx과 esi 값은 한줄 한줄 디버깅을 하면서 그 자리에서 마우스를 올려다보면 아는데 ebx값은 1이고, esi는 00240F54 라는 주소값입니다.

 

ebx, esi 레지스터 값을 수정하기 전에 미리 스택 메모리에 백업하는 용도로 보입니다.

 

mov     esi, offset aR3versing ; "R3versing"
lea     eax, [esp+70h+var_60]

이후 mov 명령어를 통해 "R3versing" 라는 문자열을 esi 레지스터가 참조할 수 있도록 하고,

[esp+70h+var_60]의 값의 주소를 eax 레지스터에 저장합니다. (mov 가 아니라 lea 명령어임)

 

 

참고로 [esp+70h+var_60]는 [0019F71C] 이며 여기엔 우리가 입력한 값 "ba5y???????" 에서 y 다음의 글자가 저장되어 있습니다. "ba5y???????"  (저희가 입력한 비밀번호중 5번째 글자)

 

일단은 mov 명령어가 아니라 lea 명령어기 때문에 lea     eax, [esp+70h+var_60] 에 의해 eax 레지스터엔 0019F71C이 저장되어 있게 됩니다.

 

위 4가지 명령어를 실행하고 이후의 EAX와 ESI 레지스터의 상황입니다.

 

이제 여기 명령어를 보겠습니다.

 

mov     dl, [eax]
mov     bl, [esi]

위에서 세팅된 eax, esi 레지스터를 기반으로 eax 값에 저장된 주소가 가리키는 메모리 값을 dl 레지스터에, esi 레지스터에 저장된 주소가 가리키는 메모리 값을 bl 레지스터에 저장합니다.

 

지금 eax 레지스터엔 저희가 입력한 비밀번호의 5번째 글자에 대한 주소, esi 레지스터엔 "R3versing" 이라는 문자열의 시작주소를 가리키고 있습니다.

 

위 명령어를 실행하면 주소에 해당하는 값을 읽어오므로 dl에는 "ba5y???????" 이므로 dl 에는 ? 에 대한 ASCII 코드가, bl에는 "R3versing" 이므로 R에 대한 ASCII 코드가 저장되게 됩니다.

 

참고로 dl은 EDX 레지스터의 하위 1바이트(16진수 2자리), bl은 EBX 레지스터의 하위 1바이트 입니다.
* 16진수 1글자 당 4비트임.

 

mov     cl, dl
cmp     dl, bl

이후 dl 레지스터의 값을 cl에 복사합니다.

마지막으로 cmp를 통해 dl과 bl을 비교해서 같으면 성공 메세지로, 같지 않으면 실패 메세지로 이동합니다.

 

dl 은 우리가 입력한 입력값의 5번째 글자, bl은 'R' 이였으니깐 일단 또 다시 시리얼 키가 "ba5yR??????" 임이 추정되네요 ㅋㅋ.  또 다시 입력값을 수정해줍니다.

 

실패메세지로 뛰지 않고 또 다시 성공 메세지로 한걸음 다가섰습니다.

이제 test cl, cl 을 통해 cl 레지스터의 값이 0인지 체크하고, 이에 따라 분기합니다.

 

cl 레지스터는 방금 전에 확인했듯이 dl 레지스터의 값이 복사되어 저장되어 있는데, dl 레지스터는 우리가 입력한 5번째 값에 대한 ASCII 코드 값이 저장되어 있었죠.. 우리가 지금 5번째 글자로 "R" 을 입력한 상태고 또 어짜피 답은 "R" 을 입력해야 하므로 이번 분기는 별로 신경쓰지 않아도 될 거 같습니다.

 

또 나타난 분기입니다. 어셈블리 코드를 너무 많이 읽으니 슬슬 구역질이...🤮

 

mov     dl, [eax+1]
mov     bl, [esi+1]
mov     cl, dl
cmp     dl, bl
jnz     short loc_401102

일단은 많이 왔으니 조금더 힘내봅시다.

 

[eax+1] 에 있는 값을 dl에, [esi+1] 에 있는 값을 bl에 저장합니다.

그리고 dl 레지스터에 있는 값을 cl에 복사 후 ,  dl과 bl같은지 체크하고 분기합니다.

 

일단은 다시 앞에 있는 기억을 되살려보면 eax 레지스터엔 우리가 입력한 값의 5번째 글자 주소, esi 레지스터엔 "R3versing"의 1번째 시작주소가 저장되어 있습니다.

 

eax+1의 값은 포인터 연산에 의해 우리가 입력한 값의 6번째 글자값의 주소, esi + 1은 "R3versing"의 2번째 글자 주소가 되겠네요.

 

[]는 메모리 참조 모드니깐 결론적으로 dl엔 우리가 입력한 6번째 글자값의 ASCII 코드 값, bl에는 R다음의 글자인 3이라는 글자의 주소가 저장되게 되고 이를 비교함을 알 수 있습니다.


그럼 현재까지 추정되는 시리얼 키 값은 "ba5yR3?????" 가 됩니다.

 

 

입력값을 "ba5yR3?????" 로 바꾸고 실행해보면 eax와 esi에 2를 더하고, cl 레지스터 값을 체크 이후 이제 위로 돌아가면서 루프가 돌아가면서 앞에서 했던 행위를 반복함이 확인됩니다.

 

반복되는 문자열 값들을 체크해보면 알겠지만 결론적으로 우리 문자열의 뒷 부분이 "R3versing" 와 같은지 체크 하는 코드가 되겠습니다.

 

그럼 답은 "ba5yR3versing" 가 되는걸까요?

 

네 아직은 아닙니다 ㅋㅋ 뭔가 빼먹은게 있죠? 바로 첫번째 글자입니다.

프로그램에서 첫번째 글자에 대한 힌트는 주지 않았습니다.

 

맨 마지막에 최종적으로 비교하는 코드가 있는데요. cmp     [esp+68h+String], 45h 분이 바로 첫번째 글자가 'E' 인지 체크하는 코드가 되겠습니다.

 

결론적으로 시리얼은 "Ea5yR3versing" 가 되네요..

 

오와!! 드디어 값을 찾았습니다. 

답은 "EasyReversing" 을 숫자와 알파벳을 이용해 일종의 언어유희(?)로 표현한 문자였습니다.

 

사실 올리디버거를 썼으면 이렇게 자세히 분석할 필요 없이 그냥 어떤 문자랑 비교하는지 대충 눈치밥으로 보고 비슷하게 입력해서 프로그램을 풀수도 있었는데요. 오늘은 특별히 본인의 실력 향상을 위해 IDA로 손수 하나하나 어셈블리 코드를 분석하면서 풀어보았습니다... Easy Crack 이라는데 Easy 가 맞는지도 모르겠네요 ㅠㅠ

SNS 공유하기
최근 글
파일의 IT 블로그
추천하는 글
파일의 IT 블로그
💬 댓글 개
이모티콘창 닫기
울음
안녕
감사해요
당황
피폐

이모티콘을 클릭하면 댓글창에 입력됩니다.