HxD.exe 기능 추가!

tool 2012.06.14 00:35

리버싱 기술을 조금 응용하면 다른 응용프로그램의 기능을 추가/변경시킬 수 있습니다. 


제가 애용하는 헥스 에디터 HxD.exe 에 필요한 기능을 살짝 추가하였습니다.



HxD.exe



DLL InjectionAPI Hooking 기술을 이용하여 HxD.exe 프로그램에 새로운 기능을 추가하였습니다.



제가 추가시킨 기능은 PE 파일 Offset 의 RVA 값을 계산해서 출력해 주는 것입니다. (위 그림 참고)


키보드나 마우스를 이용하여 커서 위치를 이동하면 Offset 값이 변함에 따라서 자동으로 그에 맞는 RVA 값이 계산되어 표시됩니다.



사용방법



HxD.exe  - Hex Editor

hxdhook.dll  - hooking dll

InjDll32.exe  - Injector

PEInfo.dll  - File Offset <->RVA converter



1. HxD.exe 실행


2. HxD 에서 아무 PE 파일을 오픈


3. InjDll32.exe 를 이용하여 HxD.exe 프로세스에 hxdhook.dll 을 인젝션





HxD.exe 프로세스에 hxdhook.dll 이 정상적으로 인젝션 되었다면 이제부터 프로그램 하단의 Status Bar 에 Offset 과 RVA 가 동시에 표시될 것입니다. 


크게 유용하다고 볼 수 는 없지만 리버싱 기술을 응용하여 기존 프로그램에 새로운 기능을 추가하는 방법을 잘 보여주는 예제라고 생각합니다. 필요하신 분께서는 자유롭게 가져다 쓰실 수 있습니다.



* 블로그에 리버싱 기술 응용편을 올리기 위해 준비한 간단한 시연입니다. 자세한 동작 원리, 후킹 과정, 모듈 개발 과정 등은 따로 포스팅 하도록 하겠습니다.



ReverseCore



저작자 표시 비영리 변경 금지
신고

'tool' 카테고리의 다른 글

PEView.exe  (30) 2013.01.27
HxD.exe 기능 추가!  (12) 2012.06.14
리버싱 현업에서 사용되는 디버거(Debugger)들  (31) 2010.09.29
InjDll.exe – DLL Injection/Ejection 전용 도구  (83) 2010.03.15
Process Explorer - 최고의 작업 관리자  (1) 2009.05.03
www.virustotal.com  (3) 2009.03.20
www.google.com  (2) 2009.03.06
  1. rgs 2012.06.14 19:13 신고 댓글주소 | 수정 | 삭제 | 댓글

    와 제가 댔글 처음인건가요 ?
    주인장님
    블로그 다시 시작하시는군요
    매일 들려보는 사람입니다.
    블로그 정말 재밋어요
    계속 열심히 해주세요

  2. 카이섹이 좋아 2012.06.14 20:33 신고 댓글주소 | 수정 | 삭제 | 댓글

    리버스 코어님 ㅠ ㅠ 언제 책이.....

  3. 감염오리 2012.06.15 00:44 신고 댓글주소 | 수정 | 삭제 | 댓글

    드디어!!! 포스팅 재개인가요^^

  4. 마리오 2012.06.15 10:25 신고 댓글주소 | 수정 | 삭제 | 댓글

    오호 포스팅 되었군요ㅎ
    책출판될거보다 포스팅된게 더 반가워요ㅎ

  5. db_click 2012.06.16 10:27 신고 댓글주소 | 수정 | 삭제 | 댓글

    항상 포스팅 감사히 보고 있습니다.

  6. 안녕하세요 2012.06.20 09:52 신고 댓글주소 | 수정 | 삭제 | 댓글

    책이 나오기만을 학수고대하고 있습니다.
    책 나오면 바로 살게요! ^^

  7. reversecore 2012.06.23 00:52 신고 댓글주소 | 수정 | 삭제 | 댓글

    관심 감사드립니다. ^^~

  8. Lr라 2013.03.04 14:47 신고 댓글주소 | 수정 | 삭제 | 댓글

    항상 포스팅 하신글 잘보고있습니다.
    음...~~~ 저걸 이용해서 헥스 만큼 바이트 채우고 시작되는 부분에 call로
    때려서 영역에서 불러와서 인라인 어셈으로 자동인젝션되도록 만들어야겟습니다 ㅎㅎ
    잘쓰겠습니다 ㅎㅎ

  9. absolujin 2013.05.02 15:22 신고 댓글주소 | 수정 | 삭제 | 댓글

    전 이렇게 batch 파일 만들어서 사용하고 있어요.
    잘쓸께요. 감사합니다.
    --------------------------------------------------------------
    @echo off
    start Hxd.exe
    start InjDll32.exe HxD.exe -i hxdhook.dll

  10. yO Kang 2014.03.13 02:12 신고 댓글주소 | 수정 | 삭제 | 댓글

    똑같이 했는데, 저는 "The token does not have the specified privilege" 이런 문구뜨면서 안되네요..
    뭐가 문제일까요?

  11. RBWSN 2016.02.25 06:36 댓글주소 | 수정 | 삭제 | 댓글

    관리자의 승인을 기다리고 있는 댓글입니다



Global API Hooking 개념과 구현 방법에 대해서 알아보도록 하겠습니다.


<Fig - Global API Hooking>

본 내용은 이전 포스트에서 이어지는 내용입니다.

API Hooking – ‘스텔스’ 프로세스 (3)

* 참고!
모든 소스 코드는 MS Visual C++ 2008 Express Edition 으로 개발 되었으며, Windows XP SP3 & Windows 7 환경에서 테스트 되었습니다.



Global API Hooking


Global API Hooking 이란 1) 현재 실행중인 모든 프로세스2) 앞으로 실행될 모든 프로세스에 대해서 API Hooking 을 시키는 것입니다.

지난번에 설명한 예제 프로그램(HideProc.exe, stealth.dll)은 global hooking 이 아닙니다. 위에서 설명한 2) 번 조건이 만족되지 않기 때문입니다.

HideProc.exe 를 실행 하여 notepad.exe 프로세스를 은폐시켜도 이후에 Process Explorer (혹은 task manager) 를 실행시키면 이들 프로세스에서는 notepad.exe 프로세스를 볼 수 있습니다.

그 이유는 HideProc.exe 실행 이후에 생성된 프로세스들에게는 stealth.dll 파일이 (자동으로) 인젝션 되지 않기 때문입니다.

아래 링크를 참조하여 직접 실습해 보세요.

API Hooking – ‘스텔스’ 프로세스 (2)


이러한 문제를 해결하기 위한 다양한 방법이 있을 수 있습니다.
그 중에서 또 다른 API 를 Hooking 하여 Global API Hooking 을 구현하는 방법에 대해서 설명 드리겠습니다.


 
Kernel32!CreateProcess() API


새로운 프로세스가 생성되려면 kernel32!CreateProcess() API 를 사용해야 합니다. WinExec(), ShellExecute(), system() 등의 API 도 내부적으로는 CreateProcess() 를 호출합니다.

BOOL WINAPI CreateProcess(
  __in_opt     LPCTSTR lpApplicationName,
  __inout_opt  LPTSTR lpCommandLine,
  __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
  __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
  __in         BOOL bInheritHandles,
  __in         DWORD dwCreationFlags,
  __in_opt     LPVOID lpEnvironment,
  __in_opt     LPCTSTR lpCurrentDirectory,
  __in         LPSTARTUPINFO lpStartupInfo,
  __out        LPPROCESS_INFORMATION lpProcessInformation
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms682425(VS.85).aspx

따라서 현재 실행중인 모든 프로세스에 stealth.dll 을 인젝션 하고, stealth.dll 에서 CreateProcess() API 를 후킹하면 이후 실행되는 프로세스에게도 자동으로 stealth.dll 을 인젝션 시키도록 만들 수 있습니다.

다시 설명 드리면 모든 프로세스는 부모 프로세스에서 (CreateProcess() 를 이용하여) 생성시켜주기 때문에 부모 프로세스의 CreateProcess() API 를 후킹하여 자식 프로세스에게 stealth.dll 을 인젝션 하도록 만들면 됩니다. (보통 부모 프로세스는 explorer.exe 가 될 것입니다.)

어떤가요? 좋은 아이디어 이지요?
이와 같이 Global API Hooking 의 개념은 어렵지 않습니다.

하지만 CreateProcess() API 를 후킹하면 아래와 같이 고려해야 할 사항들이 있습니다.

1) CreateProcess() API 를 후킹할 때는 kernel32!CreateProcessA(), kernel32!CreateProcessW() 두 개의 API 를 각각 후킹해야 합니다. (ASCII 버전과 UniCode 버전)

2) CreateProcessA(), CreateProcessW() 는 각각 내부적으로 CreateProcessInternalA(), CreateProcessInternalW() 를 호출합니다. 실제 MS 제품들 중에서 일부는 CreateProcessInternalA/W 를 직접 호출하기도 하지요. 따라서 좀 더 정확히 Global API Hooking 을 구현하기 위해서는 이 두 함수를 더 후킹해줘야 합니다.

3) 후킹 함수(NewCreateProcess) 는 원본 함수(CreateProcess) 를 호출 한 후 생성된 자식 프로세스에 대해서 API 를 후킹 해야 합니다. 따라서 아주 짧은 시간동안 자식 프로세스가 후킹 되지 않은 채로 실행될 수 있습니다.


많은 리버싱 선배님들에 의하여 kernel32!CreateProcess() 보다 더 후킹하기 좋은 함수가 발견 되었습니다.
바로 ntdll!ZwResumeThread() API 입니다.

NtResumeThread(
    IN    HANDLE    ThreadHandle,
    OUT   PULONG    SuspendCount OPTIONAL
);

* 유저 모드에서는 NtXXX 계열과 ZwXXX 계열은 동일합니다.

* 출처 : http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Thread/NtResumeThread.html


ZwResumeThread() 는 프로세스가 생성 된 후 메인 스레드 실행 직전에 호출되는 함수입니다. 따라서 이 함수 하나만 후킹하면 자식 프로세스의 코드가 하나도 실행되지 않은 상태에서 API 를 후킹시킬 수 있습니다.

단점은 ZwResumeThread() 는 undocumented API 라서 언제 바뀔지 알 수 없으며, 그만큼 안정성을 보장 할 수 없습니다. 따라서 ZwResumeThread() 같은 undocumented API 를 후킹 할 때는 OS 가 패치되면서 변경될 수 있다는 것을 항상 염두에 두어야 합니다. 하위 버전에서는 잘 되던 후킹이 최신 버전에서는 갑자기 안 되는 일이 많기 때문입니다.



실습


HideProc2.exe

stealth2.dll



* 참고
위 stealth2.dll 는 CreateProcess 후킹 버전입니다.
ZwResumeThread 후킹 버전을 원하시는 분께서는 따로 요청해 주시면 보내드리겠습니다.

실습을 간단히 하기 위해서 은폐 프로세스를 notepad.exe 로 고정하였습니다. 참고하시기 바랍니다.

#1. stealth2.dll 파일 -> %SYSTEM% 폴더에 복사


<Fig. 1>

실행 중인 모든 프로세스에 stealth2.dll 파일을 인젝션 시킬 예정입니다. 따라서 모든 프로세스에서 공통적으로 인식할 수 있는 path 인 %SYSTEM% 폴더에 stealth2.dll 파일을 복사합니다.

#2. HideProc2.exe –hide 실행


<Fig. 2>

기존 HideProc.exe 와 비교해서 실행 파라미터가 변경되었습니다. 은폐 프로세스 이름이notepad.exe 로 하드코딩 되어있습니다.
HideProc2.exe 를 –hide 옵션으로 실행시키면 이제부터 글로벌 후킹이 시작됩니다.

#3. ProcExp.exe & notepad.exe 실행

Process Explorer(혹은 작업관리자) 와 notepad 를 여러 개 실행 해 주세요.

 
<Fig. 3>

위 그림을 보시면 ProcExp.exe 와 notepad.exe 프로세스가 각각 2개씩 실행되고 있습니다.
하지만 ProcExp.exe 에서는 notepad.exe 프로세스가 은폐되어 있습니다.

추가로 ProcExp.exe 를 몇 개 더 실행해 보시기 바랍니다. 마찬가지로 새로 생성된 ProcExp.exe 프로세스에서도 notepad.exe 프로세스가 은폐되어서 보이지 않을 것입니다.

이것이 바로 Global API Hooking 의 효과입니다.

#4. HideProc2.exe –show 실행

Global API Hooking 을 해제 합니다.

 
<Fig. 4>

이제 Process Explorer(혹은 작업관리자) 에서 notepad.exe 프로세스가 보일 것입니다.



소스 코드


# HideProc2.cpp

HideProc2.cpp


HideProc2.cpp 는 기존 HideProc.cpp 에서 실행 파라미터를 줄인 것뿐이므로, 기존 설명을 참고하시면 되겠습니다.

API Hooking – ‘스텔스’ 프로세스 (3)


# stealth2.cpp

stealth2.cpp


stealth2.cpp 는 기존 stealth.cpp 에서 은폐 프로세스 이름을 "notepad.exe" 로 하드 코딩 하였고, global hooking 을 위해서 CreateProcessA() API 와 CreateProcessW() API 를 후킹 하는 코드가 추가 되었습니다.


