-->

[CodeEngn] Basic RCE L20 풀이

드디어 코드엔진 Basic RCE 마지막 문제이다. 2017년도에 풀었던 풀이를 옮기는데 이상하게 생각한 것도 있고 별로 효율적이지 못한 방법을 사용한 것도 많고 해서 풀이를 많이 수정했다.

 

 

CodeEngn Basic RCE L20 문제 확인

이번 문제는 Cracked by: 뒤에 CodeEngn! 이 출력되려면 crackme3.key 파일 안의 데이터가 무엇이어야 하는지 구하는 문제이다.

 

 

파일을 실행시켜보면 지금은 알맞는 key파일이 없기 때문에 아래와 같이 아무런 문구도 뜨지 않는다.

 

 

CodeEngn Basic RCE L20 풀이

 

/* 앞부분에 삽질이 좀 있어서 알맞는 풀이는 밑으로 쭉쭉 내려서 마지막 부분을 보시기 바랍니다 */

 

 

제일 처음, 만들어진 CRACKME3.KEY 파일을 생성한 후 ReadFile로 Key 파일을 읽어서 ebx(402008)에 저장하고 있다. 그리고 읽어들인 값이 0x12(18) 바이트인지 확인하고 있다.

 

 

 

따라서 Key 파일 안에 18바이트 값 ABCDEFGHIJKLMNOPQR 를 넣어주고 실행시켜주었다. 그러면 402008에 해당 값이 저장된 것을 볼 수 있고 jne 분기문도 통과하게 된다.

 

 

 

그 다음, 아래와 같은 알고리즘이 나오는데 어셈으로만 봐서는 정확히 뭘 하는건지 이해하기가 힘들어서 실행시키면서 보기로 했다.

 

 

 

중요 부분을 살펴보면, 아래와 같은데 결국 call 20.40133C에 의해반환된 4바이트 값이 0x12345678 과 같은지를 비교하고 있다. 


00401074  call 20.401311  # [402008] = _BCDEFGHIJKLMNOPQR 
00401079  xor dword ptr ds:[4020F9],12345678  # [4020F9] = 12345678 
0040108B  call 20.40133C  # EAX=5251504F (OPQR), ESI=402016, [402016]=OPQR 
00401093  cmp eax,dword ptr ds:[4020F9]  # eax와 [4020F9]를 비교함

 

 

 

아직 401311 함수를 제대로 본 것이 아니라 정확하진 않지만 일단 끝 4바이트를 변경해보았다. 아래와 같이 새로운 문구를 확인할 수 있다.

 

 

 

즉, 아래 부분까지 진행된 것이고 두번째 크랙을 해야할 부분은 sub_401362 이다. 왜냐하면 해당 함수 안에 "Good work cracker!"와 같은 문자열이 있기 때문이다. 

 

 

 

해당 함수를 봐보면, rep movsb를 사용하고 있는 것으로 보아 버퍼 복사를 통해 Cracked by: 뒤에 CodeEngn이 들어가게끔 하는 것이 목적인 것 같다. 참고로 rep movsb는 esi가 가리키는 버퍼의 값을 edi가 가리키는 주소로 ecx만큼 복사하는 어셈이다.

 

 

 

해당 함수를 rep movsb 직전까지만 실행시킨 후 레지스터 값만 보면 key 값을 어떻게 구성해야 하는지 알 수 있다. ESI는 key 파일의 값이고 EDI는 "~~~~ Now try the next ~" 이다. ~~~~가 비어있으므로 key 파일의 첫바이트 제외한 부분을 CodeEngn! 으로 채워주면 된다. 그런데 한가지 이상한게 ECX가 0이다. 그래서 제대로 복사가 안되고 있다.

 

 

 

[402149]인데 sub_401311에서 쓰이고 있는데 여기서 ecx가 0이면 안되는데 0이었나보다.

 

 

 

어셈을 하나하나 따라가면서 보면 알겠지만, key 파일에 저장되어 있는 첫바이트가 "A"이면 401319에서 bl에 저장한 0x41과 같기 때문에 40131D의 xor 결과가 0이고 이게 al에 저장되고 다시 [esi]에 저장된다. 즉 첫바이트가 "A"였기 때문에 key 파일의 첫바이트가 0x00이 되었던 것이다. 그런데 여기서 al이 0이면 40132E의 inc cl 연산이 진행되지 않고 ret으로 끝나버리게 된다. 

 

 

 

