멍~~ 때리면서 Process Explorer를 보고 있었습니다. 프로세스를 죽이고 살리고 하는 과정에서 저.. PID라는 놈은 어떻게 결정되는거지? 라는 의문이 들더군요. 그냥 랜덤으로 돌리나?.. -.-; 그러진 않겠죠.. 중복도 안생기게끔 해야되고 흠..


계산기를 순식간에 수십개를 팍 띄워봤습니다.



딱히 무슨 규칙같은건 모르겠군요.


주말이니 쫌 시간이 남아 직접 분석해보기로 했습니다.

접근 방법은 ?! 어떻게 접근할지 살짝 고민했는데요. 


유저에서 프로세스를 생성하기 위해 CreateProcessW 함수를 호출합니다. 이때 

  __out        LPPROCESS_INFORMATION lpProcessInformation

마지막 인자로 PROCESS_INFORMATION 구조체 변수의 주소를 넘겨줍니다.

함수 호출이 성공적으로 끝나면 해당 구조체에 값이 적절히 채워지는데요.

typedef struct _PROCESS_INFORMATION {

  HANDLE hProcess;

  HANDLE hThread;

  DWORD  dwProcessId;

  DWORD  dwThreadId;

} PROCESS_INFORMATION, *LPPROCESS_INFORMATION;

해당 구조체입니다. 3번째 멤버변수에 PID를 리턴 받습니다.


그러면 저 세번째 변수에 값이 쓰여지는 시점에 하드웨어 BP를 걸고 역추적해나가면됩니다.

여러군데에서 값을 참조하고 뭐 하기 때문에 추적하는데 시간이 쫌 걸렸지만 그 부분은 생략 ㅠㅠ

유저디버거론 할 수 없고 WinDbg를 사용해 커널 디버깅 상태에서 접근을 해나가야합니다. :-)


그래서 추적해본 결과입니다.


유저에서 CreateProcess 함수가 호출되면 ntdll.dll의 ZwCreateUserProcess 함수가 호출됩니다. 이는 시스템 콜로 이어지게되며 커널의 NtCreateUserProcess 함수가 호출되겠지요.


NtCreateUserProcess에서는 당연히 프로세스를 생성하기 위해서 EPROCESS 구조체를 만들고 초기화 하는 부분이 있을 것입니다. NtCreateUserProcess 내부를 보면 

8306f2c4 8d85d0f9ffff    lea     eax,[ebp-630h]                            // EPROCESS 구조체 포인터가 리턴되는 위치

8306f2ca 50              push    eax

8306f2cb 8d8598f9ffff    lea     eax,[ebp-668h]

8306f2d1 50              push    eax

8306f2d2 ff7520          push    dword ptr [ebp+20h]

8306f2d5 ffb574ffffff    push    dword ptr [ebp-8Ch]

8306f2db ff758c          push    dword ptr [ebp-74h]

8306f2de ffb5a0f9ffff    push    dword ptr [ebp-660h]

8306f2e4 ffb5ccf9ffff    push    dword ptr [ebp-634h]

8306f2ea 8d8d20ffffff    lea     ecx,[ebp-0E0h]

8306f2f0 8b95bcf9ffff    mov     edx,dword ptr [ebp-644h]

8306f2f6 e868880100      call    nt!PspAllocateProcess (83087b63)


이런 부분이 있는데요. PspAllocateProcess 함수에서 EPROCESS 구조체를 생성하고 초기화를 하게됩니다.

PspAllocateProcess 내부를 들어가보죠.

83088888     lea     edi,[ebx+0B4h]                     // [ebx+0B4h] 는 EPROCESS . UniqueProcessId 나타냄.

8308888e     lea     eax,[ebp-0B8h]

83088894     push    eax

83088895     push    dword ptr [nt!PspCidTable (82f59eb4)]    // [PspCidTable]은 HANDLE_TABLE 구조체 포인터

8308889b     call    nt!ExCreateHandle (83055c16) // PID를 리턴 함.

830888a0     mov     dword ptr [edi],eax


내부에 위와 같은 코드가 존재하는데요. ExCreateHandle 함수가 사용되고 있고 2개의 인자가 사용되고있습니다. 첫 번째 변수는 PspCidTable 인데 이는 프로세스 관련 커널 변수이고 HANDLE_TABLE 구조체를 가리키는 포인터 변수입니다. 조금 더 자세히 설명하면 프로세스와 스레드 ID에 해당되는 핸들 테이블의 포인터이죠. 일반적인 EPROCESS 구조체 내부에 있는 HANDLE_TABLE 변수와는 다릅니다. [PspCidTable] 값을 봐보겠습니다.

