종종 프로세스의 메모리를 덤프해야될때가 있는데, 이 프로그램은 특정 프로세스의 COMMIT && READ 가능한 영역은 모두다 찾아서 덤프해줍니다. 참고로 유저모드 프로그램이며, 커널 영역으로는 내려가지 않습니다.
(커널에서 후킹이 이루어지는 프로세스인 경우, (ex. 자가보호)  덤프가 안될수 있음)
잘못된 점이 있으면 알려주세요 !





#include <stdio.h>
#include <Windows.h>
#include <Tlhelp32.h>
#include <tchar.h>


//to set super privilege for current process
VOID SetSuperPrivilege(IN LPCTSTR PrivilegeName)
{
HANDLE hToken = NULL;
TOKEN_PRIVILEGES tp;

//get token handle from current process
if ( !OpenProcessToken( 
GetCurrentProcess(), 
TOKEN_ADJUST_PRIVILEGES, 
&hToken)
) {
_tprintf(_T("OPToken error. %d\n"), GetLastError());
return;
}
__try {


//query debug privilege
if ( !LookupPrivilegeValue(
NULL, 
PrivilegeName, 
&tp.Privileges[0].Luid)
) {
_tprintf(_T("LUPPrivilege error. %d\n"), GetLastError());
return;
}


tp.PrivilegeCount = 1; //must be 1
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; //privilege enabled


//adjust token privilege
if ( !AdjustTokenPrivileges(
hToken, 
FALSE, 
&tp, 
sizeof(TOKEN_PRIVILEGES), 
NULL, 
NULL)
) {
_tprintf(_T("ADJUPrivilege error. %d\n"), GetLastError());
return;
}

}
__finally {
CloseHandle(hToken);
}
}


//Enum process list on display
BOOL EnumerateProcessList(VOID)
{
HANDLE hSnapShot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe32;

//get a snapshot handle
hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if ( hSnapShot == INVALID_HANDLE_VALUE ) {
_tprintf(_T("CTHelp32 error. %d\n"), GetLastError());
return FALSE;
}

__try {

//initialize PROCESSENTRY structure
ZeroMemory(&pe32, sizeof(PROCESSENTRY32));
pe32.dwSize = sizeof(PROCESSENTRY32);

//call first
if ( !Process32First(hSnapShot, &pe32) ) {
_tprintf(_T("PE32First error. %d\n"), GetLastError());
return FALSE;
}

//call next until ERROR_FILE_NOT_FOUND or failed to call next
while( Process32Next(hSnapShot, &pe32) ) {

if ( GetLastError() == ERROR_FILE_NOT_FOUND ) {
break;
}

//Enum process list on display
_tprintf(
_T("%-30s\t\t%-4d\n"), 
pe32.szExeFile, 
pe32.th32ProcessID
);

}

return TRUE;

}
__finally {
CloseHandle(hSnapShot);
}

}

