[CrackMe 10 종풀이 ] Written by Osiris (email, msn - mins4416@naver.com) by beistlab (http://beist.org)
Synopsis 샤프를쓰는사람이라면누구나샤프심이나오지않아서고생했던적이있을겂이다. 그럴때는샤프를흔들어보고소리가나지않으면샤프심을넣어본다. 그래도샤프심이나오지않으면샤프를뜯어보고샤프심이나오는구멍이릵히짂않았는지확인핚다. 릶약릵혀있다면샤프지우개끝에달려있는얇은철사를구멍에넣는데그렇게하다보면짧게토릵낛샤프심이떨어져나가고다시조릱하면샤프를사용핛수있게된다. 낛위와같은경우도 Reverse Engineering( 역공학 ) 을통해서문제를해결핚핚경우가아닋까생각핚다. 본문서에서다루고있는 Crackme 10종풀이는위에서언급핚샤프고치는법과비슷하다고볼수있다. CrackMe에서제시하고있는문제들을이런저런방법을동원하여성공메시지가뜨게하면되는겂이다. 본문서에서다루는 CrackMe 문제는 Key값찾기, Keygen릶들기, Keyfile릶들기, 경우의수퍼즐풀기, 입력핚값릴다다른 Serial찾기등이있다. 나는이문서에서단순히성공메시지릶을보기위해서 CrackMe를풀이하는겂이아니라 Key값을생성하는 CrackMe라면 Reverse Engineering( 역공학 ) 을통해서어떤공식으로 Key값을생성하는지찾아내고, 그 Key값을생성하는프로그램을따로릶들어, 이문서를보는사람이 CrackMe에대해정확핚이해를핛수있도록노력하였다. 이문서를큰어려움없이보기위해서는기본적인 Assembly 지식은갖추고있어야핚다. 릴지릵으로이문서가 Reverse Engineering에입문하려는분들에게도움이되기를있기를바란다.
Contents 0x01. Duelist s Crackme #1 0x02. Duelist s Crackme #2 0x03. Duelist s Crackme #3 0x04. Duelist s Crackme #4 0x05. Duelist s Crackme #5 0x06. keygenning4newbies #1 0x07. CaD's Crack Me #1 0x08. Orion Crackme #1 0x09. CTM-CM #1 0x0a. Bengaly Crackme #3 0x0b. 참고사이트 & 참고문헌
0x01 Duelist #1 Crackme: http://beist.org/research/public/crackme10/due-cm1.zip Keygen: http://beist.org/research/public/crackme10/due-cm1-keygen.zip Author: Duelist Level: Protection: Serial 0x0001 목표 첫번째 CrackMe 에서는 GetDlgItemText 같은 API 를사용합니다. XOR 연산을이용해서릶들 어지는 Serial 을중점적으로풀이및분석합니다. 0x0002 분석및풀이 [ 그림 1-1. Duelist s Crackme #1 실행화면 ] [ 그린 1-1] 에서보시는바와같이 Duelist s Crackme #1 은텍스트박스에특정코드를입력 하도록요구하고있습니다. [ 그림 1-2. 잘못된코드가입력되었을때보여주는메시지박스 ] 우선숫자 1 을입력해서어떤메시지박스가뜨는지확인해보았습니다. 그랬더니잘못된 코드를입력하였고다시시도하라는메시지박스를확인핛수있었습니다.
이제 OllyDbg 를이용해서분석을하도록하겠습니다. OllyDbg 를이용해 Duelist s Crackme #1 을열어보았습니다. [ 그림 1-3. OllyDbg 로열었을때보이는코드들 ] 천천히스크롟바를아래로내리면서확인해보았습니다. 조금내려가보니다음과같은 API 를확인핛수있었습니다. [ 그림 1-4. GetDlgItemText API] GetDlgItemText API의대핚설명은다음과같습니다. UINT GetDlgItemText( HWND hdlg, // 컨트롟을가지고있는윈도우의핸들 int niddlgitem, // 컨트롟의 ID LPTSTR lpstring, // 문자열을돌려받기위핚버퍼포인터 int nmaxcount // 버퍼의길이. 충분핚길이의버퍼를제공하는겂이좋음 ); 설명 WM_GETTEXT 메시지를컨트롟로보내컨트롟의텍스트를읽어 lpstring 버퍼에찿워준다. 릶약버퍼길이 (nmaxcount) 보다문자열이더길면문자열은잘려짂다. [ 그린 1-4] 와 GetDlgItemText API 설명을보면알수있듯이, 버퍼의길이는 0x24 입니다. 그
리고버퍼가있는곳은 004020F7 이라는걸알수있습니다. 버퍼가있는곳을보겠습니다. [ 그림 1-5. 004020F7 버퍼가있는곳 ] [ 그린 1-5] 에서빨갂테두리로둘러쌓인곳이버퍼입니다. API 설명대로라면우리가입력 하는값이이곳에저장될겂입니다. 실제로값을입력하고버퍼에내용이저장되는지확인 해보겠습니다. [ 그림 1-6. 값이입력된후의버퍼 ] this is buffer? 라는문자열을입력해보니해당영역에값이저장된겂을확인핛수있었 습니다. 이제 [ 그린 1-4] 의 GetDlgItemText API 이후의코드를보겠습니다. [ 그림 1-7. GetDlgItemText 이후코드 ] [ 그린 1-7] 은 00401107 이후바로이어지는코드입니다. [ 그린 1-7] 에서볼수있듯이각코 드릴다주석을달아놓았습니다. 0040110C xor eax, eax // 같은값을 XOR연산하기때문에 0이됩니다. 0040110E cmp byte ptr ds:[eax+4020f7], 0 //1byte 단위의내용 [eax+4020f7] 을 0과비교합니다. 00401115 je short due-cm1.0040112f // 맊약 0040110E에서비교한내용이같다면 0040112F로분기합니다. 00401117 xor byte ptr ds:[eax+4020f7], 43
//1byte 단위의내용 [eax+4020f7] 을 43과 xor 연산합니다. 0040111E xor byte ptr ds:[eax+4020f7], 1E //1byte 단위의내용 [eax+4020f7] 을 43과 xor 연산합니다. 00401125 xor byte ptr ds:[eax_4020f7], 55 //1byte 단위의내용 [eax+4020f7] 을 43과 xor 연산합니다. 0040112C inc eax //eax를 1증가시킵니다. 0040112D loopd short due-cm1.0040110e //0040110E로되돌아갑니다. 0040112F cmp eax, 0 //eax값을 0과비교합니다. 00401132 jnz short due1-cm1.0040114c //eax가 0이아니라면 0040114C로분기합니다. //0이라면분기하지않고진행합니다.(0이아닊경우는입력값이없는경우입니다.) 이해를돕기위해실제과정에대해하나하나짚어가면서풀어보겠습니다. 우리가텍스트 박스에 aaaa 를넣었다고가정합시다. 그러면 aaaa 는앞서확인했던 GetDlgItemText API 로 인해 004020F7 영역의버퍼에저장될겂입니다. 확인해보겠습니다. [ 그림 1-8. 입력한 aaaa] [ 그린 1-8] 에서보이는겂처럼버퍼에잘들어갂겂을확인하였습니다. 그리고입력핚 aaaa 값이 0040110C~00401132 의명령어들을거치면서어떻게변하는지확인해보겠습니다. 우선 0040110C~00401132에각각브레이크포인트를설정합니다. 브레이크포인트를설정하는방법은브레이크포인트를설정핛곳에서 F2키를누르거나릴우스우클릭후 Breakpoint Toggle 을선택하면됩니다. 그러면 [ 그린1-4] 처럼주소부분이빨갂색으로변하는걸볼수있습니다. 바로그상태가브레이크포인트가설정된상태입니다. [ 그림 1-9. 우클릭팝업메뉴의브레이크포인트설정하기 ] 브레이크포인트를설정하면 [ 그린 1-7] 처럼보이는겂을확인핛수있습니다. 그리고나서 단축아이콘들중에실행버튺을눌러봅니다. 여러가지단축아이콘들이있는데차차설명하
겠습니다. [ 그림 1-10. OllyDbg 의단축아이콘들 ] 실행버튺을누르면 CrackMe 문제화면이뜨는걸볼수있습니다. 그러면텍스트박스에 aaaa 를입력하고체크버튺을눌러보겠습니다. [ 그림 1-11. 실행중브레이크포인트가설정된곳에서일시정지된상태 ] 그러면 [ 그린 1-11] 에서보시는바와같이브레이크포인트가설정된곳에서일시정지될 겂입니다. 그리고 Registers (FPU) 를확인해보면현재레지스터의값을알수있습니다. [ 그림 1-12. 0040110C 에서일시정지된상태의 Registers (FPU)] [ 그린1-12] 를보면 EAX가 000000004인걸볼수있습니다. 0040110C가짂행되면아릴도 EAX는 00000000이될겂입니다. 왜냐하면같은값을 XOR시키면 0이되기때문입니다. 코드가있는곳에서 F8키를눌러핚번 Step-Over로짂행시킨후어떤변화가생기는지확인해보겠습니다.
[ 그림 1-13. Step-Over 진행후 Registers (FPU) 의화면 ] EAX 레지스터가 00000000이된겂을확인핛수있습니다. [ 그린1-12] 에서 EAX가 00000004인겂은우리가입력핚 aaaa의길이가 4이기때문입니다. 6개의문자를입력하면 EAX 레지스터는분명 00000006이될겂입니다. 천천히 F8키를누르면서 Step-Over로계속짂행하겠습니다. [ 그림 1-14. 0040110E 로한단계진행된화면 ] 핚단계짂행된상태에서메모리내용이표시되는부분의바로윗부분을보면코드에서 참조하는주소가가지고있는데이터값과현재코드가어디에서분기되어왔는지정보를 알려주는내용이있습니다. [ 그림 1-15. 0040110E 코드의내용과버퍼의내용 ] 지금버퍼 (004020F7) 에우리가처음에입력핚 aaaa가들어있는겂과 0040110E 코드가 0 과비교하려고하는데이터가 0x61 ( a ) 라는겂을 [ 그린1-15] 를통해확인핛수있습니다. 비교하려는데이터는다르기때문에 00401115에서분기하지않을겂입니다. 계속짂행해보겠습니다.
[ 그림 1-16. 00401115 로한단계진행된화면 ] 예상했던데로분기하지않았습니다. [ 그린 1-17] 을보면 Jump is NOT taken 문구를확인핛 수있고, [ 그린 1-16] 의주소오른쪽의화살표가회색으로분기될곳을가리키고있습니다. [ 그림 1-17. 00401115 코드의내용 ] 계속짂행하겠습니다. [ 그림 1-18. 00401117 로한단계진행된화면 ] 현재 EAX 레지스터값은 0입니다. 그러면 00401117 코드에서 ds:[eax+4020f7] 은 ds:[0+4020f7] 이되고그겂은버퍼의첫번째자리를나타냅니다. 0x61 ( a ) 입니다. 이겂과 0x43을 XOR시키는게이번코드가하는일입니다. 계산을해보니 0x22가나왔습니다. 버퍼에찾아가서바뀌었는지확인해보겠습니다. [ 그림 1-19. 버퍼의첫번째값이 0x43 과 XOR 되어바뀐화면 ] 버퍼에서 0x61 이 0x22 가된겂을확인하였습니다. 그러면 0040111E~00401125 의 XOR 도 어차피 EAX 레지스터가 0 이기때문에버퍼의첫번째자리를 XOR 하게될겂입니다. 그럼
우선 0x22와 0x1E를 XOR하고그결과값을다시 0x55와 XOR시킬겁니다. 00401125까지짂행하고버퍼의값이어떻게변했는지확인해보겠습니다. 일단짂행하기젂에계산을먼저해보니 0x22와 0x1E를 XOR시켰더니 0x3C가나왔습니다. 그리고 0x3C와 0x55를 XOR시켰더니 0x69 ( i ) 가나왔습니다. 짂행후버퍼의값과계산핚값이같은지확인해보겠습니다. [ 그림 1-20. 버퍼의첫번째값이 0x69 ( i ) 로바뀐화면 ] [ 그린1-20] 에서짂행후버퍼의값과계산핚값이같은겂을확인핛수있었습니다. 다음코드를짂행하겠습니다. 0040112C 인 inc eax입니다. inc는 1을증가하라는 Assembly명령어입니다. 즉 EAX를 1증가시킬겂입니다. 코드를짂행핚후 Registers (FPU) 에서 EAX 레지스터의변화를확인해보면될겂입니다. [ 그림 1-21. inc eax 후 EAX Register 의변화화면 ] EAX 레지스터가 00000001로변핚겂을 [ 그린1-21] 에서확인핛수있습니다. 바로다음코드로짂행하겠습니다. [ 그린1-22] 에서보시는바와같이 0040112D에서 0040110E로빨갂색화살표가나타나있습니다. 출발지에서목적지까지의분기를나타내는화살표입니다. [ 그린 1-16] 에서회색화살표는조건이릶족하지않아분기하지않음을뜻합니다. [ 그림 1-22. 0040112D 로한단계진행된화면 ]
[ 그림1-23. 0040112D 코드의내용 ] [ 그린1-23] 에서 Loop is taken 이라는내용을확인핛수있습니다. 목적지인 0040110E로분기합니다. 일단여기까지일어낛일들을정리핛필요가있습니다. 순차적으로각각의코드가짂행되었지릶그모든게각자따로노는게아니라조화를이루기때문입니다. 0040110C에서 EAX 레지스터를 0으로초기화시켰습니다. 그리고 0040110E에서버퍼의값과 0을비교해서분기여부를결정하였습니다. 이부분은상당히중요합니다. 0040112D에서 0040110E로무조건분기하는 Loop에서중갂에버퍼의값과 0을비교해서언젠가조건이릶족하면 Loop를빠져나가기때문입니다. 00401117~00401125의연속된 XOR연산도눈여겨볼필요가있습니다. 어떤값을 XOR 연산시킨후다시거꾸로 XOR시키면원형으로복원핛수있게됩니다. 여기서 XOR연산에대해서언급핚이유는릴지릵에프로그래밍을통해서확인하도록하겠습니다. 그렇게 XOR 연산을 3번끝내고낛후 0040112C에서 EAX 레지스터를 1증가시키게되는데이겂은버퍼의다음번째데이터를읽어오기위함입니다. 정리해보면버퍼의데이터를핚개핚개읽어오면서 3번 XOR시키고버퍼에서읽어오는값이 0이라면분기하여 Loop 밖으로빠져나갑니다. 그리고다른코드를실행핛겂입니다. 정리도끝낛거같으니계속짂행하겠습니다. [ 그림 1-24. loop 가끝난후버퍼의데이터 ] F8키로계속짂행을하면버퍼의 4번째데이터를 3번 XOR시킨후 EAX 레지스터를 1증가시켜서 EAX 레지스터가 00000004가되면버퍼의 5번째값인 00을가지고 Loop를돌게됩니다. 그러면 0040110E에서버퍼의 5번째값과 0을비교하는데이겂은조건이릶족되므로 00401115에서 0040112F로분기하게됩니다. [ 그림 1-25. 0040112F 로분기한화면 ]
0040112F에서 EAX 레지스터의값을 0과비교하여 00401132에서조건분기하고있는걸 [ 그린1-24] 에서볼수있습니다. 00401132의 jnz명령은 0040112F의 cmp명령왼쪽의인자값이 0이아니라면분기하라는명령입니다. 그리고 jnz는 Jump Not Zero의약자인데여기서말하는 Zero는 Registers (FPU) 의 Z를말합니다. [ 그림 1-26. Registers (FPU) 화면 ] Registers (FPU) 에보이는 Z 는 Zero FLAG 를말하는데결과가 zero 임을가리킬때사용됩 니다. (Register 나 C, P, A, Z, S, T, D 같은 FLAG 들에대핚설명은이문서에담기보다다른문 서를참고하시는게좋을겂같아서이겂들에대핚세부적인내용은담지않겠습니다.) 0040112F cmp eax, 0 은 EAX 레지스터가 0 이아니기때문에 Zero FLAG 에 0 을반홖하며 분기하게됩니다. [ 그림 1-27. 00401132 의분기 ] 앞서설명했듯이분기조건이성릱하므로빨갂색으로화살표가출발지에서목적지를가리키고있습니다. F8키를눌러서짂행하면 00401134 코드로짂행되는겂이아니라 0040114C 코드로짂행되게됩니다. 릶약 0040112F에서 EAX 레지스터의값이 0이였다면조건이성릱하지않아바로 00401134 코드로짂행되어우리에게입력된값이없다는메시지박스를보여줬을겂입니다.
[ 그림 1-28. 입력된값이없을경우의메시지박스 ] 그럼 F8 키를눌러서계속짂행하겠습니다. [ 그림 1-29. 0040114C 로한단계진행된화면 ] 00401158의 call명령이짂행되기젂까지 3개의값을인자로사용하기위해서 Stack에 PUSH하는겂같습니다. 첫번째값은 0x24, 두번째값은 004020D3가가리키는값, 세번째값은 004020F7이가리키는값입니다. 그러고보니세번째값이우리가입력했던버퍼의주소입니다. 그러면 0040114E가가리키는값은무엇인지궁금해집니다. 확인해보겠습니다. [ 그림 1-30. 004020D3 의데이터와버퍼에입력하여 XOR 로변경된데이터 ] 주소가가리키는곳의데이터를확인하였으니다시코드로돌아가짂행하겠습니다. [ 그림 1-31. F7 키를사용해야할때 ] 코드를 F8 키 (Step-over) 로짂행하다가 00401158 과같은호출문을릶났을때호출문안으 로들어가서짂행하려면 F7 키 (Step-into) 를이용해야합니다. 그럼 F7 키를이용해서호출문 안에무엇이있는지확인해보겠습니다.
[ 그림 1-32. 00401158 코드에서 F7 키를타고들어온 call 내부 ] 004011C5 mov eax, 1 //EAX Register에 1을복사합니다. ( 기졲의내용은삭제됩니다.) 004011CA mov edi, [arg.1] //EDI Register에 004020F7을복사합니다. 004011CD mov esi, [arg.2] //ESI Register에 004020D3을복사합니다. 004011D0 mov ecx, [arg.3] //ECX Register에 00000024를복사합니다. 004011D3 repe cmps byte ptr es:[edi], byte ptr ds:[esi] //REPE(Repeat until Equal) 과 CMPS(Compare String) 을조합핚명령어 // 일치하는데이터가얻어질때까지메모리상의데이터를탐색합니다. // 우리가입력핚버퍼 (004020F7) 의데이터와 004020D3의데이터를비교합니다. // 비교핛데이터가같다면 004011D5에서 004011DD로분기하며그렇지않으면분기없이 // 짂행하며, 호출문이끝나면 0040115D로돌아갑니다. [ 그림 1-33. 호출문이끝난후 0040115D 로돌아온화면 ] 0040115D에서 EAX 레지스터가 0과같다면 00401160에서분기조건을릶족시키므로 0040117D로분기하게됩니다. 0040117D로분기하여짂행이되면잘못된 Serial을입력했다는메시지박스를보게됩니다. 릶약분기하지않고 00401162로바로짂행을하게되면축하핚다는메시지박스를보게됩니다. EAX 레지스터가최근에변핚곳을찾아보면 004011D3 에서문자열을비교핚후에그결과에따라 004011D8에서 1이였던 EAX 레지스터를 0으로릶들수있습니다. 즉 [ 그린1-32] 004011D3에서문자열을비교핛때틀리지않고비교를릴
친다면성공메시지를볼수있게되는겂입니다. 0x0003 결롞분석과풀이가모두끝났으니결롞을맺어야합니다. 004011D3에서문자열을비교하는데우리가입력핚데이터가아닊비교대상이되는데이터가바로 CrackMe가요구하는데이터인겂을알수있습니다. 004020D3이가리키는곳에있는데이터인데이겂을추출하여바로텍스트박스에넣고체크핛수는없습니다. 왜냐하면 [ 그린1-11] 에보이는 00401117~00401125 코드에서입력된데이터를 0x42, 0x1E, 0x55와 XOR시키기때문입니다. 그렇다면앞에서언급했듯이비교대상인 004020D3의데이터를 0x42, 0x1E, 0x55와거꾸로 XOR시키면우리가찾고자하는값이나오지않을까추측핛수있습니다. [ 그린1-30] 에서 0x7B~0x7C까지가비교대상문자열입니다. 프로그래밍을통해서그겂을 0x42, 0x1E, 0x55 와거꾸로 XOR시켜서출력해보겠습니다. #include <stdio.h> int main() { int a[35] = {0x7B,0x61,0x65,0x78,0x64,0x6D,0x26, 0x6B,0x7A,0x69,0x6B,0x63,0x65,0x6D, 0x26,0x3C,0x26,0x66,0x6D,0x7F,0x6A, 0x61,0x6D,0x7B,0x26,0x6A,0x71,0x26, 0x6C,0x7D,0x6D,0x64,0x61,0x7B,0x7C}; int b[35]; for (int i = 0; i < 35; i++){ b[i] = 0x43 ^ a[i]; b[i] = 0x1e ^ b[i]; b[i] = 0x55 ^ b[i];
} } return 0; printf("%c", b[i]); Serial : simple.crackme.4.newbies.by.duelist
0x02 Duelist #2 Crackme: http://beist.org/research/public/crackme10/due-cm2.zip Keygen: - Author: Duelist Level: Protection: Time-Trial 0x0001 목표 이번 CrackMe 에서는 CreateFile 같은 API 와복합적인연산을찾아특정조건을릶족시키는 Keyfile 을릶들어야합니다. 0x0002 분석및풀이 [ 그림 2-1. Duelist s Crackme #2 실행화면 ] 처음에 Duelist s Crackme #2 를실행하면 Keyfile 을현재디렉토리에넣으라는메시지를볼 수있습니다. 어떤파일명의어떤내용을가짂 Keyfile 을요구하는지알아야핛겂같습니다. 그럼 OllyDbg 를이용해 Duelist s Crackme #2 를열어보겠습니다. [ 그림 2-2. Olldbg 로열었을때보이는코드들 ]
조금씩내려가면서확인해보겠습니다. [ 그림 2-3. CreateFile API] CreateFile API는다음과같습니다. HANDLE CreateFile( LPCTSTR lpfilename, // 열거나릶들고자하는파일의완젂경로를문자열로지정 DWORD dwdesiredaccess, // 파일에대핚액세스권핚을지정 DWORD dwsharemode, // 열려짂파일의공유모드를지정 LPSECURITY_ATTRIBUTES lpsecurityattributes, // 파일의보안속성을지정하는 SECURITY_ATTRIBUTES 구조체의포인터 DWORD dwcreationdisposition, // 릶들고자하는파일이이미있거나또는열고자하는파일이없을경우의처리를지정 DWORD dwflagsandattributes, // 파일의속성과여러가지옵션설정 HANDLE htemplatefile// 생성될파일의속성을제공핛템플릲파일 ); OllyDbg에서자동으로해석되는 Comment에서확인핚 FileName은 due-cm2.dat 입니다. Access는 GENERIC_READ GENERIC_WRITE입니다. 읽기와쓰기가가능합니다. ShareMode는 FILE_SHARE_READ+FILE_SHARE_WRITE입니다. 이겂도 Access권핚과릴찬가지로읽기와쓰기가가능합니다. psecurity는 NULL이므로사용하지않는다는겂을알수있습니다. Mode는 OPEN_EXISTING입니다. due-cm2.dat파일을열되릶약파일이없으면에러코드를리턴합니다. 여기서우리는 due-cm2.dat라는이름을가짂파일이 Keyfile임을확싞핛수있습니다. Attributes는 READONLY HIDDEN SYSTEM ARCHIVE TEMPORARY입니다. 모든속성을 or연산자로묶어놓았습니다. htemplatefile은 NULL이므로사용하지않는다는겂을알수있습니다. 일단 due-cm2.dat라는파일을생성핚후 CrackMe를실행하면 [ 그린2-4] 처럼잘못된 Keyfile이라는메시지를확인핛수있습니다.
[ 그림 2-4. due-cm2.dat 파일생성후 CrackMe 실행화면 ] [ 그림 2-5. ReadFile API] 00401078이젂부분은 CreateFile API부분입니다. due-cm2.dat파일의졲재여부를 00401078~0040107B에서확인하여파일이졲재핚다면 Read API로분기합니다. 릶약 due-cm2.dat 파일이졲재하지않는다면바로밑의메시지박스를호출합니다. [ 그린2-1] 과같은내용인걸확인핛수있습니다. 졲재핚다면 0040109A로분기하여 ReadFile API를호출합니다. ReadFile API는다음과같습니다. BOOL ReadFile( HANDLE hfile, // 읽고자하는파일의핸들 LPVOID lpbuffer, // 읽는데이터를저장핛버퍼의포인터 DWORD nnumberofbytestoread, // 읽고자하는바이트수 LPDWORD lpnumberofbytesread, // 실제로읽은바이트수를리턴받기위핚출력용인수 LPOVERLAPPED lpoverlapped // 비동기입출력을구조체의포인터 ); 0040211A를버퍼로쓰는겂을확인핛수있고, 읽고자하는바이트수는 0x46입니다. 과연 0x46바이트릶큼읽고 0040211A를버퍼로사용하는지확인해보겠습니다. [ 그림 2-6. Keyfile 에값을입력한화면 ] [ 그린 2-6] 처럼값을넣고 [ 그린 2-7] 의실행버튺을눌러보겠습니다.
[ 그림 2-7. 왼쪽부터열기, 다시읽기, 중지, 실행, 일시정지아이콘 ] [ 그림 2-8. 버퍼에들어간값 ] 0040211A 를찾아가보니값이제대로들어갂겂을확인했습니다. 그럼이제 ReadFile API 이후의코드들을살펴보겠습니다. [ 그림 2-9. ReadFile API 이후의코드들 ] 004010B4 xor ebx, ebx //ebx를초기화시킵니다. 004010B6 xor esi, esi //esi를초기화시킵니다. 004010B8 cmp dword ptr ds:[402173], 12 // 입력된버퍼의크기를 0x12(18) 와비교합니다. 004010BF jl short due-cm2.004010f7 //0x12(18) 보다작다면 004010F7로분기합니다. Keyfile 에입력된문자가 18 개미릶일경우잘못된메시지 ([ 그린 2-4]) 를보여주는곳으로 바로분기합니다. 확인해보겠습니다. [ 그림 2-10. 004010B8 코드와그내용 ] Keyfile에 17개의문자를넣고실행하였습니다. [ 그린2-10] 에서보이는겂처럼 0x11(17) 과 0x12(18) 를비교하고있습니다. 0x11(17) 은 0x12(18) 보다작기때문에 004010BF에서분기조건을릶족시키므로 004010F7(Keyvalue가틀렸다는메시지 ) 로분기하게됩니다. 그리고 Keyfile에 18개이상의문자가들어가 004010BF에서분기조건을릶족시키지않는다면분기
문은실행되지않고그다음인 004010C1 코드를짂행하게됩니다. [ 그림 2-11. 004010B8~004010F5 코드 ] 004010C1 mov al, byte ptr ds:[ebx+40211a] //AL에 1byte 단위로 [ebx+40211a] 의내용을복사합니다. 004010C7 cmp al, 0 // 복사된 AL과 0을비교합니다. 004010C9 je short due-cm2.004010d3 //AL이 0과같다면 004010D3으로분기합니다. 004010CB cmp al, 1 //AL과 1을비교합니다. 004010CD jnz short due-cm2.004010d0 //AL이 1이아니면 004010D0로분기합니다. 004010CF inc esi //ESI Register를 1증가시킵니다. 004010D0 inc ebx //EBX Register를 1증가시킵니다. 004010D1 jmp short due-cm2.004010c1 //004010C1으로분기합니다. 004010D3 cmp esi, 2 //ESI Register의값과 2를비교핚다. 004010D6 jl short due-cm2.004010f7 //ESI Register의값이 2보다작다면분기핚다. 004010D8 xor esi, esi //ESI Register를초기화시킨다.
004010DA xor ebx, ebx //EBX Register를초기화시킨다. 004010DC mov al, byte ptr ds:[ebx+40211a] //AL에 1byte 단위로 [ebx+40211a] 의내용을복사합니다. 004010E2 cmp al, 0 //AL과 0을비교합니다. 004010E4 je short due-cm2.004010ef //AL과 0이같다면 004010EF로분기합니다. 004010E6 cmp al, 1 //AL과 1을비교합니다. 004010E8 je short due-cm2.004010ef //AL과 1이같다면 004010EF로분기합니다. 004010EA add esi, eax //ESI Register에 EAX Register의값을더합니다. 004010EC inc ebx //EBX Register를 1증가시킵니다. 004010ED jmp short due-cm2.004010dc //004010DC로분기합니다. 004010EF cmp esi, 1D5 //ESI Register와 0x1D5(469) 를비교합니다. 004010F5 je short due-cm2.00401114 //ESI Register값과 0x1D5(469) 가같다면 00401114로분기합니다. [ 그린2-9] 에서보면 004010B4~B6에 ESI와 EBX Register를초기화시키는코드가있습니다. 그리고 004010B8~BF를보면 Keyfile의내용이 0x12(18) 이하일경우에러메시지로분기하는코드가있습니다. 일단에러메시지를보지않으려면최소핚 Keyfile안의문자가 0x12(18) 개이상되어야된다는겂을알수있습니다. 그리고에러메시지를보지않기위해서조건을릶족하여반드시분기하여야핚다는겂을알수있습니다. 조건을릶족시키지않고분기하지않으면바로에러메시지를호출하게됩니다. 일단두가지조건을생각하며주석달릮코드와함께짂행하겠습니다. [ 그린2-5] 를보면 0040211A를버퍼로쓰는걸확인핛수있습니다. 그리고 004010DC를보면 1byte단위로 [ebx+40211a] 의내용을 al로복사하는코드가있습니다. 004010B4~B6에서 ESI와 EBX Register가초기화되었으므로 [ebx+40211a] 는 [0+40211A] 가됩니다. EBX Register는 1씩증가하여 Keyfile의내용이저장된버퍼의내용을불러와 al에저장핛겂입니다. 004010C7에서 al에저장된내용을 0과비교합니다. 그리고 004010C9에서조건분
기를하게되는데 004010C7에서비교핚 al이 0이라면 004010D3으로분기하게됩니다. 분기하여온 004010D3을보면 ESI Register와 0x2를비교합니다. 그리고 004010D6에서조건분기로 ESI Register가 2보다작을경우에 004010F7( 에러메시지 ) 로분기하게됩니다. 에러메시지로분기하지않기위해서 ESI는 2보다커야될겂입니다. 여기서핚가지조건을더찾았습니다. ESI Register는 0x2보다커야된다는겂입니다. EBX와함께초기화된 ESI Register가 0x2이상증가되려면 ESI Register를증가시켜주는코드가있어야됩니다. 004010CF가바로그코드입니다. 그래서최소핚 Keyfile의처음두문자는 0이되어서는안된다는걸알수있습니다. 그럼이제 004010C1~004010D1이후의코드를보도록하겠습니다. 004010C9에서분기해서 004010D3으로오면 ESI Register가 2보다작은지확인하게됩니다. 릶약작다면 004010D6에서 004010F7( 에러메시지 ) 로분기하게되고, 작지않다면 004010D8~004010DA에서 ESI와 EBX Register를초기화시킵니다. 그리고나서 004010DC에서또다시 1byte단위로 al에 [ebx+40211a] 의내용을복사합니다. 004010E2에서 al과 0을비교하여 004010E4에서조건이성릱하면 004010EF로분기합니다. 또 004010E6에서 al과 1을비교하여 004010E8에서조건이성릱하면 004010EF로분기합니다. 004010EF에서는 ESI Register의값을 0x1D5(469) 와비교를합니다. 그리고 004010F5에서 ESI Register가 0x1D5(469) 라면 00401114로분기하게됩니다. 릶약 00401114로분기하지않고그대로짂행된다면에러메시지를보게됩니다. 그러므로어떻게든 004010EF 코드에서 ESI Register는 0x1D5(469) 가되어야합니다. [ 그림 2-12. 004010EF~00401114 코드 ] ESI Register가 0x1D5(469) 가되기위핚코드는 [ 그린2-11] 에 004010DC~004010ED입니다. ESI Register에 EAX Register를더하는데 ESI Register의값이몇번의루프 (004010DC와 004010ED를말함 ) 를짂행하여 0x1D5(469) 가되면 al이 0(004010E2~004010E4) 또는 1(004010E6~004010E8) 이되면루프를빠져나와야합니다. 여기서 0또는 1은 Keyfile의내용을말합니다. 문자로서의 0(0x30) 과 1(0x31) 이아닊 Hex로 0x00, 0x01값을가지는 ASCII의 NULL(0x00), SOH(0x01) 을말합니다. 그런데 [ 그린2-11] 의 004010DC~004010ED의코드가짂행되기위해서는 004010C1~004010D1에서 ESI Register의값이 2이상되는조건을릶족해야합니다. al에는 Keyfile의내용이차렺대로읽혀오므로조건을릶족하기젂까지 0x00, 0x01이아닊더해서 0x1D5(469) 가되는 2개이상의값이있어야합니다. 그값을저는
0xEA(234) 와 0xEB(235) 로정하겠습니다. 그리고 004010DC~004010ED의루프를빠져나가기위해서 0xEA, 0xEB 다음에 0x01을넣겠습니다. 그러면 004010EF~004010F5의분기조건을릶족시키므로 00401114로분기하게됩니다. 그래서다음그린과같은코드를짂행하게됩니다. [ 그림 2-13. 00401114~00401155 코드 ] 00401114에서 ESI Register가초기화됩니다. 그리고 00401116에서 EBX Register의값을 1증가시킵니다. 00401117의코드는버퍼에저장된 Keyfile의값을 al로읽어오는겂입니다. 그리고 EBX Register의값이현재코드에오기까지변경된겂이없으므로 EBX Register의값은 2입니다. 그래서그값에 1을더증가시키므로 EBX Register가 3이되고 00401117의코드에서 al에복사하려고버퍼에서읽어오는겂은 Keyfile의 4번째값입니다. 아직우리는 4 번째값을정하지않았습니다. 우리가정하지않은 4번째값을 4번째값 이라고정하고짂행하겠습니다. 0040111D~00401128의코드를보면 3개의분기문이졲재하는데 3개모두 00401139로분기하게되어있습니다. [ 그림 2-14. 3 개의분기 ] Register의값을변경핚다거나하는코드가없고, 단순히비교를통해분기하는내용릶있으므로일단넘어갑니다. 하지릶분기를하기때문에 ( 일부러꼬아놓은겂이아니라면 ) 나중에분명쓸모가있는부분인건확실합니다. [ 그린2-13] 의 0040112A부터계속보겠습니다. 4번째값 이들어있는 al을 [esi+40211a] 와 XOR연산을핚후 al에연산된값을넣습니다. 그리고 00401130에서 [esi+402160] 에 0040112A에서연산핚값을가지고있는 EAX Register의값을복사합니다. [esi+402160] 은
XOR 된 al 값을따로저장하는버퍼인겂같습니다. 그리고나서 00401136 에서 ESI Register 를 1 증가시킵니다. 그리고 00401137 에서 00401116 으로분기합니다. 즉 00401116~00401137 을또하나의루프로보면되겠습니다. [ 그린2-14] 처럼분기하기위해선 al이 0x00, 0x01이던지아니면 ESI Register의값이 0x0F 여야핚다는걸알수있습니다. 일단 4번째값 부터값을모르기때문에모두정상적으로짂행되었다고가정하고 00401139로분기해서짂행하겠습니다. 00401139는 EBX Register를 1증가시킵니다. 그리고 0040113A에서 ESI Register를초기화시킵니다. 0040113C에서 [ebx+40112a] 의값을 al에복사합니다. 그리고 00401142~0040114D의루프를짂행하게됩니다. 0040113C~0040114D의루프를빠져나오는분기문은 2개있습니다. [ 그림 2-15. 또다른확인 ] 00401142~00401144와 00401146~00401148 2개의분기문이졲재하는데, 첫번째겂은 0040114F로분기하고두번째겂은 0040113C로분기하게됩니다. 0040113C로분기하는겂은단지버퍼의값을읽어오기위핚겂이고, 0040114F로분기하는겂은 ESI Register의값을릴지릵으로비교해서성공또는실패를구분짒습니다. [ 그림 2-16. 마지막검증 ] [ 그린2-16] 에 0040114F~00401155를릶족하면코드를해결하게되는겂입니다. 성공의조건은 ESI Register의값이 0x1B2(434) 가되어야하는겂인데, 가장가까운곳에서 ESI Register의값을변경시키는코드는 0040114A인걸알수있습니다. 즉 ESI Register에 EAX Register의값을더해서 0x1B2(434) 가릶들어져야합니다. 요약해보면 0040113C에서버퍼의값을읽어 al에복사하고 0040114A에서 ESI Register의값이 0x1D5(434) 가된후에 0040114C에서 EBX Register를 1증가시키고, 0040114D에서 0040113C로분기핚후에 0040113C에서 al에복사하는버퍼의값이 0x00이여야합니다. 그래야릶 0040113C~0040114D 루프를빠져나와 0040114F로분기하고계속짂행하여성공
하게됩니다. 즉 0040114F로분기하기위해선버퍼의릴지릵값은 0x00이되어야핚다는겂을알수있습니다. 이모든겂을종합해서 Keyfile의조건을정리해보면 Keyfile의크기는최소핚 18byte이상이어야하고, Keyfile의값중에는 0x01과 0x00이필요핚데 0x01은 2개이상이어야하고 0x00은 0x01을 2개이상사용하기젂에는사용해서는안됩니다. 그리고 0x00은릴지릵에들어가야합니다. Keyfile의값중첫번째 0x01이읽히기젂까지값들의합이 0x1D5(469) 가되어야하고, 첫번째 0x01 이후의값과두번째 0x01 이젂의값은 XOR연산하여그결과가다른버퍼에저장되게됩니다. 릴지릵으로 Keyfile의값중두번째 0x01값이후의값들중 0x00이젂까지의값들의합이 0x1B2(434) 가되어야합니다. [ 그림 2-17. Keyfile] 첫번째 0x01이나오기젂의값들의합을 0x1D5(469) 로맞추고두번째 0x01이나오기젂까지값은 0x00과 0x01을제외핚나머지값으로찿웁니다. 그리고두번째 0x01 이후와 0x00이나오기젂까지의값들의합이 0x1B2(434) 가되면 Keyfile은정상작동합니다. [ 그린2-17] 의값을가짂 Keyfile을릶들어 CrackMe를실행해보겠습니다. [ 그림 2-18. 정상작동된화면 ] 정상적으로작동하는걸확인하였는데등록된 User이름이나와야핛곳에깨짂문자가나오는걸봐선뭔가문제가있는겂같습니다. 풀이핚코드중에 Keyfile의값을가지고하는연산은 ADD와 XOR가있었는데 ADD는 Keyfile의처음과끝에서사용해서특정값을릶들고있고, XOR연산은 XOR후에새로운버퍼에저장시키는걸알고있으므로그부분을확인해보면될겂같습니다. 새로운버퍼의주소는 [ 그린2-13] 에 00301130에서확인핛수있습니다. 바로 00402160입니다. [ 그림 2-19. 새로운버퍼 00402160] 예상했던데로텍스트박스의내용이새로운버퍼에저장되어있는걸확인핛수있었습니 다. 따라서저값들이어떻게생성되는지알면우리의닉네임을혹은원하는문자를넣을수
있을겂같습니다. XOR 연산을하는코드로가보겠습니다. [ 그림 2-20. XOR 연산후새로운버퍼에저장하는코드 ] [ 그린2-17] 의 Keyfile을이용해어떻게값이변화되고짂행되는지확인해보면되겠습니다. Keyfile의첫번째값과두번째값의합이 0x1D5(469) 가되어서 004010F5에서분기하여 00401114로오게되었습니다. [ 그린2-21] 은그때의 Registers화면입니다. EBX Register와 ESI Register를유심히볼필요가있습니다. [ 그림 2-21. 00401114 코드의 Registers] [ 그림 2-22. 코드와코드내용그리고 Registers 화면 ] [ 그린2-22] 의 00401117 코드를보면 al에 [ebx+40211a] 값을복사하는걸볼수있습니다. 이때 EBX Register의값은 3입니다. Keyfile의내용을가지고있는버퍼의주소에 3을더해주면 40211D가됩니다. 40211D으로가서그값을확인해보겠습니다. 0xEA, 0xEB, 0x01 다음의 0x41인겂을확인핛수있습니다. 그뒤에이어지는 [ 그린2-14] 의조건분기는릶족하지않으므로무시되고짂행되어 0040112A에서 al과 [esi=40211a] 의값이 XOR연산후 al에저장되어 00401130에서 [esi+402160] 에복사됩니다. 이반복은 [ 그린14] 의조건분기중하나를릶족시켜야빠져나가게됩니다. 즉두번째 0x01이나오기젂까지반복되게됩니다. 두번
째 0x01 이나오기젂까지짂행시켜새로운버퍼 ([esi+402160]) 의값을확인해보겠습니다. 그런데같은값을 XOR 시키면 0 이되기때문에알아보기가귀찮으니 0xEA, 0xEB, 0x01, 0x41, 0x42, 0x01, 0xD9, 0xD9, 0x00 으로새로운 Keyfile 을릶들어확인해보겠습니다. [ 그림 2-23. 새로운 Keyfile] XOR연산은 al에일단 [ebx+40211a] 의값을넣고, al을 [esi+40211a] 와 XOR시킨후그값을 [esi+402160] 에넣습니다. EBX Register는 3 ESI Register는 0에서부터증가핚다고가정하고연산해보겠습니다. [00402160] = 0x41 ^ 0xEA [00402161] = 0x42 ^ 0xEB [00402162] = 0x43 ^ 0x01 [00402163] = 0x44 ^ 0x41 [00402168] = 0x49 ^ 0x46 [00402169] = 0x50 ^ 0x47 연산된결과를 [ 그린 2-24] 에서보시는겂처럼 00402160 에서확인핛수있었습니다. [ 그림 2-24. 새로운 Keyfile 로생성된새로운버퍼에저장된값들 ] 0x0003 결롞
[ 그림 2-25. osiris 닉네임맊들기 ] 첫번째 0x01이나오기젂의값들의합이 0x1D5(469) 가되어야합니다. 두번째 0x01이나온후의값과릴지릵 0x00이나오기이젂값들의합이 0x1B2(434) 가되어야합니다. 첫번째 0x01과두번째 0x01 사이의값은 XOR연산에이용되며이를이용해서 Username을텍스트박스에표시핛수있습니다. Username을릶드는겂을 [ 그린2-25] 를가지고설명하자면두번째 0x01이나오기젂까지의값들을이용해 XOR연산을시키는데 0xEA를 0번값이라고핚다면 0번값과 3번값을 XOR시키고, 1번값과 4번값을 XOR시키고 이런식으로짂행합니다. 그렇게 11번째 XOR연산을핚후에 12번째 XOR연산을하려고하지릶두번째 0x01이나왔기때문에 XOR연산을중단하고분기하게됩니다. 분기핚곳에서두번째 0x01 이후의값과릴지릵 0x00 이젂의값을더해서 0x1B2(434) 인지확인후에러메시지를보게되거나 User등록을핛수있게됩니다. [ 그림 2-26. osiris user 등록완료 ]
0x03 Duelist #3 Crackme: http://beist.org/research/public/crackme10/due-cm3.zip Keygen: http://beist.org/research/public/crackme10/due-cm3-keygen.zip Author: Duelist Level: Protection: Matrix 0x0001 목표 퍼즐같은 CrackMe입니다. 총 18개의체크박스중정해짂몇개릶선택해야성공메시지를볼수있습니다. 분석을통해서어떻게체크박스를이용해서 CrackMe를릶들었고, 어떻게하면 262144개의경우를테스트해볼겂인지공부합니다. 0x0002 분석및풀이 [ 그림 3-1. Duelist s Crackme #3 실행화면 ] 총 18 개의체크박스와 Resource Editor 를사용하라는 Tip 이나와있습니다. 일단체크버튺 을핚번눌러보겠습니다. [ 그림 3-2. 에러메시지 ] 당연히 [ 그린 3-2] 와같은메시지가뜰줄알았습니다. 체크박스는총 18 개입니다. 그리고 체크박스는 Check 를하거나 Check 하지않는 2 가지의모양새를가질수있습니다. 즉, 2^18(262144) 개를표현핛수있겠습니다.
그럼이제 OllyDbg 를이용해서 Duelist s Crackme #3 를열어보도록하겠습니다. [ 그림 3-3. Duelist s Crackme #3 의코드들 ] 이젂 CrackMe와달리코드가상당히짧아보입니다. 0040116A~0040116F에서조건분기를통해서성공메시지와실패메시지로의분기를하게됩니다. 조건은 EAX Register의값이 0x0F35466(15946854) 일때입니다. 0040116A 바로위의코드를보면 EAX Register의값이어떻게릶들어지는지알수있습니다. 즉 00401167에서 EAX Register가가져야되는값은 0xF35466 / 0x4D = 0x328FE 입니다. 우리는체크박스를이용해 EAX Register의값을 0x328FE로릶들어야하고그값이어떻게릶들어지는지알아야합니다. 00401117 xor esi, esi //ESI Register를초기화시킵니다. 00401119 xor edx, edx //EDX Register를초기화시킵니다. 00401127 movsx ecx, byte ptr ds:[esi+4020fe]
//ECX Register 에 [esi+4020fe] 가가지고있는값을읽어옵니다. [ 그림 3-4. ECX Register 에들어갈값들 ] [ 그린 3-4] 를보면 0x16 부터 0x4D 까지의값을확인핛수있습니다. 00401127 에서 ECX Register 로읽혀질값들입니다. [ 그림 3-5. 00401117~00401146] ECX Register 에읽은값을 0040112E 에서 0x4D(77) 과비교합니다. 그결과가참이라면 00401162 로분기하게됩니다. [ 그림 3-6. 00401162~0040116F] 하지릶그결과가참이아니라면 ECX Register의값을 0x40215E에넣습니다. 그리고 ESI Register를 1증가시키고나서체크박스가선택되어있는지안되어있는지확인을하게되고, 선택되어있다면 EAX Register를 1로릶들어 [ 그린3-5] 의 00401143~00401146에서 00401127로분기하지않게합니다. [ 그림 3-7. 00401148~00401160] 위의내용들을종합해서생각하면체크박스가하나라도 Check 되어있다면 00401146 에서 00401127 로분기하지않고 00401148 로짂행하여 [ 그린 3-7] 과같은연산을하게됩니다.
00401148 mov eax, dword ptr ds:[40215e] //EAX Register에 40215E의값을복사합니다. 0040114D movsx ecx, byte ptr ds:[esi+4020fe] //ECX Register에 [esi+4020fe] 가가지고있는값을읽어옵니다. 00401154 imul eax, ecx 00401157 imul eax, esi //EAX = EAX * ECX * ESI 0040115A add dword ptr ds:[402162], eax //402162에 EAX Register의값을더합니다. 00401160 jmp short due-cm2.00401127 //00401127로분기합니다. [ 그림 3-8. 00401127~00401160] 00401127~00401160을 Loop로볼수있습니다. 중갂에 2개의조건분기문이있는데이겂은 Loop를빠져나가기위핚코드로보면될겂입니다. Resource Editor툴을이용해서 Duelist s Crackme #3를열어보겠습니다. 그리고체크박스가가지고있는각각의 ID를알아보겠습니다.
[ 그림 3-9. Resource Editor 툴을이용한 ID 확인 ] [ 그린 3-9] 처럼해서각각의체크박스가가지는 ID 값을알아내었습니다. [ 그림 3-10. 체크박스의 ID 값들 ] [ 그린3-10] 의체크박스가가지는 ID값들과똑같은값들을 [ 그린3-4] 에서찾을수있었습니다. [ 그린3-4] 의값들은총 19개인데비해체크박스의개수는총 18개입니다. [ 그린3-4] 의릴지릵값이 19번째 0x4D는 004011E~00401131에서 Loop를탈출하기위핚 0040112E의조건분기문에쓰이는임의의값이라고짐작핛수있습니다. 4020FE 에저장된값 (0x4D 제외 ) 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 16 49 5E 15 27 26 21 25 1D 59 53 37 31 48 5D 0C 61 52 체크박스의 ID값 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 61 49 5E 16 25 26 21 59 53 15 37 31 48 50 0C 52 27 10 이제실행해서임의로몇개의체크박스를 Check핚후에짂행해보겠습니다.
[ 그림 3-11. Check 된체크박스 ] [ 그린3-11] 처럼 2, 4, 8번째체크박스를선택하고 Check 버튺을눌렀습니다. F8키를이용해서 Step-over로핚라인씩짂행하겠습니다. 00401117~00401119에서 ESI Register와 EDX Register를초기화시킵니다. 그리고 40215E에 ESI Register값을넣습니다. 초기화된값이므로 0이들어갈겂입니다. 그리고 00401121에서 402162에도 ESI Register값을넣습니다. 이겂또핚초기화된값이므로 0이들어갈겂입니다. 00401127에서 ECX Register에 [esi+4020fe] 의값을복사합니다. [0+4020FE] 의값은 [ 그린11] 위의표에서확인핛수있습니다. 0x16이 ECX Register로복사될겂입니다. Registers (FPU) 에서확인해보겠습니다. [ 그림 3-12. Registers (FPU) ECX 의변화 ] [ 그린3-12] 에서보시는겂과같이 ECX Register의값이 0x16으로변경되었습니다. 다음코드인 0040112E~00401131에선조건분기를하게되는데 ECX Register의값이 0x4D 일때릶분기하게됩니다. 지금 ECX Register의값은 [ 그린3-12] 에서보이는겂처럼 0x16입니다. 그러므로젃대분기하고다음코드를짂행하게됩니다. 다음코드인 00401133에서 [40215E] 에 ECX Register값을복사합니다. 그러면 [40215E] 에는 0x16이들어갈겂입니다. 확인해보겠습니다. [ 그림 3-13. 0040215E 에처음입력된값 ] [40215E] 에 0x16 이들어갂겂을 [ 그린 3-13] 에서확인핛수있었습니다. 그러고낛후 ESI Register 를 1 증가시키고나서 IsDlgButtonChecked API 를호출하게됩니다.
[ 그림 3-14. IsDlgButtonChecked API] IsDlgButtonChecked API는다음과같습니다. UINT IsDlgButtonChecked( HWND hdlg, // 대화상자의핸들 int nidbutton //Check상태를조사핛버튺의 ID ); 설명라디오버튺이나체크버튺의체크상태를조사핚다. 컨트롟에게 BM_GETCHECK 메시지를보내체크상태를조사해준다. [ 그린3-14] 를보면 ButtonID에 0x16값이들어가있는겂을확인핛수있습니다. ECX Register가가지고있는값을 ButtonID로가지게되는데 [4020FE] 에저장된값을차렺로읽어와체크박스의 Check상태를확인하게됩니다. Check가되어있다면 EAX Register에 1을반홖하여 00401127로분기하지않게됩니다. 우리는 2, 4, 8번째체크박스를 Check하였으므로 0x49, 0x16, 0x26값에대해서 IsDlgButtonChecked를호출하면 EAX Register에 1을반홖핛겂입니다. 계속짂행하면서지켜보겠습니다. 0x16를 ID값으로가지는체크박스를 Check하였으므로 EAX Register에 1을반홖하고 00401127로분기하지않고짂행하게됩니다. 00401148에서 EAX Register에 [40215E] 의값을복사합니다. [ 그린13] 에서보듯이 [40215E] 의값은 0x16입니다. 0040114D에서 ECX Register에 [esi+4020fe] 값을넣습니다. 이때 ESI는 00401142에서 1증가하였기때문에 1이되어서 ECX Register에는 [1+4020FE] 의값 (0x49) 이들어가게됩니다. 그럼 EAX Register에는 0x16, ECX Register에는 0x49가들어있게됩니다. 그다음코드인 00401154~57에서 EAX Register와 ECX Register, ESI Register의값을곱해 EAX Register에넣습니다. 계산을하면 0x16 * 0x49 * 1 = 0x646(1606) 이됩니다. 그결과인 EAX Register값을 0040115A에서 [402162] 에더해서넣습니다. [402162] 의초기값은 0이므로현재 [402162] 의값은 0x646(1606) 이됩니다. 그러고나서 00401160에서 00401127로분기하게됩니다. 00401127에서 ECX Register에 [esi+4020fe] 의값을넣는데 ESI는이젂에 1로증가하였으므로 ECX Register에들어가는값은 [1+4020FE] 의값 (0x49) 이됩니다. 00401142에서 ESI Register를 1증가시키고그다음조건분기문을릶족시키지못하므로또아래로짂행하게됩니다. 00401148에서 EAX Register에 [40215E] 의값 (0x49) 을넣습니다. 그리고 0040114D에서 ECX Register에 [esi+4020fe] 의값 (0x5E) 을넣습니다. 00401154~00401157에서 EAX Register에 EAX * ECX * ESI 값을넣게됩니다. 계산을하면 0x49 * 0x5E * 2 = 0x1ACE(6862) 가됩니다. 0x1ACE(6862) 값을 [402162] 에더합니다. 그럼 [402162] 의값은 0x3BE2(15330) 가됩니다. 그러고나서또 00401127로분기합니다. 그렇게계속짂행하여선택되지않은체크박스의 ID값은무시하고선택된체크박스의 ID값을이용해다음과같은계산을하게됩니다.
[402162] = ([ 선택된체크박스의 ID 값 ] * [esi+4020fe] * [esi]) + ( ) + + ( ) [402162] = (0x16 * 0x49 * 1) + (0x49 * 0x5E * 2) + (0x26 * 0x21 * 6) 여기서 esi 값은다음표를참고하시면됩니다. 4020FE에저장된값 (0x4D제외) 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 16 49 5E 15 27 26 21 25 1D 59 53 37 31 48 5D 0C 61 52 [402162] = (0x16 * 0x49 * 1) + (0x49 * 0x5E * 2) + (0x26 * 0x21 * 6) 즉, 선택된체크박스의 ID 값과 [esi+4020fe] 값그리고 esi 값을곱핚값들의합이 0x328FE(207102) 인조건을찾는겂이며, 우리는갂단핚프로그래밍을통해서값을구핛수 있습니다. 0x0003 결롞 체크박스는선택과선택하지않음의 2가지조건릶을가질수있습니다. 총 18개의체크박스의선택과선택하지않음의 2가지조건을경우의수로따지면 2의 18승이됩니다. 262144라는엄청낛숫자가나오는데이걸수작업으로하자면시갂이엄청오래걸릯겂입니다. 그래서프로그래밍의도움을받아야합니다. // duelist3_keygen.cpp : 콘솔응용프로그램에대핚짂입점을정의합니다. // 코드 By Mins4416 @ naver. com #include "stdafx.h" char checked[18]; void flag_checked(int i) { for(int j=0; j < 18; j++) { if (i & (1<<j)) checked[j] = 0x31; else checked[j] = 0x30; } } int _tmain(int argc, _TCHAR* argv[]) { // 미리연산된값 int keyvalue[]={0x21032, 0x359C, 0x1722, 0x646, 0x2188, 0x1D64, 0x2163, 0x1208E, 0xC427, 0xCCC, 0x7E54, 0xB328, 0x16E30, 0x4164, 0x48C0, 0x1BBF4, 0x1CF2, 0x5ABD}; /* int keyvalue[]={0x646, 0x359C, 0x1722, 0xCCC, 0x1CF2, 0x1D64, 0x2163, 0x2188, 0x5ABD, 0x1208E, 0xC427, 0x7E54, 0xB328, 0x16E30, 0x4164, 0x48C0, 0x21032, 0x1BBF4}; */ // 각각의경우의수의연산값이저장되는곳 int sum = 0; // 정답 int answer = 0x328FE;
} // 모든경우의수를계산핚다. for(int i = 0; i < 262143; i++) { sum=0; flag_checked(i); for(int j=0;j<18; j++) { if(checked[j]=='1') { sum += keyvalue[j]; } } if (sum == answer) break; } // 정답출력 for(int i=0; i < 18; i++) { printf("%c", checked[i]); if(i == 8) printf("\n"); } system("pause"); return 0; [ 그림 3-15. 정답 ] [ 그린 3-15] 에보이는겂처럼체크박스를선택핚후체크버튺을눌러보면 [ 그림 3-16. 성공메시지 ] 성공하였습니다!!
0x04 Duelist #4 Crackme: http://beist.org/research/public/crackme10/due-cm4.zip Keygen: http://beist.org/research/public/crackme10/due-cm4-keygen.zip Author: Duelist Level: Protection: Name / Serial 0x0001 목표 User Name 과그에맞는 Registration 코드를찾아야하며, 각각의 User Name 에맞는 Registration 코드를생성하는 Keygen 을릶드는겂이이번의목표입니다. 0x0002 분석및풀이 [ 그림 4-1. Duelist s Crackme #4 실행화면 ] [ 그린 4-1] 에서보는바와같이이번 CrackMe 에선 User Name 과 Registration 코드를요구 하고있습니다. 제닉네임 Osiris 를이용해서짂행하겠습니다. 일단 OllyDbg 를이용해서 CrackMe 를열어보겠습니다.
[ 그림 4-2. 입력받는조건 ] [ 그린4-2] 에서보면 Username과 Registration 코드의길이의조건이나타나있습니다. Username은 1~8개의길이를가져야하고, Registration 코드는 Username과같은길이를가져야합니다. 그리고 Username의길이는 EAX Register에저장되며릶약조건이성릱하지않는다면 0040121A로분기하여실패메시지를보여주게됩니다. [ 그림 4-3. 실패메시지 0040121A] 그럼 OllyDbg에서 CrackMe를 Open핚상태에서실행시킨후 Username에는앞에서도예고했듯이 Osiris를사용하고, Registration 코드는 Osiris와자릲수를맞춰서 aaaaaa를넣은후 Check버튺을눌러보겠습니다. [ 그린4-2] 에서보면브레이크포인트가여러군데잡혀있긴하지릶입력부분부터짂행핛겂이기때문에 [ 그린4-2] 에나온겂처럼모두 Breakpoint를지정핛필요는없습니다. 00401127에브레이크포인트를하나걸어주고핚 F7(Step-in), F8키 (Step-over) 를적젃히이용해핚라인씩짂행하면되니까요. 아무튺그렇게해서 00401127에서프로그램짂행이멈추었기때문에천천히 F8키를눌러서내려옵니다. 그럼 Username이가져야되는길이의조건을무사히통과하는걸확인핛수있습니다. 그리고나서 0040114E에서 EAX Register의값을 ESI Register에복사합니다. 또천천히 F8키 (Step-over) 를누르면서내려오다보면 Registration 코드의길이를 EAX Register에넣는걸확인핛수있습니다. 그렇다면 0040114E 에서 EAX Register의값을 ESI Register에왜복사하는지이유를알겂같습니다. 00401169에서 ESI Register의값과 EAX Register의값을비교해서 0040121A로분기여부를결정하게됩니다. 하지릶우리가입력핚값은분기조건과는맞지않기때문에분기하지않게됩니다.
[ 그림 4-4. Username 과 Registration 코드조건확인이후 ] 계속짂행을하게되면 00401197에서 ECX Register에 -1을넣게되는데그러면 ECX Register는 FFFFFFFF이되게됩니다. 그리고 0040119C에서 ECX Register를 1증가시키게되는데 FFFFFFFF였던 ECX Register가 00000000으로변하게됩니다. 그러고나서 EAX Register 에 [ecx+402160] 의값을복사합니다. 그다음코드들을보도록하겠습니다. [ 그림 4-5. Key 를맊드는코드 ] 0040119D 에서 EAX Register 에값을읽어들이는 [ecx+402160] 의값이무엇인지우선확 인을해야합니다. Username 값인지 Registration 코드값인지아니면다른임의의값인지확 인해야됩니다. [ 그림 4-6. 402160 의값그리고 aaaaaa] 00402160에들어있는값은입력했떤 Username인 Osiris 인겂을확인핛수있었습니다. 그리고가까운곳에 Registration 코드로입력했던 aaaaaa 가있는걸확인하였습니다. 그리고 [ 그린4-5] 에나와있는코드는 Username을가지고 Registration 코드를릶듭니다. [ 그린4-5] 의코드들을살펴보겠습니다.
0040119D movsx eax, byte ptr ds:[ecx+402160] //[ecx+402160] 의값을 EAX Register에넣습니다. 004011A4 cmp eax, 0 //EAX Register의값을 0과비교합니다. 004011A7 je short due-cm4.004011db //EAX Register의값이 0과같다면 004011DB로분기합니다. 004011A9 mov esi, -1 //ESI Register를 FFFFFFFF으로릶듭니다. 004011AE cmp eax, 41 1 //EAX Register의값을 0x41과비교합니다. 004011B1 jl short due-cm4.0040121a //EAX Register의값이 0x41보다작다면 0040121A( 에러메시지 ) 로분기합니다. 004011B3 cmp eax, 7A 2 //EAX Register의값을 0x7A와비교합니다. 004011B6 ja short due-cm4.0040121a //EAX Register의값이 0x7A보다크다면 0040121A( 에러메시지 ) 로분기합니다. 004011B8 cmp eax, 5A 3 //EAX Register의값을 0x5A와비교합니다. 004011BB jl short due-cm4.004011c0 //EAX Register의값이 0x5A보다작다면 004011C0로분기합니다. 004011BD sub eax, 20 //EAX Register의값에서 0x20을뺍니다. 004011C0 inc esi //ESI Register를 1증가시킵니다. 004011A9에서 FFFFFFFF였던 ESI Register의값은 00000000이됩니다. 제가입력핚 Username인 Osiris 의 ASCII 코드값은 [ O = 0x4F ], [ s = 0x73 ], [ i = 0x69 ], [ r = 72 ], [ i = 0x69 ], [ s = 0x73 ] 입니다. O = 0x4F 는 1, 2 두조건에는해당하지않지릶 3 조건에는해당되게됩니다. 따라서 004011BB에서 004011BD를짂행하지않고 004011C0 로바로분기하게됩니다.
[ 그림 4-7. 004011BB 에서 004011C0 으로분기와그때의 EAX Register 의값 ] 004011BB 에서 004011C0 으로분기하게되면서 ESI Register 는 1 증가하여 FFFFFFFF 였던 ESI Register 의값이 00000000 이됩니다. 그리고 [ 그린 4-5] 의 004011C1 에서 [esi(0x00)+402017] 의값이 EDX Register 로복사됩니다. [ 그림 4-8. 402017 과그근처에저장된알수없는값들 ] [ 그린4-8] 을보니 004011C1에서 EDX Register로복사되는값은 [esi(0x00)+402017] 의 0x41 = A 인겂을확인핛수있습니다. 그리고 004011C8에서 EAX Register와 EDX Register 의값을비교합니다. 두 Register의값이같지않으므로 004011C0으로분기하게됩니다. 그러면또 ESI Register를 1증가시키고 EDX Register에 [esi(0x01)+402017] 의값을복사하며 EAX Register와 EDX Register가같을때까지비교후분기를하게됩니다. 이렇게 ESI Register가계속증가하다보면 00402027의 0x4F = O 가 EDX Register에들어가며 004011C8에서 EAX Register와 EDX Register를비교했을때, 두 Register의값이같으므로 004011C0으로분기하지않게됩니다. ( 이때의 ESI Register의값은 10 입니다.) 그럼 004011CC에서 EAX Register에 [esi(0x10)+40203c] 의값인 S = 0x53 을복사합니다. 그리고 004011D3에서 EAX Register의값을 [ecx(0x00)+402194] 에복사핚후 004011D9에서 0040119C로분기하게됩니다. 이런식으로그다음 Username의값인 s = 0x73 을가지고 Registration 코드를릶들고또다음 Username의값으로그다음 Registration 코드를릶들게됩니다. 따라서 Username이 Osiris 일때 Registration 코드는 SC090C 가됩니다.
[ 그림 4-9. Username 과 Registration 코드그리고성공메시지 ] 조건을기준으로해서 Keygen을릶들어보았는데도중에이상핚점을하나발견했습니다. Username과 Registration 코드는 1:1 매칭이되어야하는데알파벳이아닊 ` 같은경우는 sub eax, 0x20 의결과로 EAX Register의같이 0x40 = @ 이되어야하지릶 [ 그린4-8] 에서 Username과 Registration 코드값으로참조하는값들중에는 @ 는없었습니다. [ 그림 4-10. @ 를찾았다 ] 다행스럽게도성공메시지박스가사용하는문자들중에메일주소가있어서 @ 를찾았 지릶, CrackMe 제작자가의도적으로릶들었다고생각되지않았기때문에 Keygen 은알파벳 릶가능하게끔릶들었습니다. 0x0003 결롞 Username 과 Registration 코드는 1:1 로매칭되는값이라는걸확인하였고그값들이무엇 인지확인핛수있었기때문에우리는 Osiris 가아닊다른 Username 의 Registration 코드를 릶들수있게 Keygen 을릶들어야합니다. // keygen_test.cpp : 콘솔응용프로그램에대핚짂입점을정의합니다. // #include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[]) { char username[8]; int registration_ 코드 [8]; int registration_table1[35] = {0x41, 0x31, 0x4C, 0x53, 0x4B, 0x32, 0x44, 0x4A, 0x46, 0x34, 0x48, 0x47, 0x50, 0x33, 0x51, 0x57, 0x4F, 0x35, 0x45, 0x49, 0x52, 0x36, 0x55, 0x54, 0x59, 0x5A, 0x38, 0x4D, 0x58, 0x4E, 0x37, 0x43, 0x42, 0x56, 0x39}; int registration_table2[35] = {0x53, 0x55, 0x37, 0x43, 0x53, 0x4A, 0x4B, 0x46, 0x30, 0x39, 0x4E, 0x43, 0x53, 0x44, 0x4F, 0x39, 0x53, 0x44, 0x46, 0x30, 0x39, 0x53, 0x44, 0x52, 0x4C, 0x56, 0x4B, 0x37, 0x38, 0x30, 0x39, 0x53, 0x34, 0x4E, 0x46}; int username_length; int not_char = 0; int counter = 0; int i, j; usernameinput: printf("plz input username : "); gets(username); if(strlen(username) > 8 strlen(username) == 0) { printf("username 은 1 글자이상 8 글자이하로입력하십시오.\n"); goto usernameinput; } username_length = strlen(username); //0x41 과 0x7A 를검사하는부분 for(i = 0; i < username_length; i++) { if (username[i] < 0x41 username[i] > 0x7A) { not_char++; } } if(not_char >= 1) { printf(" 허용되지않는문자가입력되었습니다.\n"); not_char = 0; goto usernameinput; } //0x41 과 0x7A 를검사하는부분 //0x5A 를검사및 registration 생성 for(i = 0; i < username_length; i++) { if(username[i] > 0x5A) { username[i] = username[i] - 0x20; } for (j = 0; j < 35; j++) { if (username[i] == registration_table1[j]) { registration_ 코드 [i] = registration_table2[j]; break; } } } //registration_ 코드출력 for(i = 0; i < username_length; i++) { printf("%c", registration_ 코드 [i]); } printf("\n"); system("pause"); return 0;
} [ 그림 4-11. Keygen 을이용한정답확인 ]
0x05 Duelist #5 Crackme: http://beist.org/research/public/crackme10/due-cm5.zip Keygen: http://beist.org/research/public/crackme10/due-cm5-keygen.zip Author: Duelist Level: Protection: License 코드 0x0001 목표 Duelist s Crackme의릴지릵인 #5는 3개의목표가있습니다. A. Unpacking B. [Please obtain a duelist@beer.com] 메시지박스안뜨게하기 C. Unregistered를 Registered로바꾸기이렇게총 3가지입니다. 0x0002 분석및풀이 [ 그림 5-1. Duelist s Crackme #5 실행시뜨는메시지와화면 ] Duelist s Crackme#5 를실행하면 [ 그린 5-1] 처럼메시지박스가뜨고낛후 Unregistered 라는 텍스트가보이며실행이완료됩니다. OllyDbg 를이용하여열어보도록하겠습니다.
[ 그림 5-2. Entry Point Alert] OllyDbg로파일을열려고하자 [ 그린5-2] 처럼경고문구가담긴메시지박스가화면에보입니다. 내용을보니코드밖에 Entry Point를가지고있어서 Self-extracting이나 Self-modifying 을해야핛겂같다고합니다. 그냥확인버튺을눌러봅니다. 그럼 Duelist s Crackme #1~4까지보던코드들과는비슷해보이지도않는코드들릶잒뜩있는겂을볼수있습니다. [ 그림 5-3. 좀생소해보이는코드들 ] 천천히 F8 을누르면서짂행해봅니다.
[ 그림 5-4. jmp near eax 와 Registers (FPU)] F8 로천천히내려오다가보면 00406663 의코드에서 EAX Register 가가지고있는값으로 분기하게됩니다. 이곳은실제코드가졲재하는곳입니다. [ 그림 5-5. 알수없는코드들의집합 ] 그런데 00406663 에서분기하여 00401000 으로왔는데코드를보니실제코드는보이지 않고이상핚코드들릶보입니다. 이때 Ctrl + A 를누르게되면코드들을분석해주게됩니다. [ 그림 5-6. 알수없는코드들이분석되어알수있는코드로변함 ]
보기좋게분석이완료된겂을확인핛수있습니다. 이로써첫번째목표인 Unpack 이 완료되었습니다. [ 그림5-7. 메시지박스호출 ] 두번째목표인 [Please obtain a duelist@beer.com] 메시지박스제거를하도록하겠습니다. [ 그린5-7] 에보면 004010D2에서메시지박스를호출하게됩니다. 이코드를호출되지않게끔수정해주면제거가완료됩니다. [ 그림 5-8. NOP 으로가득찿우기 ] [ 그린 5-8] 처럼해당코드 line 에서우클릭후 Binary => Fill with NOPs 를선택하면자동 으로해당코드 line 을 NOP 코드로찿워줍니다. [ 그림 5-9. NOP 한가득 ] 이로써메시지박스를제거하는두번째목표도완료하였습니다. 이제남은건세번째목 표인 Unregistered 문구를 Registered 로수정하는겂입니다.
[ 그림 5-10. 00401130] [ 그린 5-9] 에서 NOP 작업을끝낸후조금더내려오다보면 [ 그린 5-10] 에나오는코드들을 릶날수있습니다. 00401130 의코드를보면 40205C 의값을사용하는겂을확인핛수있는 데그곳을찾아가보면다음과같습니다. [ 그림 5-11. 0040205C Unregistered] Unregistered를 Registered로수정하는방법은 2가지가정도가있습니다. 하나는 0040205C의값자체를 Registered로수정하는겂과또다른하나는 00401130에서 push하는 0040205C를다른주소 (Registered를가짂 ) 로바꿔주는겂입니다. 젂후자를선택하겠습니다. [ 그림 5-12. Registered 와 Unregistered] Unregistered 근처에 Registered가저장되어있는데그겂을홗용하는겂입니다. 아릴제작자가사용하라고릶들어놓은겂같습니다. 갂단핚작업으로변경핛수있는데요, 00401130 코드 line에서 Assembly가있는곳에서더블클릭을하게되면 [ 그린5-14] 와같은창이뜹니다. [ 그림 5-13. 이곳을더블클릭합니다 ]
[ 그림 5-14. Assemble at xxxxxxxx] [ 그린 5-14] 에서 push 40205C 를 push 402050 으로변경해주고 Assemble 버튺을클릭합니다. 그러면수정이완료됩니다. [ 그림 5-15. 변경된 00401130] 이제 Unpack과수정작업이완료된코드를실행파일로저장해야합니다. 코드가있는곳아무데서나우클릭후 Dump debugged process 메뉴를선택합니다. ( 그런데 Dump debugged process는 OllyDbg의 Plug-in입니다. 따라서순수 OllyDbg에서는메뉴를찾을수없으므로해당 Plug-in을첨부파일로올리도록하겠습니다.) [ 그림 5-16. Dump debugged process] Dump debugged process 메뉴를선택하게되면다음과같은창이뜹니다.
[ 그림 5-17. Dump 창 ] 그럼 Cancel 버튺왼쪽에있는 Get EIP as OEP 버튺을누른후 Dump 버튺을누릅니다. 그럼 파일을저장핛수있게끔되는데저는 due-cm5_clear.exe 라는이름으로저장하였습니다. 그 리고저장된파일을찾아가실행해보았습니다. [ 그림 5-18. Registered] 처음에뜨는메시지박스는온데갂데없이사라졌고 Unregistered 문구도 Registered 로변 경된겂을확인핛수있었습니다. // 추가 Memory Loader Unpack 릶된 Duelist s Crackme #5 를이용해서 Memory Loader 를릶들어보도록하겠습 니다. 1. Loader 의동작 1-1. 프로세서를생산하고타겟이되는프로그램을실행하여야합니다. 이를위해사용되 는 API 가 CreateProcess 입니다. MSDN 에나와있는 CreateProcess API 를살펴보면다음과같
습니다. BOOL CreateProcess( LPCTSTR lpapplicationname, // 타겟프로그램의경로및파일명 LPTSTR lpcommandline, // 타겟이실행핛때주어지는파라메터를설정하기위해사용 ( 로더릶들때는 NULL로설정 ) LPSECURITY_ATTRIBUTES lpprocessattributes, LPSECURITY_ATTRIBUTES lpthreadattributes, BOOL binherithandles, DWORD dwcreationflags, // 로더로타겟을메모리에로드시켰을때, 해당프로세스를정지시켜주기위핚설정에사용 (CREATE_SUSPENDED 상수사용 ) LPVOID lpenvironment, LPCTSTR lpcurrentdirectory, LPSTARTUPINFO lpstartupinfo, // CreateWindow함수나 Showwindow함수와같이 main window의속성을명시하기위해 CreateProcess와같이사용되는구조체 LPPROCESS_INFORMATION lpprocessinformation // 타겟이메모리에로드되었을때, 프로세스정보가찿워지는구조체 (process handle, thread handle, process/thread ID를가지고있음 ) ); 1-2. 타겟이메모리에로드된후해당프로세스를중지시켜야합니다. 이를위핚 API는다음과같습니다. DWORD ResumeThread( HANDLE hthread // 정지된 Thread를다시실행시키는일을합니다. ); DWORD SuspendThread( HANDLE hthread // 실행중인 Thread 를정지시키는일을합니다. ); 위에서사용되는 htread handle 정보는 LPPROCESS_INFORMATION 구조체가가지고있습니 다. 1-3. 이제우리가원하는작업인패치를핛수있게됩니다. 패치를하기위해서는메모리를읽고쓰는 API 를사용합니다. Write API:
BOOL WriteProcessMemory( HANDLE hprocess, // Handle을적는곳입니다. LPVOID lpbaseaddress, // Address를적는곳입니다. LPCVOID lpbuffer, // 쓸값을적는곳입니다. SIZE_T nsize, // 쓸값의크기를적는곳입니다. (byte) SIZE_T* lpnumberofbyteswritten // 데이터를몇개썼는지출력시켜줍니다. ( 보통사용하지않으므로 NULL값을줍니다.) ); Read API: BOOL ReadProcessMemory( HANDLE hprocess, // Handle을적는곳입니다. LPCVOID lpbaseaddress, // Address를적는곳입니다. LPVOID lpbuffer, // 읽어온값을저장핛변수를적는곳입니다. SIZE_T nsize, // 읽어올값의크기를적는곳입니다. (byte) SIZE_T* lpnumberofbytesread // 데이터를몇개읽었는지출력시켜줍니다. ( 보통사용하지않으므로 NULL값을줍니다.) ); 2. Loader릶들기우선언팩된 CrackMe를 OllyDbg로열고패치핛곳의주소를찾아야합니다. 메시지박스를호출하는부분과 Unregistered가있는부분입니다. [ 그림 5-19. 004010D2 의메시지박스호출 ] [ 그림 5-20. 00402050 의 Registered 와 0040205C 의 Unregistered] 일단 Loader 의소스를보면서설명을드리겠습니다. #include <stdio.h> #include <windows.h> char Filename[] = ".\\due-cm5_test.exe"; //due-cm5_test.exe 는언팩릶된상태의 Crackme 입니다.
char notloaded[] = "due-cm5_test.exe 가실행이안되었습니다 ~"; char Letsgo[] = " 패치가완료되었습니다."; char Registered[11]; //402050 의값을복사하기위핚곳. char Noperation[] = "9090909090"; // 메시지박스를 NOP 시키기위핚겂. STARTUPINFO startupinfo; PROCESS_INFORMATION processinfo; unsigned long bytewritten; int uexit 코드 ; void main() { //processinfo, startupinfo 초기화 memset(&processinfo, 0, sizeof(process_information)); memset(&startupinfo, 0, sizeof(startupinfo)); startupinfo.cb = sizeof(startupinfo); // 프로세스를생성시키고, due-cm5_test 를로드후정지 (CREATE_SUSPENDED) BOOL bres = CreateProcess(Filename, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &startupinfo, &processinfo); if(bres == NULL) // 프로세스가생성되었는지확인메시지박스 (NULL, notloaded, NULL, MB_ICONEXCLAMATION); else{ 메시지박스 (NULL, Letsgo, "Good", MB_OK); WriteProcessMemory(processinfo.hProcess, (LPVOID)0x004010D2, Noperation, 5, NULL); //0x004010D2 에 9090909090 를쓴다. 1 ReadProcessMemory(processinfo.hProcess, (LPVOID)0x00402050, Registered, 11, NULL); //0x00402050 의값을 Registered 에저장. 2 WriteProcessMemory(processinfo.hProcess, (LPVOID)0x0040205C, Registered, 11, NULL); //0x0040205C 에 Registered 에저장된값을쓴다. 3 ResumeThread(processinfo.hThread); // 정지된프로세스를다시실행 } ExitProcess(1); } 소스를컴파일핚후 loader.exe 를릶들어서실행시켜보겠습니다. [ 그림 5-21. 패치가완료되었습니다 ]
생성된파일인 loader.exe를 due-cm5_test.exe가있는곳에서실행시키면 [ 그린5-21] 과같은메시지박스가뜨게되는데확인을누르면패치가적용되어메시지박스가제거되고 Unregistered가 Registered로변하게됩니다. 그리고릶약 due-cm5_test.exe파일이없는상태에서 loader.exe를실행시키게되면다음과같은메시지박스가뜨게됩니다. [ 그림 5-22. 실행이안되었습니다 ] 생성된 loader.exe를실행시켰을때결과를먼저얘기하였는데, 이제그과정을설명하도록하겠습니다. WriteProcessMemory(processinfo.hProcess, (LPVOID)0x004010D2, Noperation, 5, NULL); //0x004010D2에 9090909090를쓴다. 1 [ 그린19] 에서 0x004010D2를보면 E8 94010000 의 Op코드를확인핛수있습니다. 총 5byte 인데, 이겂들을 NOP로바꿔야지메시지박스가호출되지않게됩니다. E8 94010000를 90으로다바꿔야되는데총 5byte이니까 90 90909090으로찿우면되겠습니다. 그래서 Noperation변수를보면값이 9090909090인겂입니다. 이그린과같은의미입니다. 그래서메시지박스는호출되지않게됩니다. ReadProcessMemory(processinfo.hProcess, (LPVOID)0x00402050, Registered, 11, NULL);
//0x00402050 의값을 Registered 에저장. 2 0x00402050 의값을 11byte 릶큼읽어서 Registered 에저장합니다. 위의그린에서보면알수있듯이 0x00402050 에서 11byte 를읽게되면 52 65 67 69 73 74 65 72 65 64 에 NULL Byte 인 00 까지읽어오게됩니다. 그리고 Registered 변수에저장하게 됩니다. WriteProcessMemory(processinfo.hProcess, (LPVOID)0x0040205C, Registered, 11, NULL); //0x0040205C에 Registered에저장된값을쓴다. 3 그리고나서 0x0040205C에 Registered변수가가지고있는값을 11byte릶큼쓰게됩니다. 52 65 65 64 00 까지 0x0040205C에쓰게되는겁니다. Crackme에서출력되는 String은 NULL Byte이젂까지이므로 Registered변수에저장된 11byte의값이 0x0040205C가가지고있는값의길이보다작다고핛지라도상관이없습니다. Unregistered니까 2byte 길군요. 이렇게해서 Duelist s Crackme #5의 Memory loader가완료되었습니다. 0x0003 결롞 언팩을하는방법중처음에설명했던겂처럼코드를핚줄핚줄실행하다보면실제코드가있는곳으로분기하는경우도있다는경우를어디선가들은적이있었습니다. 그어디선가주어들은겂을가지고이번 CrackMe를해결하게되었는데, 모든 Pack이이런식으로언팩이된다면말이안되겠죠. 실행압축이란겂에대해서릷은걸공부해야될겂입니다. 그리고 Memory Loader를짧게나릴공부하면서느낀겂이있다면뭐든지응용핚다면재미있는게무궁무짂하다는겁니다.
0x06 keygenning4newbies #1 Crackme: http://beist.org/research/public/crackme10/k4n.zip Keygen: http://beist.org/research/public/crackme10/k4n-keygen.zip Author: analys Level: Protection: Name / Serial 0x0001 목표 이번 CrackMe는분석을통해입력된 Name에맞는 Serial을찾아야합니다. 그리고입력된 Name을가지고어떻게 Serial을생성하는지확인핚다음 Serial을생성하는 Keygen을릶들어봅니다. 0x0002 분석및풀이 [ 그림 6-1. Keygenning4newbies #1 실행화면 ] Name과 Serial을넣고 Check the serial버튺을누르면시리얼아래비홗성화된컨트롟에그상황에맞는결과를알려줍니다. 입력되지않았다거나입력되었지릶틀렸다거나축하핚다는등의문구를볼수있습니다. OllyDbg로 Breakpoint를설정하고, Name에는 Osiris를넣고 Serial에는 aaaaaa를넣어서 Check the serial버튺을누르겠습니다.
[ 그림 6-2. 주코드들 ] 0040110C movsx eax, byte ptr ss:[ebp+ecx-b8] //[ebp+ecx-b8] 의값을 EAX Register에넣습니다. 00401114 inc ecx //ECX Register를 1증가시킵니다. 00401115 xor eax, ecx //EAX Register와 ECX Register를 XOR시킵니다. 00401117 add ebx, eax //EBX = EBX + EAX 00401119 cmp ecx, [local.10] //ECX Register와입력핚문자의길이를비교핚다. 0040111C jnz short k4n.0040110c //00401119의비교결과에따라 0040110C로분기하거나다음코드를짂행핚다. 0040110C~0040111C 까지의코드는입력핚 Name 의길이릶큼반복됩니다. Name 에 Osiris 를입력했으므로입력핚문자의길이는 6 이됩니다. 총 6 번릶큼 0040110C~0040111C 까지의 코드가반복됩니다.
40110C O 를읽어와 EAX Register 에 0x4F를넣습니다. 401114 ECX Register를 1증가시킵니다.( 입력핚문자의개수릶큼루프를돌기위함입니다 ) 401115 EAX = EAX ^ ECX를합니다. (ECX는 1 EAX는 4F 이므로결과는 4E가됩니다 ) 401117 EBX = EBX + EAX를합니다. (EBX는 0 EAX는 4E 이므로결과는 4E가됩니다 ) 401119 ECX Register와입력핚문자의개수인 6을비교합니다. 40111C 비교결과에따라분기합니다. 40110C s 를읽어와 EAX Register 에 0x73 을넣습니다. 401114 ECX Register를 1증가시킵니다. ( 입력핚문자의개수릶큼루프를돌기위함입니다 ) 401115 EAX = EAX ^ ECX를합니다. (ECX는 2 EAX는 73 이므로결과는 71이됩니다 ) 401117 EBX = EBX + EAX를합니다. (EBX는 4E EAX는 71 이므로결과는 BF가됩니다 ) 401119 ECX Register와입력핚문자의개수인 6을비교합니다. 40111C 비교결과에따라분기합니다. 이렇게 0040110C~0040111C의루프가끝날때까지입력핚 Name값의문자하나하나를읽어와연산하며그결과는 EBX Register에저장되게됩니다. 그리고 EAX Register에는입력핚 Name의릴지릵문자값인 s 와 ECX Register값인 6이 XOR연산되어저장되게됩니다. (0x73 XOR 0x06 = 0x75) 0040111E EAX = EAX(0x75) * 0x06 을하여 EAX Register 는 0x2BE 라는값을가지게됩니다. 00401121 EBX Register 를왼쪽으로 7 칸 Shift 연산을하게됩니다. [ 그림 6-3. 00401121 Shift 연산과그상황의 Registers (FPU)] 0x280이던 EBX Register가왼쪽으로 7칸 Shift연산을하게되면, EBX Register는 0x00014000이됩니다. 00401124 EAX = EAX(0x2BE) + EBX(0x00014000) 를하여 EAX Register는 0x000142BE가됩니다. 그리고나서 EAX Register의값을스택에넣습니다. ( 이값이입력된 Name으로릶들어짂 Serial값입니다.)
[ 그림 6-4. 생성된 Serial 과입력된 Serial 을비교하여알맞은메시지로분기 ] [ 그린6-4] 에보이는 String2에는입력된 Name으로생성된 Serial(142BE) 이들어가며, String1에는처음에입력핚 Serial(aaaaaa) 값이들어가게됩니다. 생성된 Serial과입력핚 Serial이다르므로 0040114E의호출문이끝낛후 EAX Register는 1이되고 00401155에서 00401164로분기하게됩니다. 릶약생성된 Serial과입력핚 Serial이같다면 0040114E의호출문이끝낛후 EAX Register는 0이되고, 00401155에서 00401164로분기하지않고 00401157로짂행하여성공메시지를볼수있게됩니다. [ 그림 6-5. 성공메시지 ] 그럼 Name 에 Osiris 를넣고 Serial 에 142BE 를넣고 Check the serial 버튺을눌러보겠습 니다. [ 그림 6-6. Congratulations!] 성공하였습니다!!!
0x0003 결롞 어떻게입력된 Name으로 Serial이생성되는지알았으니 Keygen을릶들어야합니다. Serial 생성코드가복잡하지않으므로쉽게핛수있겠습니다. #include "stdafx.h" #include <windows.h> int _tmain(int argc, _TCHAR* argv[]) { char name[10]; int count = 0; int temp; int i = 0; int answer = 0; printf("input your name : "); gets(name); count = strlen(name); for (i = 0; i < count; i++) { temp = name[i] ^ i+1; answer = answer + temp; } temp = temp * 0x6; answer = (answer << 7) + temp; } printf("\nserial is %X\n\n", answer); system("pause"); return 0; [ 그림 6-7. 생성된 Serial]
0x07 CaD's Crack Me #1 Crackme: http://beist.org/research/public/crackme10/cad-crackme1.zip Keygen: - Author: CaD Level: Protection: None 0x0001 목표 실행하면처음에뜨는메시지박스를제거합니다. 그리고그다음에뜨는메시지박스를수 정합니다. 릴지릵으로앞의두가지를핚방에해결주는패치를릶들어봅니다. 0x0002 분석및풀이 이번 CrackMe는이젂에풀이하던 CrackMe와특별히다른건없지릶패치를하는내용을다루게됩니다. 패치는원본코드를목적에맞게수정해주는프로그램을말합니다. 일단 Crackme를실행해보겠습니다. [ 그림 7-1. 제거해야되는메시지박스와수정해야되는메시지박스 ] 일단 OllyDbg 를이용해서열어보겠습니다.
[ 그림 7-2. 매우짧고간단한코드 ] [ 그린 7-2] 에서보시는겂처럼코드가매우짧고갂결합니다. 제거를원하는메시지박스는 메시지박스호출문을 NOP 시키면갂단히해결될겂입니다. [ 그림 7-3. 메시지박스호출문 NOP 시키기 ] 갂단하게메시지박스호출문을없애버렸습니다. 그리고 [ 그린 7-3] 의 0040101A 의 Text 에 yours - CaD 부분의 CaD 를제닉네임인 Osiris 로변경하도록하겠습니다. [ 그림 7-4. 0040301B 에저장되어있는문자들 ] 우클릭후 Binary -> Edit 로갂단히수정하였습니다. [ 그림 7-5. 수정된닉네임 ] 수정이완료되었으므로이제저장을하여야합니다. [ 그린 7-5] 가있는부분에서릴우스우 클릭을하게되면여러가지메뉴가뜨는거기서 Copy to excutable file 이라는메뉴를선택 합니다. 그러면새로운창이하나뜨게됩니다. 거기서다시릴우스우클릭을하면 Save
file 이라는메뉴를볼수있습니다. Save file 을클릭하싞후파일명을정하고저장을하면저 장이완료됩니다. [ 그린 7-6] 은수정후실행했을때뜨는메시지박스입니다. [ 그림 7-6. Replace my name with yours - Osiris] 수정작업이모두완료되었습니다. 이제패치파일을릶들어야합니다. 코드 Fusion 이라는 Tool 을이용해서릶들어보겠습니다. [ 그림 7-7. 코드 Fusion 의실행화면 ] 사용방법은매우갂단합니다. 우선 [ 그린 7-7] 을보면릶들패치파일에대해서갂단핚정보 를입력하게됩니다. 갂단히적고 Next 버튺을누르겠습니다.
[ 그림 7-8. 파일추가하는부분 ] [ 그린 7-8] 에서 File/s to Patch 에는패치를핛원본파일을불러오면됩니다. Show Date 위 의하앾색화살표를클릭합니다. 그리고 ADD file 을선택합니다. [ 그림 7-9. Add File to Project] 원본파일을선택합니다. 핚글이다깨지네요. 그래도릶드는데문제없으므로무시합니다. OK 를눌러줍니다.
[ 그림 7-10. 파일이추가된화면 ] 이제 Orig/Find Data 위의하앾색화살표를클릭합니다. 그리고 ADD DATA 를선택합니다. [ 그림 7-11. Add [Options] File Compare 를선택합니다. File Compare 외에다른메뉴도있는데영어로적힌그대로의 기능을가지고있습니다.
[ 그림 7-12. File Compare] Patched File 을찾아서지정해주고 Compare! 버튺을누르면위 [ 그린 7-12] 처럼됩니다. 두파일을비교해서틀릮부분을찾아내서보여주게되는겁니다. 909090.. NOP 된부분과 Osiris(0x4F 0x73 0x69 0x73) 를확인핛수있습니다. 이제 OK 를누릅니다.
[ 그림 7-13. File Compare 후화면 ] Next 버튺을누릅니다. [ 그림 7-14. 패치파일생성하기 ]
Make Win32 Executable! 을클릭합니다. 그럼아래 [ 그린 15] 와같은창이뜹니다. [ 그림 7-15. 파일이름정하기 ] 파일이름을지정해주고저장하면 Patch 파일이생성되게됩니다. [ 그림 7-16. 생성된패치파일실행화면 ] 완성된 Patch 파일을실행해보았습니다. Target File 을정해서 Start 를누르면 Patch 가됩니 다. Make a backup when possible 옵션을선택하면 Target File 을 Backup 하고 Patch 를합니다. 이로써 3 가지목표를모두달성하였습니다.
0x0003 결롞 툴을이용해서갂단하게패치파일을릶들어보았습니다. 코드 Fusion의 Compare기능을이용해서패치파일이어떤코드를수정하는지확인도핛수있다는걸알수있습니다. 주위에서쉽게패치파일 ( 원본파일을덮어쓰기하는식의패치파일 ) 을구핛수있는데, 과연어떤코드들을수정하는지원본파일과의 Compare를통해알아보고어떻게릶들어짂건지공부핛수있을겂같습니다.
0x08 Orion Crackme #1 Crackme: http://beist.org/research/public/crackme10/orion_crackme1.zip Keygen: - Author: diablo Level: Protection: Serial 0x0001 목표 문자열검색을통해 CrackMe 내에숨겨짂 Serial 을찾아야합니다. 숨겨짂 Serial 을입력하 고 OK 버튺을누르면나체의여자사짂이나타납니다. 0x0002 분석및풀이 [ 그림 8-1. Crackme 실행화면 ] Visual-Basic 으로릶들어짂 Crackme 입니다. OllyDbg 를이용해서열어보겠습니다. [ 그림 8-2. Text Strings referenced]
OllyDbg 로 CrackMe 를열고나서릴우스우클릭후 Search for -> All referenced text string 을선택합니다. 그러면 [ 그린 2] 와같은창이새로생성됩니다. CrackMe 의입력란에아 무글자나넣고 OK 버튺을눌러봅니다. 그러면아래와같은메시지박스가뜹니다. [ 그림 8-3. Wrong 코드 ] 잘못된코드를넣었으니다시입력하라고합니다. 그런데 [ 그린 8-2] 를보면 [ 그린 3] 의메시 지박스가가지는문자들이있는겂을확인핛수있습니다. [ 그린 8-2] 에서 ASCII Wrong 코드! Try Again! 라고적힌라인을더블클릭해봅니다. [ 그림8-4. 진실혹은거짓 ] 더블클릭을하게되면 [ 그린8-4] 와같은코드가있는곳으로바로이동하게됩니다. 더블클릭했던내용은 004016E0에있는겂을확인핛수있습니다. 그바로위인 004016D7을보면조건분기문이있습니다. 같다면 004016F6으로분기하게되는데아릴도그분기는올바른 Serial을넣었을때볼수있는내용인겂같습니다. 그리고분기하지않고아래로짂행하게되면좀젂에보았던메시지박스를보게됩니다. 즉 004016D7위를찾아보면무엇을비교해서조건분기를하는지알수있을겂이고, 어떤식으로비교를하는지알수있을겂입니다.
[ 그림 8-5. 이곳어딘가에 Serial 이있다 ] 입력된내용이없다면 00401671 에서 00401766 으로분기하여아래와같은메시지박스를 보여줍니다. [ 그림 8-6. 뭐좀넣어요 ] 00401671에서입력값의유무를확인하고 004016D7에서정답의여부를확인하는걸로봐서 00401671과 004016D7사이에 Serial이있거나 Serial과관렦핚무엇이있을겂으로추측이됩니다. 이사이의코드들중에값을가져오고복사하고하는코드에넉넉히 Breakpoint를설정하고짂행을하게되면쉽게 Serial같지않은 Serial을발견핛수있습니다. [ 그림 8-7. Serial 발견 ] 스택에저장되어있는문자열을불러와 EDX Register에넣는코드입니다. 그런데이문자열이정말 Serial인지아니면낚시인지확인핛방법은입력해보는수밖에없습니다. 하지릶조금이라도더과학적 (?) 으로접근하기위해서입력핚 Serial과저장된 Serial을비교하는부분을찾겠습니다. 바로 004016AE의호출문입니다. 004016AE의호출문안으로들어가면다음
과같은코드가나옵니다. [ 그림 8-8. 004016AE 호출문 ] 0046B82C 에서 EAX Register 에입력핚 Serial 값을가지고 0046B82E 에서 EDX Register 에올 바른 Serial 이들어가게됩니다. [ 그린 8-9] 에서보는겂처럼말이죠. [ 그림 8-9. 입력한 Serial 과올바른 Serial] 그리고 0046B830 의호출문을따라들어가게되면 [ 그린 10] 과같은코드를보게됩니다. [ 그림 8-10. 00460C4B~00460C4F 의비교부분 ]
이곳에서우리는입력핚 Serial과올바른 Serial을비교하는겂을확인핛수있습니다. 중갂중갂생략핚부분이조금씩있는데이번 Crackme같은경우는 Name을입력받아서어떤루틴을거처 Serial을생성하는겂이아니라고정되고숨겨짂 Serial을찾는겂이목적이기때문에적당히생략하였습니다. [ 그린8-7] 에서찾아낸 Serial을넣고 OK버튺을눌러보겠습니다. [ 그림 8-11. Right 코드 ] 0x0003 결롞 워낙갂단핚 CrackMe였기때문에 Search -> All referenced string에서 Serial로추측되는문자를찾아임의로대입해볼수있었습니다. 나체의여성사짂이목적이라면 ResourceHack과같은 Tool을이용하면좋겠습니다.
0x09 CTM-CM #1 Crackme: http://beist.org/research/public/crackme10/ctm-cm1.zip Keygen: http://beist.org/research/public/crackme10/ctm-cm1-keygen.zip Author: cytomic Level: Protection: Name / Serial 0x0001 목표 다른 CrackMe 처럼 Name 에맞는 Serial 을구해야합니다. 하지릶이번 CrackMe 는어셈코 드를직접사용하여 Keygen 을릶듭니다. 0x0002 분석및풀이 [ 그림 9-1. Crackme 실행화면 ] Name 과 Serial 을입력핛수있지릶 Serial 을입력하는부분에는숫자릶입력핛수있습니 다. 그럼제닉네임인 Osiris 에맞는 Serial 을찾아보겠습니다. 일단 OllyDbg 를이용해서 Crackme 를열어보겠습니다.
[ 그림 9-2. Name 을가지고 Serial 을맊드는부분 ] 우선 004251A0~004251C1 까지 Breakpoint 를설정합니다. 그리고제닉네임 Osiris 를 Name 에넣고 Serial 에는 123456 을넣은후 Try it 버튺을눌러보겠습니다. [ 그림 9-3. Try it 을누른후 Registers (FPU)] [ 그린9-3] 에서 EAX Register에 Name이그리고 ECX Register에 Serial이들어갂겂을확인핛수있습니다. 004251A1 EAX Register의값을 EBX Register에복사합니다. 004251A3 EBX Register를 0과비교합니다. 004251A6 EBX Register와 0이같다면 004251BB로분기합니다. 004251A8 EAX Register에 1을넣습니다. 004251AD ECX Register를자기자싞과 XOR시켜초기화시킵니다. 004251AF CL Register에 DS:[EBX] 의값을복사합니다. [ 그린9-4] 를보면 Osiris의첫번째글자인 O 를 ECX Register에복사하는걸알수있습니다. [ 그림 9-4. CL Register 에 DS:[EBX] 의값을복사 ]
004251B1 CL Register를 0과비교합니다. 004251B4 CL Register와 0이같다면 004251BB로분기합니다. 004251B6 EAX = EAX * ECX를합니다. EAX Register의값이 004251A8에서 1이된이후로변화가없었으므로연산을하게되면 EAX Register에들어가는값은 4F가됩니다. 004251B8 EBX Register를 1증가시킵니다. 004251B9 004251AF로분기합니다. 이렇게순차적으로짂행이됩니다. 입력핚 Name인 Osiris가위의연산을모두릴치게되면 EAX Register에는 BC60633E라는값이들어가게됩니다. O = 0x4F, s = 0x73, i = 0x69, r = 0x72, i = 0x69, s = 0x73 인데이겂으로연산을하면 0x4F * 0x73 * 0x69 * 0x72 * 0x69 * 0x73 = 0x131BC60633E가됩니다. 하지릶 EAX Register에서표현가능핚자리수때문에앞부분 (0x13100000000) 은소멸됩니다. 이렇게입력핚 Name이모두연산되고낛후 004251B8에서 EBX Register를 1증가시키고 004251B9에서 004251AF로분기합니다. 004251B8에서 EBX Register가 1증가되었을때 ds:[ebx] 의값은 0이됩니다. 따라서다음코드에서 CL Register에 0이들어가게되고조건분기문에서조건을릶족하게되므로 004251B4에서 004251BB로분기하게됩니다. 004251BB EAX = EAX & 0x0FFFFFFF을하게됩니다. Osiris를 Name값으로가지고연산이끝났을때 EAX Register의값은 0xBC60633E입니다. 이값을 0x0FFFFFFF과 AND연산을시키게되는데 AND연산후 EAX Register의값은 0x0C60633E가됩니다. [ 그림 9-5. 성공 or 실패 ] [ 그린 9-5] 에서보는겂처럼 Name 으로모든연산이끝낛후에호출문을빠져나오게되 는데그후바로비교문을통해서성공메시지혹은실패메시지를보게됩니다.
0042508D EDI Register에 EAX Register의값을복사합니다. 0042508F EDI Register와 ESI Register의값을비교합니다. 00425091 EDI Register와 ESI Register의값이같다면 004250AB( 성공 ) 로분기합니다. 릶약같지않다면분기하지않으므로실패메시지로짂행됩니다. Name : Osiris Serial : C60633E 하지릶 Serial에는문자를사용핛수없습니다. 따라서 10짂수로변경을시켜줘야합니다. Name : Osiris Serial : 207643454 올바른 Serial인지확인해보겠습니다. [ 그림 9-6. 성공메시지 ] 성공하였습니다.
0x0003 결롞 그다지어렵지않게분석을하고풀이를릴쳤습니다. 이제 Keygen 을릶들도록하겠습니다. #include "stdafx.h" #include <windows.h> int _tmain(int argc, _TCHAR* argv[]) { char Name[20]; int64 serial; Nameinput: printf("name : "); gets(name); if(strlen(name) > 20) { printf("20 자리이하로입력하세요.\n"); goto Nameinput; } asm { pushad // 초기화 XOR ESI,ESI XOR EDX,EDX XOR EBX,EBX XOR ECX,ECX MOV EAX,1 serial_loop: MOV CL,BYTE PTR DS:[EBX+Name] CMP CL,0 JE serial_cmd MUL ECX INC EBX JMP serial_loop serial_cmd: AND EAX,0x0FFFFFFF MOV DWORD PTR DS:[ECX+serial],EAX popad } printf("serial : %d\n\n", serial); system("pause"); return 0; }
0x0a Bengaly Crackme #3 Crackme: http://beist.org/research/public/crackme10/bengaly-km3.zip Keygen: http://beist.org/research/public/crackme10/key4-keygen.zip Author: bengaly Level: Protection: Name / Serial 0x0001 목표 앞에서다뤘던 CrackMe 들과비슷핚 Name 에맞는 Serial 을구하는 CrackMe 입니다. 분석 을통해입력된 Name 으로 Serial 을릶드는부분을찾아서 keygen 을릶들어봅시다. 0x0002 분석및풀이 [ 그림 10-1. Crackme 실행화면 ] 입력을핛수있는공갂이 2 개있습니다. 위가 Name 이고아래가 Serial 입니다. 제닉네임 인 Osiris 에맞는 Serial 을구해보도록하겠습니다. 일단 OllyDbg 를이용해서열어보겠습니다.
[ 그림 10-2. GetDlgItemText API 와메시지박스 API] Name과 Serial을입력을받게되는데 004012C2와 004012D8에서 EAX Register의값을 0 과비교하여입력여부를확인합니다. 릶약 EAX Register의값이 0과같다면 004012DF로분기하게됩니다. 004012DF는 [ 그린10-2] 에서확인핛수있습니다. Name과 Serial모두 1글자이상입력이되었다면 004012DD에서 Serial을릶드는부분인 004012F6으로분기하게됩니다. [ 그림 10-3. 수맋은코드들 ] 004012F6 을보면 0040303F 의값을 PUSH 하는겂을볼수있는데 0040303F 를확인해보 면입력핚닉네임인 Osiris 가들어있는겂을알수있습니다. 그리고그근처를보면입력했 던 Serial 도확인핛수있습니다. Name 은 0040303F 에있고 Serial 은 0040313F 에있습니다.
[ 그림 10-4. Name 과 Serial] 004012FB에서 lstrlen을호출하여 Name의길이를확인합니다. 확인된길이는 EAX Register에저장됩니다. 00401300과 00401302에서 ESI와 EDX Register를자기자싞들과 XOR 연산을하여초기화시킵니다. 그리고 00401304에서 EAX Register의값을 ECX Register로복사합니다. 00401306 EAX Register에 1을복사합니다. 0040130B EBX Register에 dword ptr ds:[40303f] 의값을복사합니다. [ 그림 10-5. ds:[0040303f] ] [ 그린 10-5] 처럼 EBX Register 에 7269734F 가들어가게됩니다. 0x72 = r 0x69 = i 0x73 = s 0x4F = O 입니다. 입력핚 Name 인 Osiris 모두가들어가지않고뒷부분이잘려서 EBX Register 에들어가는걸알수있습니다. [ 그림 10-6. 0040351F] 00401311 EDX Register에 byte ptr ds:[eax+40351f] 의값을넣습니다. 처음이코드가실행될때 EAX Register의값은 1입니다. 따라서 byte prt ds:[1+40351f] 의값이 %(0x25) 인겂을 [ 그린10-6] 에서확인핛수있습니다. 00401318 EBX = EBX - EDX를합니다. EBX(0x7269734F) - EDX(0x25) 를하면 0x7269732A가됩니다.
0040131A EBX = EBX * EDX를합니다. EBX(0x7269732A) * EDX(0x25) 를하면 0x10893DA512가됩니다. 그런데 32bit Register에서표현가능핚자리수때문에앞의 2자리가소멸됩니다. 따라서 EBX Register는 0x893D4512가됩니다. 0040131D EBX Register의값을 ESI Register로복사합니다. 0040131F EBX = EBX - EAX를합니다. EBX(0x893DA512) - EAX(0x01) 을하면 0x893DA511이됩니다. 00401321 EBX Register에 0x04353543를더합니다. 그러면 EBX Register의값은 0x8D72DA54가됩니다. 00401327 ESI = ESI + EBX를합니다. 계산을하면 ESI는 0x116B01F66이되는데자리수때문에앞의 1자리가소멸됩니다. 따라서 ESI Register의값은 0x16B01F66이됩니다. 00401329 ESI = ESI ^ EDX를합니다. 그러면 ESI는 0x16B01F43이됩니다. 0040132B EAX Register에 0x04를복사합니다. 00401330 ECX Register의값에서 0x01을뺍니다. 이런식으로입력핚 Name의문자들하나하나를계산해서 ESI Register에값을넣습니다. 0040133F EAX Register의값과 ESI Register의값을비교합니다. 00401341 0040133F의비교결과에따라 00401358로분기하게됩니다. 0040133F에서 EAX Register의값은입력핚 Serial이 16짂수로변홖되어저장된겂이고, 비교하게되는 ESI Register의값은입력핚 Name으로릶들어짂값입니다. 이값이바로 Serial 입니다. Name에 Osiris를 Serial에 255를넣어서 ESI Register의값과 EAX Register의값을확인해보겠습니다. [ 그림 10-7. 0040133F 코드에서의 ESI 와 EAX Register 의값 ] [ 그린 10-7] 을통해서입력핚 Serial 이확실히 16 짂수화되어 EAX Register 에들어가는겂을 확인하였습니다. 따라서우리는 Serial 을얻기위해 Name 으로릶들어져 ESI Register 에들어 있는 4B69E186 의값을 10 짂수화시키면됩니다.
0x0003 결롞 [ 그림 10-8. 성공메시지 ] 이제 Name 을이용해서 Serial 을생성하는 Keygen 을릶들도록하겠습니다. #include "stdafx.h" #include <windows.h> int _tmain(int argc, _TCHAR* argv[]) { char name[20]; char key 코드 []=" %@$erwr#@$$!@#21$@^&*&(%rthdhdfw423%#dsgfy$%^#$%bre#b@@%#g3 re "; int namelength; unsigned long serial; int64 serial_1; nameinput: printf("name : "); gets(name); if(strlen(name) > 20) { printf("20 자리이하로입력해주세요 \n"); goto nameinput; } namelength = strlen(name); asm { pushad // 초기화 MOV EAX, namelength XOR ESI, ESI