DllMain()

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    char            szCurProc[MAX_PATH] = {0,};
    char            *p = NULL;

    // HideProc2.exe 프로세스에는 인젝션 되지 않도록 예외처리

    GetModuleFileName(NULL, szCurProc, MAX_PATH);
    p = strrchr(szCurProc, '\\');
    if( (p != NULL) && !_stricmp(p+1, "HideProc2.exe") )
        return TRUE;

    // change privilege
 
   SetPrivilege(SE_DEBUG_NAME, TRUE);

    switch( fdwReason )
    {
        case DLL_PROCESS_ATTACH :
            // hook
            hook_by_code("kernel32.dll", "CreateProcessA",
                         (PROC)NewCreateProcessA, g_pOrgCPA);
            hook_by_code("kernel32.dll", "CreateProcessW",
                         (PROC)NewCreateProcessW, g_pOrgCPW);
            hook_by_code("ntdll.dll", "ZwQuerySystemInformation",
                         (PROC)NewZwQuerySystemInformation, g_pOrgZwQSI);
            break;

        case DLL_PROCESS_DETACH :
            // unhook
            unhook_by_code("kernel32.dll", "CreateProcessA",
                           g_pOrgCPA);
            unhook_by_code("kernel32.dll", "CreateProcessW",
                           g_pOrgCPW);

            unhook_by_code("ntdll.dll", "ZwQuerySystemInformation",
                           g_pOrgZwQSI);
            break;
    }

    return TRUE;
}

위 DllMain() 함수를 보시면 CreateProcessA, CreateProcessW 를 후킹하는 코드가 추가되었습니다.


NewCreateProcessA()

CreateProcessA() API 의 후킹 함수인 NewCreateProcessA() 코드를 살펴 보겠습니다. (NewCreateProcessW() 코드도 거의 동일합니다.)

BOOL WINAPI NewCreateProcessA(
    LPCTSTR lpApplicationName,
    LPTSTR lpCommandLine,
    LPSECURITY_ATTRIBUTES lpProcessAttributes,
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    BOOL bInheritHandles,
    DWORD dwCreationFlags,
    LPVOID lpEnvironment,
    LPCTSTR lpCurrentDirectory,
    LPSTARTUPINFO lpStartupInfo,
    LPPROCESS_INFORMATION lpProcessInformation
)
{
    BOOL bRet;
    FARPROC pFunc;

    // unhook

    unhook_by_code("kernel32.dll", "CreateProcessA", g_pOrgCPA);

    // original API 호출

    pFunc = GetProcAddress(GetModuleHandle("kernel32.dll"), "CreateProcessA");
    bRet = ((PFCREATEPROCESSA)pFunc)(lpApplicationName,
                                     lpCommandLine,
                                     lpProcessAttributes,
                                     lpThreadAttributes,
                                     bInheritHandles,
                                     dwCreationFlags,
                                     lpEnvironment,
                                     lpCurrentDirectory,
                                     lpStartupInfo,
                                     lpProcessInformation);

    // 생성된 자식 프로세스에 stealth2.dll 을 인젝션 시킴

    if( bRet )
        InjectDll2(lpProcessInformation->hProcess, STR_MODULE_NAME);

    // hook

    hook_by_code("kernel32.dll", "CreateProcessA",
                 (PROC)NewCreateProcessA, g_pOrgCPA);

    return bRet;

}

코드는 매우 단순합니다.
일단 후킹을 풀고 원본 함수를 실행해서 생성된 자식 프로세스에 stealth2.dll 을 인젝션 시킵니다.
그 후 다음 실행을 위해서 다시 후킹해 줍니다.

제 글을 꾸준히 읽어오셨다면 쉽게 이해하실 수 있으실 겁니다.

한가지 눈여겨 볼 사항은 인젝션 함수인 InjectDll2() 입니다.
기존 InjectDll() 함수는 프로세스 ID (PID) 를 이용하여 프로세스 핸들을 얻어 인젝션 시키는 방법이었습니다. (OpenProcess() API 이용)

하지만 위의 경우는 CreateProcessA() API 를 호출하면서 자연스럽게 자식 프로세스의 핸들(lpProcessInformaiton->hProcess)을 얻을 수 있습니다. 이 내용도 같이 참고하시면 좋을 것 같습니다.



+---+

지금까지 Global API Hooking 에 대해서 알아보았습니다.

시스템 전체 프로세스에 대해서 후킹을 하는 기술이기 때문에 예상치 못한 에러가 발생할 수 있습니다. 따라서 사전에 꼼꼼한 테스트가 필요합니다. 그리고 undocumented API 를 후킹할 때는 현재 OS 버전에서 예상대로 동작하는지 반드시 확인하셔야 합니다.


다음번에는 API Hooking 시리즈의 마지막 테마인 Code Injection 기법에 대해서 설명드리도록 하겠습니다.

많이 기대해 주세요.

ReverseCore

위 글이 도움이 되셨다면 추천(VIEW ON) 부탁 드려요~
저작자 표시 비영리 변경 금지
신고
    이전 댓글 더보기
  1. kmslife 2011.04.15 18:02 신고 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요.
    질문이 있어서 이렇게 글을 올립니다.
    첨부되어 있는 프로그램으로 실행은 잘 되는데요
    문제는 vs2008로 만들어진 프로그램들, 그리고 vs2008 툴 자체는 위 dll을 injection 시키면
    프로그램 자체가 실행되지 않습니다.
    vs2003이나, 그외 프로그램들은 잘 동작하는데 말이죠
    ASLR기능 인가 찾아 보았지만 제 pc의 환경은 xp sp3환경이어서 문제가 되지는 않을 것 같습니다.
    원래 vs2008관련되어서는 dll injection 기능이 동작하지 않는것인가요?
    동작하게 하려면 어떻게 해야 할지요?

    • reversecore 2011.04.20 10:41 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      지금 제 환경은 Win7 64bit, WinXP SP3 32bit 인데요.

      말씀하신 VS2008 은 없어서 Visual C++ 2008 Express (VCExpress.exe 프로세스)에 테스트 해본 결과 정상동작 하네요.

      VS2008 로 제작된 파일에 인젝션 시키면 동작 안한다고 하셨는데요.
      그 파일을 제게 보내주시면 테스트 해보겠습니다.

      실행 파일을 zip 압축 하신 후 확장자를 zipx 로 변경하셔서 아래 메일로 보내주시기 바랍니다.

      reversecore@gmail.com

      감사합니다.

  2. jjengae 2011.09.28 20:06 신고 댓글주소 | 수정 | 삭제 | 댓글

    스텔스 프로세스(2)에서 올려주셨던 HideProc.exe는 XP기반에서만 작동하는 것 같더라구요

    윈도우 7(Enterprise 32bit)에서 vmware-vmx.exe라는 프로세스를 숨기기 하고 싶은데

    이번에 포스팅에 올려주신 파일은 notepad.exe로 고정되어 있는것 같아서요

    제가 임의의 프로세스를 숨기려면 어떤 부분을 수정해서 어떻게 컴파일 해야 하는건가요?

    .exe와 .dll은 한번도 컴파일해보지 않아서...

    또, 수정하려면 무조건 Visual Studio가 설치되어 있어야 하는건가요?

    기초 지식 없이 이렇게 물어봐서 죄송합니다

    가능하시다면 jjengae@nate.com 으로 메일 부탁드릴게요

    • reversecore 2011.09.28 20:36 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      위 예제에서 은폐프로세스 이름은 notepad.exe 로 하드코딩 되어 있습니다.

      소스코드에서 "notepad.exe" 문자열을 "vmware-vmx.exe" 문자열로 수정 후 빌드 하시면 됩니다.

      Visual C++ 을 한번도 사용해보지 않으셨다면 조금 어려움이 있을 수 있습니다.

      감사합니다.

  3. jjengae 2011.09.29 23:32 신고 댓글주소 | 수정 | 삭제 | 댓글

    빌드를 해야된다고 하셔서 VS2008 설치했습니다.

    "notepad.exe"를 수정해야 한다고 하시는건 "stealth2.cpp"에 있는 내용을 수정하고 빌드해야 된다는 말씀이시죠?

    "HideProc2.cpp"는 "멀티 바이트 문자 집합" 설정하고 컴파일 했더니 debug폴더에 실행파일이 생성되더군요. 문제는 stealth2.cpp 파일입니다.

    여차여차해서 stealth2.cpp 파일을 컴파일 하려고 프로젝트 생성시에 응용프로그램 종류에서 콘솔 응용프로그램 대신 "DLL"로 셋팅하고 멀티바이트 문자 집합으로 설정했더니 오류가 나지 않더군요.

    근데 컴파일 후에 나타나는 창이 무엇인지 잘 모르겠습니다. "디버그 세션이 사용할 실행 파일"이라는 창이 뜨고 "실행 파일 이름"과 "프로젝트에 액세스할 수 있는 URL"을 입력하라고 하는데 그것을 무엇으로 해야할지 잘 모르겠습니다.

    요약하자면 stealth2.cpp 파일을 어떻게 .dll로 컴파일하는지 좀 알려주시면 감사하겠습니다.

    혹시 .dll로 컴파일하는 포스팅은 없는건가요? 시간 되신다면 vmware-vmx.exe로 적용하여 jjengae@nate.com으로 보내주시면 감사하겠습니다. ㅜㅜ

    도움 주시면 감사하겠습니다.

  4. jjengae_father CPP 2011.09.30 10:47 신고 댓글주소 | 수정 | 삭제 | 댓글

    글 보고 메일 발송하였는데요.

    • jjengae 2011.09.30 11:38 신고 댓글주소 | 수정 | 삭제

      관심 감사합니다.
      그런데 메일을 확인해보니 메일이 도착하질 않았네요..
      죄송하지만 다시한번 메일 보내주셨으면 감사하겠습니다

  5. jjengae 2011.10.01 14:31 신고 댓글주소 | 수정 | 삭제 | 댓글

    제발 누가 stealth2.cpp dll로 빌드하는 방법좀 알려주실 수 없나요 ....

  6. jjengae_father CPP 2011.10.06 22:32 신고 댓글주소 | 수정 | 삭제 | 댓글

    안녕하셰요.
    메일 확인하니 주소가 틀렸어요.
    VS키고 dll프로젝트를 하나 창조하지요.
    다음 dll main에서 stealth2.cpp를 코피하다놓고 빌드하면 되겠지요.

    • jjengae 2011.10.08 00:58 신고 댓글주소 | 수정 | 삭제

      전에 보내셨던 메일이 어떤 내용이었는지는 모르겠지만
      보낸메일함에서 전달로 다시한번 보내주실 수 없나요?

      그리구 제가 일반 콘솔 프로젝트는 많이 해봤는데
      dll은 컴파일을 하면 이상한 창이 하나 뜨더라구요
      "디버그 세션이 사용할 실행 파일"이라고...
      실례가 안된다면 프로젝트 생성부터 과정을 좀 자세하게 설명좀 부탁드릴 수 있을까요?

  7. jjengage_father CPP 2011.10.11 20:20 신고 댓글주소 | 수정 | 삭제 | 댓글

    안녕하셰요.
    VS를 켜시고 File/New/Project/Win32를 선택하시지요.
    dll옵션을 체크하시고 empty를 선택하세요.
    그렇게하군서 stealth2.cpp를 코피하다가 빌드하세요.
    dll컴파일을 하구선 loadlibrary를 사용하셰야 디버그를 할수잇어요.

  8. jjengae 2011.10.13 07:05 신고 댓글주소 | 수정 | 삭제 | 댓글

    드디어 빌드를 완료했습니다.

    그런데 아래와 같은 증상이 나타나면서 프로세스는 사라지지 않는데, 무엇이 문제인가요 ?

    C:\Work>HideProc2.exe -hide stealth.dll
    OpenProcess(968) failed!!!
    OpenProcess(1572) failed!!!
    OpenProcess(2296) failed!!!

  9. jjengage_father CPP 2011.10.13 14:14 신고 댓글주소 | 수정 | 삭제 | 댓글

    와 정말 장하시네요.
    근데 또 에러가 있군요.
    오~~~~그거 권한문제때문같아요.
    안녕하셰요.

  10. jjengae 2011.10.14 04:35 신고 댓글주소 | 수정 | 삭제 | 댓글

    father님 항상 조언 감사합니다.
    덕분에 dll 빌드하는 방법을 알았네요.
    전 항상 컴파일만 돌릴생각만 하고 안된다고 생각했었는데 빌드를 해야했군요.

    reversecore님 이게 권한문제때문에 발생하는 에러인가요?
    만약 권한문제라면 권한 문제는 어떻게 해결해야 하나요?

  11. kimpilgu 2012.05.12 15:36 신고 댓글주소 | 수정 | 삭제 | 댓글

    질문있습니다.
    1. 새로 생성(기존x)되는 하나의 모든 exe 프로세스만 감지(CreatePocess 함수)해서 후킹할려면 어떻게 하나요
    2. 그리고 후킹후에 제가 원하는 프로세스 실행할려면
    무슨 함수가 있어야하나요?
    3. 디바이스 드라이버 파일에 dll파일을 로드시켜 사용할수 있나요??
    ...답변해주시면 정말 감사하겠습니다.

  12. 셔벳클록 2012.12.02 17:52 신고 댓글주소 | 수정 | 삭제 | 댓글

    CreateRemoteThread() 가 잘 안먹히는 경우 어떻게 하는게 좋을까요?
    Sleep(2000)을 넣으면 전역후킹이 되긴 하는데,
    예를 들어 시작메뉴에서 프로그램을 시작시키면
    시작메뉴에서 일정 시작 렉을 보이네요.
    환경은 xp sp3를 깔았습니다.

    • reversecore 2012.12.02 19:07 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      CreateRemoteThread() 가 안먹히는 경우가 몇 가지 있습니다.
      그래서 NtCreateThreadEx() 를 사용(Vista 이상)하기도 하지요.

      Sleep() 을 넣으면 전역후킹이 된다는 말씀이 잘 이해가 안되는데요.
      실행중 뚜렷한 딜레이가 발생한다면 구조를 좀 변경하는 것이 좋을 것 같습니다. (물론 전역 후킹 자체가 시스템에 부하를 주는 방식이라 어느정도의 딜레이는 감수해야 합니다.)

      감사합니다.

    • 셔벳클록 2012.12.04 09:56 신고 댓글주소 | 수정 | 삭제

      노트패드 같은건 올려놓으신 소스코드로 충분히 전역후킹이 잘되는데요,
      윈도우 미디어나 다른 프로그램 같은경우 잘 안되더라구요??
      (해당 라이브러리가 인젝션 되지 않았더라구요)
      그래서 찾아보니까, 적당한 딜레이를 주면 전역후킹이 된다고 하더라구요?
      ( http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=50&MAEULNo=20&no=507700&ref=507700 )
      실제도 잘 먹힌거 같구요.
      단순히 일정시간 지연 후 전역후킹만 되면 괜찮겠는데..
      렉까지 잠깐동안 발생해서 말이죠.. 제 지식수준으로는 잘 이해가 안되었구요..

      http://blog.naver.com/siam18/172501113 에 첨부를 올렸는데
      실행해보하고 프로그램을 열면, 시작메뉴나 탐색기가 잠깐 멈춤니다
      (세 파일을 c:\에 놓고, 실행파일을 실행시키면 injection되고, Ejection시는 practice.exe -unexe입니다)

    • reversecore 2012.12.04 22:02 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      일반적인 DLL Injection 의 경우 Sleep() 의 유무와 상관없이 잘 먹혀야 정상입니다.

      인젝션된 DLL 에서 다시 인젝션하는 구조인가요?
      제가 디버깅을 좀 해본 후 답변 달아드리도록 하겠습니다.

      감사합니다.

    • 셔벳클록 2012.12.04 22:23 신고 댓글주소 | 수정 | 삭제

      http://blog.naver.com/siam18/172501113
      여기에 소스도 같이 올렸습니다
      enumwindows.dll은 api 후킹 중 실행되는 다이얼로그에 들어가는 프로그램이 들어있습니다.
      기본적으로 리버스코어님의 예제 소스와 구조가 같습니다.

  13. 조영용 2013.01.29 10:42 신고 댓글주소 | 수정 | 삭제 | 댓글

    게시글에 올려주신
    HideProc2.exe 와 stealth2.dll를 다운받아서 실행해보면 아무 문제가 없는데
    첨부하신 소스파일로 빌드해서 같은 방법으로 실행해보면 최초 인젝션 후에
    실행하는 프로세스에 대해서는 프로세스은폐가 이루어 지지 않습니다.

    또한 최초 인젝션 후에 작업관리자나 프로세스익스플로러를 두 번실행해야
    그때부터 정상적으로 실행되네요.

    확인부탁드릴께요.

  14. ugg baratas 2013.04.13 23:38 신고 댓글주소 | 수정 | 삭제 | 댓글

    번, 생각할 시간이 필요하다고 할 경우 하루만 생각할 시간을 주고 당장
    골라

  15. nike shox sko 2013.04.23 03:12 신고 댓글주소 | 수정 | 삭제 | 댓글

    나도 용기가 없어서 아플 것 같아서 아무 말 못 하네요

  16. 난나놔 2013.07.07 07:00 신고 댓글주소 | 수정 | 삭제 | 댓글

    잘보고 갑니다. 글을 참 깔끔하게 잘 쓰시네요.

  17. 2014.03.23 17:04 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

  18. 2014.05.11 19:25 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

  19. cr 2014.07.02 14:46 신고 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요. 현재 책을 도서관에서 빌려 보고있습니다. 한 가지 질문드릴게 있는데.
    IE를 사용하며 파일 업로드 같은 행동을 할 때 FileDialog가 불려지는데, IE에 dll을 심은 후 이 FileDialog가 불려질 때마다 자동으로 메세지박스를 띄운다던지 행동을 취하고 싶습니다.
    해서 Process Explorer로 살펴보았더니 FileDialog event명은 db!rwWriterEvent 더군요.
    여기까지만 파악이되고 정작 이 event가 작동할 때 마다 어떻게 체크해야할 지 감이 잘 오지 않네요.

    책의 어느 부분을 보면 도움이 될 까요 ?

  20. 암드래 2014.08.14 17:09 신고 댓글주소 | 수정 | 삭제 | 댓글

    여러 프로그램을 한꺼번에 후킹할려면 코드를

    어떤식으로 수정해야좋을까요?



