Named-mutant를 이용한 다중실행 방지 기법은 , 이미 지겹도록 다루어졌고 널리 퍼졌기때문에
이번엔 다른 커널 객체를 이용한 방법을 간단히 다루고자 한다.

결론부터 말하면, 별거없고 그냥 다른 커널객체를 이용할 뿐이다.

바로 소스를 살펴보면,

NTSTATUS ntRet = STATUS_SUCCESS;
HANDLE hSection;
UNICODE_STRING SectionName;
OBJECT_ATTRIBUTES SectionAttributes;
LARGE_INTEGER SectionSize;
CHAR Dbgmsg[100];

RtlInitUnicodeString(&SectionName, L"\\BaseNamedObjects\\s0neSectionObject");
InitializeObjectAttributes(&SectionAttributes, &SectionName, OBJ_OPENIF, NULL, NULL);
SectionSize.QuadPart = 0x1000;   //optional

ntRet = ZwOpenSection(&hSection, SECTION_ALL_ACCESS, &SectionAttributes);

//Is named-section already exist?
if( NT_SUCCESS(ntRet) )
{
ZwClose(hSection);
sprintf_s(Dbgmsg, sizeof(Dbgmsg), "Application is already running. (0x%08X)", ntRet);
MessageBoxA(GetForegroundWindow(), Dbgmsg, "Error", MB_OK);
return EXIT_SUCCESS;
}
else
{
//Create new named-section.
ntRet = ZwCreateSection(&hSection, SECTION_ALL_ACCESS, &SectionAttributes, &SectionSize, PAGE_READWRITE, SEC_COMMIT, NULL);

if( !NT_SUCCESS(ntRet) )
{
sprintf_s(Dbgmsg, sizeof(Dbgmsg), "Section create failed. (0x%08X)", ntRet);
MessageBoxA(GetForegroundWindow(), Dbgmsg, "Error", MB_OK);
return EXIT_FAILURE;
}


//.....continue execution......
         }


먼저 특정 이름의 섹션이 존재하는지 확인한 뒤, 존재한다면 이미 실행중이라 판단하고 종료하고, 
존재하지 않는다면 실행중이지 않다고 판단해서 고유의 이름을 가진 섹션을 새로 생성한다.
사람들이 거의다 뮤텍스만 사용하기때문에, 섹션은 조금 생소할수도 있으나,
따지고보면 결국은 이름 지정이 가능한 다른 커널 객체를 이용한다고 볼수있다.

VS2010 빌드

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2011.02.27 10:25

다중실행이라 함은 ,  사람들이 흔히 말하길   "멀티로드" 라고 부르는 것이다.
그리고 다중실행이 가능하게끔 유도해주는 프로그램을  "멀티로더" 라고 부르는게 맞는것 같다.

이렇게 멀티로드는  크랙 분야에서도  알고나면 매우 쉬운 분야에 속한다. (조금만 공부해도 알수 있음)
그 이유는  다중실행방지 기법은  아무리 기고날뛴다 한들 ,  현재까지는 윈도우API함수에 매우 종속적이기 때문이다.
(코딩을 어찌하던간에 윈도우라는 운영체제에 종속적임,아직까지 다중실행방지부분을 창의적으로 코딩한것은 보지 못하였다.
사실 기업 입장에서 시간 엄청 투자해가면서 다중실행방지에 투자할 필요는 없다고 본다. - 지극히 주관적인 생각)


아래는 다중실행 방지에 보통 많이 쓰이는 API함수들을 열거한 것이다.

CreateMutex - GetLastError  의 순서.
> CreateMutex 함수는 뮤텍스를 생성하는데 , 만약에 동일네임의 뮤텍스 커널 오브젝트가 이미 생성되어져 있는 상태라면, GetLastError 함수의 호출결과는  0x0B7이 반환된다.  CMP EAX,0x0B7  혹은 그 바로 다음의 분기점프문을 주목하는게 좋을듯.

CreateSemaphore - GetLastError 의 순서.
위 뮤텍스 기법과 동일하다고 봐도 무관함.

FindWindow 함수.
> 이름이 매우 직관적이니 무슨 부연 설명이 필요하겠는가?
FindWindow 함수는 Window Name 혹은 Class Name이 인자로 전달되며 ,  만약에 인자로 전달된 해당 윈도우창이 존재한다면,
그에 맞는 HWND 값을 반환한다. 이 API함수가 호출된 이후의  분기점프문을 주목하는게 좋을듯.

CreateWindowEx (온라인게임에서 가끔 이용)
>역시나 이름이 매우 직관적이다.  이 API함수 뒤에 나오는 분기점프문을 주목하는게 좋을듯.




다른 API들도 있지만 ,  근래의 어플리케이션 대부분은  위 API함수들을 이용할것이다.

