PalmProgramming/간단한 Cracking으로부터 프로그램 보호하기

마지막으로 [b]

저작권 침해나 소프트웨어의 무단 변조로부터 소프트웨어를 보호하는 일은 어려운 일이다. 개발자는 비즈니스 모델에 맞는 라이센스 기법을 결정해야 하고 그것을 구현해야 한다. 윈칙적으로 프로그램의 무단변조를 막는 일은 불가능에 가깝다. 여기서는 프로그램의 무단 변조의 속도를 느리게 하거나 귀찮게 하는 방법을 논의해 보고자 한다.

  1. 등록 기법
    1. Copy Protection Bit 사용법 : FileZ와 같은 tool 덕분에 거의 무용지물이다.
      {
        UInt16 card, attributes;
        LocalID dbID;
        SysCurrAppDatabase(&card, &dbID);
        if (dbID != NULL) {
          DmDatabaseInfo(card, dbID, NULL, &attributes, NULL, NULL, NULL,
          NULL, NULL, NULL, NULL, NULL, NULL);
          attributes |= dmHdrAttrCopyPrevention;
          DmSetDatabaseInfo(card, dbID, NULL, &attributes, NULL, NULL, NULL,
          NULL, NULL, NULL, NULL, NULL, NULL);
        }
      }
    2. Serial Number : 프로그램마다 일정한 pool의 serial number를 제공하는 것. 그 serial number가 배포되거나 disassember를 통해 프로그램 이미지 내에 있는 number가 손쉽게 알려질 수 있다.
      #define SERIALNUMBER 12345
      #define incorrectSerialAlert 1001
      {
        codeEntered = StrAToI(strCode);
        if (codeEntered != SERIALNUMBER)
          FrmAlert(incorrectSerialAlert);
        else
          registered = true;
        }
    3. Nag Screen : 등록하라는 Nag 스크린을 보여주는 기법이다. 해당 위치를 찾아 '4E4FA192' (FrmAlert)를 '4E714E71' (NOP)로 바꾸는 것으로 손쉽게 무력화된다.
      #define nagScreenAlert 1000
      {
        FrmAlert(nagScreenAlert);
      }
    4. 코드 생성 시스템 : Hotsync ID나 ROM Id를 바탕으로 등록코드를 만들어 내는 기법이다. 등록코드 생성 코드가 프로그램에 내장되어 있어 어렵지 않게 Serial number generator를 역으로 만들어 낼 수 있다.
      #include <System/DLServer.h>
      {
        CharPtr username =
          (Char *)MemPtrNew(dlkUserNameBufSize * sizeof(Char));
        DlkGetSyncInfo(NULL,NULL,NULL,&username,NULL,NULL);
        ...
        MemPtrFree(username);
      }
  2. Anti-Cracking 기법
    1. 등록판과 데모판의 구분 : 등록판과 데모판을 구별하여 데모판에는 일부만 구현되어 있고, 등록판에는 전체 기능이 다 구현되게 한다. 등록사용자는 지정된 장소로부터 등록판을 download받을 수 있게 한다. 소프트웨어 보호로는 최적의 방법이지만, 시험 사용자가 등록판의 모든 요소를 다 test해 볼 수 없게 한다. 또한 등록된 사용자에 의해 누출된 등록판이 인터넷에 떠도는 걸 막을 방법이 없다.
    2. Registration Check : Disassemble 후 result값을 항상 true가 되도록 변경하면(해당 위치를 찾아 MOVEQ #1,D0) 항상 등록된 것으로 판단된다. 함수 전체를 inline으로 만들어 checkRegistered()가 사용되는 부분을 모두 찾아 수정하게 하면 변경이 더 귀찮아진다. 하지만 프로그램 크기는 더 증가하게 된다.
      Boolean checkRegistered()
      {
        Boolean result = false;
        // check user registration (using appropriate technique)
        ...
        return result;
      }
    3. Palm OS Emulator Detection : POSE인지 check해서 POSE에서 수행될 경우 다른 루틴을 수행하게 해서 cracking을 어렵게 하는 방법이다. 하지만, 정상적인 사용자가 POSE를 이용해서 프로그램을 테스트해볼 수 있는 것조차 막는다. 또한 일부 mask rom에서는 POSE가 아님에도 불구하고 POSE로 잘못 동작할 우려가 있다.
    4. Code CheckSum : Code segment를 읽어서 Checksum을 구해 code 자체가 변경되었는지 판단한다. 변경 된 경우 프로그램을 종료하게 한다. 비정상적인 patch인 경우 프로그램의 수행이 안 되도록 한다. 하지만 Checksum check부분 자체를 수정해서 checksum이 틀린 경우에만 수행되도록 고치는데는 무력하다.
    5. Patch Detection : Code segment를 읽어 NOP(0x4E71)이나 TRAP #8(0x4E48)이 나오는지 check해서 이것이 나온경우(정상적인 경우에는 없어야 하므로) 프로그램의 수행을 중단한다. 프로그램 수행시 code segment 조사하느라 불필요한 delay가 소요되고, 이 patch detection code자체가 수정되는걸 막는 code를 또 다른 어느곳에 추가해야 한다.
    6. Byte Code : Forth나 PocketC등과 같이 runtime library와 함께 돌아가게 수행하고, 실제 code는 byte code로 이루어져 있고 runtime library에서 이걸 읽어서 수행하게 하는 방법이다. Crack이 거의 불가능하다. 하지만 이런 environment의 프로그램치고 쓸만한 걸 제대로 못봤다.
  3. 보다 현실적인 간단한 cracking으로부터 프로그램 보호법
    Cracking을 원천적으로 막는 건 불가능하다. Cracker가 프로그램을 변조하는 걸 좀 더 귀찮게 할 수 있는 방법을 생각해 보자. 단지 귀찮게 하는 방법일 뿐이다.
    1. Crack point를 찾는 걸 귀찮게 한다.
      1. FrmAlert를 쓰지 않는다.
        등록코드 입력시 'Thanks for Registration' 또는 'Invalid Registration code'등을 출력하기 위해 Alert를 쓰면 10분 내에 crack이 가능하다고 경험있는 사람이 조언을 했다. 어짜피 crack이란 if문 조건을 BEQ를 BNE등으로 한 byte만 수정하면 되는 일이다. 위치를 찾는게 귀찮을 뿐이다.
      2. FrmCustomAlert를 사용한다.
        FrmAlert는 Talt resource를 조사하면 resouce id를 알 수 있고 disassemble된 code를 살펴보면 해당 위치를 금방 알 수 있다. 모든 Alert를 대신 FrmCustomAlert를 사용하면 위치 찾는 시간을 조금 늦출 수 있다. 능력있는 cracker는 여러 Custom Alert를 NOP로 치환해 가면서 해당 위치를 찾아갈 수 있다. 이걸 좀 귀찮게 하기 위해 FrmCustomAlert 함수를 함수 내에다 넣고 이 함수를 여러군데에서 호출하게 한다. FrmCustomAlert가 호출되는 부분은 하나 이기 때문에 disassembler와 hex-editor만으로 작업하기는 힘들어진다. Debugger의 도움이 필요해져서 귀찮음을 느낄 수 있다.
      3. 등록성공/실패 메시지
        등록코드를 입력했을 때 등록 성공/또는 실패하는 메시지를 출력하게 하면 disassemble된 code로부터 해당 메시지를 찾아 위치 추적이 손쉬워진다. 이 메시지를 encoding하자. 복잡한 암호화 루틴까지는 필요없이, 문자열에 얼마를 더하거나 빼는 것 만으로도 editor에서 search가 귀찮아진다.
      4. 등록실패했을 때 Alert창 띄우지 않기
        등록성공했을 때는 '등록이 성공했다는' 메시지를 출력하지만, 등록이 실패했을 때는 그냥 무시하자. Cracker가 공략 point를 잡기가 힘들어진다. 어디에 break-point를 걸어야 할지 헤갈리게 된다.
      5. Nag check routine의 검색이 어렵게 하기
        위에 언급한 technique을 사용 Nag check routine이 어딘지 알기 힘들게 한다. 위치를 찾아내게 되면 단지 몇 바이트의 수정만으로 'Nag'가 무력화 된다는 걸 기억하자. Nag screen 자체를 random하게 뜨게 하면 그것도 tracing할 때 귀찮게 된다.
      6. Global 변수 사용의 자제
        훌륭한 disassembler는 global variable의 Read/Write point 위치까지 정확히 짚어준다. 메모리도 절약할 겸 memory를 allocation해서 사용하자. Disassembler가 cross-reference를 만들기 힘들게 해 준다.
    2. 등록코드 Generator 생성을 귀찮게 하기
      1. 등록코드 부분의 분산
        등록코드에 관련된 코드를 프로그램 전반에 나눠 위치 파악을 힘들게 한다. 설사 Crack이 되더라도 여러군데를 수정해야 하게 함으로써 Cracker를 귀찮게 한다. 그리고 Registration check부분과 등록 성공창 출력부분이나 Nag screen창 출력 부분을 분리함으로써 Alert창 위치가 파악되더라도 Registration check하는데 시간이 더 걸리도록 한다.
      2. Integer나 Long 등록 코드의 자제
        사용자가 기억하기 쉽다는 점에서 정수 등록코드를 많이 만들어 쓰지만, 이런 코드는 D0 register만 조사하면 등록코드를 금방 알 수 있는 단점이 있다.
      3. Palm Built-in API의 사용 자제
        Cracking으로부터 프로그램을 막는 것의 단점 중의 하나다. 프로그램 사이즈를 불필요하게 증가하게 하고, 속도를 느리게 한다. 원래 Palm Program Guide line에는 Palm Built-in API의 사용을 권장하고 있다. 하지만 이러면 Disassembler가 해당 위치를 정확히 짚어 준다. 등록 문자열 비교위해 StrCompare나 StrNCompare등을 사용하면 불과 수초 내에 해당 위치를 정확히 알 수 있다. 물론 다른 곳에서 이 함수를 많이 쓰면 위치 찾기가 힘들어질 수 있다. 하지만 귀찮더라도 만들어 써라.
    3. 기타
      1. Preference에 등록 유무를 check하는 bit만 저장하지 말기
        해당 bit setting부분만 수정해서 손쉽게 등록버젼을 만들 수 있다. 사용자가 입력한 등록코드 자체를 저장해서 필요할 때마다 비교하라.
      2. Preference record가 지울 때 대비
        여벌의 Preference record를 사용하거나 Program 자체 DB에 부과적인 정보를 남겨두어서 지워지는 경우에도 복구가 가능하게 한다.
      3. Debugger로 trace하기 귀찮게 하기
        자주 수행되는 루틴에 등록 코드 관련된 코드를 놓아서 debugger로 trace할 때 자주 멈추게 하라. 하지만 프로그램의 속도에는 악영향을 미치는 방법이다.
      4. Conduit 프로그램과 key를 연동되게 하기
        Conduit 프로그램쪽과 key를 연동해서 check하게 하면 PC, Palm 두쪽에서 cracking을 시도해야 한다. 은근히 귀찮은 일이다.
      5. 등록코드가 메모리에 존재하지 않도록 한다
        등록코드를 다 만든 다음에 비교를 하게 되면 등록코드가 메모리 상에 노출이 된다. 필요 부분만 만들어 비교하라.
      6. 등록관련 code를 inline으로 만든다
        Patch를 할 때 손이 많이 가게 만드는 방법이다. 하지만 프로그램 사이즈가 커지는 단점이 있다.
  4. Comments
    모든 프로그램은 깨지기 마련이다. 단지 어떻게 하면 그걸 귀찮게 하느냐는 것일뿐이다. 기본적으로 software protection을 염두에 두려면 cracking technique과 assembly language에 대한 기본적 지식이 필요하다. 프로그램내에 보호 code가 많아질수록 프로그램 code는 비대해지고(어떻게 보면 불필요한 코드), 프로그램의 속도는 느려진다. 또한 Program의 가독성 또한 떨어진다. 등록 관련 code를 프로그램 전반에 흩어 놓으로써 정보의 encapsulation과 Information hiding에 어긋나게 된다. 정상적인 사용자를 불편하게 하지 않게 하면서 Cracker가 프로그램을 무단 변조하는 걸 귀찮게 하는 건 어려운 일이다. 뭐. 어쩌랴... 대안이 없는걸.
    프로그램에 갖가지 버그를 추가해서 Cracker가 관심을 안 가지게 하는 것도 한가지 방법일 것이다. http://wiki.jmjeong.com/emoticon//emoticon-smile.gif


KPUG에서 초보가 하는 간단한 크래킹을 읽고 Omar님과 이런저런 이야기 나눴던 걸 정리해 봤습니다.

-- Jmjeong 2003-8-26 2:01 am

찬찬히 읽어보았더니, 정말 멋지군요.. 그냥 말씀을 나누시면서도 머리속으로는 정리를 해 버리시는듯..리버티 만든 사람이 쓴 논문보다 좋은것 같네요.. http://wiki.jmjeong.com/emoticon//emoticon-laugh.gif / Omar 2003-08-26 7:18 pm
제가 사용하는 방법은 2가지 종류입니다. 물론 안깨지는 방법은 없겠죠.
  1. 코드분할로 프로그램후 해당코드의 CRC를 Image Resource에 박은후 비교, 에뮬레이터에서 동작금지, 시리얼 코드 서너군대에 사용, CRC가 틀리면 크래커가 모를만한 버그 동작 루틴 동작
    1. 이건 제가 귀찮아서 잘 안쓰는 방법, 모든게 귀찮아요.
  2. MultiUserHack(HotyncID, Rom Serial 변경프로그램)
    1. 이 프로그램내에 내 프로그램이 동작 되어 있으면 MultiUserHack DB변경
    2. 1번과 같이 써야 효과적이나 1번작업자체가 워낙 귀찮으니
  3. Serial Seed에 Constant를 쓰지 않는다. 쓰게되면 Resource로 변경되기때문에 모든코드가 누출 된다. 당연히 쓰지 말아야 한다.
    // 작성자: 자손김/김성남
    // MultiUserHack DB에 내 프로그램이 등록되어 있다면 MultiUserHack의 DB의 내 Creator ID를 변경한다.
    static void FakeMultiUserHack()
    {
             Err			error;
    	UInt32		muuaType    = 'DATA';
    	UInt32 		muuaCreator = 'MuUa';
    	UInt16		openMode  = dmModeReadWrite;
    	DmOpenRef	dmRef;
    	
    	// Creator Field variable
    	MemHandle 	hCreator;
    	char        *pRecord;
    
    	UInt16 nMultiUserHackNums, nMultiUserHackIndex;
    	char   strGetCreator [5] = "";
    	char   strFileCreator[]  = "CasC"; // 내프로그램의 CreatorID
    	char   strFakeCreator[]  = "CsCa"; // 변경할 CreatorID
    	UInt16 nCreatorSize = sizeof(strFileCreator) - 1;// * StrLen(strFakeCreator);
    
    	dmRef =  DmOpenDatabaseByTypeCreator (muuaType, muuaCreator, openMode);
    	if (dmRef != 0 ) {
                      // MultiUserHack의 Records개수를 알아낸수
    		nMultiUserHackNums =  DmNumRecords (dmRef);	
                      // Records 개수만큼 검색을 한다.
    		for ( nMultiUserHackIndex = 0; nMultiUserHackIndex < nMultiUserHackNums; nMultiUserHackIndex++ ) {
    			hCreator =  DmGetRecord (dmRef, nMultiUserHackIndex);
    			if (hCreator) {
    				pRecord = (char *) MemHandleLock(hCreator);
    				
    				MemMove(strGetCreator, pRecord, nCreatorSize);					
                                         // 내 프로그램의 CreatorID가 있다면 "0"일때... 
    				if(StrCompare (strGetCreator, strFileCreator) == 0) {
                                                  // 속일 CreatorID를 써줌
    					MemMove(pRecord, strFakeCreator, sizeof(strFakeCreator));
    				}																
    				MemHandleUnlock(hCreator);
    				DmReleaseRecord (dmRef, nMultiUserHackIndex, false);				
    			}
    		}
    		DmCloseDatabase(dmRef);
    	}			
    }
-- Kittysnk 2003-9-20 11:28 am
트랙백 주고받기

마지막 편집일: 2003-9-20 11:36 am (변경사항 [d])
1235 hits | 변경내역 보기 [h] | 페이지 소스 보기