안녕하세요. 오늘은 basic rce level 20 문제를 풀어보겠습니다.

홈페이지로 들어가 basic rce level 20을 눌러주세요.

위의 창을 띄울려면 crackme3.key파일안의 데이터는 무엇이 되어야 하는가?라고 되어있네요. 일단 실행해봅시다.

문제에 적힌 창과 다른 창이 띄워집니다.  올리디버그로 열어봅시다.

실행을 하면 아까 본 창이 뜨고 창을 닫으면 종료되는 것을 볼 수 있습니다. 우리가 문제에서 본 성공창을 띄우는 부분을 찾아봅시다.

밑으로 내려가니 성공문구를 출력하는 메세지박스함수가 보이는군요. 그렇다면 이 함수를 호출할 수 있는 부분을 찾아봐야겠습니다. 코드를 뒤져봅시다.

40119B에서 메세지박스함수를 띄우는 주소를 호출하고 있습니다. 그리고 그 위에 CMP가 보이는 군요. 실행을 해보니 CMP에 의해 ZF가 0이 되어서 JNZ에 의해 점프해서 호출을 막아버립니다. 일단 확인을 위해 JNZ를 JE로 바꾸고 실행해봅시다.

ZF가 0이기 때문에 JE는 점프를 하지 않고 바로 다음줄로 갑니다.  그리고 메세지박스함수를 호출합니다.

예상대로 메세지박스함수가 호출되었습니다. 그런데 문제와는 다르게 CodeEngn! 문구는 없고 그냥 !만 출력되었습니다. 일단 문구는 잠시 접어두고 메세지박스함수가 호출이 되도록 만들어봅시다.

호출이 되도록 할려면 CMP AL,1 에서 ZF가 1이 되도록 해야합니다. 그럴려면 AL과 1값이 같아야 하지요. 여기서 AL이란 EAX레지스터의 8bit를 의미합니다. EAX가 32bit인데 그 중 8bit만을 지칭할때 AL이라고 합니다. 확인을 위해 CMP에 브레이크를 걸고 실행합시다.

레지스터부분을 확인해 봅시다.

EAX가 가리키는 주소에 문자열이 들어가 있군요. 그리고 여기서 AL은 8bit라고 했으므로 0E가 됩니다. 즉 0E랑 1이랑 비교하는데 값이 달라서 ZF가 0이 되버립니다. 그렇다면 이 EAX값을 어디서 받는지 확인해봅시다.

CMP바로 위의 코드를 보시면 401187에서 EAX값을 빼내오는 것을 보실 수 있습니다. 스택값을 확인해 봅시다.

스택에 저장된 값 0040210E를 빼내어 EAX에 저장시킵니다. 그렇다면 다시 처음부터 시작해서 어느 시점에 스택주소 0018FF88에 값이 저장시키는지 확인해봅시다. F8키를 이용하여 한줄 한줄 코드의 흐름을 봅시다.

얼마 가지 않아 401037에서 스택에 40210E를 저장시키는 것을 확인할 수 있습니다.

 마찬가지로 스택에 값이 저장되는군요. 우리는 앞서 몇번의 삽질로 0040210E가 실패로 가는 값임을 알고 있습니다. 즉 스택엔 0040210E값이 저장되면 안됩니다. 그래서 저장을 막기 위한 방법을 찾기 위해 코드를 올려보니 위에 CMP문구가 보입니다.

EAX와 -1을 비교하는 군요. CMP부분에 브레이크를 걸로 EAX값을 확인해봅시다.

EAX값이 FFFFFFFF이군요. 메모리상에서 FFFFFFFF는 00000000에서 1을 뺀 값입니다. 즉 -1이지요. 결국 CMP와 비교하여 ZF가 1이 되고 JNZ에서 점프가 되지 않아  스택에 0040210E가 저장됩니다. 우리는 이를 막아야합니다. 그럼 EAX가 왜 FFFFFFFF를 저장시키는지 알아봅시다.

EAX는 함수의 반환값을 저장시키는 레지스터입니다. 바로 위의 코드를 보니 함수 하나를 호출하는 군요.

CreateFileA함수를 호출하네요. 이 API함수는 파일을 생성하거나 읽어들이는 함수입니다. 그런데 함수의 반환값이 -1이라는 것은 파일을 만들거나 읽어들이는데 실패했다는 뜻입니다. 그런데 함수를 자세히 보면 Mode 부분에 OPEN_EXISTING라고 되어있습니다.

즉 이 CreateFileA함수는 파일을 읽어들이는 용도로 쓰이는 함수입니다. 그런데 실패했다는 것은 파일을 못읽었다는 것이지요. 파일명을 보니 CRACKME3.KEY라고 되어있네요. 아무래도 이 파일이 있어야 정상적으로 EAX값을 반환하는 것으로 보입니다.

