Subversion Repositories Aucun

Rev

Rev 40 | Rev 100 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 ixe013 1
#include <windows.h>
2
#include <winwlx.h>
3
#include <assert.h>
4
#include <lm.h>
40 ixe013 5
#include <string.h>
6
#include <security.h>
7
#include <windowsx.h>
2 ixe013 8
 
9
#include "Ginahook.h"
23 ixe013 10
#include "Settings.h"
11
#include "UnlockPolicy.h"
2 ixe013 12
 
40 ixe013 13
#include "global.h"
14
#include "debug.h"
91 ixe013 15
#include "trace.h"
16
#include "SecurityHelper.h"
2 ixe013 17
 
40 ixe013 18
 
23 ixe013 19
typedef struct
20
{
40 ixe013 21
    int IDD_SAS;
22
    int IDC_LOCKWKSTA;
23
    int IDC_LOGOFF;
24
    int IDD_UNLOCKPASSWORD;
23 ixe013 25
    int IDC_USERNAME;
26
    int IDC_PASSWORD;
27
    int IDC_DOMAIN;
40 ixe013 28
    int IDS_CAPTION;
29
    int IDS_DOMAIN_USERNAME;
30
    int IDS_USERNAME;
31
    int IDS_GENERIC_UNLOCK;
23 ixe013 32
}
33
DialogAndControlsID;
2 ixe013 34
 
23 ixe013 35
static const DialogAndControlsID gDialogsAndControls[] =
36
{
91 ixe013 37
    //Windows Server 2003
38
    //XP SP3 (and probably previeus versions also, never tested)
39
    {
40
        1800,    // IDD_SAS
41
            1800,    // IDC_LOCKWKSTA
42
            1801,    // IDC_LOGOFF
43
            1950,    // IDD_UNLOCKPASSWORD
44
            1953,    // IDC_USERNAME
45
            1954,    // IDC_PASSWORD
46
            1956,    // IDC_DOMAIN
47
            1501,    // IDS_CAPTION
48
            1528,    // IDS_DOMAIN_USERNAME
49
            1561,    // IDS_USERNAME
50
            1528     // IDS_GENERIC_UNLOCK //1607
51
    },
23 ixe013 52
};
53
 
54
static const int nbDialogsAndControlsID = sizeof gDialogsAndControls / sizeof *gDialogsAndControls;
40 ixe013 55
static int gCurrentDlgIndex = -1;
23 ixe013 56
 
2 ixe013 57
//
58
// Pointers to redirected functions.
59
//
60
 
61
static PWLX_DIALOG_BOX_PARAM pfWlxDialogBoxParam = NULL;
40 ixe013 62
typedef struct
63
{
64
    HANDLE CurrentUser;
65
    HANDLE Winlogon;
66
    LPARAM HookedLPARAM;
67
} DialogLParamHook;
2 ixe013 68
 
40 ixe013 69
const wchar_t gAucunWinlogonContext[] = L"Paralint.com_Aucun_WinlogonContext";
70
 
71
 
2 ixe013 72
//
73
// Pointers to redirected dialog box.
74
//
75
 
76
static DLGPROC pfWlxWkstaLockedSASDlgProc = NULL;
77
 
78
//
79
// Local functions.
80
//
81
 
23 ixe013 82
int WINAPI MyWlxDialogBoxParam(HANDLE, HANDLE, LPWSTR, HWND, DLGPROC, LPARAM);
2 ixe013 83
 
40 ixe013 84
BOOLEAN ShouldHookUnlockPasswordDialog();
2 ixe013 85
 
40 ixe013 86
 
