Расшифровать ключ профиля WEP WLAN с помощью CryptUnprotectData

Я пытаюсь расшифровать ключ профиля WEP с помощью CryptUnprotectData. Я получил ключ профиля, экспортировав профиль с помощью netsh.

netsh wlan export profile name="MyWEP" folder="./"

На данный момент я вручную скопировал ключевой материал из файла .xml, сгенерированного командой netsh, в свою программу. И способ, который я расшифровываю, -

DATA_BLOB DataOut, DataVerify;
DataOut.cbData = encryptData.length();
DataOut.pbData = (BYTE*)("I_Manually_Copy_The_WEP_Key_Here");

if (CryptUnprotectData( &DataOut,
                        NULL,
                        NULL,
                        NULL,
                        NULL,
                        0,
                        &DataVerify))
{
    printf("The decrypted data is: %s\n", DataVerify.pbData);
}
else
{
    printf("Failed. Error Code: %d", GetLastError());
}

Но я получаю код ошибки 13 со ссылкой на неверные данные. Что я делаю неправильно ? В Win 7 и более поздних версиях я могу напрямую использовать WlanGetProfile с параметром WLAN_PROFILE_GET_PLAINTEXT_KEY . Но у меня НЕТ варианта на Vista, чем использовать функцию CryptUnprotectData. Я видел похожие сообщения здесь, здесь, но не получил много полезной информации. Кроме того, я использую ту же систему с теми же учетными данными пользователя. Может ли кто-нибудь предложить мне, как действовать?

PS: я задавал тот же вопрос на форумах Windows Desktop SDK, но пока не получил ответа. Пытаю удачу на SO.


person Mahesh    schedule 26.05.2012    source источник


Ответы (1)


Мне нравятся вопросы о безопасности Windows. Поэтому, если я иногда вижу такую, я пытаюсь ее решить.

В вашем случае вы уже сделали первый шаг, используя netsh.exe wlan export profile ... для экспорта данных из профиля WLAN в файл XML. Файл содержит <keyMaterial> элемента. Данные внутри элемента представляют собой двоичные данные, закодированные как Hex: (что-то вроде 01000000D08C9DDF0115D1118C7A00C0...).

Итак, что вам нужно сделать в первую очередь, это декодировать строку в двоичные данные. Вы можете использовать CryptStringToBinary с параметром CRYPT_STRING_HEX для декодирования строки в двоичный код.

Следующим шагом будет заполнение DATA_BLOB бинарными данными и вызов CryptUnprotectData для получения результата, но... Есть небольшая проблема. Как вы можете прочитать в документации WlanGetProfile следующее

По умолчанию элемент keyMaterial, возвращаемый в профиле, на который указывает pstrProfileXml, зашифрован. Если ваш процесс выполняется в контексте учетной записи LocalSystem на том же компьютере, вы можете расшифровать материал ключа, вызвав функцию CryptUnprotectData.

Windows Server 2008 и Windows Vista: элемент keyMaterial, возвращаемый в схеме профиля, на которую указывает pstrProfileXml, всегда зашифрован. Если ваш процесс выполняется в контексте учетной записи LocalSystem, вы можете расшифровать материал ключа, вызвав функцию CryptUnprotectData.

Таким образом, чтобы иметь возможность расшифровать ключ, мы должны вызвать CryptUnprotectData в контексте безопасности LocalSystem. Если ваша программа уже запущена в контексте LocalSystem, вы можете сделать это напрямую. Если это не так, но у вас есть права администратора или хотя бы права отладки, вы можете «одолжить» токен LocalSystem у какого-нибудь другого процесса, работающего на компьютере. Например, можно получить токен процесса «winlogon.exe» и выдать себя за него.

Следующая демонстрационная программа перечисляет процессы, используя NtQuerySystemInformation (см. мой старый ответ), который лично я предпочитаю. Можно использовать EnumProcesses или другими известными способами сделать то же самое. Вот код, который работал у меня