0: kd> ln PspCidTable

(82f59eb4)   nt!PspCidTable   |  (82f59eb8)   nt!PopAlpcMonitorClientPort

Exact matches:

    nt!PspCidTable = <no type information>


0: kd> dd 82f59eb4 L1

82f59eb4  88e01118


0: kd> dt _HANDLE_TABLE 88e01118

ntdll!_HANDLE_TABLE

   +0x000 TableCode        : 0x94cd2001

   +0x004 QuotaProcess     : (null)

   +0x008 UniqueProcessId  : (null) 

   +0x00c HandleLock       : _EX_PUSH_LOCK

   +0x010 HandleTableList  : _LIST_ENTRY [ 0x88e01128 - 0x88e01128 ]

   +0x018 HandleContentionEvent : _EX_PUSH_LOCK

   +0x01c DebugInfo        : (null) 

   +0x020 ExtraInfoPages   : 0n0

   +0x024 Flags            : 1

   +0x024 StrictFIFO       : 0y1

   +0x028 FirstFreeHandle  : 0x280

   +0x02c LastFreeHandleEntry : 0x94ab43d0 _HANDLE_TABLE_ENTRY

   +0x030 HandleCount      : 0x23a

   +0x034 NextHandleNeedingPool : 0x1000

   +0x038 HandleCountHighWatermark : 0x2c3


하나만 설명하면 보통 QuotaProcess 에는 해당 HANDLE_TABLE 구조체를 가지고 있는 EPROCESS 구조체 포인터 값이 들어있습니다. 하지만 위 값을 보면 NULL 이므로 이는 시스템을 뜻한다고 합니다.


이렇게 생겼다는 것만 알아두고 계속 진행하겠습니다.


ExCreateHandle 함수 내부입니다.

83055c1d 8b5d08          mov     ebx,dword ptr [ebp+8] // [PspCidTable]

83055c20 83650800        and     dword ptr [ebp+8],0

83055c24 56              push    esi

83055c25 8d4508          lea     eax,[ebp+8]

83055c28 50              push    eax

83055c29 8bf3            mov     esi,ebx

83055c2b e881130000      call    nt!ExpAllocateHandleTableEntry (83056fb1) // 스택에 들어간 인자엔 PID 리턴됨

83055c30 8bf0            mov     esi,eax

83055c32 85f6            test    esi,esi


ExpAllocateHandleTableEntry 함수를 내부적으로 호출하고 있는데요. 호출 전에 보면 esi에는 [PspCidTable] 이 값을 넣고 스택에는 0으로 초기화된 포인터 값을 넣었습니다. 저 함수가 성공적으로 호출되면 스택에 들어간 변수에는 PID 값이 들어있게 됩니다. 이제 저 함수를 마지막으로 들어가보면 알 수 있겠군요.


ExpAllocateHandleTableEntry 함수에서 PID 관련 중요한 부분만 코드를 뽑아봤습니다.

...

83056fdc 8b5e28          mov     ebx,dword ptr [esi+28h]

...

8305705b 8b4508          mov     eax,dword ptr [ebp+8]

8305705e 8918            mov     dword ptr [eax],ebx


esi는 HANDLE_TABLE 구조체이고 +28h를 합니다. 그 값을 [ebp+8]에 넣으므로 일단 결론을 내리면!

프로세스가 생성될 때 PID가 결정되는 부분은 프로세스 생성 당시 PspCidTable 이 가리키고 있는

HANDLE_TABLE.FirstFreeHandle 값이었습니다.


이제 하나 더 남았습니다. HANDLE_TABLE 구조체의 FirstFreeHandle 값은 뭘까요? 이걸 알아봐야겠군요.

찾아보니 HANDLE_TABLE의 FirstFreeHandle 값은 HandleTable에서 가장 처음으로 사용가능한 Entry 주소이며 이 값이 다음으로 생성될 HANDLE 값이라고 합니다.


저 FirstFreeHandle 값이 변하는 위치를 봐보면 알 수 있겠군요. 바로 그 위치는 ...!

역시나 ExpAllocateHandleTableEntry 함수 내부에 존재합니다.

nt!ExpAllocateHandleTableEntry+0x2b:

83056fdc 8b5e28          mov     ebx,dword ptr [esi+28h]                // 이 값이 현재 생성되는 프로세스의 PID 값임.