하나의 예를 들기 위해서 ,  국내에서 가장 큰 음악 방송사의  플레이어 프로그램의 속을 들여다 보겠다.
나름대로 흐름을 추측해보기 바란다.

0043823B  /$  53                         PUSH EBX
0043823C  |.  56                         PUSH ESI                                 ; 시작 전 , EBX , ESI , EDI 레지스터를 스택에 백업해둠.
0043823D  |.  57                         PUSH EDI                                 ; 이것은 나중에 Return 되기전 , POP을 통해 복원됨.
0043823E  |.  68 A0EA5500                PUSH 0055EAA0                    ; /MutexName = ""
00438243  |.  6A 01                      PUSH 1                                   ; |InitialOwner = TRUE
00438245  |.  33DB                       XOR EBX,EBX                              ; |
00438247  |.  53                         PUSH EBX                                 ; |pSecurity = 7FFDF000
00438248  |.  8BF1                       MOV ESI,ECX                              ; |
0043824A  |.  FF15 34925500              CALL DWORD PTR DS:[<&KERNEL32.CreateMute>; \CreateMutexW
00438250  |.  8986 A8000000              MOV DWORD PTR DS:[ESI+A8],EAX
00438256  |.  33FF                       XOR EDI,EDI
00438258  |.  FF15 88925500              CALL DWORD PTR DS:[<&KERNEL32.GetLastErr>; [GetLastError
0043825E  |.  3D B7000000                CMP EAX,0B7
00438263  |.  75 01                      JNZ SHORT 00438266
00438265  |.  47                         INC EDI
00438266  |>  8B86 A8000000              MOV EAX,DWORD PTR DS:[ESI+A8]
0043826C  |.  3BC3                       CMP EAX,EBX
0043826E  |.  74 07                      JE SHORT 00438277
00438270  |.  50                         PUSH EAX                                 ; /hMutex = NULL
00438271  |.  FF15 38925500              CALL DWORD PTR DS:[<&KERNEL32.ReleaseMut>; \ReleaseMutex
00438277  |>  3BFB                       CMP EDI,EBX
00438279  |.  74 4A                      JE SHORT 004382C5
0043827B  |.  55                         PUSH EBP
0043827C  |.  53                         PUSH EBX                                 ; /Title = ""
0043827D  |.  8B1D 58995500              MOV EBX,DWORD PTR DS:[<&USER32.FindWindo>; |USER32.FindWindowW
00438283  |.  BD F0F15500                MOV EBP,0055F1F0                 ; |UNICODE ""
00438288  |.  55                         PUSH EBP                                 ; |Class = "\xA4?\x89\xB8?????xA1"
00438289  |.  FFD3                       CALL EBX                                 ; \FindWindowW
0043828B  |.  8BF8                       MOV EDI,EAX
0043828D  |.  85FF                       TEST EDI,EDI
0043828F  |.  74 1C                      JE SHORT 004382AD
00438291  |.  6A 01                      PUSH 1
00438293  |.  57                         PUSH EDI
00438294  |.  8BCE                       MOV ECX,ESI
00438296  |.  E8 72FBFFFF                CALL 00437E0D
0043829B  |.  57                         PUSH EDI                                 ; /hWnd = NULL
0043829C  |.  FF15 D4975500              CALL DWORD PTR DS:[<&USER32.SetForegroun>; \SetForegroundWindow
004382A2  |.  6A 0A                      PUSH 0A                                  ; /ShowState = SW_SHOWDEFAULT
004382A4  |.  57                         PUSH EDI                                 ; |hWnd = NULL
004382A5  |.  FF15 34995500              CALL DWORD PTR DS:[<&USER32.ShowWindow>] ; \ShowWindow
004382AB  |.  EB 13                      JMP SHORT 004382C0         ; 프로그램이 시작되지 않는 부분으로 점프(종료)
004382AD  |>  6A 00                      PUSH 0        ;프로그램이 시작되는 부분





진한 부분은 특히 주목해야할 흐름과 부분이다.



이 프로그램에서  다중실행을 컨트롤 하는 로직을 간단히 서술해본다면 아래와 같다.

1) XOR EBX,EBX 로  EBX레지스터를  0으로 초기화
(이것은 CreateMutexW 함수의  LPSECURITY_ATTRIBUTES 인자로 NULL이 전달되기 위함이지만 , 
또 다른 목적으로는  아래  순서 6 의   비교문에서 쓰인다. 이것은 컴파일러의 Optimization과 관계가 있을것이다.)





2) CreateMutexW (유니코드 함수)  를 호출하면서 뮤텍스를 생성한 후 ,
함수의 호출결과(핸들)  를 ESI+A8  변수에 보관해둔다. ( MOV DWORD PTR DS:[ESI+A8] , EAX )

여기서 주목해야할 점은 , CreateMutex 함수의 리턴 값이다.
MSDN을 참조해보면 아래와 같이 나와있다.

If the mutex is a named mutex and the object existed before this function call, the return value is a handle to the existing object, GetLastError returns ERROR_ALREADY_EXISTS(0xB7)
(이 함수가 호출되기 전에 이름있는 뮤텍스 오브젝트가 존재한다면 ,  GetLastError 함수의 리턴은 0xB7이다.)







3) 뮤텍스를 생성한 후 ,  XOR EDI,EDI 연산을 하면서 EDI  레지스터를 0으로 초기화 한다.




4) GetLastError 함수를 호출한 뒤 ,  EAX에 반환된 에러코드와 0xB7을 CMP 연산한다. (비교한다 , 즉 내부적으로 뺄셈 연산)




5)  비교연산 결과가 0이라면 (ZF=1)  JNZ에서 점프를 하지않고 , 아래로 내려오면서 EDI레지스터의 값을 1 증가시킨다. (EDI = 1)
비교연산 결과가 0이 아니라면 (ZF=0) JNZ에서 점프를 하여 EDI레지스터의 값은 그대로 0으로 유지된다.




6) ESI+A8 위치의 결과 ,  즉 2 에서  뮤텍스를 생성하면서 반환된 핸들을 보관해두었던것을 EAX레지스터로 다시 불러온다.
그 후에 EBX레지스터와 EAX레지스터를 CMP 연산한다. 
(Base Register는 앞에서 XOR연산을 통해서 0으로 초기화되었기 때문에, 0 을 지니고 있다.)