#include <Windows.h>
#include <tchar.h>
#include <stdio.h>
#pragma comment (lib, "Crypt32.lib")

#define STATUS_SUCCESS               ((NTSTATUS)0x00000000L)
#define STATUS_INFO_LENGTH_MISMATCH  ((NTSTATUS)0xC0000004L)

typedef enum _SYSTEM_INFORMATION_CLASS {
    SystemProcessInformation = 5
} SYSTEM_INFORMATION_CLASS;

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
} UNICODE_STRING;

typedef LONG KPRIORITY; // Thread priority

typedef struct _SYSTEM_PROCESS_INFORMATION_DETAILD {
    ULONG NextEntryOffset;
    ULONG NumberOfThreads;
    LARGE_INTEGER SpareLi1;
    LARGE_INTEGER SpareLi2;
    LARGE_INTEGER SpareLi3;
    LARGE_INTEGER CreateTime;
    LARGE_INTEGER UserTime;
    LARGE_INTEGER KernelTime;
    UNICODE_STRING ImageName;
    KPRIORITY BasePriority;
    HANDLE UniqueProcessId;
    ULONG InheritedFromUniqueProcessId;
    ULONG HandleCount;
    BYTE Reserved4[4];
    PVOID Reserved5[11];
    SIZE_T PeakPagefileUsage;
    SIZE_T PrivatePageCount;
    LARGE_INTEGER Reserved6[6];
} SYSTEM_PROCESS_INFORMATION_DETAILD, *PSYSTEM_PROCESS_INFORMATION_DETAILD;

typedef NTSTATUS (WINAPI *PFN_NT_QUERY_SYSTEM_INFORMATION)(
  IN       SYSTEM_INFORMATION_CLASS SystemInformationClass,
  IN OUT   PVOID SystemInformation,
  IN       ULONG SystemInformationLength,
  OUT OPTIONAL  PULONG ReturnLength
);

//
// The function changes a privilege named pszPrivilege for
// the current process. If bEnablePrivilege is FALSE, the privilege
// will be disabled, otherwise it will be enabled.
//
BOOL SetCurrentPrivilege (LPCTSTR pszPrivilege,   // Privilege to enable/disable
                          BOOL bEnablePrivilege)  // to enable or disable privilege
{
    HANDLE hToken;
    TOKEN_PRIVILEGES tp;
    LUID luid;
    TOKEN_PRIVILEGES tpPrevious;
    DWORD cbPrevious = sizeof(TOKEN_PRIVILEGES);
    BOOL bSuccess = FALSE;

    if (!LookupPrivilegeValue(NULL, pszPrivilege, &luid)) return FALSE;

    if (!OpenProcessToken (GetCurrentProcess(),
                           TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
                           &hToken
                          )) return FALSE;

    //
    // first pass.  get current privilege setting
    //
    tp.PrivilegeCount           = 1;
    tp.Privileges[0].Luid       = luid;
    tp.Privileges[0].Attributes = 0;

    AdjustTokenPrivileges(
            hToken,
            FALSE,
            &tp,
            sizeof(TOKEN_PRIVILEGES),
            &tpPrevious,
            &cbPrevious);

    if (GetLastError() == ERROR_SUCCESS) {
        //
        // second pass.  set privilege based on previous setting
        //
        tpPrevious.PrivilegeCount     = 1;
        tpPrevious.Privileges[0].Luid = luid;

        if(bEnablePrivilege)
            tpPrevious.Privileges[0].Attributes |= (SE_PRIVILEGE_ENABLED);
        else
            tpPrevious.Privileges[0].Attributes ^= (SE_PRIVILEGE_ENABLED &
                tpPrevious.Privileges[0].Attributes);

        AdjustTokenPrivileges(
                hToken,
                FALSE,
                &tpPrevious,
                cbPrevious,
                NULL,
                NULL);

        if (GetLastError() == ERROR_SUCCESS) bSuccess=TRUE;

        CloseHandle(hToken);
    }
    else {
        DWORD dwErrorCode = GetLastError();

        CloseHandle(hToken);
        SetLastError(dwErrorCode);
    }

    return bSuccess;
}