파일을 한 번 만들어봅시다. 그런데 어떻게 만들어야 할까요? 간단합니다. 그냥 메모장을 열고 파일명을 CRACKME3.KEY로 저장시키면 됩니다.

아 그런데 우리는 이 함수가 어디에서 CRACKME3.KEY를 찾는지를 모릅니다. 확인을 위해 함수를 호출하는 부분에서 F7키로 진입하여 어떤 경로에서 파일을 찾는지 알아봅시다.

대략 7527C4E1부분에서 바탕화면에 있는 CRACKME3.KEY를 찾고 있는 것을 알 수 있습니다. 바탕화면에 만들어줍시다.

올리디버그를 종료하고 메모장을 열어 바탕화면에 RACKME3.KEY이름으로 저장합시다.

저장이 완료되었으면 올리디버그로 다시 프로그램을 열어 실행해봅시다.

함수가 호출되고 난 후 EAX값이 정상적인것을 확인할 수 있습니다. 계속 한줄씩 실행해 봅시다.

실행을 하다보면 위와 같은 코드가 보입니다. 4021A0의 4byte값과 16진수(12)를 비교하여 ZF가 0일경우 JMZ에 의해 점프되는 코드입니다. 문제는 이 점프되는 주소가 아까 스택에 0040210E를 집어넣는 주소라는 것이지요... 역시 피해가야 합니다. 덤프창에서 4021A0 주소로 찾아가 값을 확인해 봅시다.

값이 없네요... 그렇다면 코드중에서 4021A0과 관련된 코드를 찾아봅시다.

바로 위의 00401054에서 004021A0에 값을 푸쉬한다고 나와있습니다. 그리고 옆에 함수에도 주소가 나와있네요. 함수명이 ReadFile인 걸로 보아 파일을 읽어들이는 함수 인  것 같습니다. 그리고 pBytesRead에 004021A0주소가 적혀있는 걸로 보아 파일을 바이트단위로 읽어 그 값을 004021A0에 넣는 것 같습니다. CRACKME3.KEY의 크기를 확인해봅시다.

0바이트라고 나와있네요. 그래서 004021A0의 값이 0이었던 거군요. 분명 아까 CMP에서는 12라고 적혀있었으니 우리는 파일을 18바이트로 맞춰줄 필요가 있습니다. 아 왜 18바이트나면 CMP의 12는 16진수이기 때문에 우리는 16진수 12를 10진수로 바꾼 값 18로 맞춰줘야 합니다. 오른쪽마우스를 눌러 편집을 눌러주세요. 편집하실때에는 올리디버그를 종료하셔야 합니다.

누르면 메모장이 뜨실텐데요. 대충 18바이트 맞춰주세요. 한글은 2byte이고 영문,숫자는 1byte입니다.

맞춰주었으면 저장하시고 올리디버거로 열어서 실행해봅시다.

함수를 호출하고 CMP부문으로 가서 확인하면 4021A0에 12가 저장된 것을 볼 수 있습니다. 이로 인해 JNZ를 무효화 시켰습니다. 계속 한줄씩 실행해봅시다.

세번째 CMP를 발견하였습니다. 그리고 그밑에는 생소한 명령어들이 보이는 군요. 하나씩 설명해드리겠습니다.

1. SETE AL  - ZF값이 1일 경우 AL을 1로 지정. ZF가 0일 경우 AL을 0으로 지정

2. TEST AL,AL  -  AL과 AL을 서로 AND연산하여 값이 TRUE일 경우 ZF값을 0으로 지정. 값이 FALSE일 경우 ZF값을 1로 지정

3. JE - ZF가 1일경우 점프

입니다. 우리는 최종적으로 JE에서 점프를 하지 않아야 합니다. 점프를 하게 되면 우리가 여태까지 피해왔던 코드인 스택에 40210E값을 집어넣는 코드를 만나게 됩니다.  그러므로 우리는 ZF를 0으로 만들어 점프를 하지 못하게 해야합니다.

그런데? 여기서 예리하신 분들은 질문을 하실 수 있습니다.

'JE명령어 바로 다음코드에도 0040210E값을 스택에 넣으라는 코드가 있는데 이렇게 되면 어떻게 되든 스택에 0040210E을 집어넣는 것이 아닌가요?'

맞습니다. 최종적으로 어떻게든 스택에는 0040210E가 들어갑니다. 하지만 최종적으로 한가지가 다른 부분이 있습니다. 같이 한번 살펴봅시다. 전체 코드에서 스택에 0040210E란 값이 들어가는 코드는 딱 두군데 입니다.

두 코드의 차이점을 찾으셨습니까? 두번째 사진을 자세히 봐주십시요.