2 ixe013 87
//
88
// Hook WlxDialogBoxParam() dispatch function.
89
//
23 ixe013 90
void HookWlxDialogBoxParam(PVOID pWinlogonFunctions, DWORD dwWlxVersion)
2 ixe013 91
{
23 ixe013 92
    //WlxDialogBoxParam
93
    pfWlxDialogBoxParam = ((PWLX_DISPATCH_VERSION_1_0) pWinlogonFunctions)->WlxDialogBoxParam;
94
    ((PWLX_DISPATCH_VERSION_1_0) pWinlogonFunctions)->WlxDialogBoxParam = MyWlxDialogBoxParam;
2 ixe013 95
}
96
 
23 ixe013 97
BOOLEAN GetDomainUsernamePassword(HWND hwndDlg, wchar_t *domain, int nbdomain, wchar_t *username, int nbusername, wchar_t *password, int nbpassword)
2 ixe013 98
{
23 ixe013 99
    BOOLEAN result = FALSE;
2 ixe013 100
 
91 ixe013 101
    if ((gCurrentDlgIndex >= 0) && (gCurrentDlgIndex < nbDialogsAndControlsID)) //sanity
23 ixe013 102
    {
91 ixe013 103
        if ((GetDlgItemText(hwndDlg, gDialogsAndControls[gCurrentDlgIndex].IDC_PASSWORD, password, nbpassword) > 0)
23 ixe013 104
            && (GetDlgItemText(hwndDlg, gDialogsAndControls[gCurrentDlgIndex].IDC_USERNAME, username, nbusername) > 0))
105
        {
106
            result = TRUE; //That's enough to keep going. Let's try the domain nonetheless
2 ixe013 107
 
23 ixe013 108
            GetDlgItemText(hwndDlg, gDialogsAndControls[gCurrentDlgIndex].IDC_DOMAIN, domain, nbdomain);
109
        }
110
    }
2 ixe013 111
 
23 ixe013 112
    return result;
2 ixe013 113
}
114
 
91 ixe013 115
BOOL IsWindowsServer()
116
{
117
    OSVERSIONINFOEX osvi;
118
    DWORDLONG dwlConditionMask = 0;
119
 
120
    // Initialize the OSVERSIONINFOEX structure.
121
    ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
122
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
123
    osvi.dwMajorVersion = 5;
124
    osvi.wProductType = VER_NT_SERVER;
125
 
126
    // Initialize the condition mask.
127
    VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL);
128
    VER_SET_CONDITION(dwlConditionMask, VER_PRODUCT_TYPE, VER_EQUAL);
129
 
130
    // Perform the test.
131
    return VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_PRODUCT_TYPE, dwlConditionMask);
132
}
133
 
134
 
40 ixe013 135
DWORD DisplayUnlockNotice(HWND hDlg, HANDLE hWlx)
136
{
137
    DWORD result = IDNO; //proceed with lock
138
 
139
    wchar_t unlock[MAX_GROUPNAME] = L"";
140
 
91 ixe013 141
    if (GetGroupName(gUnlockGroupName, unlock, sizeof unlock / sizeof *unlock) == S_OK)
40 ixe013 142
    {
143
        wchar_t caption[512];
144
        wchar_t text[2048];
145
 
91 ixe013 146
        if ((GetNoticeText(L"Caption", caption, sizeof caption / sizeof *caption) == S_OK)
147
            && (GetNoticeText(L"Text", text, sizeof text / sizeof *text) == S_OK))
40 ixe013 148
        {
149
            wchar_t message[MAX_USERNAME + sizeof text / sizeof *text];
150
            wchar_t *read = text;
151
            wchar_t *write = text;
152
 
91 ixe013 153
            TRACE(L"Unlock notice will be displayed.\n");
154
 
40 ixe013 155
            //Insert real \n caracters from the \n found in the string.
91 ixe013 156
            while (*read)
40 ixe013 157
            {
91 ixe013 158
                if ((*read == '\\') && (*(read+1) == 'n'))
40 ixe013 159
                {
160
                    *write++ = '\n';
161
                    read += 2;
162
                }
163
                else
164
                {
165
                    *write++ = *read++;
166
                }
167
            }
168
 
169
            *write = 0;
170
 
171
            wsprintf(message, text, unlock); //Will insert group name if there is a %s in the message
172
            result = ((PWLX_DISPATCH_VERSION_1_0) g_pWinlogon)->WlxMessageBox(hWlx, hDlg, message, caption, MB_YESNOCANCEL|MB_ICONEXCLAMATION);
173
        }
174
    }
175
 
176
    return result;
177
 
178
}
179
 
