시리얼번호작성루틴을뽑아내서 Brute-Force 돌리기.. 작성일 : 2005년가을작성자 : graylynx (graylynx at gmail.com) 크랙미정보 GOAL : Find the correct password No patching allowed Difficulty : 2/10 This is an exercise on brute-attacking. Written in WIN32ASM by Detten. Send solutions, questions, and comments to : Biwc@hotmail.com 역시군대에서풀었던크랙미. 이건그때인트라넷동호회에강좌같은거올리려고솔루션을문서로만들어놨네요. 부끄럽지만살짝첨부해봅니다. ^^; [ 크랙미환경 ] OS: Windows XP SP1 Disassembler: W32DASM Debugger: SoftIce + IceDump Assembler: MASM Compiler: Visual C++ crackme5(by Detten) 튜토리얼 by 태병장 자, 리버싱팀의오브젝트 #4 라는이름으로올렸던크랙미 5 에대한튜토리얼을시작하겠습니다. 이크랙미는코드패치가허용되지않으며, 오로지올바른키값을찾아야만합니다. 따라서우리는디스어셈블코드를완벽하게이해할수있어야만이녀석을공략할수있습니다. 먼저 W32DASM 으로디스어셈블을해봅니다. 실행파일이패킹되어있지도않고, 숨겨진더미코드도없기때문에올바른역코드를보여줍니다. 이코드를분석하기전에먼저소프트아이스를이용하여실시간으로코드를훑어보도록하죠. W32DASM 의 Function->Import 메뉴를선택하면, 아래와같은함수들이임포트되어있음을알수있습니다.
소프트아이스를띄운다음 bpx getdlgitemtexta 명령으로브레이크포인트를잡습니다. 그리고 crackme5.exe 를실행하고아무값이나입력하면브레이크가걸리면서소프트아이스가실행됩니다. ( 저는 12344321 을입력하였습니다 ) 소프트아이스가떳을때 getdlgitemtexta 함수내에서브레이크가걸린상태이므로, p ret 명령을내려서 ( 함수밖으로빠져나가는명령 ) 우리가원하는코드로접속할수있습니다. 이제우리는 F8 과 F10 을적절히이용하여우리가입력한 12344321 문자열이어떠한연산과정을거치는지레지스터와메모리값을추적해가며조사할수있습니다. 하지만이크랙미는 W32DASM 으로디스어셈블이가능하기때문에, 그것의디스어셈블코드로분석을하겠습니다. 밑의그림은이미제가손으로분석한것을스캐너로받아서첨부한것입니다.
실제로저는디버거상에서주석달면서내려가는것보다이렇게프린트해놓고손으로분석하는게집중이잘되더군요. ( 사실키보드를잡고있을충분한시간이없어서여기에적응되어버린건지도,,) 이제어떻게해야하느냐? 두가지방법이있습니다. 위암호화루틴을역으로분석하여복호화루틴을만드는것과순차적으로입력가능한모든경우의수를대입해보는방법이있습니다. 어떤방법이더어려울지는짐작이가시겠죠? 전자의방법은알고리즘을완벽하게파악해야함을물론이고, 상당한수학적능력이요구됩니다. 후자의방법은시간이오래걸리긴하지만, 복호화루틴없이암호화루틴만으로복호화코드를알아낼수있습니다. 우리는이미암호화루틴은알고있기때문에두번째방법을이용하는것이훨씬쉽겠죠? 저도머리쓰는것을싫어하기때문에 brute force 공격을시도하겠습니다. 먼저위루틴과동일한기능의암호화함수를만들었습니다. 이미 W32DASM 이어셈블리어로알려주었으므로 MASM 을이용하면더쉽게작성할수있습니다. 아니, MASM 을사용할수밖에없죠. (crack.asm) 저역시 icedump 를이용하여코드를덤프한뒤점프코드들에대해적절히라벨명을수정하는것만으로간단하게만들수있었습니다. MASM 으로라이브러리형태로컴파일한뒤 (ml /coff crack.asm) 확장자를.lib 으로바꿔줍니다. 함수에대한프로토타입을 long crack( char *stext ); 헤더파일 (crack.h) 에정의하고, 우리가 C 로작성할무차별대입프로그램 (solution.c) 에서 include 시켜줍니다. 이함수는우리가파라미터로넘겨준 8 자리문자열을가지고크랙미에서하는것과똑같은연산을하여 32 비트키값을리턴합니다. 크랙미에서는미리메모리에저장해둔키값 0x64F0B6D8 와비교하므로, 우리는모든값을차례대로대입하면서리턴값이 0x64F0B6D8 와같은지를체크하면되겠습니다. 처음에는숫자를입력해봤습니다만, 답이안나오더군요. readme.txt 에서도 word( 단어 ) 라고말하고있으므로 8 자리의대입가능한모든알파벳으로다시공격을시도했습니다. 결과는...? 빙고 ~! 공격한지몇분되지않아패스워드를뱉어내는군요 :) 조합가능한수많은경우의수가있기때문에, 패스워드가한두개가아니군요. 단점이있다면모든패스워드를알아내는데에는상당한시간이필요하다는것입니다. 지금의대문자 / 소문자의조합으로도 AAAAAAAA 부터 zzzzzzzz 까지연산하는데몇일은족히걸릴듯싶습니다. 거기다숫자, 특수문자까지포함된다면.. 어쩌면제대하기전에결과를못볼지도모릅니다 :) 실행되는과정을출력하게끔하면더욱더느려지기때문에 (printf 가상당히느립니다 ) 패스워드를찾았을때만출력하도록하는것이스트레스를덜받겠죠? Detten 은말이되는단어 (word) 를패스워드로설정한듯한데요, 제가알아낸것은다말이안되는것들이네요. 뭐, 그래도어때요크랙미에입력해보면맞다고하는걸요. hehe~
성공 ~ [ 첨부 1] crack.h #ifndef _CRACK_H_ #define _CRACK_H_ extern unsigned long crack( char *text ); #endif [ 첨부 2] crack.asm.386.model FLAT.DATA KEY_HIGH DD 00000000h KEY_LOW DD00000000h.CODE _crack PROC NEAR PUBLIC _crack PUSH EBP MOV EBP, ESP PUSH EDI PUSH EBX PUSH EDX PUSH ESI PUSH ECX MOV EAX, [EBP + 8] MOV EBX, [EAX] MOV KEY_HIGH, EBX
MOV EBX, [EAX + 4] MOV KEY_LOW, EBX XOR ESI, ESI MOV EAX, 0000DEADh MOV ECX, EAX XOR ECX, ECX XOR EBX, EBX LOOP2: MOV EDI, 000004D2h MOV EBX, KEY_HIGH XOR EDX, EDX CMP EBX, 40h JL ERROR LOOP1: MOV DL, BL ADD EDI, EDX ADD ESI, ESI XOR ESI, EDI SHL EDI, 02h SHR EBX, 08h CMP EBX, 00h JNZ LOOP1 INC ECX CMP ECX, 06h JNZ LOOP2 ADD ESI, EDI MOV EAX, ESI XOR ECX, ECX LOOP4: MOV EBX, KEY_LOW MOV EDI, 0000162Eh XOR EDX, EDX LOOP3: MOV DL, BL ADD EDI, EDX IMUL ESI, 03h XOR ESI, EDI SHL EDI, 03h SHR EBX, 08h CMP EBX, 00h
JNZ LOOP3 INC ECX CMP ECX, 06h JNZ LOOP4 ADD ESI, EDI ADD ESI, EAX MOV EAX, ESI JMP EXIT ERROR: MOV EAX, 0 EXIT: POP ECX POP ESI POP EDX POP EBX POP EDI MOV ESP, EBP POP EBP RET _crack ENDP END [ 첨부 3] solution.c #include <stdio.h> #include "crack.h" #define CRYPTED_KEY 0x64F0B6D8 int main( void ) { static char stext[8] = "AAAAAAAA"; register char bcnt; unsigned long lkey = 0; printf( "Solution for crackme5(by Detten) written by TAE-4 n" ); printf( "Searching Key.. (maybe it takes very long time) n n" ); //printf( "Input Text Crypted Key n" ); //printf( "---------- ----------- n" );
while( stext[0] < 'z' ) { for( bcnt = 7; bcnt > 0; bcnt-- ) { if( stext[bcnt] == ('Z' + 1) ) stext[bcnt] = 'a'; if( stext[bcnt] == ('z' + 1) ) { stext[bcnt] = 'A'; stext[bcnt - 1]++; } } lkey = crack( stext ); //printf( " r %s %X", stext, lkey ); if( lkey == CRYPTED_KEY ) printf( " => I got it! Key correct! n" ); stext[7]++; } return( 0 ); }