//dump memory which is on target process
VOID DumpAllMemory(IN HANDLE hProcess)
{
HANDLE hFile;
LPVOID lpMem, lpBuffer;
DWORD NumberofBytesWritten;
TCHAR szFileName[MAX_PATH];
SYSTEM_INFO si;
MEMORY_BASIC_INFORMATION mbi;

if ( hProcess == NULL ) {
_tprintf(_T("hProcess is an invalid value.\n"));
return;
}


/* Get maximum address range from system info */
GetSystemInfo(&si);
//initialize MBI
ZeroMemory(&mbi, sizeof(MEMORY_BASIC_INFORMATION));
lpMem = NULL;

while (lpMem < si.lpMaximumApplicationAddress) {
if ( !VirtualQueryEx(
hProcess, 
lpMem, 
&mbi, 
sizeof(MEMORY_BASIC_INFORMATION)) 
) {

_tprintf(_T("VQuery failed. %d\n"), GetLastError());
CloseHandle(hProcess);
return;

}

/* Increase lpMem to next region of memory */
lpMem = (PVOID)( ( (DWORD_PTR)mbi.BaseAddress + 
(DWORD_PTR)mbi.RegionSize) );


if ( (mbi.BaseAddress != NULL) &&
(mbi.State == MEM_COMMIT) ) {


switch (mbi.Protect)
{
case PAGE_EXECUTE_READ:
case PAGE_EXECUTE_READWRITE:
case PAGE_EXECUTE_WRITECOPY:
case PAGE_READONLY:
case PAGE_READWRITE:
case PAGE_WRITECOPY:

//allocate temp buffer to save data
lpBuffer = VirtualAlloc(
NULL, 
mbi.RegionSize, 
MEM_COMMIT, 
PAGE_READWRITE
);

//read data from target process's memory, data is gonna be saved into lpBuffer
if ( !ReadProcessMemory(
hProcess, 
mbi.BaseAddress, 
lpBuffer, 
mbi.RegionSize, 
NULL) 
) {
_tprintf(_T("ReadMemory failed. %d\n"), GetLastError());
VirtualFree(lpBuffer, mbi.RegionSize, MEM_FREE);
break;
}
//generate file name
_stprintf_s(
szFileName, 
sizeof(szFileName), 
_T("%X - %X.dmp"), 
mbi.BaseAddress, 
((DWORD_PTR)mbi.BaseAddress + (DWORD_PTR)mbi.RegionSize)
);

//create file
hFile = CreateFile(
szFileName, 
GENERIC_WRITE, 
0, 
NULL, 
CREATE_ALWAYS, 
FILE_ATTRIBUTE_NORMAL, 
NULL
);

//write data into file
if ( WriteFile(
hFile, 
lpBuffer, 
(DWORD_PTR)mbi.RegionSize, 
&NumberofBytesWritten, 
NULL) 
) {
_tprintf(_T("Dumped successfully : %s\n"), szFileName);
} else _tprintf(_T("failed to dump : %s,  %x\n"), szFileName, GetLastError());

//return resources
VirtualFree(lpBuffer, mbi.RegionSize, MEM_FREE);
CloseHandle(hFile);
break;

default:
break;
}


}


}

CloseHandle(hProcess);
return;
}