91 ixe013 180
DWORD DisplayForceLogoffNotice(HWND hDlg, HANDLE hWlx, HANDLE current_user)
40 ixe013 181
{
182
    DWORD result = IDCANCEL;
183
 
91 ixe013 184
    TRACE(L"About to display a notice for dialog index %d\n", gCurrentDlgIndex);
185
 
186
    if ((gCurrentDlgIndex >= 0) && (gCurrentDlgIndex < nbDialogsAndControlsID)) //sanity
40 ixe013 187
    {
91 ixe013 188
        wchar_t buf[2048];
40 ixe013 189
        wchar_t caption[512];
190
 
91 ixe013 191
        //Start with the caption
192
        LoadString(hDll, gDialogsAndControls[gCurrentDlgIndex].IDS_CAPTION, caption, sizeof caption / sizeof *caption);
40 ixe013 193
 
91 ixe013 194
        //Windows XP has a plain vanilla message, no insert. Let's start with that
195
        LoadString(hDll, gDialogsAndControls[gCurrentDlgIndex].IDS_GENERIC_UNLOCK, buf, sizeof buf / sizeof *buf);
40 ixe013 196
 
91 ixe013 197
        //The format of the message is different on Windows Server. This test is somewhat short sighted,
198
        //but we know that in the future versions there is no Gina at all ! That's why we shortcut
199
        //the test to either Windows XP or Windows Server.
200
        if (IsWindowsServer())
40 ixe013 201
        {
91 ixe013 202
            wchar_t format[1024];
203
            wchar_t username[1024];
204
            wchar_t domain[1024];
205
            int howmany;
40 ixe013 206
 
91 ixe013 207
            howmany = GetUsernameAndDomainFromToken(current_user, domain, sizeof domain / sizeof *domain, username, sizeof username /  sizeof *username);
40 ixe013 208
 
91 ixe013 209
            switch(howmany)
40 ixe013 210
            {
91 ixe013 211
            case 2:
212
                {
213
                    LoadString(hDll, gDialogsAndControls[gCurrentDlgIndex].IDS_DOMAIN_USERNAME, format, sizeof format / sizeof *format);
214
                    wsprintf(buf, format, domain, username, L"some time");
215
                }
216
                break;
217
            case 1:
218
                {
219
                    LoadString(hDll, gDialogsAndControls[gCurrentDlgIndex].IDS_USERNAME, format, sizeof format / sizeof *format);
220
                    wsprintf(buf, format, username, L"some time");
221
                }
222
                break;
40 ixe013 223
            }
224
        }
225
 
91 ixe013 226
        TRACE(buf);
227
        TRACEMORE(L"\n");
40 ixe013 228
 
229
        result = ((PWLX_DISPATCH_VERSION_1_0) g_pWinlogon)->WlxMessageBox(hWlx, hDlg, buf, caption, MB_OKCANCEL|MB_ICONEXCLAMATION);
230
    }
231
 
232
    return result;
233
}
234
 
91 ixe013 235
// DelPropProc is an application-defined callback function
236
// that deletes a window property.
237
BOOL CALLBACK DelPropProc(HWND hwndSubclass, LPTSTR lpszString, HANDLE hData, ULONG_PTR x)       // data handle
238
{
239
    RemoveProp(hwndSubclass, lpszString);
240
    return TRUE;
241
}
40 ixe013 242
 
