Podczas tworzenia nowego procesu system tworzy odpowiedni obiekt jądra, o którym już na tym blogu pisałem (w kontekście żetonu zabezpieczeń, ukrywania procesów, czy też ochrony procesu). Przy okazji ukrywania procesów wspominałem o liście aktywnych procesów, która wykorzystywana jest głównie do celów statystycznych, jednak nie napisałem, jak procesy dołączane są do tej listy. Dziś spróbujemy to nadrobić.
Mark Russinovich w swojej Książce (przez ‘duże K’, żeby wszyscy wiedzieli o której mowa ;)) w rozdziale poświęconym procesom i ich tworzeniu pisze, iż nowy obiekt związany z procesem dołączany jest do listy aktywnych procesów (PsActiveProcessHead), przy użyciu funkcji PspInsertProcess. Nie ma jeszcze żadnego aktywnego wątku, ale mamy już całkiem sporo informacji o procesie, w tym ‘ImageFileName’, czyli 15-bajtową tablicę zawierającą nazwę procesu, a także przydzielony identyfikator procesu ‘UniqueProcessId’.
Korzystając z tego faktu, postanowiłem dla ćwiczenia zrobić log uruchamianych procesów na wirtualnym Windows 7 x64.
Analiza
Startujemy system i zatrzymujemy się na samym początku
kd> k
Child-SP RetAddr Call Site
fffff800`00b9cdf8 fffff800`0292117d nt!DebugService2+0x5
fffff800`00b9ce00 fffff800`02d13449 nt!DbgLoadImageSymbols+0x4d
fffff800`00b9ce50 fffff800`02ac5816 nt!KdInitSystem+0x429
fffff800`00b9cfc0 00000000`00000000 nt!KiSystemStartup+0x126
kd> lm
start end module name
fffff800`02811000 fffff800`02df9000 nt (pdb symbols) c:\websymbols\ntkrnlmp.pdb\ABD176D2C7AE41B88BBF2837A09A462C2\ntkrnlmp.pdb
Unable to enumerate kernel-mode unloaded modules, HRESULT 0x80004005
kd> x nt!PspInsertProcess
fffff800`02b36c50 nt!PspInsertProcess (<no parameter info>)
Jak widać, mamy jeden moduł i jesteśmy ‘na dzień dobry’. Plik z symbolami jest, więc możemy zakładać pułapkę.
kd> bp nt!PspInsertProcess
kd> g
Breakpoint 0 hit
nt!PspInsertProcess:
fffff800`02b36c50 4489442418 mov dword ptr [rsp+18h],r8d
Zaczynamy od określenia parametrów funkcji. Skoro jest to Windows 7 64-bit, to parametry funkcji przekazywane są przez rejestry rcx, rdx, r8, r9, a dalej stos (vide post Skywinga). Robimy zatem zrzut stosu wywołań oraz rejestrów.
kd> kb
RetAddr : Args to Child : Call Site
fffff800`02cffd72 : fffff6fb`7e280000 fffff800`00000004 fffff8a0`00005000 0000007f`ffffffff : nt!PspInsertProcess
fffff800`02db8875 : 00000000`00000002 fffff800`00812700 fffff800`00812700 00000000`00000000 : nt!PspCreateProcess+0x212
fffff800`02db8a00 : fffff800`00812700 fffff800`00812700 00000000`00000001 00000000`0000005c : nt!PspInitPhase0+0x495
fffff800`02dbd15c : fffff800`00812700 00000000`0000005c 00000000`00000100 00000000`00000006 : nt!PsInitSystem+0x40
fffff800`02ad7f73 : fffff800`00000000 fffff800`02a02e80 fffff800`00b9c8c0 00000000`00000001 : nt!InitBootProcessor+0x93c
fffff800`02ac5886 : 12131011`16171415 1a1b1819`1e1f1c1d 02030001`06070405 0a0b0809`0e0f0c0d : nt!KiInitializeKernel+0x833
00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemStartup+0x196
kd> r
rax=0000000000000000 rbx=0000000000000000 rcx=fffffa80018d0450
rdx=0000000000000000 rsi=0000000000000000 rdi=fffffa80018d0450
rip=fffff80002b36c50 rsp=fffff80000b9c258 rbp=0000000000000000
r8=00000000001fffff r9=0000000000000000 r10=0000000000000001
r11=fffff80002a88300 r12=0000000000000000 r13=0000000000000000
r14=fffff80002a10cc0 r15=0000000000000000
iopl=0 nv up ei pl zr na po nc
cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00000246
nt!PspInsertProcess:
fffff800`02b36c50 4489442418 mov dword ptr [rsp+18h],r8d ss:0018:fffff800`00b9c270=00500000
Zobaczmy co się działo z rejestrami i stosem przed wywołaniem PspInsertProcess
kd> ub nt!PspCreateProcess+0x212 L20
nt!PspCreateProcess+0x181:
fffff800`02cffce1 488d44245c lea rax,[rsp+5Ch]
fffff800`02cffce6 4889442438 mov qword ptr [rsp+38h],rax
fffff800`02cffceb 488364243000 and qword ptr [rsp+30h],0
fffff800`02cffcf1 44897c2428 mov dword ptr [rsp+28h],r15d
fffff800`02cffcf6 488b842488000000 mov rax,qword ptr [rsp+88h]
fffff800`02cffcfe 4889442420 mov qword ptr [rsp+20h],rax
fffff800`02cffd03 4c8bce mov r9,rsi
fffff800`02cffd06 4c8b442478 mov r8,qword ptr [rsp+78h]
fffff800`02cffd0b 418ad5 mov dl,r13b
fffff800`02cffd0e 488bcb mov rcx,rbx
fffff800`02cffd11 e8ca38e3ff call nt!PspAllocateProcess (fffff800`02b335e0)
fffff800`02cffd16 448be0 mov r12d,eax
fffff800`02cffd19 85c0 test eax,eax
fffff800`02cffd1b 0f88c9000000 js nt!PspCreateProcess+0x28a (fffff800`02cffdea)
fffff800`02cffd21 448be8 mov r13d,eax
fffff800`02cffd24 89442450 mov dword ptr [rsp+50h],eax
fffff800`02cffd28 b805000000 mov eax,5
fffff800`02cffd2d 8d4802 lea ecx,[rax+2]
fffff800`02cffd30 837c245c00 cmp dword ptr [rsp+5Ch],0
fffff800`02cffd35 0f45c1 cmovne eax,ecx
fffff800`02cffd38 488d8c24a0000000 lea rcx,[rsp+0A0h]
fffff800`02cffd40 48894c2438 mov qword ptr [rsp+38h],rcx
fffff800`02cffd45 89442430 mov dword ptr [rsp+30h],eax
fffff800`02cffd49 488b842480000000 mov rax,qword ptr [rsp+80h]
fffff800`02cffd51 4889442428 mov qword ptr [rsp+28h],rax
fffff800`02cffd56 897c2420 mov dword ptr [rsp+20h],edi
fffff800`02cffd5a 458bcf mov r9d,r15d
fffff800`02cffd5d 448b442458 mov r8d,dword ptr [rsp+58h]
fffff800`02cffd62 488bd3 mov rdx,rbx
fffff800`02cffd65 488b7c2468 mov rdi,qword ptr [rsp+68h]
fffff800`02cffd6a 488bcf mov rcx,rdi
fffff800`02cffd6d e8de6ee3ff call nt!PspInsertProcess (fffff800`02b36c50)
Strzelam (bo dokumentacji nie ma), że w rcx jest adres nowego procesu (a właściwie struktury _EPROCESS reprezentującej proces), natomiast czuj mi podpowiada, że w rdx znajdziemy adres procesu rodzica :) Na tym etapie jeszcze tego nie widać (rdx = 0), ale spróbujmy to zebrać do kupy i zrobić zrzut.
kd> dt nt!_EPROCESS @rcx ImageFileName UniqueProcessId
+0x180 UniqueProcessId : 0x00000000`00000004 Void
+0x2e0 ImageFileName : [15] ""
Jak się okazuje, nazwa procesu jest pusta, do tego proces macierzysty to same zera, więc nie ma sensu robić zrzutu.
Zakładam pułapkę (tu uwaga - zdecydowanie lepiej byłoby wyrzucić całe to polecenie, ładnie sformatowane do osobnego skryptu i pułapkę ustawić na skrypt, ale pozostawiam to jako proste ćwiczenie)
kd> bp nt!PspInsertProcess "r? $t0=((nt!_EPROCESS*) @rcx);r? $t1=((nt!_EPROCESS*) @rdx);as /ma ${/v:ProcName} @@c++(@$t0->ImageFileName);as /ma ${/v:PProcName} @@c++(@$t1->ImageFileName);as /x ${/v:Pid} @@c++(@$t0->UniqueProcessId);as /x ${/v:PPid} @@c++(@$t1->UniqueProcessId);.echo ${ProcName}, ${PProcName}, ${Pid}, ${PPid};g"
i robię zrzut całości. Poniżej moje wyniki, zebrane w tabelki, oczywiście kolejne pozycje w kolejności chronologicznej.
Wyniki
Process Name | Parent Process Name | Pid | Parent Pid |
0x4 | |||
smss.exe | System | 0xf4 | 0x4 |
autochk.exe | smss.exe | 0x100 | 0xf4 |
smss.exe | smss.exe | 0x148 | 0xf4 |
csrss.exe | smss.exe | 0x150 | 0x148 |
smss.exe | smss.exe | 0x17c | 0xf4 |
wininit.exe | smss.exe | 0x184 | 0x148 |
csrss.exe | smss.exe | 0x18c | 0x17c |
winlogon.exe | smss.exe | 0x1b0 | 0x17c |
services.exe | wininit.exe | 0x1f8 | 0x184 |
lsass.exe | wininit.exe | 0x200 | 0x184 |
lsm.exe | wininit.exe | 0x208 | 0x184 |
svchost.exe | services.exe | 0x264 | 0x1f8 |
svchost.exe | services.exe | 0x2b0 | 0x1f8 |
svchost.exe | services.exe | 0x2ec | 0x1f8 |
LogonUI.exe | winlogon.exe | 0x324 | 0x1b0 |
svchost.exe | services.exe | 0x350 | 0x1f8 |
svchost.exe | services.exe | 0x368 | 0x1f8 |
audiodg.exe | svchost.exe | 0x3b8 | 0x2ec |
svchost.exe | services.exe | 0x64 | 0x1f8 |
svchost.exe | services.exe | 0x1f0 | 0x1f8 |
spoolsv.exe | services.exe | 0x42c | 0x1f8 |
svchost.exe | services.exe | 0x45c | 0x1f8 |
svchost.exe | services.exe | 0x4c8 | 0x1f8 |
vmtoolsd.exe | services.exe | 0x534 | 0x1f8 |
net.exe | vmtoolsd.exe | 0x5c8 | 0x534 |
conhost.exe | csrss.exe | 0x5fc | 0x150 |
net1.exe | net.exe | 0x61c | 0x5c8 |
TPAutoConnSvc. | services.exe | 0x634 | 0x1f8 |
cmd.exe | vmtoolsd.exe | 0x6c0 | 0x534 |
conhost.exe | csrss.exe | 0x6c8 | 0x150 |
dllhost.exe | services.exe | 0x6dc | 0x1f8 |
WmiPrvSE.exe | svchost.exe | 0x708 | 0x264 |
dllhost.exe | services.exe | 0x734 | 0x1f8 |
msdtc.exe | services.exe | 0x794 | 0x1f8 |
Lista procesów uruchamianych podczas startu systemu, do momentu pojawienia się okna logowania.
Process Name | Parent Process Name | Pid | Parent Pid |
VSSVC.exe | services.exe | 0x4a4 | 0x1f8 |
dllhost.exe | svchost.exe | 0x598 | 0x264 |
taskhost.exe | services.exe | 0x6cc | 0x1f8 |
AtBroker.exe | winlogon.exe | 0x620 | 0x1b0 |
userinit.exe | winlogon.exe | 0x17c | 0x1b0 |
dwm.exe | svchost.exe | 0xfc | 0x350 |
explorer.exe | userinit.exe | 0x584 | 0x17c |
TPAutoConnect. | TPAutoConnSvc. | 0x870 | 0x634 |
conhost.exe | csrss.exe | 0x878 | 0x18c |
VMwareTray.exe | explorer.exe | 0x894 | 0x584 |
vmtoolsd.exe | explorer.exe | 0x89c | 0x584 |
SearchIndexer. | services.exe | 0x92c | 0x1f8 |
SearchProtocol | SearchIndexer. | 0x9a8 | 0x92c |
SearchFilterHo | SearchIndexer. | 0x9bc | 0x92c |
mscorsvw.exe | services.exe | 0xa78 | 0x1f8 |
mscorsvw.exe | services.exe | 0xa94 | 0x1f8 |
sppsvc.exe | services.exe | 0xabc | 0x1f8 |
svchost.exe | services.exe | 0xae0 | 0x1f8 |
WmiPrvSE.exe | svchost.exe | 0xb58 | 0x264 |
taskhost.exe | services.exe | 0x88c | 0x1f8 |
wuauclt.exe | svchost.exe | 0x858 | 0x368 |
Lista procesów uruchamianych po zalogowaniu do systemu
Process Name | Parent Process Name | Pid | Parent Pid |
LogonUI.exe | winlogon.exe | 0x698 | 0x1b0 |
LogonUI.exe | wininit.exe | 0x9a0 | 0x184 |
Lista procesów uruchamianych podczas zamykania systemu
Jako ćwiczenie pozostawiam zrzut w przypadku Windows XP, tudzież wersji 32-bit Visty, lub Windows 7. Nie ma tam problemu z przekazywaniem parametrów - wszystko idzie przez stos, a adres struktury _EPROCESS mamy w rejestrze eax. Miłej zabawy! :)