Próba stworzenia mechanizmu autoochrony (SELF-DEFENCE)
#1
Przez ostatnie kilka dni siedziałem, wykorzystywałem przycisk "Search" w wyszukiwarce Google aż nazbyt dużo razy, gdyż próbowałem znaleźć jakiś sposób, na autoochronę w C#. Jest to strasznie ciekawe zagadnienie, dlatego postanowiłem o tym napisać. Od razu mówię, post nie będzie zbyt ładny, tekst będzie dość precyzyjny.

UWAGA
Aby zakończyć proces uruchomiony z użyciem następującego kodu, wymagane są uprawnienia administratora (ring 1), a więc sposób ten nie jest całkowicie efektywny. Krótko mówiąc, działa on na takiej zasadzie - uruchamia proces z wirtualnego konta innego użytkownika, co wykorzystuje Windowsowe restrykcje do zablokowania możliwości ubicia procesu, jeśli nie ma się wystarczających uprawnień.

Wykorzystane źródła (większość msdn, trochę pinvoke (bez którego trwało by to wieki (znajdowanie DLL z danymi funkcjami w systemowym API pod C# <3)) i jeden blog, gość pisał niemal to samo co ja (znalazłem jak szukałem informacji o RawSecurityDescriptor)):
  • [Aby zobaczyć linki, zarejestruj się tutaj]

  • [Aby zobaczyć linki, zarejestruj się tutaj]

  • [Aby zobaczyć linki, zarejestruj się tutaj]

  • [Aby zobaczyć linki, zarejestruj się tutaj]

  • [Aby zobaczyć linki, zarejestruj się tutaj]

  • [Aby zobaczyć linki, zarejestruj się tutaj]

  • [Aby zobaczyć linki, zarejestruj się tutaj]


Cały kod jest dostępny tutaj: 

[Aby zobaczyć linki, zarejestruj się tutaj]


A więc prócz zadeklarowanych przestrzeni nazw, musimy pobawić się trochę z WinAPI, a szczególnie dwoma bibliotekami - advapi32.dll oraz kernel32.dll. Będziemy potrzebować tylko kilku funkcji.

Zacznijmy od advapi32 i funkcji GetKernelObjectSecurity (boolean)
Kod:
       [DllImport("advapi32.dll", SetLastError = true)]
       private static extern bool GetKernelObjectSecurity(IntPtr handle, int secInfo, [Out] byte[] sDescriptor, uint len, out uint len2);

Dalej advapi32 i funckja SetKernelObjectSecurity (boolean, analogicznie - tu set, tam get)
Kod:
       [DllImport("advapi32.dll", SetLastError = true)]
       private static extern bool SetKernelObjectSecurity(IntPtr Handle, int secInfo, [In] byte[] psd);
Tym razem pseudo uchwyt do procesu, który weźmiemy z kernel32.dll
Kod:
       [DllImport("kernel32.dll")]
       private static extern IntPtr GetCurrentProcess();
Teraz tworzymy obiekt RawSecurityDesriptor (znajdujący się w System.Security.AccessControl), będący naszym swego rodzaju getterem (będzie zbierał właściwości procesu poprzez uchwyt do niego).
Kod:
private static RawSecurityDescriptor GetProcessSecurityDescriptor(IntPtr processHandle)
       {
           const int DACL_SECURITY_INFORMATION = 0x00000004;
           byte[] psecdesc = new byte[0];
           uint BytesNeed;

           GetKernelObjectSecurity(processHandle, DACL_SECURITY_INFORMATION, psecdesc, 0, out BytesNeed);
           if (BytesNeed < 0 || BytesNeed > short.MaxValue)
               throw new Win32Exception();

           if (!GetKernelObjectSecurity(processHandle,
               DACL_SECURITY_INFORMATION,
               psecdesc = new byte[BytesNeed],
               BytesNeed, out BytesNeed)) throw new Win32Exception();
               

           return new RawSecurityDescriptor(psecdesc, 0);
       }

Co tu się dzieje? Jako argument funkcji mamy podać uchwyt do aktualnego procesu. Zgodnie z MSDN tworzymy stały typ int o wartości 0x00000004 (advice WinAPI), dalej tablicę bajtów o zerowej wielkości, i typ int bez znaku, w którym określimy potem ilość bajtów które muszą zostać sparsowane. Teraz korzystamy z naszego "gettera" -> getKernelObjSec (pierwszy omówiony dllimport) z takimi argumentami, które ta funkcja WinAPI wymaga. Wyrzuci ona nam jednak do naszego uinta BytesNeed pewną ilość, która jest nam potrzebna do poprawnego działania aplikacji. Jeżeli jest < 0 albo większe od  32767 wyrzuci ona nam wyjątek (MSDN), podobnie, jeżeli operacja "gettera" się nam nie uda (jest on typem BOOL). W przeciwnym razie (który dzieje się prawie zawsze) zostanie nam zwrócony poprawny RawSecurityDescriptor (dokładnie DACL (Discretionary Access Control List)), na którym możemy spokojnie operować.

Teraz stworzyliśmy coś w stylu gettera na DACL, teraz musimy mieć możliwość ustawienia go wedle naszych własnych preferencji.
Kod:
private static void setProcessSecurityDescriptor(IntPtr processHandle, RawSecurityDescriptor dacl) //ustawiamy DACL
       {
           const int DACL_SECURITY_INFORMATION = 0x00000004;
           byte[] rawSecurityDescriptor = new byte[dacl.BinaryLength];
           dacl.GetBinaryForm(rawSecurityDescriptor, 0);
           if (!SetKernelObjectSecurity(processHandle, DACL_SECURITY_INFORMATION, rawSecurityDescriptor)) throw new Win32Exception();
       }
W argumentach pobieramy uchwyt do naszego procesu i wcześniej wczytany RSD/DACL. Jest to zwyczajna funkcja typu void, nad którą jak myślę nie trzeba się rozwodzić.

Teraz musimy stworzyć coś w stylu listy z wartościami wykorzystywanymi przez WinAPI, a dokładniej DACL i ACE (Access Control Entries). Nadaje się do tego idealnie enum, w którym to zhardcodujemy te wartości (a w zasadzie flagi). Jak zwykle MSDN jest naszym zbawieniem

Kod:
       [Flags]
       private enum PAR //flagi dostępu do procesów --> Process Access Rights po kolei z MSDN
       {
           PROCESS_CREATE_PROCESS = 0x0080,                                        //do tworzenia procesu
           PROCESS_CREATE_THREAD = 0x0002,                                         //do tworzenia wątku
           SYNCHRONIZE = 0x00100000,                                               //synchronizacja (?)
           PROCESS_DUP_HANDLE = 0x0040,                                            //do łapania uchwytu
           PROCESS_QUERY_INFORMATION = 0x0400,                                     //do zdobywania informacji o procesie (token etc.)
           PROCESS_QUERY_LIMITED_INFORMATION = 0x1000,                             //do zdobywania innych informacji, analogiczne
           PROCESS_SET_QUOTA = 0x0100,                                             //do ingerencji w working set
           PROCESS_SET_INFORMATION = 0x0200,                                       //do ustawiania dla procesu X informacji
           WRITE_OWNER = 0x00080000,                                               //process owner - "posiadacz" procesu - chodzi o konto
           PROCESS_SUSPEND_RESUME = 0x0800,                                        //jak wskazuje nazwa, wstrzymaj lub wznów
           PROCESS_TERMINATE = 0x0001,                                             //zakończ proces
           PROCESS_VM_OPERATION = 0x0008,                                          //zawiera w sobie funkcję - WriteProcessMemory (kernel32.dll -> zawiera się w <windows.h>)
           PROCESS_VM_READ = 0x0010,                                               //do odczytywania pamięci procesu
           PROCESS_VM_WRITE = 0x0020,                                              //do nadpisywania pamięci procesu
           DELETE = 0x00010000,                                                    //do usuwania niektórych rzeczy
           READ_CONTROL = 0x00020000,                                              //kontrola odczytu          
           WRITE_DAC = 0x00040000,                                                 //advice DACL
           STANDARD_RIGHTS_REQUIRED = 0x000f0000,                                  //nie mam pojęcia jak to wyjaśnić, ale nazwa STANDARD_RIGHTS_REQUIRED powinna być wystarczająca ;)
           PROCESS_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF),  //reszta
       }
 
I w zasadzie tyle. Teraz pozostało nam tylko stworzyć nowe ACE i powiedzieć programowi, aby z nich korzystał. Robi się to mniej więcej tak (żywcem z tamtego bloga):
Kod:
           IntPtr hProcess = GetCurrentProcess();
           var dacl = GetProcessSecurityDescriptor(hProcess);
           dacl.DiscretionaryAcl.InsertAce(0, new CommonAce(AceFlags.None, AceQualifier.AccessDenied /* blokujemy dostęp do procesu */, (int)PAR.PROCESS_ALL_ACCESS, new SecurityIdentifier(WellKnownSidType.WorldSid, null), false, null));
           SetProcessSecurityDescriptor(hProcess, dacl);

Ja to sobie wrzuciłem do voida, i mam to jako funkcję. Spróbujmy teraz dany program tak uruchomiony "zabić". (polecenie taskkill /im <nazwapliku>)

[Aby zobaczyć linki, zarejestruj się tutaj]

Jak widać działa! Nie mamy możliwości zakończenia działania procesu bez uprawnień administratora.
To by było na tyle, jeżeli macie pomysły jak lepiej napisać taki mechanizm, proszę piszcie, także, jak czegoś nie rozumiecie również. Liczę na Ciebie @TW Tongue
0x DEADBEEF
Odpowiedz
#2
Kapitanie, a takie jedno pytanie, tak na szybkości...
Szansa, że taki program się zawiesi, i Windows go nie zamknie, bo odmowa dostępu, to są?
Uprzedzając, w C# ja nigdy nic...
Człowiek, któremu zazdroszczą najlepszych pomysłów na sygnatury...
Odpowiedz
#3
IMO (ale strzelam) jak się zawiesi to go nie ubijesz z wiersza poleceń, ale z menadżera zadań już tak - bo on wymaga uprawnień admina.
No ale... chomikos może puść mu tam coś typu sleep albo sleep + nieskończony loop i zobacz, czy da się ubić? Pewnie tak Smile

A sam temat - chylę czoła, bo ja sam w C# i owszem, coś robiłem, ale raczej okienkowo Grin
Odpowiedz
#4
(16.04.2016, 12:31)lukasamd napisał(a):

[Aby zobaczyć linki, zarejestruj się tutaj]

IMO (ale strzelam) jak się zawiesi to go nie ubijesz z wiersza poleceń, ale z menadżera zadań już tak - bo on wymaga uprawnień admina.
No ale... chomikos może puść mu tam coś typu sleep albo sleep + nieskończony loop i zobacz, czy da się ubić? Pewnie tak Smile

A sam temat - chylę czoła, bo ja sam w C# i owszem, coś robiłem, ale raczej okienkowo Grin

Można to też zastosować do aplikacji okienkowych Wink

Ale kontynuując - Process Hacker też nie ubija jeżeli jest uruchamiany poprzez control+alt+delete lub z paska, zapewne także taskmgr tak samo.

[Aby zobaczyć linki, zarejestruj się tutaj]

Cały czas zastanawiam się, jak Kardo u siebie zrobił autoochronę Tongue

Cytat:Kapitanie, a takie jedno pytanie, tak na szybkości... 
Szansa, że taki program się zawiesi, i Windows go nie zamknie, bo odmowa dostępu, to są?
Uprzedzając, w C# ja nigdy nic...
 Się nawet nad tym nie zastanawiałem Craze
 Ale raczej da rady go zamknąć bo to już z poziomu systemu. W sumie to ciekawy grunt pod exploitacje Windowsa.
0x DEADBEEF
Odpowiedz
#5
Próbuj. Jestem ciekaw Suspicious
Człowiek, któremu zazdroszczą najlepszych pomysłów na sygnatury...
Odpowiedz
#6
Chomik Smile

A czemu nie używasz parametru /f na końcu np. C:\>Taskkill /IM firefox.exe /F dodatkowo ten parametr powoduje siłowe zamykanie procesu.
Odpowiedz
#7
(17.04.2016, 20:39)tachion napisał(a):

[Aby zobaczyć linki, zarejestruj się tutaj]

Chomik Smile

A czemu nie używasz parametru /f na końcu np. C:\>Taskkill /IM firefox.exe /F  dodatkowo  ten parametr powoduje siłowe zamykanie procesu.

@tachion:

[Aby zobaczyć linki, zarejestruj się tutaj]

0x DEADBEEF
Odpowiedz


Skocz do:


Użytkownicy przeglądający ten wątek: 1 gości