실습 예제 프로그램의 소스를 분석하면서 "Code Patch" 방식의 API Hooking 에 대하여 알아 보도록 하겠습니다.

<stealth.cpp 코드 일부>


본 내용은 이전 포스트에서 이어지는 내용입니다.
API Hooking – '스텔스' 프로세스 (2)


* 참고!
모든 소스 코드는 MS Visual C++ 2008 Express Edition 으로 개발 되었으며, Windows XP SP3 환경에서 테스트 되었습니다.



HookProc.exe


HideProc.cpp


HookProc.exe 는 실행중인 모든 프로세스에 특정 DLL 파일을 Injection/Ejection 시켜주는 프로그램 입니다. 기존의 InjectDll.exe 프로그램에 모든 프로세스를 검사하는 기능을 추가한 거라고 생각하시면 됩니다.

대부분의 인젝션 코드는 기존의 InjectDll.cpp 와 같습니다. 아래 링크를 참고해 주세요.
DLL Injection – 프로세스에 침투하기 (2)


# InjectAllProcess()

새로 추가된 InjectAllProcess() 함수에 대해서 설명하겠습니다. 이 함수에서 실행중인 모든 프로세스를 검색하여 각각 DLL Injection/Ejection 을 수행합니다.

BOOL InjectAllProcess(int nMode, LPCTSTR szDllPath)
{
    DWORD                   dwPID = 0;
    HANDLE                  hSnapShot = INVALID_HANDLE_VALUE;
    PROCESSENTRY32          pe;

    // Get the snapshot of the system
    pe.dwSize = sizeof(PROCESSENTRY32);
    hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);

    // find process
    Process32First(hSnapShot, &pe);
    do
    {
        dwPID = pe.th32ProcessID;

        // 시스템의 안정성을 위해서
        // PID 가 100 보다 작은 시스템 프로세스에 대해서는
        // DLL Injection 을 수행하지 않는다.

        if( dwPID < 100 )
            continue;

        if( nMode == INJECTION_MODE )
            InjectDll(dwPID, szDllPath);
        else
            EjectDll(dwPID, szDllPath);
    }while( Process32Next(hSnapShot, &pe) );

    CloseHandle(hSnapShot);

    return TRUE;
}

CreateToolhelp32Snapshot() API 를 이용해서 시스템에 실행중인 모든 프로세스 리스트를 얻어내고 Process32First() 와 Process32Next() API 를 이용해서 PID 를 구합니다.

HANDLE WINAPI CreateToolhelp32Snapshot(
  __in  DWORD dwFlags,
  __in  DWORD th32ProcessID
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms682489(VS.85).aspx

BOOL WINAPI Process32First(
  __in     HANDLE hSnapshot,
  __inout  LPPROCESSENTRY32 lppe
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms684834(VS.85).aspx

BOOL WINAPI Process32Next(
  __in   HANDLE hSnapshot,
  __out  LPPROCESSENTRY32 lppe
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms684836(VS.85).aspx


* 주의!
미리 HideProc.exe 프로세스의 권한(특권)을 상승시켜 놓아야 전체 프로세스의 리스트를 정확하게 얻을 수 있습니다. HideProc.cpp 에서는 main() 함수에서 미리 _EnableNTPrivilege() 함수를 호출하고 이 함수 내부에서 AdjustTokenPrivileges() API 를 이용해서 권한을 얻습니다.

PID 를 구했으면 모드(INJECTION / EJECTION)에 따라 InjectDll() / EjectDll() 함수를 호출합니다.

한가지 특이한 점은 PID 값이 100 보다 작다면, 그 프로세스는 작업하지 않고 그냥 통과합니다. 그 이유는 시스템의 안정성을 위해서 시스템 프로세스(PID = 0, 4, 8, … ) 에게는 DLL Injection 하지 않는 것입니다. (이런 PID 값은 Windows XP 에 대해서 경험적으로 얻은 값으로써 다른 Windows 버전에서는 시스템 프로세스의 PID 값이 달라 질 수 있습니다.)



stealth.dll


stealth.cpp


# SetProcName()

먼저 export 함수인 SetProcName() 을 살펴보겠습니다.

// global variable (in sharing memory)
#pragma comment(linker, "/SECTION:.SHARE,RWS")
#pragma data_seg(".SHARE")
    char g_szProcName[MAX_PATH] = {0,};
#pragma data_seg()

// export function
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) void SetProcName(LPCTSTR szProcName)
{
    strcpy_s(g_szProcName, szProcName);
}
#ifdef __cplusplus
}
#endif

위 코드를 보시면 “.SHARE” 이름의 공유 메모리 섹션을 만들고 g_szProcName 버퍼를 생성합니다. 그리고 export 함수인 SetProcName() 에 의해서 은폐 시키고 싶은 프로세스 이름을 g_szProcName 에 저장시킵니다. (SetProcName() 함수는 HookProc.exe 에서 호출됩니다.)

* 참고
g_szProcName 버퍼를 공유 메모리 섹션에 만들면 stealth.dll 이 모든 프로세스에게 인젝션 될 때 간단하게 은폐 프로세스 이름을 공유 시킬 수 있는 장점이 있습니다. (향후 프로그램을 더 발전 시켜서 동적으로 은폐 프로세스를 다른 걸로 변경시킬 수도 있습니다.)



# DllMain()

그럼 DllMain() 함수를 살펴볼까요?

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    char            szCurProc[MAX_PATH] = {0,};
    char            *p = NULL;

    // #1. 예외처리
    // 현재 프로세스가 HookProc.exe 라면 후킹하지 않고 종료
    GetModuleFileName(NULL, szCurProc, MAX_PATH);
    p = strrchr(szCurProc, '\\');
    if( (p != NULL) && !_stricmp(p+1, "HideProc.exe") )
        return TRUE;

    switch( fdwReason )
    {
        // #2. API Hooking
        case DLL_PROCESS_ATTACH :
        hook_by_code("ntdll.dll", "ZwQuerySystemInformation",
                     (PROC)NewZwQuerySystemInformation, g_pOrgBytes);
        break;

        // #3. API Unhooking
        case DLL_PROCESS_DETACH :
        unhook_by_code("ntdll.dll", "ZwQuerySystemInformation",
                       g_pOrgBytes);
        break;
    }

    return TRUE;
}

DllMain() 은 보시다시피 매우 간단합니다.

먼저 문자열 비교를 통해 프로세스 이름이 "HookProc.exe" 라면 API 후킹하지 않도록 예외처리를 해둡니다. 모든 프로세스를 정확히 검색하기 위해서 HookProc.exe 자신은 후킹 당하면 안되겠죠? (후킹 당하면 HookProc.exe 자신도 은폐 프로세스를 볼 수 없고, stealth.dll 을 인젝션 시킬 수 없기 때문입니다.)

그리고 DLL_PROCESS_ATTACH 이벤트에 hook_by_code() 함수로 API 를 후킹하고, DLL_PROCESS_DETACH 이벤트에 unhook_by_code() 함수로 API 후킹을 해제 시킵니다.


# hook_by_code()

코드 패치를 이용하여 API 를 후킹하는 hook_by_code() 함수 입니다.

BOOL hook_by_code(LPCTSTR szDllName, LPCTSTR szFuncName, PROC pfnNew, PBYTE pOrgBytes)
{
    FARPROC pfnOrg;
    DWORD dwOldProtect, dwAddress;
    BYTE pBuf[5] = {0xE9, 0, };
    PBYTE pByte;

    // 후킹대상 API 주소를 구한다
    pfnOrg = (FARPROC)GetProcAddress(GetModuleHandle(szDllName), szFuncName);
    pByte = (PBYTE)pfnOrg;

    // 만약 이미 후킹되어 있다면 return FALSE
    if( pByte[0] == 0xE9 )
        return FALSE;

    // 5 byte 패치를 위하여 메모리에 WRITE 속성 추가
    VirtualProtect((LPVOID)pfnOrg, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);

    // 기존코드 (5 byte) 백업
    memcpy(pOrgBytes, pfnOrg, 5);

    // JMP 주소계산 (E9 XXXX)
    // => XXXX = pfnNew - pfnOrg - 5
    dwAddress = (DWORD)pfnNew - (DWORD)pfnOrg - 5;
    memcpy(&pBuf[1], &dwAddress, 4);

    // Hook - 5 byte 패치(JMP XXXX)
    memcpy(pfnOrg, pBuf, 5);

    // 메모리 속성 복원
    VirtualProtect((LPVOID)pfnOrg, 5, dwOldProtect, &dwOldProtect);

    return TRUE;
}

hook_by_code() 함수 파라미터 소개입니다.

LPCTSTR szDllName  : [IN] 후킹하려는 API 가 포함된 DLL 파일 이름
LPCTSTR szFuncName : [IN] 후킹하려는 API 이름
PROC pfnNew        : [IN] 사용자가 제공한 후킹함수 주소
PBYTE pOrgBytes    : [OUT] 원본 5 byte 를 저장시킬 버퍼 – 나중에 unhook 에서 사용됨

동작원리에서 설명 드렸듯이 hook_by_code() 함수의 기능은 원본 API 코드 시작부분의 5 byte 를 "JMP XXXX" 명령어로 변경하는 것입니다.

참고 API Hooking – '스텔스' 프로세스 (1)

소스 코드가 간단하여 주석을 보시면 대부분 쉽게 이해가 되는 내용입니다만, 중간의 점프 주소 계산 부분은 리버싱에서 매우 중요한 내용이기 때문에 자세히 살펴보도록 하겠습니다.

Intel x86 (IA-32) Instruction Format 에 따르면 JMP 명령어의 Op Code(Operation Code) 는 'E9' 입니다. 그리고 뒤에 4 byte 값이 이어집니다.

즉, JMP 명령어의 Op Code 는 "E9 XXXX" 형태가 됩니다.