DWORD GetProcessIdByProcessName (LPCWSTR pszProcessName)
{
    SIZE_T bufferSize = 1024*sizeof(SYSTEM_PROCESS_INFORMATION_DETAILD);
    PSYSTEM_PROCESS_INFORMATION_DETAILD pspid = NULL;
    HANDLE hHeap = GetProcessHeap();
    PBYTE pBuffer = NULL;
    ULONG ReturnLength;
    PFN_NT_QUERY_SYSTEM_INFORMATION pfnNtQuerySystemInformation = (PFN_NT_QUERY_SYSTEM_INFORMATION)
        GetProcAddress (GetModuleHandle(TEXT("ntdll.dll")), "NtQuerySystemInformation");
    NTSTATUS status;
    int uLen = lstrlenW(pszProcessName)*sizeof(WCHAR);

    __try {
        pBuffer = (PBYTE) HeapAlloc (hHeap, 0, bufferSize);
#pragma warning(disable: 4127)
        while (TRUE) {
#pragma warning(default: 4127)
            status = pfnNtQuerySystemInformation (SystemProcessInformation, (PVOID)pBuffer,
                                                  bufferSize, &ReturnLength);
            if (status == STATUS_SUCCESS)
                break;
            else if (status != STATUS_INFO_LENGTH_MISMATCH) { // 0xC0000004L
                _tprintf (TEXT("ERROR 0x%X\n"), status);
                return 1;   // error
            }

            bufferSize *= 2;
            pBuffer = (PBYTE) HeapReAlloc (hHeap, 0, (PVOID)pBuffer, bufferSize);
        }

        for (pspid = (PSYSTEM_PROCESS_INFORMATION_DETAILD)pBuffer; ;
             pspid = (PSYSTEM_PROCESS_INFORMATION_DETAILD)(pspid->NextEntryOffset + (PBYTE)pspid)) {

            if (pspid->ImageName.Length == uLen && lstrcmpiW(pspid->ImageName.Buffer, pszProcessName) == 0)
                return (DWORD)pspid->UniqueProcessId;

            if (pspid->NextEntryOffset == 0) break;
        }
    }
    __finally {
        pBuffer = (PBYTE) HeapFree (hHeap, 0, pBuffer);
    }
    return 0;
}