243
INT_PTR CALLBACK MyWlxWkstaLoggedOnSASDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
244
{
245
    INT_PTR bResult = FALSE;
246
 
247
    // We hook a click on OK
91 ixe013 248
    if (uMsg == WM_INITDIALOG)
40 ixe013 249
    {
250
        DialogLParamHook *myinitparam = (DialogLParamHook*)lParam;
251
 
252
        lParam = myinitparam->HookedLPARAM;
253
 
254
        SetProp(hwndDlg, gAucunWinlogonContext, myinitparam->Winlogon);
91 ixe013 255
 
256
        TRACE(L"Hooked dialog shown.\n");
40 ixe013 257
    }
91 ixe013 258
    else if (uMsg == WM_DESTROY)
40 ixe013 259
    {
91 ixe013 260
        EnumPropsEx(hwndDlg, DelPropProc, 0);
40 ixe013 261
    }
262
    else if ((uMsg == WM_COMMAND) && (wParam == gDialogsAndControls[gCurrentDlgIndex].IDC_LOCKWKSTA))
263
    {
91 ixe013 264
        TRACE(L"User locking workstation.\n");
40 ixe013 265
        /*
266
        There is a race condition here (time of check, time of use).
91 ixe013 267
        We check for a certain condition and display a warning. Then we let go
40 ixe013 268
        and make the same test again to hook the dialog or not. An administrator
91 ixe013 269
        with a good sense of timing could manage set the registry just after the
270
        test of ShouldHookUnlockPasswordDialog is made but before the actual
271
        dialog would be hooked.
40 ixe013 272
 
91 ixe013 273
        In other words: with good timing, an administrator with access to the
40 ixe013 274
        registry can prevent the unlock notice from showing.
275
 
276
        Spotting the flaw is 80% of the fun... I will probably never fix it.
277
        */
91 ixe013 278
        if (ShouldHookUnlockPasswordDialog(pgAucunContext->mCurrentUser))
40 ixe013 279
        {
91 ixe013 280
            TRACE(L"Will hook dialog if allowed to.\n");
281
            switch (DisplayUnlockNotice(hwndDlg, GetProp(hwndDlg, gAucunWinlogonContext)))
40 ixe013 282
            {
91 ixe013 283
                //We said that a custom Gina was installed, and asked "do you want
284
                //to lof off instead" ?
285
            case IDYES:
286
                //Why 113 ? I didn't find this value anywhere in the header files,
287
                //but it is the value returned by the original MSGINA DialogProc
288
                //When you click YES on the "Are you sure you want to log off" dialog box.
289
                TRACE(L"User wants to logoff instead.\n");
290
                EndDialog(hwndDlg, 113);
291
                bResult = TRUE;
292
                break;
40 ixe013 293
 
91 ixe013 294
                //Forget about it, I am not locking at all
295
            case IDCANCEL:
296
                TRACE(L"Lock request cancelled.\n");
297
                bResult = TRUE;
298
                break;
299
 
300
                //I don't care. Lock my workstation
301
            case IDNO:
302
            default:
303
                break;
40 ixe013 304
            }
305
        }
306
    }
307
 
308
    if (!bResult)
309
        bResult = pfWlxWkstaLockedSASDlgProc(hwndDlg, uMsg, wParam, lParam);
310
 
311
    return bResult;
312
}
313
 
314
 