문제는 XXXX 의 값이 JMP 할 절대 주소 값이 아니라, 현재 JMP 명령어 에서부터 JMP 할 위치까지의 상대 거리입니다. 이것은 아래의 공식으로 구할 수 있습니다.

XXXX = 점프할 주소 – 현재 명령어 주소 - 5

마지막에 5 를 더 빼주는 이유는 JMP 명령어 자체 길이 5 byte 를 보정해 주는 것입니다.

예를 들어 402000 주소의 JMP 명령어에서 401000 주소로 가고 싶을 때는 "E9 00104000" 이라고 쓰는 것이 아니고, 위 계산 공식대로 적용해서 XXXX 값을 구해야 합니다.

XXXX = 401000 – 402000 – 5 = FFFFEFFB

따라서 이 JMP 명령어의 Op Code 는 "E9 FBEFFFFF" 입니다.

OllyDbg 의 [Assemble] 또는 [Edit] 기능으로 확인해 보시기 바랍니다.


<Fig. 1>

* 참고 1
JMP 말고 short JMP 명령이 있습니다. 말 그대로 짧은 거리를 점프 할 때 쓰이는 명령어로 Op Code 는 "EB X" 입니다. (명령어 크기 2 byte)
OllyDbg 에서 'EB' 명령어도 테스트 해보시기 바랍니다.

* 참고 2
위와 같이 상대 거리 를 계산해서 JMP 명령어를 써주는 것이 좀 불편해 보일 수 있습니다.
물론 다른 명령어를 써서 절대 주소로 JMP 할 수 도 있습니다.

예1) PUSH + RET 
  68 00104000    PUSH 00401000
  C3             RETN

예2) MOV + JMP
  B8 00104000    MOV EAX, 00401000
  FFE0           JMP EAX


* 참고 3
32bit 주소 계산 하실 때 Windows 계산기는 좀 불편합니다.
다양한 기능이 있는 32bit Calculator v1.7 by cybult 를 추천합니다.

* 향후 x86 Op Code Map 을 해석하는 방법에 대해서 글을 올리도록 하겠습니다.


실제로 hook_by_code() 함수에 의해서 ZwQuerySystemInformation() API 가 후킹 되기 전/후의 모습을 디버거를 통하여 살펴 보도록 하겠습니다. (해당 프로세스는 procexp.exe 입니다.)

먼저 API 후킹 전의 ZwQuerySystemInformation() API 코드 입니다.

<Fig. 2>

ZwQuerySystemInformation() 주소는 7C93D92E 이며, 명령어 코드는 아래와 같습니다.

7C93D92E    B8 AD000000     MOV EAX, 0AD


이제 stealth.dll 이 인젝션 되면서 hook_by_code() 함수에 의해서 API 가 후킹된 이후의 코드입니다.

<Fig. 3>

ZwQuerySystemInformation() 코드 시작 명령어가 아래와 같이 변경 되었습니다. (정확히 5 byte)

7C93D92E    E9 ED376C93     JMP 10001120

10001120 주소는 바로 후킹 함수 NewZwQuerySystemInformation() 의 주소입니다.
그리고 E9 뒤의 4 byte (936C37ED) 값은 바로 위에서 설명 드린 계산 공식에 의해서 구할 수 있습니다. (한번씩 직접 해보시기 바랍니다.)


# unhook_by_code() 

후킹을 해제하는 unhook_by_code() 코드 입니다.

BOOL unhook_by_code(LPCTSTR szDllName, LPCTSTR szFuncName, PBYTE pOrgBytes)
{
    FARPROC pFunc;
    DWORD dwOldProtect;

    // API 주소 구한다
    pFunc = GetProcAddress(GetModuleHandle(szDllName), szFuncName);

    // 원래 코드 (5 byte)를 덮어쓰기 위해 메모리에 WRITE 속성 추가
    VirtualProtect((LPVOID)pFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);

    // Unhook
    memcpy(pFunc, pOrgBytes, 5);

    // 메모리 속성 복원
    VirtualProtect((LPVOID)pFunc, 5, dwOldProtect, &dwOldProtect);

    return TRUE;
}

Unhook 의 동작원리는 원래 코드의 5 byte 를 복원해 주는 것입니다.
코드가 단순하므로 설명은 주석으로 대체 합니다.


# NewZwQuerySystemInformation()

마지막으로 후킹함수 NewZwQuerySystemInformation() 에 대해서 살펴볼 시간입니다.

하지만 그전에 먼저 ntdll!ZwQuerySystemInfomation() API 에 대해서 알아야 합니다.

NTSTATUS WINAPI ZwQuerySystemInformation(
  __in       SYSTEM_INFORMATION_CLASS SystemInformationClass,
  __inout    PVOID SystemInformation,
  __in       ULONG SystemInformationLength,
  __out_opt  PULONG ReturnLength
);

typedef struct _SYSTEM_PROCESS_INFORMATION {
    ULONG NextEntryOffset;
    ULONG NumberOfThreads;
    BYTE Reserved1[48];
    PVOID Reserved2[3];
    HANDLE UniqueProcessId;
    PVOID Reserved3;
    ULONG HandleCount;
    BYTE Reserved4[4];
    PVOID Reserved5[11];
    SIZE_T PeakPagefileUsage;
    SIZE_T PrivatePageCount;
    LARGE_INTEGER Reserved6[6];
} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;

* 출처 : http://msdn.microsoft.com/en-us/library/ms725506(VS.85).aspx

간단히 설명해서 SystemInformationClass 파라미터에 SystemProcessInformation (5) 으로 세팅하고 ZwQuerySystemInformation() API 를 호출하면 SystemInformation [inout] 파라미터에 SYSTEM_PROCESS_INFORMATION 구조체 단방향 연결 리스트(single linked list)의 시작 주소가 얻어집니다.

바로 이 구조체 연결 리스트에 실행중인 모든 프로세스들의 정보가 담겨 있습니다.

따라서 프로세스 은폐를 구현하려면 은폐하고 싶은 프로세스에 해당하는 리스트 멤버를 찾아서 리스트 연결을 끊어 버리면 됩니다.

아래 NewZwQuerySystemInformation() 코드를 살펴 보면서 실제로 어떤식으로 구현되었는지 알아보겠습니다.

NTSTATUS WINAPI NewZwQuerySystemInformation(
                SYSTEM_INFORMATION_CLASS SystemInformationClass,
                PVOID SystemInformation,
                ULONG SystemInformationLength,
                PULONG ReturnLength)
{
    NTSTATUS status;
    FARPROC pFunc;
    PSYSTEM_PROCESS_INFORMATION pCur, pPrev;
    char szProcName[MAX_PATH] = {0,};

    // 작업 전에 unhook
    unhook_by_code("ntdll.dll", "ZwQuerySystemInformation", g_pOrgBytes);

    // original API 호출
    pFunc = GetProcAddress(GetModuleHandle("ntdll.dll"),
                           "ZwQuerySystemInformation");
    status = ((PFZWQUERYSYSTEMINFORMATION)pFunc)
              (SystemInformationClass, SystemInformation,
              SystemInformationLength, ReturnLength);

    if( status != STATUS_SUCCESS )
        goto __NTQUERYSYSTEMINFORMATION_END;

    // SystemProcessInformation 인 경우만 작업함
    if( SystemInformationClass == SystemProcessInformation )
    {
        // SYSTEM_PROCESS_INFORMATION 타입 캐스팅
        // pCur 는 single linked list 의 head

        pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;

        while(TRUE)
        {
            // wide character => multi byte 변환
            WideCharToMultiByte(CP_ACP, 0, (PWSTR)pCur->Reserved2[1],
                                -1, szProcName, MAX_PATH, NULL, NULL);

            // 프로세스 이름 비교
            // g_szProcName = 은폐 하려는 프로세스 이름
            // (=> SetProcName() 에서 세팅됨)

            if(!_strcmpi(szProcName, g_szProcName))
            {
                // 연결 리스트에서 은폐 프로세스 제거
                if(pCur->NextEntryOffset == 0)
                    pPrev->NextEntryOffset = 0;
                else
                    pPrev->NextEntryOffset += pCur->NextEntryOffset;
            }
            else  
                pPrev = pCur;

            if(pCur->NextEntryOffset == 0)
                break;

            // 연결 리스트의 다음 항목
            pCur = (PSYSTEM_PROCESS_INFORMATION)
                    ((ULONG)pCur + pCur->NextEntryOffset);
        }
    }

__NTQUERYSYSTEMINFORMATION_END:

    // 함수 종료전에 다시 API Hooking
    hook_by_code("ntdll.dll", "ZwQuerySystemInformation",
                 (PROC)NewZwQuerySystemInformation, g_pOrgBytes);

    return status;
}

위 NewZwQuerySystemInformation() 함수의 구조를 간단히 설명 드리면 아래와 같습니다.

- 언훅(unhook) ZwQuerySystemInformation()
- ZwQuerySystemInformation() 호출
- SYSTEM_PROCESS_INFORMATION 구조체 연결 리스트를 검사하면서 은폐 프로세스 찾음
- 은폐 프로세스를 찾으면 리스트에서 제거
- 훅(hook) ZwQuerySystemInformation()


NewZwQuerySystemInformation() 코드의 중간쯤 while() 문을 보시면 SYSTEM_PROCESS_INFORMATION 구조체 연결 리스트를 검사하면서 프로세스 이름(pCur->Reserved2[1])을 비교합니다. (프로세스 이름은 Unicode 문자열이기 때문에 간단히 ASCII 로 변경 후 strcmp_i() 함수를 사용했습니다.)

...
// wide character => multi byte 변환
WideCharToMultiByte(CP_ACP, 0, (PWSTR)pCur->Reserved2[1],
-1, szProcName, MAX_PATH, NULL, NULL);

// 프로세스 이름 비교
// g_szProcName = 은폐 하려는 프로세스 이름
// (=> SetProcName() 에서 세팅됨)