7) 같다면,    즉  EAX가 0 이라면  2번 순서에서  CreateMutex 함수 호출에 실패한것이 되므로(다중실행 된 상황) ,  뮤텍스 해제 함수 ReleaseMutex 를 호출하지 않고 , FindWindow API를 호출하러 간다.
( 프로그램이 실행중이라고 의심되는 상황이기 때문에 FindWindow API를 이용해서 확실하게 잡겠다는 의미)

EAX에   0이 아닌 값이 존재한다면 ,  뮤텍스가 생성된것이라고 볼수있으므로(다중실행 되지 않은 상황)  , ReleaseMutex 함수를 호출하여 , 뮤텍스를 해제시킨다. 그리고는   FindWindow API의 호출은 필요가 없게되므로  , 바로 프로그램 실행 루틴으로 넘어간다.

즉 이 프로그램은 단순히 뮤텍스 오브젝트를   다중실행방지에만 이용하고 ,  그 외의 목적으로는 이용하지 않는다는것이 명백해졌다.





8) 마지막으로 FIndWindow API를 이용해서  윈도우를 찾는다.   호출결과를  EDI 레지스터에 넣고  TEST EDI,EDI 연산을 한다.
TEST EDI,EDI 는 내부적으로 AND 연산을 하는데 ,  FindWindow 호출결과에서 윈도우를 발견하지 못하고 NULL이 반환되었다면 (즉 , 다중실행 된 상태가 아니라면)  AND 연산의 결과 역시  0이 되므로 , JE 분기 점프문에서  점프를 해서 프로그램 실행 루틴으로 간다. 

FindWindow API에서  윈도우를 찾고 특정 HWND가 반환되었다면  TEST EDI,EDI 연산에서 당연히 TRUE 가 될것이므로 ,  점프를 하지않고 아래로 내려와서  SetForeGroundWindow , ShowWindow  함수를 호출하면서  현재 실행중인 창을 맨 앞으로 띄운다.








이를 고급언어로  작성하면  아래와 같다.
(위 디스어셈블 된 내용을  토대로 하여,  고급언어로 직접 작성한것이며 ,  Hexray  디컴파일 플러그인은 사용하지 않았음.)




HANDLE ESI_A8;
 HWND hwnd;

 ESI_A8=CreateMutexW(NULL,TRUE,"lpName");
 
 BOOL EDI=FALSE;
 if(GetLastError()==ERROR_ALREADY_EXISTS)
  EDI=TRUE;

 if(ESI_A8!=NULL)
  ReleaseMutex(ESI_A8);

 else //ESI_A8 이 NULL 이라면 (즉,  뮤텍스 생성에 실패하였다면)
 {
  if(EDI==TRUE) //GetLastError 함수 호출결과가 0x0B7 이라면
  {
   hwnd=FindWindowW("lpClass","lpWindowName");
   if(hwnd==NULL)
   {
    프로그램 실행으로 점프
   }
   else
   {
    SetForegroundWindow(hwnd);
    ShowWindow(hwnd,SW_SHOWDEFAULT);
    프로그램 종료로 점프
   }
  }
 }
 프로그램 실행으로 점프

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
by Sone 2009.08.12 23:19
| 1 |

티스토리 툴바