2 ixe013 315
//
316
// Redirected WlxWkstaLockedSASDlgProc().
317
//
23 ixe013 318
INT_PTR CALLBACK MyWlxWkstaLockedSASDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
2 ixe013 319
{
23 ixe013 320
    INT_PTR bResult = FALSE;
2 ixe013 321
 
23 ixe013 322
    // We hook a click on OK
91 ixe013 323
    if (uMsg == WM_INITDIALOG)
23 ixe013 324
    {
40 ixe013 325
        DialogLParamHook *myinitparam = (DialogLParamHook*)lParam;
326
 
327
        lParam = myinitparam->HookedLPARAM;
328
 
329
        SetProp(hwndDlg, gAucunWinlogonContext, myinitparam->Winlogon);
91 ixe013 330
        TRACE(L"Hooked dialog shown.\n");
40 ixe013 331
    }
91 ixe013 332
    else if (uMsg == WM_DESTROY)
40 ixe013 333
    {
91 ixe013 334
        EnumPropsEx(hwndDlg, DelPropProc, 0);
40 ixe013 335
    }
336
    else if ((uMsg == WM_COMMAND) && (wParam == IDOK))
337
    {
23 ixe013 338
        wchar_t rawdomain[MAX_DOMAIN];
339
        wchar_t rawusername[MAX_USERNAME];
340
        wchar_t password[MAX_PASSWORD];
2 ixe013 341
 
91 ixe013 342
        TRACE(L"Unlock or logoff attemp\n");
343
 
23 ixe013 344
        //Get the username and password for this particular Dialog template
91 ixe013 345
        if (GetDomainUsernamePassword(hwndDlg, rawdomain, sizeof rawdomain / sizeof *rawdomain,
346
            rawusername, sizeof rawusername / sizeof *rawusername,
347
            password, sizeof password / sizeof *password))
23 ixe013 348
        {
349
            wchar_t *username = 0;
350
            wchar_t *domain = 0;
2 ixe013 351
 
40 ixe013 352
            //Replace this hack with CredUIParseUserName
23 ixe013 353
            username = wcsstr(rawusername, L"\\");
2 ixe013 354
 
91 ixe013 355
            if (username)
23 ixe013 356
            {
357
                domain = rawusername;
358
                *username++ = 0; //Null terminate the domain name and skip the separator
359
            }
360
            else
361
            {
362
                username = rawusername; //No domain entered, so point directly to the supplied buffer
91 ixe013 363
                if (*rawdomain)
23 ixe013 364
                    domain = rawdomain;
365
            }
2 ixe013 366
 
23 ixe013 367
            if (*username && *password)
368
            {
91 ixe013 369
                // Can you spot the buffer overflow vulnerability in this next line ?
370
                TRACE(L"User %s has entered his password.\n", username);
371
                // Don't worry, GetDomainUsernamePassword validated input length. We are safe.
372
 
373
                switch (ShouldUnlockForUser(pgAucunContext->mLSA, pgAucunContext->mCurrentUser, domain, username, password))
23 ixe013 374
                {
40 ixe013 375
                case eForceLogoff:
376
                    //Might help with house keeping, instead of directly calling EndDialog
91 ixe013 377
                    if (DisplayForceLogoffNotice(hwndDlg, GetProp(hwndDlg, gAucunWinlogonContext), pgAucunContext->mCurrentUser) == IDOK)
40 ixe013 378
                    {
91 ixe013 379
                        TRACE(L"User was allowed (and agreed) to forcing a logoff.\n");
40 ixe013 380
                        PostMessage(hwndDlg, WLX_WM_SAS, WLX_SAS_TYPE_USER_LOGOFF, 0);
381
                    }
382
                    else
383
                    {
384
                        //mimic MSGINA behavior
385
                        SetDlgItemText(hwndDlg, gDialogsAndControls[gCurrentDlgIndex].IDC_PASSWORD, L"");
386
                    }
387
                    bResult = TRUE;
388
                    break;
389
                case eUnlock:
91 ixe013 390
                    TRACE(L"User was allowed to unlock.\n");
23 ixe013 391
                    EndDialog(hwndDlg, IDOK);
392
                    bResult = TRUE;
40 ixe013 393
                    break;
91 ixe013 394
 
395
                case eLetMSGINAHandleIt:
396
                default:
397
                    TRACE(L"Will be handled by MSGINA.\n");
40 ixe013 398
                    //Most of the time, we end up here with nothing to do
399
                    break;
23 ixe013 400
                }
40 ixe013 401
 
402
                SecureZeroMemory(password, sizeof password);
23 ixe013 403
            }
404
        }
405
    }
2 ixe013 406
 
23 ixe013 407
    if (!bResult)
408
        bResult = pfWlxWkstaLockedSASDlgProc(hwndDlg, uMsg, wParam, lParam);
2 ixe013 409
 
23 ixe013 410
    return bResult;
2 ixe013 411
}
412
 