if(!_strcmpi(szProcName, g_szProcName))
{
...

함수의 동작 원리만 이해하시면 (주석과 함께) 코드를 보시는데 어려움이 없으실 걸로 생각합니다.


다음 번에는 Global API Hooking 에 대해서 배워보도록 하겠습니다.

API Hooking – '스텔스' 프로세스 (4)


ReverseCore

위 글이 도움이 되셨다면 추천(VIEW ON) 부탁 드려요~

저작자 표시 비영리 변경 금지
신고
    이전 댓글 더보기
  1. Ezbeat 2010.04.24 17:25 신고 댓글주소 | 수정 | 삭제 | 댓글

    아~;; 소스코드 수정해보면서 OPCODE봐보니..정말 상대거리군요.. 지금까지 왜 몰랐을까요..-_-;;ㅋ
    아무튼 하나하나 알아가니 재미있군요..ㅋㅋ 이제 reversecore님이 쓰신 모든 코드는 이해가 끝났습니다 ㅋㅋ ㅜ ㅜ

    여기서 DLL인젝션과 API후킹을 공부해서 어디서 응용해보지 하다가..ㅋ 올디에서 제 프로그램 Attach를 막아보자라는 생각으로
    DebugActiveProcess함수 후킹을 해서 성공했습니다 ;;
    뭐.. 구현은.. 올디가 Attach하려는 PID값과 제 프로세스 PID값 비교해서 같으면 PID값을 임의적으로 바꿔버리는 방법을 썼어요 ㅜㅜㅋ 화이팅입니다!! ㅜㅜ 글로벌 후킹은 또 잘 안되는군요..
    다시 해봐야겠습니다 흐엉~ 오늘은 토요일..

    • reversecore 2010.04.25 22:57 신고 댓글주소 | 수정 | 삭제

      Ezbeat님, 안녕하세요.

      와~ 응용까지 성공하셨군요...

      리버싱에 대해 하나하나 배워가시고 재미까지 느끼셨다니...
      대단하세요~ 아무리 선생이 우수해도 배우는 사람이 노력하지 않으면 전혀 성과가 없기 마련인데, 열심히 하셨기 때문에 좋은 성과가 나타난것 같습니다.

      제가 이 블로그를 운영하는 목표가 Ezbeat 님덕에 이루어 졌군요.
      드디어 성공했습니다. ^^

      감사합니다. 화이팅~ ^^

  2. Somebody 2010.07.22 10:53 신고 댓글주소 | 수정 | 삭제 | 댓글

    질문 있습니다.

    kernel32.dll이 export 하고 있는 API함수를 후킹하여 시작 주소를 수정하는 것은
    어느 프로세스에서 실행되든 프로세스가 영향을 받는지 궁금합니다.

    DLL의 경우 한 번만 메모리에 로딩되고 나머지 프로세스들은 그 DLL을 매핑하는 방식이라고 들어서 API함수가 후킹될 경우 모든 프로세스에 영향을 미칠 것 같은데 제 생각이 맞나요?

    만약 맞다고 한다면 이 함수를 후킹하고 있는 프로세스에서는 NewZwQuerySystemInformation 함수의 주소가 있으니 제대로 동작하겠지만, 다른 프로세스에서는 주소가 틀려지니 NewZwQuerySystemInformation 함수를 DLL Injection 방법으로 삽입한 다음에 사용해야 하는 건가요?

    • newbie 2010.07.22 18:53 신고 댓글주소 | 수정 | 삭제

      안녕하세요 :)
      PE파일들은 CopyOnWrite방식을 사용하고 있습니다.
      즉 dll들이 메모리에 올라와있으면 모든 프로세스가 그것을 공유하지만
      한 프로세스가 그걸 수정해버리면 수정한 부분만 그 프로세스의 고유메모리 영역에 복사해줍니다.
      그럼 수정한 부분은 더 이상 공유되는 부분이 아닌 특정 프로세스의 영역이 되고 앞서 같은
      dll을 공유하고 있던 프로세스들은 아무런 영향을 받지 않게 됩니다.
      이해 되지 않는 부분이 있으면 reversecore님께서 좀더 자세한 설명을 해주시리라 생각됩니다 :)

    • reversecore 2010.07.28 16:55 신고 댓글주소 | 수정 | 삭제

      ^^ newbie 님의 정확하고 친절한 답변 너무 감사합니다.

      명쾌하게 설명하셔서 저도 같이 배웠습니다.

      감사합니다. ^^

  3. 토터스 2010.11.17 21:03 신고 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요? 좋은 정보 감사 드립니다.
    hook_by_code부분이 64bit에서 제대로 동작하기 위해서는 아무래도 점프 주소 계산 부분이 변경되어야 할 듯 싶은데, 어떻게 바꾸어야 할지 전혀 감이 없네요.

    혹시 관련 정보, 책등이 있으시면 알려주시면 너무 감사 하겠습니다.
    종종 들리겠습니다. 감사 합니다. ^^

    • reversecore 2010.11.18 16:30 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      64bit Native Mode 에서 API 후킹을 말씀하시는거죠?

      확장된 주소체계에서 CALL rel32 형태의 명령어 패치로는 원하는 모듈 주소로 갈 수 없는게 사실입니다. FF 로 시작하는 CALL r/m64 형태의 명령어를 써야 하지요.

      아직 직접 관련 자료를 찾아보지는 않았습니다만... x64 가 꽤 보급된만큼 어느정도 자료는 있을꺼라 생각됩니다. ^^

      감사합니다.

  4. 안녕하세요 2011.01.20 04:20 신고 댓글주소 | 수정 | 삭제 | 댓글

    지금만들고 있는 프로그램에서 후킹을 사용해야하는 부분이 있어서 블로그를 참조하고 있습니다
    그런데 코드를 보다가 궁금한점이 생겨서 몇가지만 질문하겠습니다
    코드를 보다보면 PROC, FARPROC 이런게 보이는데 선언을 찾아보니
    typedef int (_stdcall *PROC)()
    typedef int (_stdcall *FARPROC)()
    이렇게 되있더라구요
    이 둘의 차이점이 무엇인가요? 똑같은 선언임에도 불구하고 두가지로 선언되어있는 이유가 호환성 때문인거 같은데, 각각 어떤용도로 쓰이는 것인지 모르겠습니다 ㅠ
    그리고 ZwQuerySystemInformation를 가리키는 함수포인터가 FARPROC형으로 선언이되있던데 ZwQuerySystemInformation의 리턴형은 NTSTATUS이고 FARPROC의 리턴형은 int형인데 FARPROC으로 선언해준이유가 NTSTATUS 구조체를 가리키는 주소값이 어차피 int형이기 때문인가요?
    또, typedef int (_stdcall *PROC)() 이런 선언같은 경우는 파라메터의 종류는 상관없이 모든 함수를 전부 가리킬수 있는건가요? 예를들어
    int a(void);
    int b(int);
    int c(char,int,long);
    PROC d=a;
    PROC e=b;
    PROC f=c;
    이렇게해도 전부 괜찮은건가요?

    • reversecore 2011.04.13 00:27 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      C언어 문법에 관련된 질문을 해주셨네요~

      FAR 혹은 L(LONG) 등의 접두어는 의미상 편하게 구분하기 위해서 정의하는 듯 합니다. 당연히 Windows API 설계자들이 정의한 것이겠구요... 나름대로 규칙을 만든 것이라고 봐야겠죠.

      C 언어 타입 캐스팅은 컴파일러 문법체크를 통과하기 위한 기교라고 할 수 있고요... 어셈으로 보면 자료형의 크기만 중요시 합니다. 포인터는 타입에 상관없이 무조건 4 byte (32bit 의 경우) 이런 식으로요.

      그리고 올려주신 코드로 해도 되는데요. VC++ 버전에 따라서 에러를 낼 소지가 있으므로 PROC d = (PROC)a; 식으로 해주시면 좋겠지요.

      감사합니다.

  5. 뤼챠드 2011.03.23 23:01 신고 댓글주소 | 수정 | 삭제 | 댓글

    win32API( )를 myAPI( )로 hooking을 한 상태에서 win32API()을 호출해준다고 가정할때 해당 win32API( )를 unhook을 하고 호출하는 방식을 제시하셨는데요. 이경우에대한 문제점도 지적하셨고, 매번 myAPI함수 내에서 unhook() hook()을 호출해주는 코드는 좀 거추장 스럽다는 느낌이 들더라구요.
    그래서 곰곰히 생각을 해보니 해당 win32API( )의 code block을 통째로 어딘가에 copy를 해두었다가 호출하는 방식이면 어떨까요? 예상되는 문제는 win32API()의 code block의 크기과 주소가 바뀌면서 생기는 side-effect인데요. 일단 test를 해보려 하니 win32API( )의 code block의 크기를 구하는 방법을 잘 모르겠더군요.
    혹시 아이디어 있으십니까?

    • 뤼챠드 2011.03.25 14:22 신고 댓글주소 | 수정 | 삭제

      훔 여기 저기 뒤져보니 실행 code block을 copy하는 방식은 아니고, 일종의 wrapper 함수를 만들어 그 안에서 my api를 호출하는 방식으로 사용하는 것 같군요. wrapper안에서 hook() unhook()을 내부적으로 처리하고 sharing 문제도 CRITICAL_SECTION을 써서 해결하면 되더라군요.
      그런데 myAPI()를 호출하기 전에 각종 parameter와 return 처리를 하기 위해 asm으로 잔뜩 먼가를 하는데 잘 이해가 안되는 군요.
      API마다 parameter가 다른데 이를 하나의 wrapper로 해결하려니 먼가 꽁수를 쓰는 것 같습니다. 필요하시면 우연히 얻는 sample을 공유해드리면 해독하는 것을 도와주시면 어떠실까요 ^^

    • reversecore 2011.04.13 00:47 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      제가 소개한 방법(5 byte 패치)은 말그대로 일반적인 상황에서 잘 돌 수 있는 방법입니다.

      저거 외에 7 byte 패치 방법이 있는데요, 후킹 함수내에서 hook()/unhook() 을 할 필요가 없습니다. 그런데 모든 API 에서 가능한 것은 아닙니다. 특히 ntdll 에서 서비스하는 native API 들은 7 byte 후킹이 불가능 하지요.

      말씀하신 대로 API 코드를 통째로 copy 해두는 방법도 있습니다만, relocation 이슈를 해결해야 하는 상황이 발생합니다. (근데 이방법은 ntdll 의 native API 들한테는 잘 먹힙니다. 코드가 매우 단순하거든요.)

      즉, 범용적인 간단한 방법이 바로 위에서 소개한 5 byte 패치 방법이지요. ^^

      감사합니다.

  6. BM 2011.06.02 10:08 신고 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요, 항상 올려주시는 글을 감사히 보며 배우는 초보입니다.

    질문이 두 가지 있는데요..

    hook_by_code와 unhook_by_code 에서 후킹할 API를 주소를 매번 구하는데요.. 이 과정을 처음에만 해주어서 얻은 주소를 계속 사용해도 문제가 없겠죠?? 괜찮을 것 같지만 혹시 제가 모르는 예외가 있을까 싶어서 여쭤봅니다.

    다른 질문은 바로 위 댓글과 관련된 내용인데요.. 글에서 소개하신 5 byte 패치 방법은 일반적인 상황에서 잘 돌 수 있는 방법이라고 하셨는데.. 이 방법이 안먹힐 경우는 어떤 경우인지 알 수 있을까요??

    또 그럴 경우에는 어떤 방법을 써서 후킹할 수 있는지 간단하게나마 알려주시면 감사하겠습니다. :)

    • reversecore 2011.06.03 21:17 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      정확한 API 주소를 구하셨다면 저장해 놓고 사용하셔도 무방합니다.

      5 byte LONG JUMP 패치 방법은 범용적인 (잘 동작하는) 방법입니다. Win32 API 를 후킹할 때 이 방법이 안먹히는 경우는 아직 본 적이 없습니다. ^^

      2 byte SHORT JUMP 패치 방법도 있는데요. 점프 위치가 현재 기준 -128 ~ 128 으로 제한됩니다. 만약 Win32 API 라면 Kernel32.dll 등의 라이브러리 영역 내부입니다. 근처에 빈 영역이 거의 없기 때문에 의미있는 후킹 코드를 써놓기 어렵지요.

      7 byte 패치는 바로 2 byte SHORT JUMP 이후에 바로 5 byte LONG JUMP 를 하는 것입니다. kernel32.dll 의 코드를 잘 보시면 함수와 함수 사이에 최소 7 byte 의 빈 영역(NOP 또는 MOV EDI, EDI 명령어)을 보실 수 있습니다. 그곳에 7 byte 점프 코드를 설치하는 것이죠.

      감사합니다.

  7. jjengage_father CPP 2011.10.11 20:33 신고 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요.
    디버거님, 답변 궁금하시갔는데요.
    NTSTATUS는 DDK를 태우면 되시는데요 VS2008,VS2010을 태우시고
    쓰시면 되세요.
    안녕하세요.

  8. 디버거 2011.10.13 11:55 신고 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요.
    좋은 글 감사합니다.

    한가지 질문이 있는데요...

    VS6.0 에서 컴파일 하려고 하는데 NTSTATUS, SYSTEM_INFORMATION_CLASS 등이 문제네요.
    이런것 들은 도대체 어디에 있는 건지 알 수가 없는데 좀 가르쳐 주세요.

    행복하세요.

  9. 팔극진 2011.10.19 15:50 신고 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요 ^^
    덕분에 좋은 지식 많이 얻어가고 있습니다.
    실력이 부족해서 구글링이나 웹사이트 뒤지면서 공부 하는데요~
    아래 부분은 도저히 이해가 가지 않아서 질문 좀 드립니다.

    숨길프로세스 정보를 찾아서 비교한 다음 같으면 숨기는거 같은데요~
    !_strcmpi 하면은 같지 않으면 아래로 이동 하는것 같은데요~

    그리고, pCur->NextEntryOffset == 0 는 연결리스트 마지막인거 같은데요~
    아래 소스가 이해가 되지 않습니다. ㅜㅠ
    조언 좀 부탁드립니다. 수고하세요~

    if(!_strcmpi(szProcName, g_szProcName))
    {
    // 연결 리스트에서 은폐 프로세스 제거
    if(pCur->NextEntryOffset == 0)
    pPrev->NextEntryOffset = 0;
    else
    pPrev->NextEntryOffset += pCur->NextEntryOffset;
    }
    else
    pPrev = pCur;

    • reversecore 2011.10.28 00:41 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      if(!_strcmpi(szProcName, g_szProcName)) 의 의미는 두 문자열이 같으면~ 이라는 뜻이구요.

      pCur->NextEntryOffset == 0 는 연결리스트의 마지막이 맞습니다. 현재 프로세스가 프로세스 연결 리스트의 마지막이라면 pPrev->NextEntryOffset = 0; 명령으로 앞의 프로세스를 연결리스트 마지막으로 설정하란 뜻입니다.

      감사합니다.

  10. 팔극진 2011.10.31 14:51 신고 댓글주소 | 수정 | 삭제 | 댓글

    친절하신 답변 정말 감사드립니다. ^^
    수고하세요~!

  11. Midkiz 2011.11.07 11:00 신고 댓글주소 | 수정 | 삭제 | 댓글

    ㅎㅎ 이거 꿀같은 자료입니다... 공부열심히 해서 뒤따라 가도록 하겠습니다

  12. kimpilgu 2012.05.09 18:11 신고 댓글주소 | 수정 | 삭제 | 댓글

    수고많으십니다.^^ SetPrivilege() 함수는 권한설정같은데 어떤역할을 하는지 궁금합니다...

  13. HongJi 2012.06.19 23:22 신고 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요 코님.
    좋은글 정말 잘 읽고 많이 배웁니다.
    한가지 질문이 있습니다.
    팔극진님이 질문하셨던 NewZwQuerySystemInformation에서 말이지요.
    만일 첫번째 프로쎄스가 stealth 프로쎄스여서 그것을 숨기자면 어찌 해야 할지요.
    그리고 현재 코드에 버그가 있네요 첫번째 프로세스가 stealth프로세스면 동작은 물론 pPrev가 초기화되지 않은 상태에서 작동하므로 예상치 못할 버그를 발생시킬수 있을것 같습니다.
    아무리 머리를 굴려도 첫번째 프로세스가 스텔스프로세스라면 어떻게 감춰야 할지 떠오르지 않네요.
    일단은 다음과 같이 수정은 해놓았습니다.
    if(pCur->NextEntryOffset == 0 && pPrev)
    pPrev->NextEntryOffset = 0;
    else
    pPrev->NextEntryOffset += pCur->NextEntryOffset;

    *((PWSTR)pCur->Reserved2[1]) = 0; //erase process name;

    정확한 답변부탁드립니다.

    • reversecore 2012.06.23 01:05 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      네, 코드상으로 보니 pPrev 를 초기화 하지 않았고, 사용함에 있어서도 유효한 포인터인지 검증하지 않았네요.

      좋은 지적 감사합니다. ^^~

      감사합니다.

  14. 2012.06.20 00:20 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

    • reversecore 2012.06.23 01:08 신고 댓글주소 | 수정 | 삭제

      온라인 게임 클라이언트 말씀이신가요?
      게임 클라이언트가 실행되는 IP 를 숨기고 싶으시다고요?
      DDoS 공격을 위해서요? (그 반대인가요?)
      어쨌든 제가 약간 우려되는 부분이네요~ @@~

  15. HongJi 2012.06.25 10:41 신고 댓글주소 | 수정 | 삭제 | 댓글

    답변감사합니다.
    어떤 온라인 게임 클라이언트를 노는 사람들이 가능한 어떤 방법으로든 그게임의 써버 ip를 알지 못하게 하여 DDOS공격을 시도조차 못하게 하려고 합니다.
    인터넷에 보니 DDOS공격 도구들이 Free로 나돌아 누구나 막 DDOS를 시도할수 있는 가능성이 높아서요. 프로가 아닌 사람들이 맘대로 공격하는것을 막으려고 합니다.

  16. HongJi 2012.06.25 10:47 신고 댓글주소 | 수정 | 삭제 | 댓글

    한가지 질문 더 드릴게요.
    코어님의 코드를 다운하여 Win7에서 DLL Injection은 성공했습니다.
    그런데 MFC Dialog형식으로 제가 만든 프로젝트에서 DLL Injection시에는 NtCreateThreadEx를 API후킹한 부분을 실행하다 BlueScreen이 나오는데 초보인 저로서는 원인이 무엇인지 감이 전혀 안오네요.
    필요하시다면 저의 쏘스를 보내드릴수 있는데 원인해결 부탁드리고 싶습니다.
    언제나 방문자님들에게 친절한 코어님께 감사드립니다.

  17. 알려주세요 2012.07.01 21:04 신고 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요.

    되도록 구글신의 힘을 빌려서 혼자 해결해려 했지만 너무 어려워서 질문 올립니다.

    Hookproc.cpp 의 내용을 쭉 읽다가 _EnableNTPrivilege 함수 부분에서 꽉 막혀버렸습니다.

    읽어도 이해가 가질 않네요.... 혹시 이 함수의 내용이나 내부 함수에 대해서 설명 해 주시거나

    링크가 있을까요...?

  18. 인찡 2012.10.23 01:08 신고 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요 프로그래밍 지식이 부족한 상황에서 선생님의 책을 사서 보고 있습니다
    c를 잘 모르는데 리버싱을 공부 할 수 있나요 api를 잘 모르는데 공부 할 수있나요
    서문에 적어주신 가장 많은 질문이라고 그에 대한답변을 적어 주신게 너무 좋게 들렸습니다 ㅠ
    결론은 제가 프로그래밍을 잘몰라서요 ㅠㅠ
    책에 보면 스텔스 프로세스 부분에 stealth2.dll 부터는 notepad를 자동으로 되게 하드코딩하셨자나요

    그런식으로 그냥 hideproc2.exe -hide stealth2.dll 이것까지 다 하드코딩 하려면 어느부분을 수정하여야 하나 힌트 좀 구할수 잇을까요 ㅠ

    • reversecore 2012.10.23 21:41 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      HideProc2.cpp 파일의 _tmain() 을 아래와 같이 변경하시면 됩니다.

      int _tmain(int argc, TCHAR* argv[])
      {
      int nMode = INJECTION_MODE;

      // change privilege
      SetPrivilege(SE_DEBUG_NAME, TRUE);

      // Inject(Eject) Dll to all process

      InjectAllProcess(nMode, L"stealth2.dll");

      return 0;
      }


      stealth2.dll 파일을 %SYSTEM% 폴더에 복사하는것 잊지 마시구요.

      감사합니다.

  19. 셔벳클록 2012.11.06 21:41 신고 댓글주소 | 수정 | 삭제 | 댓글

    공용대화상자를 후킹하려고 하는데요..
    다음과 같이 코드를 작성하였는데요..
    getopenfilename이 호출될때 메시지 박스를 띄우는 작업을 하고
    있거든요.
    그런데.. getopenfilename 후킹이 제대로 되기는 하는건지..
    아니면 중간코드가 잘못되었는지..
    후킹성공 메시지 박스가 뜨지 않네요
    (제가 목표로 하려는건 열려있는 탐색기 디렉터리를 얻어와서,
    그 디렉터리들 중 사용자가 원하는 것을 선택해서 그 위치에서
    파일관련대화상자를 여는 겁니다)

    소스 코드는 아래 저의 블로그에 임시로 올려놓았습니다.
    http://blog.naver.com/siam18/170382475

    • reversecore 2012.11.07 06:20 신고 댓글주소 | 수정 | 삭제

      안녕하세요.

      comdlg32.GetOpenFileName 에는 ASCII 버전과 UNICODE 버전이 있습니다.

      한번 GetOpenFileNameA (또는 GetOpenFileNameW) 로 후킹해 보시기 바랍니다. ^^~

      감사합니다.

  20. 난나놔 2013.07.07 06:55 신고 댓글주소 | 수정 | 삭제 | 댓글

    dll 인젝션은 석기시대 유물.