int _tmain()
{
    BOOL bIsSuccess, bImpersonated = FALSE;
    HANDLE hProcess = NULL, hProcessToken = NULL;
    DATA_BLOB DataOut, DataVerify;
    // !!! in the next line you should copy the string from <keyMaterial>
    WCHAR szKey[] = L"01000000D08C9DDF0115D1118C7....";
    BYTE byKey[1024];
    DWORD cbBinary, dwFlags, dwSkip;
    DWORD dwProcessId = GetProcessIdByProcessName(L"winlogon.exe");
    if (dwProcessId == 0) return 1;

    bIsSuccess = SetCurrentPrivilege(SE_DEBUG_NAME, TRUE);
    if (!bIsSuccess) return GetLastError();

    __try {
        hProcess = OpenProcess(MAXIMUM_ALLOWED, FALSE, dwProcessId);
        if (!hProcess) __leave;
        bIsSuccess = OpenProcessToken (hProcess, MAXIMUM_ALLOWED, &hProcessToken);
        if (!bIsSuccess) __leave;
        bIsSuccess = ImpersonateLoggedOnUser(hProcessToken);
        if (!bIsSuccess) __leave;
        bImpersonated = TRUE;

        cbBinary = sizeof(byKey);
        bIsSuccess = CryptStringToBinary (szKey, lstrlenW(szKey), CRYPT_STRING_HEX, // CRYPT_STRING_HEX_ANY
            byKey, &cbBinary, &dwSkip, &dwFlags);
        if (!bIsSuccess) __leave;
        DataOut.cbData = cbBinary;
        DataOut.pbData = (BYTE*)byKey;

        if (CryptUnprotectData (&DataOut, NULL, NULL, NULL, NULL, 0, &DataVerify)) {
            _tprintf(TEXT("The decrypted data is: %hs\n"), DataVerify.pbData);
        }
    }
    __finally {
        if (bImpersonated)
            RevertToSelf();
        if (hProcess)
            CloseHandle(hProcess);
        if (hProcessToken)
            CloseHandle(hProcessToken);
    }

    return 0;
}
person Oleg    schedule 30.05.2012
comment
Большое спасибо за подробное объяснение. Код работает нормально для всех сетей и частично для сети WEP. При извлечении ключа сети WEP DataVerify.pbData имеет значение мусора в конце ( LLLMEM+, это значение согласовано). Однако DataVerify.cbData правильно показывает длину ключа. Не могли бы вы проверить, происходит ли то же самое и на вашей машине? Я использую VS 2010. - person Mahesh; 31.05.2012
comment
Я вручную добавил сеть типа WEP в Центр управления сетями и общим доступом > Управление беспроводными сетями. Еще раз, большое спасибо :) - person Mahesh; 31.05.2012
comment
Мне никогда не нравился термин LocalSystem, потому что я работал как Администратор и у меня сложилось впечатление, что Администратор больше похож на суперпользователя и может выполнять что-либо. - person Mahesh; 31.05.2012
comment
@Махеш: Ты прав! Данные из DataVerify.pbData не должны заканчиваться NULL. Поэтому нужно исправить строку _tprintf(TEXT("The decrypted data is: %hs\n"), DataVerify.pbData);, чтобы отображалось только DataVerify.cbData байт данных. Можно использовать, например, _tprintf(TEXT("The decrypted data is: %*hs\n"), DataVerify.cbData, DataVerify.pbData); или просто скопировать строковые данные и добавить туда \0. - person Oleg; 31.05.2012
comment
В порядке. Большое большое спасибо. Вы сделали мою работу. Спасибо за код :) - person Mahesh; 31.05.2012
comment
@Mahesh: я вижу, это твой первый вопрос, в котором ты использовал вознаграждение. Так что я не уверен, знаете ли вы, как это работает. Если вы планируете присудить награду, вы должны сделать это явным образом (дополнительную информацию см. здесь). - person Oleg; 01.06.2012
comment
Теперь я вижу, что вы вознаграждены 200. Я думал, что, приняв ответ, награда будет вознаграждена автоматически. - person Mahesh; 01.06.2012
comment
@Mahesh: Это работает не так интуитивно, но это не проблема. С наилучшими пожеланиями! - person Oleg; 01.06.2012
comment
С чего вы взяли, что двоичные данные нужно передавать CryptUnProtectData ? MSDN не указал это. Это только по вашему опыту или я что-то пропустил из MSDN? Просто любопытно. - person Mahesh; 01.06.2012
comment
@Mahesh: входной параметр имеет тип DATA_BLOB, а не что-то вроде LPWSTR. Таким образом, входные данные имеют двоичный тип. Это зашифрованные данные, поэтому обычно данные являются двоичными. - person Oleg; 01.06.2012
comment
Это Питон? Или как использовать этот код в Windows? - person Sonalk; 27.06.2020
comment
@Sonalk: код написан на C. Ответ старый, но я попробовал еще раз, и он все еще работает. Вам нужно запустить netsh.exe wlan export profile от имени администратора, чтобы экспортировать профили WLAN в файлы, получить значение ‹keyMaterial› из экспортированного файла и поместить в мой код как значение переменной szKey. Затем вам нужно скомпилировать код в Visual Studio для примера и запустить программу с правами администратора. Программа отобразит расшифрованное значение ключа WLAN. Код легко изменить для ввода szKey через параметры командной строки. - person Oleg; 28.06.2020
comment
@Sonalk: можно использовать код из docs.microsoft.com/en-us/windows/win32/api/wlanapi/, чтобы получить ‹keyMaterial› вместо использования профиля экспорта wlan netsh.exe - person Oleg; 28.06.2020