CodeEngn Reversing Basic_Level20


프로그램 실행 화면이다. 딱히 유의미한 정보는 없다.

PEiD로 확인한 결과이다. 패킹은 없는 듯 하다. 올리 디버거로 돌려보자.

맨 앞부분 코드이다. 문제에서 언급한 이름(crakeme3.key)의 파일을 연다. 제대로 열지 못 했을 시 오류 처리문도 있다.
동일한 이름의 파일을 만들고 더미값을 쓴 뒤 실행해보았다.

파일을 연 후 ReadFile을 호출한다. 인자로 4021A0 주소를 넣는데, 이는 lpNumberOfBytesRead 파라미터로, 파일에서 읽은 글자 수가 저장될 주소이다. nNumberOfBytesToRead 파라미터로 0x12를 넣어 최대 0x12(18) 바이트를 읽게 한다. 읽은 후 바로 4021A0 주소에 저장된 값과 0x12를 비교한다. 파일 크기(바이트)가 0x12보다 작다면 위의 이미지처럼 점프한다. 점프한 코드는 에러 화면을 띄우는 코드이다.
따라서 일단은 파일에 0x12 바이트 이상 있어야 한다는 사실을 알아냈다. 조건에 맞게 파일을 수정한 뒤 다시 실행해보았다.

0x12 글자 체크 뒤의 코드이다. 파일에 적어둔내용을 0x12 바이트 읽어온 문자열의 주소를 push하고 함수를 호출한다.
그 밑에서는 4020F9의 내용과 0x2345678를 XOR한다. 조금 더 진행하면 EAX와 4020F9의 내용을 비교하고, SETE AL 명령어를 실행한다.
SET 명령어는 flag의 값에 따라 레지스터를 세팅해주는 명령어이다. 그 중 E가 붙은 SETE는 ZF를 이용한다. 여기에서는 ZF가 1이라면 AL이 1이 되고, 0이면 0이 된다.
즉, 바로 위에서 EAX와 4020F9의 내용을 비교했을 때 같으면 AL이 1이 된다. 그 후 AL을 TEST한다. AL이 0이면 점프하고, 0이 아니면 점프하지 않는다. 즉, EAX와 4020F9의 내용이 같을 시 점프하지 않는다.
그렇다면 핵심은 4020F9에 내용을 채워 넣는 일이겠다. Constant Search를 이용해 4020F9 주소가 이용되는 곳을 찾아보았더니 파일에서 읽은 문자열을 인자로 받는 함수가 나왔다. 그래서 이 함수를 분석해 보았다.

함수 내부 코드이다. 이 함수 로직을 C로 핸드레이 해보았다. 402149에 저장되는 값은 루프를 돈 수이다.

결론부터 말하자면, 시리얼은 0x41(A)부터 0x4F(O)까지를 입력 문자열의 앞쪽 14개 문자와 차례대로 XOR한 값을 더하여 만들어진다.

이 함수에서 만들어진 값은 함수를 나온 뒤 바로 0x12345678과 XOR 하고 다시 저장된다.
그 후 또 다른 함수(바로 아래에서 분석한다)를 호출한다. 그 후 EAX 값을 4020F9와 비교하고, SETE한다. SETE는 위에서 설명했던 것처럼, ZF의 값을 넣는다.
즉, cmp값에 따라 AL의 값이 달라진다. 함수에서 설정된 EAX값과 4020F9 주소의 내용과 비교하고, EAX값과 4020F9의 내용이 같다면 AL이 1로 설정된다. 그러면 아래 있는 TEST AL, AL 에서 ZF가 0으로 설정되어 점프하지 않는다.
반대로 EAX의 값이 4020F9 주소의 내용과 다르다면 점프한다. 그렇다면 EAX를 세팅해주는 함수의 내용은 어떨까?

아주 짧은 함수이다. 함수에 인자로 넣어준 주소를 ESI에 넣고, 그것에 0x0E를 더한 주소에 있는 내용을 EAX에 가져온다.
인자로 넣은 주소는 402008이다. 메모리 덤프에서 주소를 찾아보았다. 뒷쪽에는 입력값의 일부가 보인다. 앞 부분은 앞서 호출된 함수에서 시리얼을 만들며 변형된 값이다.

402008 + 0x0E에 위치한 값은 31, 즉 내가 입력한 1의 아스키 코드이다. 입력 값에서 아직 사용되지 않은(앞의 함수에서는 0x0D까지만 사용했음) 부분이다. EAX에 DWORD로 가져오니 내 입력값의 0x0E번부터 4글자를 숫자로 가져온다. (여기서, 리틀엔디안으로 인해 순서가 반대임에 주의한다.)
즉 내 입력값의 14번째 문자부터 4글자를 int로 변환하여, 앞서 만든 key와 비교한다. 앞 14개 문자가 ‘1’일 때 4020F9 주소에 저장된 값을 찾아보았다.

F1 50 34 12이다. 이 값을 입력값의 14~18번째에 넣었다.
아스키 범위를 넘어가기 때문에 파일을 만들었다. 헥사 편집기를 이용하면 더 편하다..

입력값 파일을 만드는 코드이다.

성공 메시지가 뜬다.
그런데 문제의 요구사항과 다르다. 문제는 Cracked by 부분에 CodeEngn이라 써 있다. 입격값을 이용하여 이름을 적는 것 같다. 이름을 쓰는 부분을 찾기 위해 Cracked by 문자열을 찾아 하드웨어 bp를 걸었다.

그랬더니 위 이미지에서 커서를 둔 이 함수 내부에서 bp가 걸렸다. 이 함수를 살펴보았다.

402149에는 앞서 분석한 키 만드는 함수에서 반복문을 돈 횟수를 저장했다.
스택에 저장된 402008를 ESI에 넣는다. 402008에는 입력값이 있었다. 이 값은 키를 만드는 함수에서, A~O와 XOR된 값으로 수정되었다.
EDI에도 스택에 저장된 값을 넣는다. ESP + 8 에 저장된 값은 Cracked by~ 문장이 적힌 주소이다.
EDI에 0xC를 더한다. Cracked by 문장 뒤에 있는 빈 공간(버퍼)의 주소를 얻기 위함이다.
그 후 REP 이라는 명령어가 보인다. REP 명령어는 ECX에 있는 수만큼 뒤에 있는 명령어를 반복하라는 의미이다. 반복하고자 하는 명령어는 MOVS ~이다. 이 명령어는 문자열을 옮기는 데 쓰이는 명령어이다. ED(destination)I의 주소에 ES(source)I의 주소에 적힌 글자를 가져오고, 주소를 1씩 업데이트한다.
즉 앞서 함수에서 루프를 반복하여 만든 XOR된 글자들을 붙인다.

그래서 위와 같은 코드로 파일을 만들었다. 빈 공간은 0x00으로 채웠다. 뒤 네 글자는 앞서 했던 방법과 동일하게 올리디버거를 통해 알아냈다.

그리고 드디어 끝났다! 플래그는 저 더미값(CodeEngn과 뒤 4글자 사이)으로 무엇을 이용하느냐에 따라 여러가지가 가능하다.
인증 가능한 플래그는 더미값을 00으로 채운 022D2721002820264900000000007B553412 이다.