특정 프로세스를 감추는 은폐(stealth) 기법에 대해 설명하고, 예제 파일을 통해서 실습을 해보도록 하겠습니다.

 

<photo : mashleymorgan on flickr>


아래 내용은 이전 포스트에서 이어지는 내용입니다.
API Hooking – '스텔스' 프로세스 (1)



프로세스 은폐(stealth) 동작 원리 (User Mode)


프로세스 은폐(stealth)에 관한 내용은 이미 많은 정보가 공개되어 있습니다.
그 중에서 유저 모드(User Mode)에서 가장 널리 사용되는 "ntdll!ZwQuerySystemInformation() API 후킹 방법"에 대해서 살펴보겠습니다.

* 커널 모드의 프로세스 은폐 기법은 (유저 모드에 비해) 더 강력하고, 더 고급 기법입니다. 향후 제 블로그에서 자세히 다루도록 하겠습니다.


#1. 프로세스 은폐(stealth) 개념

스텔스 전투기는 레이다에 포착되지 않기 위해서 각종 첨단 과학을 동원하여 전투기 자체를 (기존 전투기와 다르게) 완전히 새롭게 개발 하였습니다. 즉, 작업 대상이 스텔스 전투기 자신입니다.

반면에 은폐 프로세스의 개념은 이와는 정반대입니다.
특정 프로세스를 은폐시키기 위해서 나머지 모든 프로세스들의 메모리에 침투하여 API 를 후킹합니다. 즉, 작업 대상은 다른 프로세스입니다.

은폐 프로세스의 개념을 스텔스 전투기의 설명에 적용해보면 아래와 같습니다.

그냥 일반 전투기를 띄워 보낸 후 모든 레이다를 고장내면(조작하면), 그 일반 전투기는 그 순간부터 스텔스 전투기가 되는 것과 같은 이치입니다.

이것이 바로 은폐(stealth) 프로세스의 개념입니다.


#2. 관련 API

프로세스(Process)는 커널 객체이기 때문에 (유저 모드 프로그램은) API 를 통해서만 접근이 가능합니다. 일반적으로 유저 모드에서 프로세스를 검색하기 위한 API 는 아래와 같이 2 종류가 있습니다.