두번째 사진에선 스택에 40210E값을 넣고 ADD ESP,4 코드를 실행하여 현재 스택주소의 값을 4증가시켜 버립니다. 즉 이게 무슨 말이냐 하면 18FF84란 스택 주소에 40210E값을 집어넣고 스택주소를 증가시켜 현재 스택의 위치를 18FF88로 만든다는 것이지요. 그렇게 되면 나중에 코드에서 POP EAX를 수행할 때 18FF84가 아닌 18FF88에서 값을 꺼내오기 때문에 실패로 되버리는 40210E값을 꺼내오지 않는다는 뜻입니다.

말이 너무 길었군요. 결론만 말씀드리자면 JE에서 점프 하지 않도록 ZF를 0으로 만들자 입니다. 그럼 어떻게 해야할까요? 거꾸로 한번 가봅시다.

1. ZF가 0이 될려면 TEST AL,AL에서 ZF값이 0이 되어야 함. 즉 AL과 AL을 and연산하여 0이 아닌 값이 나와야 함.

2. AL과 AL을 and연산하여 0이 아닌 값이 나오게 할려면 AL은 00을 제외한 값이 되어야함.

3. AL이 00이 되지 않을려면 ZF가 1이 되도록 해줌으로써 SETE AL에서 AL이 01로 세팅되게 해주어야 함.

4. CMP부문에서 ZF가 1이되도록 해주어야 함.  즉 EAX값과 4020F9의 값이 같아야함.

결국 4번만 해결하면 나머지는 자동으로 해결이 되는 것을 알 수 있습니다. 어디한번해결해봅시다. 4020F9의 값을 확인해봅시다.

읽을때는 리틀엔디언방식으로 읽어야하니 123450F1입니다. 이젠 EAX값을 확인해봅시다.

EAX값은 31313131입니다. 뭔가 부자연스러운 값입니다. 여기서 우리는 16진수31이란 값이 아스키코드로 1이라는 것을 알아야 합니다. 즉 우리가 CRACKME3.KEY에서 입력한 값중 일부를 끌고와 16진수값으로 EAX에 저장시킨다는 것을 알 수 있습니다. 31이 4개이므로 4개의 글자를 끌고 왔겠군요. 어느 부분을 끌고 왔는지 알아보기 위해 값을 한번 바꿔보겠습니다. 이 작업은 따로 사진을 안올리겠습니다.

삽질 결과 18글자의 마지막 4글자를 갖고오는 것을 알 수 있었습니다. 그렇다면 그 4글자를 123450F1로 맞춰주어야 합니다. 아스키코드로 입력할려니 12같은 경우는 아스키코드 입력이 불가능해서 그냥 헥스값으로 맞춰주겠습니다. 헥스에디터로 파일을 열어주시고 123450F1으로 수정해줍시다.

레지스터에서 값을 읽어들일땐 리틀엔디언방식으로 읽으니 반대로 값을 입력해줍시다. 수정을 하였으면 올리디버그로 파일을 열어 실행하여 401093부분으로 가서 확인해봅시다.

EAX값이 성공적으로 수정되었습니다. 이제 코드가 진행되면서 JE구문에서 점프를 하지 않을 것입니다. 실행해봅시다.

실행을 하게 되면 성공적으로 창을 띄울 수 있습니다. 그런데 말입니다. 출력된 문자열이 엉망입니다. 우리는 CodeEngn!을 띄워야 비로소 최종적으로 문제를 푼 것입니다. 이러한 문자열을 어디서 받아오는 건지 살펴봅시다. 코드에서 메세지박스 부분을 살펴보겠습니다.

40216A에서 값을 받아오는 군요. 덤프창으로 확인합시다.

보시다시피 이상한 값이 들어가 있습니다. 새로 시작해서 40216A를 살펴봅시다.

문자열 부분이 비워있는 것을 볼 수 있습니다. 한줄씩 실행하여 어느 부분에 40216A를 집어넣는지 봅시다.

401373에서 코드를 실행 후 40216A에 값이 들어가는 것을 볼 수 있습니다. 코드를 보니  ESI의 값을 EDI에 집어넣는 것을 볼 수 있습니다. 레지스터를 확인해 봅시다.

402176에 402008을 집어넣는군요. 402008주소의 값을 확인해봅시다.

메세지박스에서 본 문자열이 적혀있습니다. 새로시작한 후 주소를 확인해봅시다.

비어있습니다. F8키를 눌러 언제 삽입되는지 알아봅시다.

ReadFile함수를 호출하고 난뒤 402008에 우리가 입력한 데이터값이 들어가는 것을 볼 수 있습니다. 다만 메세지박스함수에서 뜬 문자열이랑 값이 다르군요. 계속 한줄씩 실행해봅시다.

401074의 401311함수를 호출하고 문자열이 바뀌는 것을 볼 수 있습니다. 그럼 저 안에서 문자열처리를 하는군요. F7키로 들어갑시다.