413
//
414
// Redirected WlxDialogBoxParam() function.
415
//
23 ixe013 416
int WINAPI MyWlxDialogBoxParam(HANDLE hWlx, HANDLE hInst, LPWSTR lpszTemplate, HWND hwndOwner, DLGPROC dlgprc, LPARAM dwInitParam)
2 ixe013 417
{
23 ixe013 418
    DLGPROC proc2use = dlgprc;
40 ixe013 419
    LPARAM lparam2use = dwInitParam;
420
    DialogLParamHook myInitParam = {0};
91 ixe013 421
    DWORD dlgid = 0;
2 ixe013 422
 
40 ixe013 423
    //We might doint this for nothing (if dialog is not hooked)
424
    myInitParam.HookedLPARAM = dwInitParam;
425
    myInitParam.Winlogon = hWlx;
2 ixe013 426
 
40 ixe013 427
    pfWlxWkstaLockedSASDlgProc = dlgprc;
428
 
91 ixe013 429
    TRACE(L"About to create the dialog");
23 ixe013 430
    //
431
    // We only know MSGINA dialogs by identifiers.
432
    //
433
    if (!HIWORD(lpszTemplate))
434
    {
435
        // Hook appropriate dialog boxes as necessary.
436
        int i;
91 ixe013 437
        dlgid = LOWORD(lpszTemplate);    //Cast to remove warning C4311
2 ixe013 438
 
23 ixe013 439
        //Try to find the dialog
91 ixe013 440
        for (i=0; i<nbDialogsAndControlsID; ++i)
23 ixe013 441
        {
442
            //Is it one of the ID we know ?
91 ixe013 443
            if (gDialogsAndControls[i].IDD_SAS == dlgid)
23 ixe013 444
            {
91 ixe013 445
                //The dialog that asks if you would like to change password, lock, taskmgr, etc.
446
                TRACEMORE(L" to change password, lock wkst, taskmgr, etc.\n");
23 ixe013 447
                gCurrentDlgIndex = i;
40 ixe013 448
 
449
                proc2use = MyWlxWkstaLoggedOnSASDlgProc;
450
                lparam2use = (LPARAM)&myInitParam;
451
 
23 ixe013 452
                break;
453
            }
91 ixe013 454
            else if (gDialogsAndControls[i].IDD_UNLOCKPASSWORD == dlgid)
40 ixe013 455
            {
91 ixe013 456
                //The dialog where you enter your password
457
                TRACEMORE(L" where you try to unlock a locked session\n");
458
 
40 ixe013 459
                gCurrentDlgIndex = i;
460
 
91 ixe013 461
                if (ShouldHookUnlockPasswordDialog(pgAucunContext->mCurrentUser))
40 ixe013 462
                {
91 ixe013 463
                    TRACE(L"Hooking the unlock dialog\n");
40 ixe013 464
                    proc2use = MyWlxWkstaLockedSASDlgProc; //Use our proc instead
465
                    lparam2use = (LPARAM)&myInitParam;
91 ixe013 466
                }
40 ixe013 467
 
468
                //No need to go on, even if nothing was hooked
469
                break;
470
            }
23 ixe013 471
        }
472
    }
2 ixe013 473
 
91 ixe013 474
    if (proc2use == dlgprc)
475
    {
476
        TRACE(L"(%d). it was not hooked.\n", dlgid);
477
    }
478
 
40 ixe013 479
    return pfWlxDialogBoxParam(hWlx, hInst, lpszTemplate, hwndOwner, proc2use, lparam2use);
8 ixe013 480
}