83056fdf 85db            test    ebx,ebx

83056fe1 7510            jne     nt!ExpAllocateHandleTableEntry+0x42 (83056ff3)

...

83056ff3 53              push    ebx

83056ff4 8bce            mov     ecx,esi

83056ff6 e87a2cfeff      call    nt!ExpLookupHandleTableEntry (83039c75)   // 이놈이 리턴하는 주소+4 위치에 PID 존재

83056ffb 8945f8          mov     dword ptr [ebp-8],eax                    // HANDLE_TABLE_ENTRY

83056ffe 8b4004          mov     eax,dword ptr [eax+4]                    // HANDLE_TABLE_ENTRY . NextFreeTableEntry


// HANDLE_TABLE . FirstFreeHandle = HANDLE_TABLE_ENTRY . NextFreeTableEntry

83057001 894628          mov     dword ptr [esi+28h],eax   

83057004 85c0            test    eax,eax

83057006 7503            jne     nt!ExpAllocateHandleTableEntry+0x5a (8305700b)


ExpLookupHandleTableEntry 함수에서 다음 PID가 결정됩니다.

함수 인자로 현재 설정된 PID를 넘기고 있습니다.

내부적으로 현재 설정된 PID를 지지고 볶고 하면서 다음 생성될 PID 값을 만들겠지요.


결론을 내면..


EPROCESS 구조체를 초기화하는데 사용되는 함수인 PspAllocateProcess 

PspAllocateProcess -> ExCreateHandle ->ExpAllocateHandleTableEntry


ExpAllocateHandleTableEntry 함수에서 PspCidTable 의 FirstFreeHandle 값으로 현재 PID를 설정하고

그 아래에서 ExpLookupHandleTableEntry 함수를 호출함으로써 NextFreeHandleEntry 값으로  PspCidTable 의 FirstFreeHandle 값을 다시 설정합니다. 다음에 생성될 프로세스의 PID가 되겠지요.


사실 저 값이 어떻게 만들어지는지 알려면 ExpLookupHandleTableEntry 를 분석해야되지만.. 나중에 시간되면 해보겠습니다. 일단 react os에 나와있는 코드는 다음과 같습니다.

...

    /* Get the table code */

    TableBase = *(volatile ULONG_PTR*)&HandleTable->TableCode;


    /* Extract the table level and actual table base */

    TableLevel = (ULONG)(TableBase & 3);

    TableBase = TableBase - TableLevel;

...

        case 1:


            /* Get the second table and index into it */

            Level2 = (PUCHAR)TableBase;

            i = Handle.Value % SizeOfHandle(LOW_LEVEL_ENTRIES);


            /* Substract this index, and get the next one */

            Handle.Value -= i;

            j = Handle.Value /

                (SizeOfHandle(LOW_LEVEL_ENTRIES) / sizeof(PHANDLE_TABLE_ENTRY));


            /* Now get the next table and get the entry from it */

            Level1 = (PUCHAR)*(PHANDLE_TABLE_ENTRY*)&Level2[j];

            Entry = (PVOID)&Level1[i *

                                   (sizeof(HANDLE_TABLE_ENTRY) /

                                    SizeOfHandle(1))];

            break;

...


중요 부분만..!


대충 분석해보니 Windows 7의  ExpLookupHandleTableEntry 와 비슷한것 같은데 정확한건 분석을 해봐야..!

참고로 ExpLookupHandleTableEntry 내부적으로 HANDLE_TABLE . NextHandleNeedingPool(0x1000) 과 나누는 부분이 있는걸로 보아 Windows 7 x86 에선 pid가 4096을 넘는일은 없을 것입니다. x64에서는 pid가 4096을 넘는걸로보아 NextHandleNeedingPool 값이 0x1000이 아니거나 다른 메커니즘을 가지고 있을 수도 있겠군요. 이것도 시간나면.. 


지금까지 프로세스가 생성될 때 PID가 어디서 만들어지고 어떻게 생성되는지(이건 쫌 아니지만) 알아보았는데요.

분석하다보니 루트킷 작성할 때 써먹을 수 있는 부분이 몇 군데 있는것 같더군요. 내 프로세스 보호할 때 말이죠 ^^

실제로 Avast같은 경우도 HANDLE_TABLE을 조작해 OpenProcess를 못하게 막아놨는데요. 다른 활용법도 많을 것 같습니다.

Posted by Ezbeat