따라서 첫바이트를 A가 아닌 다른 것으로 바꿔주면, 아래와 같이 ecx 값이 0xA가 되고 [402149]에 저장되게 된다. "CodeEngn!" 이게 정확히 0xA 바이트이다.

 

 

 

그런데 문제가 생긴게 두번째 크랙을 시도해야 하는 함수 호출부분이 실행되지 않는 문제가 생겼다. 401362가 호출되어야 하는데 pop eax로 eax에 40210E가 들어갔고 al이 1이 아니기 때문에 jne 구문에 의해 넘어간 것이다.

 

 

 

40210E가 스택에 push되는 부분을 살펴보면 sub_4021F5함수 때문인데, jz문 때문에 401037분기를 타게 됐기 때문이다. 여기서는 오른족 분기를 타야 모든 크랙이 정상적으로 수행된다. 

 

 

 

test al, al이 문제가 된 것인데, sub_401311부터 다시 봐야할 것 같다. 즉, bl은 A(0x41)부터 한바이트씩 증가하고 이 bl 값을 key 파일 입력 바이트끼리 xor연산을 하고 이 연산 결과를 [4020F9]에 계속 더한다. A~O까지 최대 16번 수행하는데 중간에 xor결과가 0이면 ret해서 끝난다. 그리고 이 연산 결과를 0x12345678과 다시 xor을 했을 때 결과값이 key파일의 뒤에 4바이트 값과 같아야 한다.

 

+ 앞 9바이트는 xor 결과가 CodeEngn이 나와야 하는데 xor은 앞부분 변수에 결과값이 저장되므로 역으로 xor을 해서 CodeEngn 이 나오게 해야한다. 그런데 여기서 ! 를 제외한 이유는 나중에 rep movsb로 복사한 후 !를 나중에 추가해주기 때문이다. 처음부터 좀 자세히 봐볼껄 그랬다...

 


00401315  mov esi,dword ptr ss:[esp+4]  #key 파일 입력값
00401319  mov bl,41 #bl="A"
0040131B  mov al,byte ptr ds:[esi]  #key파일에서 한바이트 가져옴
0040131D  xor al,bl  #두개 xor 연산 수행     
00401324  add dword ptr ds:[4020F9],eax  #[4020F9]에 더함

...

00401330  cmp bl,4F #bl은 0x4F까지 증가했을 때 ret

...

00401079  xor dword ptr ds:[4020F9],12345678  #[4020F9]와 0x12345678을 xor 연산

0040108B  call 20.40133C  #함수 호출 결과 key파일 뒤에 4바이트가 eax에 저장됨 

00401093  cmp eax,dword ptr ds:[4020F9]   #두개가 같은지 비교

 

 

 

따라서 중요한 것을 정리하자면, 먼저 A부터 H까지 xor 했을 때 결과값이 CodeEngn 이 나오게 하는 값은 결국 CodeEngn과 A부터 H까지 xor한 값이기 때문에 해당 9바이트를 key 파일의 첫 9바이트로 주고 총 합과 0x12345678를 xor한 값은 key파일 맨뒤에 4바이트에 넣어주면 되는 것이다. 파이썬 코드로 간단히 9바이트를 얻을 수 있다.

 

 

 

결과값은 아래와 같다.

 

 

 

그리고 뒤에 4바이트는 아래 xor 결과를 직접 확인해주었다. 

 

 

 

그럼 아래와 같이 "Cracked by: CodeEngn!" 문구를 볼 수 있다.

 

 

 

'리버싱 > CodeEngn' 카테고리의 다른 글

[CodeEngn] Basic RCE L19 풀이  (0) 2020.03.05
[CodeEngn] Basic RCE L18 풀이  (0) 2020.03.05
[CodeEngn] Basic RCE L17 풀이  (0) 2020.03.02
[CodeEngn] Basic RCE L16 풀이  (0) 2020.03.01
[CodeEngn] Basic RCE L15 풀이  (0) 2020.02.29

댓글

Designed by JB FACTORY