HANDLE WINAPI CreateToolhelp32Snapshot(
    DWORD    dwFlags,
    DWORD    th32ProcessID
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms682489(VS.85).aspx

BOOL EnumProcesses(
    DWORD*   pProcessIds,
    DWORD    cb,
    DWORD*   pBytesReturned
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms682629(VS.85).aspx

위 2 가지 API 모두 내부적으로 ntdll!ZwQuerySystemInformation() API 를 호출합니다.

NTSTATUS ZwQuerySystemInformation(
    SYSTEM_INFORMATION_CLASS    SystemInformationClass,
    PVOID    SystemInformation,
    ULONG    SystemInformationLength,
    PULONG   ReturnLength
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms725506(VS.85).aspx

ZwQuerySystemInformation() API 를 이용하여 실행중인 모든 프로세스들의 정보(구조체)를 연결 리스트 형태로 얻을 수 있습니다.

그 연결 리스트를 조작하면(리스트에서 빼내면) 해당 프로세스는 은폐 되는 것입니다.

따라서 유저 모드에서는 CreateToolhelp32Snapshot() 나 EnumProcess() API 를 후킹할 필요 없이  ZwQuerySystemInformation() API 하나만 후킹하면 확실하게 특정 프로세스를 은폐 시킬 수 있습니다.


#3. Global Hooking 개념

우리가 은폐하고자 하는 프로세스를 test.exe 라고 하겠습니다.
실행중인 ProcExp.exe (또는 taskmgr.exe) 프로세스의 ZwQuerySystemInformation() API 를 후킹 하면 ProcExp.exe 는 test.exe 를 찾지 못할 것입니다.

* ProcExp.exe = 프로세스 익스플로러, taskmgr.exe = 윈도우 작업 관리자

위와 같이 하면 ProcExp.exe(또는 taskmgr.exe) 프로세스 하나에 대해서 test.exe 가 은폐되었다고 말할 수 있습니다.

하지만 이 방법에는 두 가지 문제점 이 있습니다.

첫 번째 문제점은 후킹 대상 프로세스 개수 입니다.
프로세스 검색 유틸리티가 과연 이 두 가지뿐 일까요?
이들 외에도 수 많은 프로세스 검색 유틸리티가 있을 것이며, 사용자가 직접 만든 유틸리티도 있을 수 있습니다. 따라서 시스템에 실행중인 모든 프로세스를 후킹 해야만 내 프로세스가 은폐되었다고 확신할 수 있습니다.

두 번째 문제점은 새로 생성되는 프로세스입니다.
만약 사용자가 ProcExp.exe (또는 taskmgr.exe) 를 하나 더 실행하면 어떻게 될까요?
첫 번째 ProcExp.exe 프로스세는 이미 후킹이 되어 있으므로 test.exe 프로세스를 찾지 못하겠지만, 두 번째 실행된 ProcExp.exe 프로세스는 후킹 되지 않았으므로 test.exe 프로세스를 정상적으로 찾아 낼 것입니다.

이 두 가지 문제를 정리해 보면 우리는 test.exe 프로세스를 완전히 숨기기 위해서 시스템에 실행 중인 모든 프로세스의 ZwQuerySystemInformation() API 를 후킹해야 하며, 추가적으로 나중에 실행되는 모든 프로세스에 대해서도 똑 같이 후킹을 해줘야 합니다. (물론 자동으로 해줘야겠지요.)

이것이 바로 global hooking 의 개념입니다.
(이러한 global hooking 에 대해서는 뒷부분에서 따로 설명하도록 하겠습니다.)



실습


HideProc.exe

stealth.dll


HideProc.exe 는 실행중인 모든 프로세스에게 Stealth.dll 파일을 인젝션 시키는 역할을 합니다.
Stealth.dll 은 인젝션 된 프로세스의 ntdll!ZwQuerySystemInformation() API 를 후킹하는 역할을 합니다.

위 두 파일을 이용해서 notepad.exe 프로세스를 은폐시켜 보도록 하겠습니다.

* 참고!
위 실습 파일은 “global hooking – 새로운 프로세스”에 대한 대책이 없는 버전입니다.
따라서 HideProc.exe 실행 이후에 생성된 프로세스는 자동으로 후킹 되지 않습니다.
그에 대해서는 따로 설명 드릴 예정입니다. 참고하세요.



#1. notepad.exe, procexp.exe, taskmgr.exe 를 실행시켜 주세요.


#2. 아래와 같이 HideProc.exe 를 실행합니다.

<Fig. 1>

실행 파라미터에 대해서 간략히 설명 드리겠습니다.

-hide/-show : 은폐 시킬 때는 –hide, 은폐 해제 시에는 –show
process name : 은폐 시킬 프로세스 이름
dll path  : 인젝션 시킬 DLL 파일 경로


#3. 모든 프로세스에 stealth.dll 파일이 제대로 인젝션 되었는지 확인합니다.

Process Explorer 을 이용해서 stealth.dll 을 검색합니다.

<Fig. 2>

실행중인 모든 프로세스에 stealth.dll 파일이 인젝션 된 것을 확인 할 수 있습니다.

* 사실은 시스템 프로세스들(PID 0 & PID 4)에 대해서는 (시스템 안정성을 위해) 인젝션 시키지 않았습니다. (notepad.exe 프로세스 은폐에는 아무 상관 없습니다.)


#4. procexp.exe 와 taskmgr.exe 에서 notepad.exe 프로세스가 사라진걸 확인합니다.

<Fig. 3>

<Fig. 4>

위의 <Fig. 3> 과 <Fig. 4> 를 보시면 notepad.exe 프로세스가 분명히 실행 중이지만, procexp.exe 와 taskmgr.exe 에서 notepad.exe 프로세스가 사라진걸 확인하실 수 있습니다.

* 메모장의 윈도우가 보이기 때문에 프로세스가 완벽히 숨은 게 아니라고 생각하실 수 있겠습니다만, 우리의 목표는 프로세스 은폐이기 때문에 윈도우는 그냥 놔뒀습니다. 참고로 윈도우를 사라지게 하려면 SetWindowPos() API 등을 사용하시면 됩니다.


#5. notepad.exe 프로세스를 다시 보이도록 합니다.

HideProc.exe 를 –show 모드로 실행시킵니다. (stealth.dll 을 ejection 시켜줍니다.)

<Fig. 5>

procexp.exe 와 taskmgr.exe 에서 notepad.exe 프로세스가 정상적으로 보이는지 직접 확인해보시기 바랍니다.


다음 번에는 예제 소스 파일을 상세하게 분석해보도록 하겠습니다.

API Hooking – '스텔스' 프로세스 (3)


ReverseCore

위 글이 도움이 되셨다면 추천(VIEW ON) 부탁 드려요~

저작자 표시 비영리 변경 금지
신고
  1. RED_BIT 2009.12.17 08:19 신고 댓글주소 | 수정 | 삭제 | 댓글

    오랫만에 1등하는것 같네요.ㅎㅎ
    노트북이라... 대략 40개 이상의 프로세스들이 히히낙낙 거리고있는데...
    웬지 무서운...
    잘봤습니다~ > <//

    • ReverseCore 2009.12.17 18:46 신고 댓글주소 | 수정 | 삭제

      RED_BIT님, 안녕하세요.
      잘 보셨나요? ^^
      감사합니다.

    • 쵸밥 2010.01.16 11:17 신고 댓글주소 | 수정 | 삭제

      헐 ㅇㅁㅇ ,, 그러고 보니

      그간 신경 안쓰고 살았는데 프로세스가 48개네요 ,,
      (포멧한 직후,, 백신깔고 SP3설치하는중,,)

      노트북은 프로세스가 많은가 ㅇㅅ ㅇ,,

  2. hoon038 2009.12.18 00:31 신고 댓글주소 | 수정 | 삭제 | 댓글

    아 코어님의 명강의 정말 많은도움 되고 있습니다.
    나중에는 은폐 프로세스를 찾는 방법도 포스팅 해주셧으면 좋겠네요 ㅎㅎ

    • reversecore 2009.12.18 10:58 신고 댓글주소 | 수정 | 삭제

      hoon038님, 안녕하세요.

      말씀하신 은폐 탐지 방법도 재밌는 글이 될꺼 같네요 ^^

      사실 제가 쓰는 글 중에는 다른 분들의 의견, 아이디어가 반영된 부분이 많답니다. 다음 글을 쓸 때 도움으 많이 되지요~

      좋은 정보 감사드립니다.

  3. 개숭이 2009.12.18 11:04 신고 댓글주소 | 수정 | 삭제 | 댓글

    1편에 CreateRemoteThreaed방법으로 Injection을 쓴다고 Tech Map에 표시는 해두셨지만, 이유는 안나와 있어서요..

    혹시 "SetWindowsHookEx쓰면 되는데... 저절로 해주는데.."라는 생각하실분들이 있을거 같네요..

    아, 그리고 Tech Map들 보면 맨 우측 API부분에 CreateRemoteThread부분에 빨간색 친거 맨 아래에 쳐야 맞는거같은데.. 이전 것두 그런게 있고요//

    사소한 것들이여서 언급안하고 넘어갈수도 있는 부분인데, 연재글을 읽는 입장으로서, 초보자입장으로서 염려되어 리플하나 남겨봅니다.. 심각하게 받아들이진 말아주세요///

    • reversecore 2009.12.18 11:06 신고 댓글주소 | 수정 | 삭제

      개숭이님, 안녕하세요.

      네~ 지금 읽어 보니 과연 그렇군요. ^^
      처음 접하시는 분들은 그런 의문이 생기겠네요~

      TechMap 그림도 지적하신 내용이 맞습니다. ^^
      아래쪽 API 에 표시해야 하는데, 위쪽에 해버렸네요.

      좋은 지적 감사합니다.
      글의 완성도를 높이는데 도움을 주셔서 정말 감사드려요~

    • 개숭이 2009.12.18 11:13 신고 댓글주소 | 수정 | 삭제

      사실 제가
      reversecore님이 API Hooking 연재 진행중이실때
      계속 CreateRemoteThread만 쓰시네? 무슨이유일까? 이런생각을 했었거든요.. SetWindowsHookEx로 인젝션해서 API Hook하는 내용도 나올까 싶었는데 그후로 계속 CreateRemoteThread만 쓰셔서^^;;

    • reversecore 2009.12.18 11:23 신고 댓글주소 | 수정 | 삭제

      개숭이님, 제 블로그를 보고계셧군요. ^^

      SetWindowsHookEx 는 메시지 후킹 설명때 했었고, 자체적인 한계 때문에 개인적으로 CreateRemoteThread 를 선호 한답니다.

      다음번(마지막) API Hooking 실습예제도 CreateRemoteThread 를 썼는데요... ㅎㅎ
      말씀하신대로 SetWindowHookEx 로 변경 가능한지 확인해 보겠습니다.

      저도 이왕이면 다양한 방법을 많이 소개해 드리는 편이 좋다고 생각합니다.

      좋은 제안 감사드려요~ ^^

  4. 개숭이 2009.12.18 11:38 신고 댓글주소 | 수정 | 삭제 | 댓글

    마음과 몸은 이미 주말이네요..
    일이 손에 안잡혀서 서핑중입니다ㅋㅋㅋ
    아 갑자기 궁금한게 있는데요, CreateRemoteThread를 사용해서 32bit프로세스(32bit dll, 32bit injector)에서 64비트 프로세스로 인젝션이 가능한가요? SetWindowsHookEx는 어떨까요?

    • ReverseCore 2009.12.21 11:14 신고 댓글주소 | 수정 | 삭제

      개숭이님, 안녕하세요.
      답변이 늦었네요~

      전 아직 64bit 를 경험해보지 못해서 뭐라 말씀드리기 어렵네요 ^^

      32->32, 64->64 는 당연히 잘 되겠죠.
      32->64 만 확인해보면 되겠네요...

  5. mAn1aS 2009.12.23 10:18 신고 댓글주소 | 수정 | 삭제 | 댓글

    좋은 자료 잘보고 있습니다. 다음 글이 기다려 지는군요^^ 연말 잘보내세요

  6. 쵸밥 2009.12.26 04:47 신고 댓글주소 | 수정 | 삭제 | 댓글

    혹시 forum.exetools (EXETOOLS) 가입되어 있는 분 있나요 T^T?

    W32Dasm Patch 3.0 final 이 그 홈페이지에 있길레 받고싶은데. .
    영어가 너무 어려워서 가입을 못하네요 마지막에 무슨 코드를 쓰라는데 T^T..
    구글 번역기 돌리며 몇시간을 시도해봤지만 실패네요 T^T..

    http://forum.exetools.com/showthread.php?t=2396

    요거 혹시 받는게 가능하신 분 있으면 tortlxkq@naver.com 으로 좀 보내주시겠어요 흑 T^T..

    P.S 메리 그리숨엇수 하세요 ^^*

    • ReverseCore 2009.12.28 00:49 신고 댓글주소 | 수정 | 삭제

      쵸밥님, 안녕하세요.

      W32Dasm 을 찾으시는군요.
      해당 링크는 패치파일 이군요?

      이번에 OllyDbg2.0 Final Beta 가 출시되었는데,
      제작자가 DisAsm 을 크게 강화했다고 하니 참고하시기 바랍니다.

      감사합니다.

  7. Elephunk 2009.12.29 20:08 신고 댓글주소 | 수정 | 삭제 | 댓글

    w32dasm 3.0 patch
    http://tinyurl.com/ydwms9y
    여기서 [W A S M . R U] 링크 들어가면 있습니다

    • reversecore 2009.12.30 10:26 신고 댓글주소 | 수정 | 삭제

      ^^
      익살스런 링크입니다.

      Elephunk님, 감사합니다.

    • 쵸밥 2009.12.31 06:23 신고 댓글주소 | 수정 | 삭제

      ㅋㅋㅋ 너무 깜찍한 메시지ㅋㅋ
      이게 그리 어렵냐 이거 만드신건가요 ㅋㅋ
      넘넘 감사해요 ㅜ

      이렇게 금방 이런 페이지를 만들수도 있나보네요 ^^;;
      웹도 참 재밋는것 같네요 ㅋ 감사합니다 ^^*

      w32dasm patch 3.0 bratpatch3.zip 으로만 검색했는데 ㅜ
      그렇게 치니깐 많이 나오네요 감사합니다 ^^*
      웹페이지 너무 귀여워요 ^^*

  8. 지나가는이 2010.03.24 14:30 신고 댓글주소 | 수정 | 삭제 | 댓글

    흠.. 이거 64비트에서는 안되네요..

    • reversecore 2010.03.25 23:33 신고 댓글주소 | 수정 | 삭제

      지나가는이님, 안녕하세요.

      아~ 그렇군요.

      제 주변에 64bit 테스트 환경이 없어서 한번도 테스트 해보지 못했습니다.

      향후 환경이 갖춰지면 디버깅해서 수정하도록 하겠습니다.

      감사합니다.

  9. QhQh 2010.05.14 18:36 신고 댓글주소 | 수정 | 삭제 | 댓글

    Windows XP SP3와 7에서 테스트 하셨다고 하셨는데

    VMware로 SP3를 설치하고 하니깐 안되네요

    혹시 방화벽 해제라던지 다른 변경을 해야 가능하고 그런가요:?

    • QhQh 2010.05.14 18:41 신고 댓글주소 | 수정 | 삭제

      아 해결했습니다
      바탕화면에서 실행을 했었는데
      폴더명에 한글이 포함되면 안되는거였군요 ㅎㅎ
      괜한 삽질

      잘 보고 있습니다^^

    • reversecore 2010.05.17 01:57 신고 댓글주소 | 수정 | 삭제

      QhQh님, 안녕하세요.

      아마 한글이 문제가 아니라 띄어쓰기가 문제일것 같네요.

      C:\Documents and Settings\ReverseCore\바탕 화면>HideProc.exe -show notepad.exe "c:\Documents and Settings\ReverseCore\바탕 화면\stealth.dll"

      위와 같이 stealth.dll 경로를 따옴표("")로 묶어 주시면 정상적으로 작동 될 것입니다.

      잘 안되시면 다시 질문 올려 주세요~

      감사합니다.

  10. 김병화 2010.06.24 14:26 신고 댓글주소 | 수정 | 삭제 | 댓글

    HideProc.exe -hide abc.exe d:\stealth.dll 을 하니,

    OpenProcess<3976> failed!!!
    OpenProcess<4040> failed!!!

    tasklist 해보니,
    V3LSvc.exe 3976 Console 0 2,276 K
    V3LTray.exe 4040 Console 0 1,028 K

    음,,, 안철수백신에는 숨겨지지가 않는군요,
    안철수백신에 숨겨지지 않는것 뿐만아니라, tasklist로는 abc.exe가 보이네요.

    백신이 설치되지 않은 다른PC에 해봤습니다.
    결과는 잘됩니다.
    tasklist로도 숨겨져 있네요.

    제가 뭔가 잘 못하는건가 싶네요, ^^;

    • reversecore 2010.06.25 10:26 신고 댓글주소 | 수정 | 삭제

      위에 언급하신 내용은 매우 정상적인 결과입니다.

      V3 제품의 자체 프로세스 보호기법 때문에 OpenProcess() 에러가 발생한 것입니다. 이 경우 보통 방법으로는 해당 프로세스에 침투할 수 없습니다.

      이런류의 프로세스 메모리에 침투하는 방법이 여러가지 있긴 한데요...
      기법마다 껀껀이 설명드리기도 어렵고...
      악성코드에서 많이 쓰는 거라 설명하기도 거북한 상황입니다. ^^

      당분간은 보호 프로세스에 침투하는 방법에 대한 강좌는 계획에 없습니다.

      감사합니다.

  11. 김병화 2010.06.25 16:03 신고 댓글주소 | 수정 | 삭제 | 댓글

    ㅎㅎㅎ;;
    정상이군요.
    백신도 숨겨지나 싶어서 궁금해서 물어봤습니다...
    근데 신기하네요.

    저는 랭귀지를 잘못해서 실제 적용은 못하겠네요.
    ㅎㅎㅎ;;여튼 좋은구경하고 갑니다.
    그리고, 제 홈피주소가 스팸이 되었더군요.
    제가 이쪽으로 트래백을 잘못보낸적이 있는거 같습니다.
    ^^

  12. 안녕하세요 2010.07.15 04:48 신고 댓글주소 | 수정 | 삭제 | 댓글

    좋은 자료 잘 보고 있습니다 ~

    한가지 궁금한 점이 있는데요

    이게 윈도우 작업 관리자에서 프로세스 탭 에서는 사라지는데요

    응용 프로그램 탭 에는 그대로 있네요 ㅋ

    혹시 응용 프로그램 탭에서도 같이 없앨 수 있는 방법이 있을까요?

    • reversecore 2010.07.15 23:54 신고 댓글주소 | 수정 | 삭제

      네, 프로세스 목록에서만 감추기 때문에 윈도우는 그대로 보이는 것입니다.

      윈도우까지 같이 없애주시면 작업관리자의 응용프로그램 탭에서 사라집니다.

      윈도우 없애는 건 윈도우 메시지를 보내거나 관련 API 를 사용하면 쉽게 될 것입니다.

      위 예제는 API Hooking 을 위주로 작성한 거라 윈도우는 그대로 놔뒀습니다. 분명 윈도우는 있는데, 프로세스만 사라지는 현상을 보여주려고 말이지요.

      윈도우 감추기는 검색해 보시면 금방 찾으실 겁니다.

      감사합니다.

  13. 안녕하세요. 질문 2010.09.02 16:03 신고 댓글주소 | 수정 | 삭제 | 댓글

    hideproc.exe 실행시켜봤는데 0.5초만에 저절로 꺼지던데 왜 이런건가요?
    윈도우xp 서비스팩2사용중입니다.

    • ReverseCore 2010.09.04 16:43 신고 댓글주소 | 수정 | 삭제

      그렇게 동작하는게 정상입니다. ^^

      작업관리자에서 notepad.exe 가 안보인다면 성공입니다.
      (WinXP SP3 에서 테스트 하였습니다.)

      감사합니다.

  14. usrobj 2011.08.04 23:42 신고 댓글주소 | 수정 | 삭제 | 댓글

    업계에서 어느 정도 일해야 저런 API까지 다 알고 그럽니까 ㅎ
    요즘 윈도우 시스템 프로그래밍 책 보면서 틈틈히 블로그 글 보지만 언제나 봐도 신기하기도하고 어렵기도하고...

  15. 난나놔 2013.07.07 06:46 신고 댓글주소 | 수정 | 삭제 | 댓글

    dll에 직접 훅을 걸거나... 이미 훅되어 있다면 32비트는 5바이트 64비트는 12바이트 점프한거나...
    커널에서 훅을 걸거나...64비트는 커널 진입전 까지 코드를 복사해서 커널로 진입하거나... ssdt 보호모드에서 구경만 하거나... 잠이나 자거나...

  16. alfheimr 2017.02.03 01:02 댓글주소 | 수정 | 삭제 | 댓글

    관리자의 승인을 기다리고 있는 댓글입니다


API Code Patch 를 통한 API Hooking 방법에 대해서 공부합니다.

또한 모든 프로세스를 후킹 하는 global hooking 방법에 대해서 살펴봅니다.

위 기법을 사용하여 특정 프로세스를 감추는 은폐(stealth) 기법에 대해 실습해 보겠습니다.



<photo : Rob Shenk on flickr>


* 은폐(Stealth) 프로세스를 리버싱 전문 용어로 루트킷(Rootkit) 이라고 합니다.
보통 루트킷이라고 하면 커널 모드 후킹을 통한 프로세스, 파일, 레지스트리 등의 은폐 기술을 지칭합니다. 루트킷은 본 포스트의 설명 범위를 벗어나기 때문에 편의상 스텔스 프로세스라고 하겠습니다. (루트킷에 대한 내용은 커널에 대한 방대한 기반 지식을 요구합니다. 향후 제 블로그에서 하나씩 살펴볼 계획입니다.)

* 본문 내용을 편하게 읽기 위해서는 아래의 배경지식이 필요합니다.
☞ DLL Injection (DLL Injection – 다른 프로세스에 침투하기
)
☞ API Hooking (API Hooking – 계산기, 한글을 배우다
)
☞ API Hooking (API Hooking - 리버싱의 '꽃')



Tech Map



<Fig. 1>

API Code Patch
기법을 위 TechMap 에서 빨간색으로 표시하였습니다.

이 기법은 API Hooking 에서 가장 널리 사용됩니다.


그 이유는 대부분의 user mode API 를 자유롭게 후킹 할 수 있기 때문입니다.

* IAT Hooking 기법은 후킹하려는 API 가 프로세스의 IAT 에 존재하지 않을 경우 후킹이 불가능한 반면에 "API Code Patch" 기법은 그러한 제약 조건이 없습니다.

덧붙여 대상 프로세스의 메모리를 자유롭게 사용하기 위해 DLL Injection 기법을 사용하였습니다. (다음 번 주제에서 DLL 파일이 아닌 Code 자체를 Injection 하는 고급 기법에 대해서 살펴볼 예정입니다.)



API Code Patch 동작 원리


API Code Patch 를 통한 API Hooking 기법의 동작 원리에 대해서 알아보겠습니다.

* IAT Hooking 방식과 비교해 살펴보시면 더 쉽게 이해 될 것입니다.
☞ API Hooking (API Hooking – 계산기, 한글을 배우다)


IAT Hooking 방식이 프로세스의 특정 IAT 값을 조작해서 후킹을 하는 방식이라면, Code Patch 방식은 실제 API 코드 시작 5 byte 값을 JMP XXXX 명령어로 변경하는 방식입니다.

후킹된 API 가 호출되면 (패치된) JMP XXXX 명령어가 실행되어 후킹 함수로 제어가 넘어옵니다.

아래 그림은 Process Explorer(procexp.exe) 프로세스에 stealth.dll 파일을 인젝션 시킨 후 ntdll!ZwQuerySystemInformation() API 를 후킹하는 방법을 설명하는 그림입니다.

먼저 후킹 되기 전의 정상적인 프로세스 메모리의 모습입니다.

<Fig. 2> 

(1) procexp.exe 의 00422CF7 주소의 CALL DWORD PTR DS:[48C69C] 명령어는 ntdll.ZwQuerySystemInformation() API 를 호출하고 있습니다. (48C69C 주소는 프로세스의 IAT 영역으로써 7C93D92E 값을 가지며, 이는 ntdll.ZwQuerySystemInformation() API 의 시작 주소입니다.)

(2) 해당 API 는 실행이 완료되면 호출 코드 다음 명령어 주소로 되돌아 갑니다.

이것은 매우 정상적인 상황의 API 호출 흐름입니다.

그럼 이제 API 가 후킹된 프로세스의 그림을 보시겠습니다.
stealth.dll 을 인젝션 하여 ntdll!ZwQuerySystemInformation() API 를 Code Patch 하였습니다.

<Fig. 3>

위 그림이 상당히 복잡하게 보입니다. 하나씩 차근차근 살펴보도록 하겠습니다.

먼저 stealth.dll 이 인젝션 되면서 ntdll!ZwQuerySystemInformation() API 를 후킹 하였습니다.

ntdll!ZwQuerySystemInformation() API 시작 주소(7C93D92E)의 5 byte 코드를 JMP 10001120 으로 덮어 써버린 것이죠. (5 byte 패치) 10001120 주소는 stealth!MyZwQuerySystemInformation() 함수 주소입니다.

이때 procexp.exe 코드에서 ntdll!ZwQuerySystemInformation() API 를 호출하면 코드 흐름은 아래 순서와 같습니다.

(1) 422CF7 주소에서 ntdll!ZwQuerySystemInformation() API 를 호출(7C93D92E)합니다.

(2) 7C93D92E 주소에 있는 패치된 코드 JMP 10001120 에 의해서 10001120 주소(후킹 함수)로 점프합니다. 1000116A 주소의 CALL unhook() 명령어에 의해서 ntdll!ZwQuerySystemInformation() API 시작 5 byte 는 원래대로 복원됩니다.

(3) 1000119B 주소의 CALL EAX(7C93D92E) 명령어에 의해서 원본 함수(ntdll!ZwQuerySystemInformation) 가 호출됩니다. (unhook 상태이기 때문에 정상적으로 실행됩니다.)

(4) ntdll!ZwQuerySystemInformation() 의 실행이 완료되면 7C93D93A 주소의 RETN 10 명령에 의해 stealth.dll 코드 영역(자신을 호출한 위치)으로 리턴됩니다. 그리고 10001212 주소의 CALL hook() 명령어에 의해서 ntdll!ZwQuerySystemInformation() API 를 다시 후킹합니다. (시작 5 byte 를 JMP 10001120 명령어로 패치함)

(5) stealth!MyZwQuerySystemInformation() 의 실행이 완료되면 10001233 주소의 RETN 10 명령에 의해서 procexp.exe 프로세스 코드 위치로 리턴됩니다.

처음에는 좀 어려운 듯 보여도 몇 번만 읽어보시면 금방 이해하실 수 있으실 겁니다.

Code Patch 방법의 장점은 프로세스에서 사용되는 어떤 API 라도 후킹할 수 있다는 것입니다. IAT Hooking 의 경우에는 후킹 가능한 API 가 제한 된다는 사실과 비교해 보세요. (비록 코드는 조금 더 복잡하지만 말이죠.)

Code Patch 방법에 있어서 제한 사항은 후킹하려는 API 코드의 길이가 최소 5 byte 보다 커야 한다는 것입니다만, 모든 API 의 코드 크기는 5 byte 보다 크기 때문에 사실상 제한이 없다고 보시면 됩니다.

* 주의!
코드 패치 방법은 말그대로 프로세스 메모리 공간에 매핑된 DLL 의 코드를 직접 수정하는 것입니다. 만약 프로세스의 다른 스레드에서 어떤 함수를 read 하고 있을때 코드 패치를 시도하면 어떻게 될까요? Access Violation 에러가 발생합니다. 따라서 이점을 유의 하셔야 합니다.

다음에는 스텔스 기법에 대한 원리와 실습 예제 파일을 가지고 직접 실습을 해보도록 하겠습니다.


API Hooking - '스텔스' 프로세스 (2)


ReverseCore

위 글이 도움이 되셨다면 추천(VIEW ON) 부탁 드려요~

저작자 표시 비영리 변경 금지
신고
  1. RED_BIT 2009.12.13 04:18 신고 댓글주소 | 수정 | 삭제 | 댓글

    아.. 전체적으로 작업관리자, procxp와 같은 녀석들에게 dll을 삽입해서...
    작업하는건가요..?! 제가 이해하고있는게 맞을까요..!? ㅎㅎ
    오늘도 좋은글 감사합니다~ ^^*

    • ReverseCore 2009.12.14 21:12 신고 댓글주소 | 수정 | 삭제

      RED_BIT님, 안녕하세요.

      네, 실행중인 모든 프로세스에 stealth.dll 을 인젝션 시킬겁니다.

      말씀하신대로 작업관리자, procexp 만 인젝션 시켜도 일반 사용자에게는 "스텔스" 처럼 보이겠지요. ^^

      감사합니다.

  2. 세의 2009.12.13 14:10 신고 댓글주소 | 수정 | 삭제 | 댓글

    잘 읽었습니다. +_+

  3. Sun2Day 2009.12.14 09:37 신고 댓글주소 | 수정 | 삭제 | 댓글

    항상 좋은 내용 잘보고 갑니다 '-'//

  4. BABOHA 2009.12.14 23:17 신고 댓글주소 | 수정 | 삭제 | 댓글

    감사합니다~
    Reversecore 님은 참 부지런 하십니다. ㅎㅎ
    다음 것도 기대할게요~

  5. Externalist 2009.12.15 20:58 신고 댓글주소 | 수정 | 삭제 | 댓글

    작성하느라 수고 많으셨습니다... 나중에 하나로 묶어서 책으로 출간해도 전혀 손색이 없을 정도로 훌륭한 퀄리티네요...^^

  6. Vice 2009.12.17 18:22 신고 댓글주소 | 수정 | 삭제 | 댓글

    앗.. 불과 며칠전에 공부했던 내용이군요!
    다시 복습하는 의미로 읽었더니 머리에 쏙쏙 들어오네요. 감사합니다!!

  7. 쭈욱 2010.02.18 14:28 신고 댓글주소 | 수정 | 삭제 | 댓글

    감사합니다. 블로그 통해서 많이 배우고 있습니다.
    바쁘시겠지만 한가지만 여쭤볼께요.
    API 중 MapViewOfFile API를 코드패치 방식으로 후킹하려고 했는데...
    5바이트에 JMP XXXX 쓰는 부분에서 항상 죽네요.
    vs8.0 디버거로 돌려서 직접 디버깅해보면 정상작동하구요
    직접 디버깅시 잘 되는걸로 봐서 사용자 권한 문제같은데요.
    방법이 없을까요??

    • reversecore 2010.02.19 23:46 신고 댓글주소 | 수정 | 삭제

      쭈욱님, 안녕하세요.

      제 글을 읽어보니 매우 중요한 "주의사항" 이 빠졌네요.

      바로 쭈욱님이 질문하신 내용인데요...

      코드 패치 방법은 말그대로 프로세스 메모리 공간에 매핑된 DLL 의 코드를 직접 수정하는 것입니다.

      만약 프로세스의 다른 스레드에서 어떤 함수를 read/write 하고 있을때 코드 패치를 시도하면 어떻게 될까요? 바로 에러 납니다.

      이 문제가 아니라면 해당 파일을 저에게 보내주시면 제가 살펴보겠습니다.

      확장자를 exex 또는 압축하셔서 zipx 등으로 변경해서 첨부해 주세요~

      감사합니다.

  8. 쭈욱 2010.02.22 16:11 신고 댓글주소 | 수정 | 삭제 | 댓글

    답변 감사드립니다. 말씀해주신 내용 잘 봤습니다.
    그래서 MapViewOfFile API는 IAT 후킹하는 방식으로 바꾸니까 잘 됩니다.
    다시 한번 감사드립니다.

    • reversecore 2010.02.23 01:52 신고 댓글주소 | 수정 | 삭제

      쭈욱님, 안녕하세요.

      다행히 IAT 후킹 방식을 잘 해결 하셨군요.

      나중에라도 코드 패치 방법을 연습삼아 시험해 보시기 바랍니다.

      감사합니다. ^^

  9. 울터치 2010.04.08 03:37 신고 댓글주소 | 수정 | 삭제 | 댓글

    아....제가 질문란에 올렸던 질문이 여기서 해결되는군요.......

    사실 여기있는 모든 글들을 저장해서 두세번 보았는데 볼때마다 새롭네요 ㅡ.ㅜ

    제가 후킹하려던 함수가 dll이 동적 로딩해서 IAT에 보이지 않았다는 사실을 뒤늦게 깨달았습니다..

    이제 코드패치로 도전해보려고 하는데요 아 정말 주옥같은글 너무 감사합니다

    도전 ㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱ^^

    • reversecore 2010.04.08 23:01 신고 댓글주소 | 수정 | 삭제

      울터치님, 안녕하세요.

      아, 벌써 문제의 원인을 파악하셨군요.

      궁금하신 점 있으시면 질문 올려주세요~

      감사합니다.

  10. 알려주세요 2012.06.27 21:40 신고 댓글주소 | 수정 | 삭제 | 댓글

    강의 잘보고 있습니다! 그런데 한가지 궁금점이 있습니다.

    CALL hook()을 호출하면 ZwQuerySystemInformation의 첫 5byte가 다시 JMP문으로 바뀔텐데

    이걸 하는 이유가 나중에 또 호출할 일이 있을때를 대비하여 미리 만들어 놓는 건가요?

    그럼 뒤에 나오는 수동으로 JMP XXXX의 XXXX를 구하는 방법은 처음 1번만 해주면 그 외에는

    hook()함수로 자동으로 JMP XXXX로 설정되는건가여?

  11. 게시물이 안보여요 2012.09.27 10:45 신고 댓글주소 | 수정 | 삭제 | 댓글

    API Hooking - 계산기, 한글을 배우다 의 글이 갑자기 안보이는데,
    그 뿐만 아니라 상당수 들이 많이 안보여요ㅠㅠ 무슨일이 있나요?

  12. 멍멍이 2013.10.22 13:58 신고 댓글주소 | 수정 | 삭제 | 댓글

    감사합니다.

  13. 무료 2017.02.13 05:43 댓글주소 | 수정 | 삭제 | 댓글

    관리자의 승인을 기다리고 있는 댓글입니다

  14. 무료 2017.04.02 04:48 댓글주소 | 수정 | 삭제 | 댓글

    관리자의 승인을 기다리고 있는 댓글입니다





티스토리 툴바