int _tmain( int argc, TCHAR* argv[] )
{
DWORD target_pid;

//obtain super privilege
SetSuperPrivilege(SE_DEBUG_NAME);

//enum process list on display
if ( !EnumerateProcessList() ) {
return EXIT_FAILURE;
}

//input target PID
_tprintf(_T("\n\n ### Choose target PID : "));
_tscanf(_T("%d"), &target_pid);

//dump
DumpAllMemory ( 
OpenProcess(
PROCESS_ALL_ACCESS, 
FALSE, 
target_pid
);

//end
system("pause");
return EXIT_SUCCESS;
}
저작자 표시 비영리 변경 금지
신고
by Sone 2012.01.19 02:44
  이미 많은 드라이버 개발자분들께서  잘 아시다시피, 프로세스와 쓰레드의 생성과 종료 감시를 위해서 , 커널단에서 NotifyRoutine을 등록함으로써, 적절한 처리를 수행할 수 있다. (콜백루틴은 그 종류가 여러가지가 있지만 여기서는 Process와 Thread만 살펴보기로 한다.) 그런데, 필자도 이때까지 사용해오면서 정확히 어느 시점에 NotifyRoutine이 호출되는지는 모르고, 그냥 생성과 종료시점에 호출이 되는구나 정도로만 이해하고 있었다.

  어느날 VS2010을 열어놓고 키보드를 갈기고 있던중, PsSetCreateProcessNotifyRoutine, PsSetThreadNotifyRoutine 에 의하여 등록된 콜백루틴은 대체 커널의 어느부분에서 최초로 호출이 이루어질까? 라는 생각이 들어서, 디버깅을 해보기로 했다. 디버깅을 위해서 코드를 아주 씸플하게 작성했다.

VOID
ProcessNotifyRoutine
(
IN HANDLE  ParentId,
IN HANDLE  ProcessId,
IN BOOLEAN  Create
)
{
__asm int 3h //breakpoint exception for debugging

return;
}

당연히 디버거가 커널에 붙어있는 상태여야하며 , 디버거없이 냅다 로드시켰다간 프로세스의 생성 또는 종료시점에 공포의 BSOD를 보게 될것이다.
이제 드라이버를 로드시키고 , 프로세스를 하나 살며시 띄워서 Breakpoint가 걸리게끔 유도 해주었다.

 Break instruction exception - code 80000003 (first chance)
h00kdrlv3r!ProcessNotifyRoutine+0x2:
f8bc098c cc              int     3

이제 현재 상태에서 스택을 살펴보자.

 0: kd> kb
ChildEBP RetAddr  Args to Child              
b21e6b80 805d24e5 000006b4 00000cf0 00000001 h00kdrlv3r!ProcessNotifyRoutine+0x2 [d:\program\h00kdrlv3r\h00kdrlv3r\process.c @ 21]
b21e6cc4 805d3144 0007d934 001f03ff 00000000 nt!PspCreateThread+0x3a7
b21e6d3c 8054367c 0007d934 001f03ff 00000000 nt!NtCreateThread+0xfc
b21e6d3c 7c93e514 0007d934 001f03ff 00000000 nt!KiFastCallEntry+0xfc
0007d560 7c93d1ba 7c7e9ea7 0007d934 001f03ff ntdll!KiFastSystemCallRet
0007d5a8 7c9446f9 0007d6b4 0007d8a0 00000004 ntdll!ZwCreateThread+0xc
0007dfb0 7c7d2362 00000000 001654ac 00163214 ntdll!RtlIntegerToUnicode+0x11d

ZwCreateThread가 호출이되고 , KiFastCallEntry를 통과해서 , NtCreateThread, PspCreateThread를 호출한다음 드라이버 영역이 호출되었다. 그렇다면 PspCreateThread 내부에서 콜백루틴을 호출했다는건데, PspCreateThread 내부를 간단히 살펴보기로 했다.

 0: kd> uf /c nt!PspCreateThread
nt!PspCreateThread (805d213e)
 ........................
  nt!PspCreateThread+0x386 (805d24c4):
    call to nt!ExReferenceCallBackBlock (8060f514)
  nt!PspCreateThread+0x392 (805d24d0):
    call to nt!ExGetCallBackBlockRoutine (805a1e54)
  nt!PspCreateThread+0x3a5 (805d24e3):
    unresolvable call: call    eax
  nt!PspCreateThread+0x3ab (805d24e9):
    call to nt!ExDereferenceCallBackBlock (8060f616)
  nt!PspCreateThread+0x3ef (805d252d):
  ..............................

PspCreateThread 함수의 규모가 생각보다 상당히 커서, 앞뒤부분은 짜르고 PspCreateThread+0x3a7 부근만 살펴보기로 했다.
call eax  를 보니 특별히 어떤 심볼을 가진 함수나 Import 함수를 내부적으로 호출하진 않고, 바로 PspCreateThread 내부에서 함수포인터를 가져와서 호출하는 방식이다. 그런데 MSDN에서 살펴보니 PsSetCreateProcessNotifyRoutine  API는  Windows 2000에서도 사용이 가능하다고 한다. 그렇다면 예전에 유출되었던 윈도우2K 소스코드에서 PspCreateThread를 살펴보면 어느시점에 호출되는지 어느정도 정확히 알아낼 수 있을것 같다. (윈도우 NT4 소스코드에서도 확인은 가능하다.)

 PspCreateThread의 소스코드는  "\win2k\private\ntos\ps\create.c" 에서 확인할 수 있다.
먼저 PspCreateThread의 형태를 살펴보자.
 

NTSTATUS
PspCreateThread(
    OUT PHANDLE ThreadHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN HANDLE ProcessHandle,
    IN PEPROCESS ProcessPointer,
    OUT PCLIENT_ID ClientId OPTIONAL,
    IN PCONTEXT ThreadContext OPTIONAL,
    IN PINITIAL_TEB InitialTeb OPTIONAL,
    IN BOOLEAN CreateSuspended,
    IN PKSTART_ROUTINE StartRoutine OPTIONAL,
    IN PVOID StartContext
    ) ;
/*++

Routine Description:

    This routine creates and initializes a thread object. It implements the
    foundation for NtCreateThread and for PsCreateSystemThread.

Arguments:  ....

++*/

파라메터가 엄청 많다. 그래도 파라메터 하나하나를 살펴보면 그다지 어렵지는 않음을 확인할 수 있다. 
프로세스 생성과정에서 Initialize Thread를 생성하는데, 그때 내부적으로 PspCreateThread가 호출되는것으로 생각된다.
이제 핵심부만 간단히 살펴보자.



.................

   //
     // If the process does not have its part of the client id, then
    // assign one
    //

    if ( !Process->UniqueProcessId ) {       //Unique한 PID가 지정되어져 있지않다면  1
        CidEntry.Object = Process;
        CidEntry.Attributes = 0;
        Process->UniqueProcessId = ExCreateHandle(PspCidTable,&CidEntry);   //PID를 지정해주고 2
        ExSetHandleTableOwner( Process->ObjectTable, Process->UniqueProcessId );
        if (!Process->UniqueProcessId) {
            PsUnlockProcess(Process);

            MmDeleteKernelStack(KernelStack, FALSE);
            ObDereferenceObject(Process);
            ObDereferenceObject(Thread);

            return STATUS_UNSUCCESSFUL;
        }

        if (PspCreateProcessNotifyRoutineCount != 0) {     //Callback Routine이 하나이상 등록되있다면 3
            ULONG i;

            for (i=0; i<PSP_MAX_CREATE_PROCESS_NOTIFY; i++) {
                if (PspCreateProcessNotifyRoutine[i] != NULL) {
                    (*PspCreateProcessNotifyRoutine[i])( Process->InheritedFromUniqueProcessId,
                                                         Process->UniqueProcessId,
                                                         TRUE
                                                       );
                }
            }
        }
    } 

.......
 
자, 그럼 좀더 자세히 살펴보자.

 1. 디스크립션을 보니  프로세스가 PID를 가지고 있지 않으면, 하나를 할당한다고 되어져 있다. IF문 스코프를 가만히 살펴보니 이 조건에 걸리지 않으면 Process Notiry Routine도 호출되지 않는다.  이 조건에 해당되는 경우를 가만히 생각해보면, 프로세스가 생성될때라는것을 알수 있다. 즉, Unique PID가 없는 시점은 , 프로세스가 처음 생성될때의 시점이며, 만약에 프로세스가 이미 생성되어져 있었다면 UniquePID는 최초 호출된 PspCreateThread에 의해서 이미 지정되어져 있었을 것이다. 그러면 Process Callback Routine이 호출되지 않게 된다. 결과적으로 Process Callback Routine은 프로세스가 처음에 생성될때만 호출될 것이다.  다른 말로 표현하면 Process의 Initialize Thread를 생성하는 시점이고,  이때는 EPROCESS에  PID가 Assign 되어져 있지 않기 때문에,  고유 PID 지정을 PspCreateThread 내부에서 처리해주면서 Process Callback도 호출하는것이다. (자세한것은 WinDBG에서 라이브디버깅으로 적절한 위치에 BP를 걸어두고 EPROCESS를 확인하면 되겠다.)
PID 지정은 PspCreateProcess 등에서 처리할줄 알았는데 의외다.

2.  PID가 없다면 ExCreateHandle을 호출하여  Unique한 PID를 지정해준다. 

3. 그 후에 Process Callback Routine이 하나이상 배열에 존재한다면 배열을 인덱스의 처음부터 시작해서 등록된 순서대로 호출한다. 

그럼 만약 이미 생성되어져 있는 프로세스의 EPROCESS->UniquePID를 NULL로 지정해버리고, 그 후에 CreateThread를 이용해서 쓰레드를 생성해버리면 어떻게 될까?  과연 이미 생성되어져 있는 프로세스 내부에서 CreateThread를 호출했을 경우, 쓰레드가 생성될때마다 Process Callback Routine이 호출될것인가?  이 내용은 다음에....-_-;;; (현재는 귀차니즘이 발동해서)
 



다음으로 Thread Notify Routine을 살펴보자.  이건 간단하다.
본문상에는 서술하지 않았지만 , 코드를 살펴보면 Process Callback이 호출된 다음, 컨텍스트와 StartRoutine을 초기화 해주고KiInitializeThread 를 호출하고 난뒤,  Thread Ready List에 집어넣기전에, Thread Callback Routine을 호출하는 부분이 존재한다.

     //
    // Notify registered callout routines of thread creation.
    //

    if (PspCreateThreadNotifyRoutineCount != 0) {
        ULONG i;

        for (i=0; i<PSP_MAX_CREATE_THREAD_NOTIFY; i++) {
            if (PspCreateThreadNotifyRoutine[i] != NULL) {
                (*PspCreateThreadNotifyRoutine[i])( Thread->Cid.UniqueProcess,
                                                    Thread->Cid.UniqueThread,
                                                    TRUE
                                                   );
            }
        }
    }

그냥 특별한 다른 조건은 존재하지 않고, Thread Notify Routine이 하나이상 등록되어져 있다면 , Thread Callback을 호출한다.

결과적으로 유저가 만약에  Process와 Thread 콜백을 둘다 등록해놓았을 경우, 프로세스가 처음에 생성될때는 Process,Thread 둘다 호출이 될것이다.



ps. Terminate 될때의 호출시점은 다루지 않았는데, 그것은 다음에.... 
저작자 표시 비영리 변경 금지
신고
by Sone 2011.05.21 00:09

Windows Native API 의 이름을 잘 살펴보면,  Ps , Ke,  Ki , Ob 등이 붙는것을 알수있다.
커널 API를 처음 접해보는사람은 도대체 무슨 의미일까...한번쯤은 생각해보기 마련인데,
MSDN에서 이에 관한 정보를 찾아볼 수 있었다.

아래는 MSDN에서 퍼온 내용이다.

Windows Driver Kit: Kernel-Mode Driver Architecture
What Does the Zw Prefix Mean? (Zw 접두어의 의미는 무엇인가?)

The Windows native system services routines have names that begin with the prefixes Nt and Zw. The Nt prefix is an abbreviation of Windows NT, but the Zw prefix has no meaning. Zw was selected partly to avoid potential naming conflicts with other APIs, and partly to avoid using any potentially useful two-letter prefixes that might be needed in the future.

> 윈도우 네이티브 시스템 서비스 루틴은  Nt , Zw  로 시작하는 이름을 가지고있다.
Nt 접두어는 Windows NT를 간단히 줄여서 말하는것이다.  그러나 Zw 접두어는  의미를 가지지 않는다.
Zw는 단지  다른 API들과의 잠재적인 Naming 혼란을 피하기 위하기 위해서 선택된것이다.
그리고 한편으로는 , 훗날 잠재적으로 유용한 2글자 접두어의 사용을 피하기 위해서(?)......

마지막 번역이 뭔가 좀 이상하긴 한데 , 어쨌든 여기까지의 결론은  Zw는  의미가 없다   라고 볼수있겠다.



Many of the Windows driver support routines have names that begin with two- or three-letter prefixes. These prefixes indicate which kernel-mode system components implement the routines. The following table contains some examples.

윈도우 드라이버를 지원하는 여러 함수들은  2글자 혹은 3글자의 접두어를 가지고 있다.
이러한 접두어들은  커널모드의 여러가지 시스템 구성요소들을 의미하는 용도로 사용된다.
다음은 여러가지 예이다.

Prefix Kernel component Example routine
Cm Configuration manager CmRegisterCallbackEx
Ex Executive ExAllocatePool
Hal Hardware abstraction layer HalGetAdapter
Io I/O manager IoAllocateIrp
Ke Kernel core KeSetEvent
Mm Memory manager MmCreateMdl
Ob Object manager ObReferenceObject
Po Power manager PoSetPowerState
Tm Transaction manager TmCommitTransaction
Nt and Zw Native system services NtCreateFile and ZwCreateFile

저작자 표시 비영리 변경 금지
신고
by Sone 2009.09.26 21:43
| 1 |