대충 위의 명령분을 반복함으로써 문자열을 특정규칙에 의해 바꿔서 저장시키네요.

대충 요약하면

1. KEY의 첫번째 값을 HEX값으로 AL(31)에 저장하여 BL(41)이랑 XOR연산후 AL에 저장시킴

2. BL을 1개 증가시킴

3. AL이 0이 아니면 CL을 하나 증가시키고 1,2를 반복.

대충 이런 식인데요. KEY의 첫번째 값을 HEX값으로 AL(31)에 저장하여 BL(41)이랑 XOR연산후 AL에 저장시킨 후 AL이 0이 아니면 다시 반복하여 KEY의 두번째 값을 HEX값으로 AL(31)에 저장하여 BL(42)이랑 XOR연산하여 402009에 저장시키는 구조입니다. 그리고 최종적으로 저장된 CL개수만큼 메세지박스함수에 변환된 문자열을 출력시킵니다. 우리가 만들려는 문자열은 CodeEngn!으로 만드러야 하므로

KEY의 첫번째 값 (XOR) 41 을 하여 C가 나오도록 하는 구조로 만들어야 합니다.

CodeEngn을 HEX값으로 바꾸면 43 6F 64 65 45 6E 67 6E 이니까 정리를 하면

KEY의 첫번째 값 (XOR) 41 을 하여 43

KEY의 두번째 값 (XOR) 42 를 하여 6F

KEY의 세번째 값 (XOR) 43 을 하여 64

.... 중략

입니다.  !가 왜 없냐면 아까 402008의 문자열을 봤을때 느낌표는 없었는데 메세지박스에서 출력된 문자열은 !가 있는 걸로 보아 함수를 호출하면서 !를 자동으로 붙이는 것 같아 일부러 안넣었습니다.

마지막으로 XOR연산은 두 값을 이진수형태로 바꿔서 값이 서로 다르면 1 같으면 0으로 연산합니다.

EX) 1101과 1110의 XOR 연산

1101

1110

0011     답은 0011

연산과정은 일일이 올리지 않겠습니다.

연산결과 : 02 2D 27 21 00 28 20 26 즉 이것이 CodeEngn으로 변환되는 KEY값입니다.

CRACKME3.KEY를 헥스에디터로 열어 수정해줍시다.

파일을 실행해서 확인해 봅시다.

문자열변환과정을 거치니 성공적으로 바뀌었습니다. 그런데 앞에 말씀드렸던 것처럼 CL값만큼 메세지박스함수에 출력시킨다 했습니다. 우리는 8개만 출력시켜야 하는데 Cl값은 8을 초과했기 때문에 CL이 8인 시점에서 멈춰야 합니다. 앞의 변환과정에서 AL이 0이면 중단한다고 했으니 AL을 0으로 만들어 변환과정을 멈추도록 합시다.  AL을 0으로 만들려면 AL XOR BL을 해서 0이 나와야 하므로 AL값을 BL값과 맞춰줘야합니다. CodeEngn을 변환하고 난 뒤의 BL값은 49입니다. 즉

02 2D 27 21 00 28 20 26뒤에 49란 값을 넣은 02 2D 27 21 00 28 20 26 49로 수정시켜봅시다.

다시 실행해봅시다.

CodeEngn을 만들고 CL은 8이 되었습니다. 실행해봅시다.

그런데 실행을 하면 창이 안뜨는 것을 볼 수 있습니다. 관련된 코드로 가봅시다.

비교값이 바뀐 것을 알 수 있습니다.

파일의 끝 4자리 수를 7B 55 34 12로 맞추어봅시다.

수정 후 올리디버그로 열어 실행해봅시다.

성공적으로 문제와 같은 메세지가 출력되었습니다. 이때의 CRACKME3.KEY값을 정답으로 한다했으니 정답은

02 2D 27 21 00 28 20 26 49 ?? ?? ?? ?? ?? 7B 55 34 12입니다.

??부분은 어떤 값이 들어와도 저 성공메세지함수를 출력하기 때문에 ??로 표현했습니다. 물론 이렇게 되면 정답이 너무 많아지기 때문에 문제에서도 여러개의 정답이 있다고 표현한 것이며 인증이 안된다고 나오면 게시판에 비공개로 올려주시면 정답을 확인시켜 준다고 합니다.

네 이것으로 문제풀이를 마치겠습니다. 감사합니다.

 

'codeengn' 카테고리의 다른 글

codeengn - basic rce level 6  (0) 2016.08.02
codeengn - basic rce level 5  (0) 2016.08.02
codeengn - basic rce level 19  (0) 2015.10.02
codeengn - basic rce level 16  (0) 2015.10.02
codeengn - basic rce level 18  (0) 2015.09.25
Posted by englishmath
,