• Thứ Hai, 07/01/2008 10:59 (GMT+7)

    Tương tác với màn hình Logon trên Windows XP & Vista

    Màn hình đăng nhập của Windows là một đối tượng được bảo vệ kĩ lưỡng vì đó là nơi người dùng cung cấp tên và mật khẩu để bắt đầu sử dụng hệ thống. Màn hình này được gọi là WinLogon desktop, được quản lý bởi tiến trình WinLogon.exe và bắt đầu hoạt động trước khi có bất kì người dùng nào đăng nhập. Về mặt kĩ thuật, để tương tác được với màn hình này, ứng dụng cần hoạt động dưới dạng một Windows service bằng tài khoản LocalSystem và được tự khởi động khi hệ thống bắt đầu làm việc. Điều này có thể thực hiện dễ dàng trên Windows XP và các phiên bản trước, tuy nhiên kiến trúc mới của Windows Vista hoàn toàn không cho phép các service và ứng dụng tương tác với màn hình này. Trên thực tế vẫn có những ứng dụng được yêu cầu phải tương tác được với cửa sổ đăng nhập của Windows Vista, ví dụ hiển thị một hộp thoại, vẽ một hình ảnh hoặc xuất một thông báo trực tiếp lên màn hình nền của cửa sổ đăng nhập.

    Bài viết này sử dụng một số thuật ngữ lập trình và bảo mật sử dụng Windows API, do đó bạn cần chuẩn bị các kiến thức liên quan để dễ tiếp cận hơn với vấn đề này và một trình biên dịch như Microsoft Visual C++.

    Hình 1 - Hiện thông báo trên màn hình WinLogon của Windows Vista

    Service tương tác với WinLogon desktop trong Windows XP

    Trong Windows XP hay Windows Server 2003, các service và ứng dụng của người dùng đầu tiên đăng nhập vào hệ thống hoạt động chung ở session 0. Khi đó service có thể tương tác dễ dàng với màn hình WinLogon vì tiến trình WinLogon.exe cũng hoạt động ở session 0 này. Điều này dẫn đến nguy cơ bảo mật khi một ứng dụng có hại có thể nâng cao quyền của mình để tương tác với các ứng dụng điều khiển hệ thống.

    Hình 3 - Mô hình ứng dụng cà service hoạt động trong các session của Windows XP

    Hình 4 - Các service và ứng dụng của người dùng đầu tiên hoạt động ở session 0 trên Windows XP

    Để cho phép service tương tác với WinLogon desktop hoặc các desktop khác, bạn có thể sử dụng một trong ba cách sau:

    - Thiết lập bằng tay thông số Allow service to interact with desktop của service, sử dụng Service Management Console. Bạn chạy dòng lệnh services.msc tại trình đơn Start/Run và thiết lập thuộc tính cho service như hình vẽ bên dưới.

    Hình 5 - Thiết lập một interactive service bằng Services Management Console

    - Sử dụng công cụ dòng lệnh Service Controller sc.exe có sẵn để cài đặt service với các thông số tương tác. Bạn cần có quyền quản trị (administrator) để có thể cài đặt thành công service.

    Ví dụ: sc.exe create MyService binPath= C:\MyTools\MyService.exe type= own type= interact start= auto

    Trong đó, tham số “type= own type= interact” kết hợp với nhau để cho phép service tương tác với desktop, “start= auto” cho phép service tự động chạy khi hệ thống Windows vừa bắt đầu. Lưu ý, cú pháp của “sc.exe” bắt buộc phải có một dấu cách sau mỗi dấu bằng “=” ở danh sách thông số trên.

    - Sử dụng Windows API CreateService để cài đặt service như ví dụ sau:

    Mở Services Control Manager để có thể cấu hình các service

    Để đoạn mã cài đặt thành công, bạn phải thực thi với quyền quản trị.

    #include <windows.h>

    SC_HANDLE handleMgr = ::OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CREATE_SERVICE);

    Cài đặt một service mới, ví dụ ta có một service MyService.exe

    SC_HANDLE handleSvc = ::CreateService(handleMgr, _T(“MyService”), _T(“MyService”), SERVICE_ALL_ACCESS, SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, _T(“C:\\Z0rr0\\MyTools\\MyService.exe”), NULL, NULL, NULL, NULL, _T(“”));

    if (handleSvc != NULL)

    {

    ::CloseServiceHandle(handleSvc);

    }

    ::CloseServiceHandle(handleMgr);

    Thao tác vẽ hoặc xuất thông báo lên WinLogon desktop trong Windows XP có thể thực hiện ngay trong entry point ServiceMain của service ngay khi nó vừa bắt đầu hoạt động vì như đã đề cập, Windows XP cho phép một service có thể tương tác.

    Các bước thực hiện như sau:

    - Mở WinLogon desktop bằng Windows API OpenDesktop

    - Đọc và lưu giữ handle của desktop hiện tại mà service đang hoạt động bằng API GetThreadDesktop

    - Gán service thread sử dụng WinLogon desktop bằng API SetThreadDesktop

    - Thực hiện việc vẽ hoặc xuất thông báo bằng các GDI, API

    - Phục hồi desktop cũ mà service hoạt động và đóng WinLogon desktop bằng API SetThreadDesktop

    Dưới đây là đoạn mã tham khảo:

    Biến lưu giữ desktop hiện tại để phục hồi sau khi thực hiện xong tương tác

    HDESK hCurrDesk = NULL;

    Mở WinLogon desktop

    HDESK hDesktop = ::OpenDesktop(_T(“Winlogon”), 0, FALSE, GENERIC_ALL);

    if (NULL != hDesktop)

    {

    Đọc / lưu giữ desktop hiện tại (default) và chuyển sang WinLogon desktop

    hCurrDesk =::GetThreadDesktop(::GetCurrentThreadId());

    Gán WinLogon desktop cho thread hiện tại của service

    if (!::SetThreadDesktop(hDesktop))

    {

    ::CloseDesktop(hDesktop);

    hCurrDesk = NULL;

    }

    }

    Xuất một thông báo lên desktop hiện tại của thread, chính là WinLogon desktop

    HDC hDC = ::GetWindowDC(NULL);

    TCHAR strMsg[] = _T(“Message on WinLogon desktop of Windows XP”);

    DrawTextDC(hDC, strMsg, 200, 200);

    Quay lại desktop cũ sau khi đã thực hiện xong việc tương tác

    if (NULL != hCurrDesk)

    {

    ::CloseDesktop(hDesktop);

    ::SetThreadDesktop(hCurrDesk);

    }

    Phương thức tiện ích xuất một chuỗi thông báo có thể hiện thực như sau:

    void DrawTextDC(HDC hdc, LPTSTR str, int x, int y)

    {

    LOGFONT lf;

    HFONT hFont;

    Lưu trữ device context (DC) hiện tại để phục hồi sau khi vẽ

    ::SaveDC(hdc);

    Tạo kiểu font chữ cho chuỗi thông báo

    memset(&lf, 0, sizeof(LOGFONT));

    lf.lfWeight = FW_BOLD;

    lf.lfHeight = 22;

    _tcscpy(lf.lfFaceName, _T(“Arial”));

    hFont = ::CreateFontIndirect (&lf);

    Sử dụng font vừa tạo cho device context hiện tại

    hFont = (HFONT)::SelectObject (hdc, hFont);

    Thiết lập hiệu ứng nền trong suốt và màu chữ

    ::SetBkMode(hdc, TRANSPARENT);

    ::SetTextColor(hdc, ::GetSysColor(COLOR_3DFACE));

    Vẽ bóng chữ

    ::SetTextColor(hdc, COLORREF(RGB(111, 111, 111)));

    ::TextOut(hdc, x, y, str, _tcslen(str));

    Vẽ thông báo

    ::SetTextColor(hdc, COLORREF(RGB(255, 255, 255)));

    ::TextOut(hdc, x - 2, y - 2, str, _tcslen(str));

    ::DeleteObject(SelectObject (hdc, hFont));

    Phục hồi DC cũ

    ::RestoreDC(hdc, -1);

    }

    Tương tác với WinLogon desktop trong Windows Vista

    Với Windows Vista, các service bắt buộc hoạt động ở session 0 và ứng dụng của người dùng sẽ hoạt động ở các session còn lại với đặc quyền hạn chế. Service chạy ở session 0 sẽ không có khả năng tương tác với desktop cũng như lấy các thông tin liên quan đến đồ họa và tiến trình WinLogon cho mỗi người dùng sẽ hoạt động bắt đầu ở session 1 trở đi. Các thông số cho phép tạo service tương tác trong Services Management Console từng hoạt động tốt trên Windows XP sẽ vô hiệu trên Windows Vista. Thuật ngữ kĩ thuật gọi sự thay đổi này là session 0 isolation. Điều này dẫn đến việc tạo một service tương tác với cửa sổ WinLogon không còn đơn giản như ở các phiên bản trước vì bây giờ service và tiến trình WinLogon nằm ở hai session khác nhau được bảo vệ chặt chẽ bởi cơ chế bảo mật của Windows Vista.

    Hình 6
    - Mô hình ứng dụng và service hoạt động trong các session của Windows Vista

    Hình 7 - Các service ở session 0 và WinLogon ở session 1 trở đi trên Windows Vista

    Tương tác với WinLogon desktop từ một service trong Windows Vista

    Mặc dù không cho phép trực tiếp truy xuất đến giao diện từ service ở session 0, Vista cho phép service gọi thực thi một ứng dụng ở session khác, hoặc trao đổi với ứng dụng đó dạng client – server thông qua các lời gọi RPC hoặc IPC named pipe, socket hoặc shared memory.

    Như vậy mọi chuyện đã trở nên rõ ràng hơn, thay vì thực hiện thao tác vẽ ngay trong service như ở Windows XP, từ service ở session 0 ta sẽ gọi thực thi một ứng dụng ở cùng session với tiến trình WinLogon, tức là session 1, 2,... Ứng dụng ẩn này sau đó sẽ thực hiện việc tương tác tương tự như với Windows XP.

    Hình 8 - Service tương tác thông qua một ứng dụng ẩn trong Windows Vista

    Ta sẽ phác thảo các bước thực hiện như sau:

    - Trong service, tìm tiến trình WinLogon để lấy được access token của người dùng thuộc session tương ứng mà WinLogon đang hoạt động.

    - Tạo bản sao token này và tạo thông tin môi trường để áp dụng cho việc thực thi tiến trình ẩn. Phương pháp này gọi là impersonate user token.

    - Tạo tiến trình để thực thi ứng dụng ẩn bằng API CreateProcessAsUser với access token vừa lấy ở trên.

    - Ứng dụng ẩn này sẽ mở và tương tác với màn hình WinLogon.

    Giải pháp trên được hiện thực với mã ví dụ sau, cài đặt trong ServiceMain:

    #include <windows.h>

    #include <tlhelp32.h>

    PROCESS_INFORMATION processInfo;

    STARTUPINFO startupInfo;

    DWORD dwWinlogonPid = 0;

    HANDLE hPToken, hProcess;

    Tìm tiến trình WinLogon để lấy access token tương ứng. Ví dụ chỉ áp dụng cho trường hợp tương tác với màn hình Winlogon khi chưa có người dùng đăng nhập, nên giả định lúc này chỉ có duy nhất một tiến trình WinLogon hoạt động ở session khác 0

    PROCESSENTRY32 procEntry;

    HANDLE hSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    if (hSnap == INVALID_HANDLE_VALUE) return FALSE;

    procEntry.dwSize = sizeof(PROCESSENTRY32);

    if (!::Process32First(hSnap, &procEntry)) return FALSE;

    do

    {

    if (_stricmp(procEntry.szExeFile, _T(“WinLogon.exe”)) == 0)

    {

    // Để mở rộng, bạn có thể kiểm tra SessionID của tiến trình WinLogon tương ứng

    dwWinlogonPid = procEntry.th32ProcessID;

    break;

    }

    } while (::Process32Next(hSnap, &procEntry));

    Đọc access token của tiến trình WinLogon

    hProcess = ::OpenProcess(MAXIMUM_ALLOWED, FALSE, dwWinlogonPid);

    DWORD dwUserResponse;

    if (!::OpenProcessToken(hProcess,TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY
    |
    TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY|TOKEN_ADJUST_SESSIONID

    |TOKEN_READ|TOKEN_WRITE,&hPToken))

    {

    // Xử lý lỗi không đọc được token }

    Bạn có thể impersonate một token và tạo thông tin môi trường ở đây bằng các API DuplicateTokenEx và CreateEnvironmentBlock, tuy nhiên để đơn giản, trong ví dụ này tôi sẽ sử dụng trực tiếp primary token của tiến trình WinLogon khi thực thi ứng dụng ẩn

    ZeroMemory(&processInfo, sizeof(processInfo));

    ZeroMemory(&startupInfo, sizeof(STARTUPINFO));

    Chỉ định ứng dụng ẩn dạng console sẽ hoạt động ở Winlogon desktop của Winsta0, trong cùng session với tiến trình WinLogon

    startupInfo.cb = sizeof(STARTUPINFO);

    startupInfo.lpDesktop = _T(“Winsta0\\Winlogon”);

    startupInfo.dwFlags = STARTF_USESHOWWINDOW;

    startupInfo.wShowWindow = SW_HIDE;

    Tạo một tiến trình mới thực thi ứng dụng ẩn sử dụng access token của tiến trình WinLogon, đồng nghĩa với việc ứng dụng ẩn của chúng ta sẽ hoạt động cùng session với WinLogon

    Trong ví dụ này, giả định ứng dụng ẩn đã được tạo tại C:\Z0rr0\MyTools\HiddenApp.exe

    if (!::CreateProcessAsUser(hPToken,

    _T(“C:\\Z0rr0\\MyTools\\HiddenApp.exe”),

    NULL, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS,

    NULL, NULL, &startupInfo, &processInfo)

    {

    // Xử lý lỗi không tạo được process

    }

    CloseHandle(hPToken);

    CloseHandle(hProcess);

    Để hoàn tất một service tương tác hoạt động cho cả Windows XP và Vista, trong ServiceMain ta cần xác định phiên bản Windows trước khi thực hiện phương pháp tương tác tương ứng.

    Chuẩn bị dữ liệu để đọc phiên bản Windows đang dùng

    OSVERSIONINFOEX osvi;

    BOOL bOsVersionInfoEx;

    ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));

    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

    if( !(bOsVersionInfoEx = GetVersionEx((OSVERSIONINFO *) &osvi)) )

    {

    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

    if ( !GetVersionEx((OSVERSIONINFO*) &osvi))

    return FALSE;

    }

    Xác định phiên bản Windows để thực hiện việc tương tác phù hợp

    if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT)

    {

    if ( osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0 )

    {

    Thực hiện tương tác trên Windows Vista hoặc Longhorn server

    }

    else if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1 )

    {

    Thực hiện tương tác trên Windows XP workstation

    }

    else return FALSE;

    }

    Việc còn lại là tạo một ứng dụng ẩn HiddenApp.exe dạng Windows console bằng Visual Studio C++, có mã như sau:

    #include <windows.h>

    Hàm vẽ DrawTextDC đã được hiện thực ở ví dụ với Windows XP

    void DrawTextDC(HDC hdc, LPTSTR str, int x, int y);

    int _tmain(int argc, _TCHAR* argv[])

    {

    HDC hDC = GetWindowDC(NULL);

    TCHAR strMsg[ ] = _T(“Message on WinLogon desktop of Windows Vista”);

    DrawTextDC(hDC, strMsg, 200, 200);

    return 0;

    }

    Lưu ý: Các đoạn mã ví dụ trên chỉ mang tính tham khảo, bạn cần điều chỉnh để phù hợp với yêu cầu thực tế của mình hoặc có thể tham khảo mã nguồn kèm theo bài viết này.

    Khi đã hoàn tất việc biên dịch service và ứng dụng ẩn, để kiểm nghiệm kết quả bạn cần phải chạy cửa sổ dòng lệnh Command Prompt cmd.exe dưới quyền quản trị (Run as Administrator) trước khi thực hiện lệnh sc.exe để cài đặt service vào hệ thống Windows Vista và đồng thời chép ứng dụng ẩn vào vị trí được chỉ định trong lệnh gọi của service. Cuối cùng là khởi động lại hệ thống và chờ một khoảng thời gian ngắn trước khi đăng nhập để service của bạn tự động chạy và xuất thông điệp lên màn hình.

    Hình 9 - Chạy dòng lệnh sc.exe để cài đặt service trong Windows Vista

    Bài viết đề cập đến nhiều thuật ngữ và vấn đề lập trình hệ thống và bảo mật trên Windows, đặc biệt là Windows Vista. Hy vọng đây sẽ là đề tài mở để các bạn bắt tay tìm hiểu một vùng đất mới, màu mỡ nhưng cũng không kém phần phức tạp. Chúc các bạn thành công.

    Sơn Nguyễn

    Email: sonnv@cybersoft-vn.com
    Global CyberSoft (VN)
    ---------------------------------------------
    Tài liệu tham khảo:
    - MSDN: http://msdn.microsoft.com
    - Vista Session 0 Isolation http://www.microsoft.com/whdc/system/vista/

    ID: A0712_142
    File đính kèm