В штате буквально всех современных ОС имеется служебный персонал теневого фронта - в Windows его назвали службы "Services". Здесь мы рассмотрим интерфейс программирования их на ассемблере fasm, а так-же способы обмена данными с пользовательским приложением. В качестве практического руководства с нуля напишем софт, который позволит расшифровать пароли Wi-Fi на текущей машине (актуально для буков).
1. Общие сведения
Служба Win - это работающее в фоне приложение без привычного интерфейса (окна). Главное отличие службы от обычной программы - контекст выполнения. Их запускает процесс svchost.exe (Service Host) в закрытой сессии(0) от имени специальных/трёх учётных записей: это Local System, Local Service, и Network Service. Они живут своей жизнью независимо от того, вошёл кто-то в систему, или нет. Службы работают в адресном пространстве пользователя, а не как драйвера в пространстве системы, со всеми вытекающими.
1.1. Ключевые характеристики
• Фоновый режим: Нет окон, диалогов, иконок в трее, хотя служба может общаться с юзер-приложением.
• Автозапуск: Можно настроить на запуск при старте ОС, или в ручную.
• Привилегии: Имеют расширенные права доступа к системе и оборудованию.
1.2. Типы запуска "Start Type"
• Auto: Стартует при загрузке системы.
• Demand: Запускается через ~2 минуты после ОС, чтобы юзер быстрее увидел рабочий стол.
• Manual: Запуск только при необходимости (например, подключаем принтер).
• Disabled: Отключена - функционал полностью заморожен.
1.3. Учётные записи "Security Context"
Службы работают под спец.учётными записями, что определяет уровень их доступа к системе:
Ранее уже обсуждались сведения по сессиям и учёткам, поэтому повторяться не буду - вот линк:
https://codeby.net/threads/asm-demony-v-windows-1-sessii-stantsii-rabochiye-stoly.79421/
2. Диспетчер SCM - Service Control Manager
Управлением служб занимается "Service Control Manager" (диспетчер управления). По факту это процесс services.exe, который запускается самым первым при загрузке ОС. SCM выполняет три главные задачи:
Современные версии Win активно используют концепцию совместного размещения служб. К примеру некоторое их подмножество может работать внутри одного процесса svchost.exe. Это сделано для экономии памяти (общие dll не дублируются). Но есть и обратная сторона медали: если упадёт один процесс svchost в котором живёт 10 служб, все они станут недоступны. Получить список служб для каждого из активных процессов можно командой
На схеме ниже видно, что любой запрос к службе от приложения проходит через SCM. Если служба находится в состоянии "Running", она обязана функцией
3. Процесс службы в сессии(0)
Служба это не просто EXE-файл. Чтобы приложение стало службой, оно должно быть написано по строго определённому протоколу:
Значит точкой входа в службу является процедура обратного вызова Main(), которая через
У функции
На точке-входа в службу ServiceMain(), происходит уже инициализация прочих компонентов, и наконец регистрация обработчика событий, для которого SCM выделит отдельный поток. Именно здесь нужно через
Из значимых моментов, выше создаются три объекта синхронизации "Event", по сигналу которых будем реагировать на команды управления от юзер-софта. Контроль этих ивентов осуществляется в колбек-процедуре
Для отправки кодов-управления в службу используется
Ну и под занавес службе требуется бесконечный цикл, который мы вызывали в хвосте предыдущего фрагмента
4. Программа управления службой в сессии(1)
Как видно из таблицы выше, работу на стороне софта поддерживает довольно внушительная агитбригада WinAPI. На что здесь нужно обратить внимание, это запуск службы функцией
Основная проблема при написании служб - это их отладка. В сети предлагают различные методы типа "юзай WinDbg" и прочие, но все они мутные и лично у меня не работают. Поэтому приходится отлавливать блох в слепую, а результат проверять например в программе "ProcessHacker". Эта крутая тулза выдаст всю необходимую инфу о службе, включая просмотр свойств по Enter. Как видим наша служба исправно запустилась и находится с состоянии Running, ожидая от нас команд.
Обратите внимание с какими правами она запустилась - это LocalSystem, что является псевдонимом SYSTEM, т.е. наивысшие права, которых нет даже у админа системы. Как уже упоминалось ранее, это может представлять уязвимость, однако в частных случаях требуется именно System, например для декрипта паролей функцией
А вообще права задаются в аргументе lpServiceStartName функции
4.1. Поиск и чтение паролей Wi-Fi
Если кто забыл, вся эта заваруха со службами нужна была для декрипта паролей Wi-Fi на локальной машине. Забегая вперёд скажу, что с основным функционалом придётся повозиться - это работа с файлами, и нудный поиск в них текстовых строк (без этого никак). В общем зайдём из далека..
В Win-XP настройки сети Wi-Fi прописывались в следующей ветке реестра.
Здесь каждый интерфейс представлен уникальным GUID, и все его настройки хранятся в значении "ActiveSettings". HKLM\Software\Microsoft\WZCSVC\Parameters\Interfaces\{GUID}
Но начиная с Vista майки отказались от реестра - теперь все параметры хранятся в файле конфигов XML по пути:
C:\ProgramData\Microsoft\Wlansvc\Profiles\Interfaces\{GUID}\Random-GUID.xml
В каждом профиле беспроводной сети хранится инфа о названии сети Wi-Fi, параметрах безопасности (аутентификация, шифрование), и собственно сам зашифрованный пароль. Вот типичное содержимое одного из таких файлов:
Дадим определение основным узлам этого xml-файла:
На резонный вопрос "зачем передавать готовый HEX-пасс службе" можно ответить, что он шифруется функцией
Имейте в виду, что крипт/декрипт должен осуществляться на одной машине, т.е. нельзя утащить чужой пароль, и пытаться расшифровать его на своём компьютере. Это механизм защиты DPAPI, который при шифровании под капотом формирует сессионный ключ текущей машины (имя юзера, GUID сеанса, и прочее), после чего сохраняет эту инфу прямо внутри зашифрованных данных. Если в момент расшифровки эти сведения не совпадут, то получим прокол.
Проблемы при работе с xml-файлом:
1. Поиск самого файла по адресу: C:\ProgramData\Microsoft\Wlansvc\Profiles\Interfaces\{GUID}\Random-GUID.xml
Ладно до папки ..\Interface у нас есть постоянный путь, а вложенную нужно вычислять, т.к. ей назначается произвольное имя {GUID}, впрочем как и самому файлу. Значит придётся через
2. Если всё ок, приступаем к поиску нужных узлов внутри xml.
Здесь вычислив общий размер файла
3. Пароль в xml-файле хранится в виде HEX-строки, в то время как функция расшифровки
Полный исходник положу в скрепку, а результат его работы выглядит так. Поскольку у меня нет бука под рукой (а только стационар с инетом по кабелю), для тестов я нашёл несколько xml с чужих машин, поэтому в окне "Pass" служба сообщила об ошибке декрипта пароля (причина обсуждалась выше). Но главное вся эта конструкция исправно воркает, и я вытащил из файла наиболее информативные строки.
5. Заключение
В скрепку положил исходник + готовый файл для тестов (запускать правой клавишей от админа). Семпл сырой и толком не протестированный. Поэтому при желании можете допилить его сами. В принципе подправить нужно будет только один аргумент в функции декрипта службы. Если коротко, то
Ссылки по теме:
• Описание сервисных функций от майков
• Про учётную запись System
• Детали DPAPI
• Софт для декрипта паролей (внедрение в lsass.exe)
1. Общие сведения
2. Диспетчер SCM - Service Control Manager
3. Процесс службы в сессии(0)
4. Программа управления службой в сессии(1)
5. Заключение
1. Общие сведения
Служба Win - это работающее в фоне приложение без привычного интерфейса (окна). Главное отличие службы от обычной программы - контекст выполнения. Их запускает процесс svchost.exe (Service Host) в закрытой сессии(0) от имени специальных/трёх учётных записей: это Local System, Local Service, и Network Service. Они живут своей жизнью независимо от того, вошёл кто-то в систему, или нет. Службы работают в адресном пространстве пользователя, а не как драйвера в пространстве системы, со всеми вытекающими.
1.1. Ключевые характеристики
• Фоновый режим: Нет окон, диалогов, иконок в трее, хотя служба может общаться с юзер-приложением.
• Автозапуск: Можно настроить на запуск при старте ОС, или в ручную.
• Привилегии: Имеют расширенные права доступа к системе и оборудованию.
1.2. Типы запуска "Start Type"
• Auto: Стартует при загрузке системы.
• Demand: Запускается через ~2 минуты после ОС, чтобы юзер быстрее увидел рабочий стол.
• Manual: Запуск только при необходимости (например, подключаем принтер).
• Disabled: Отключена - функционал полностью заморожен.
1.3. Учётные записи "Security Context"
Службы работают под спец.учётными записями, что определяет уровень их доступа к системе:
1. LocalSystem: Наивысший уровень, с полным доступом ко всей системе. В некоторых случаях опасно, т.ч. есть ещё 2 альтернативы.
2. LocalService: Мин.привилегии на лок.машине. В сеть выходит как анонимный пользователь.
3. NetworkService: Аналогичен (2), но в сети авторизуется от имени "MachineName$", что позволяет работать с сетевыми ресурсами, и ActiveDirectory.
Ранее уже обсуждались сведения по сессиям и учёткам, поэтому повторяться не буду - вот линк:
https://codeby.net/threads/asm-demony-v-windows-1-sessii-stantsii-rabochiye-stoly.79421/
2. Диспетчер SCM - Service Control Manager
Управлением служб занимается "Service Control Manager" (диспетчер управления). По факту это процесс services.exe, который запускается самым первым при загрузке ОС. SCM выполняет три главные задачи:
1. Поддержка базы-данных в ветке реестра
HKLM\System\CurrentControlSet\Services, где хранятся записи о каждой службе: её имя, тип запуска, путь к файлу, и параметры восстановления при сбое.
2. Запуск и остановка. По команде админа (или типу запуска в реестре) SCM создаёт процесс службы, и управляет её жизненным циклом.
3. Коммуникация. Службы сообщают SCM о своём состоянии: Выполняет инициализацию, Запущена, Останавливается.
Современные версии Win активно используют концепцию совместного размещения служб. К примеру некоторое их подмножество может работать внутри одного процесса svchost.exe. Это сделано для экономии памяти (общие dll не дублируются). Но есть и обратная сторона медали: если упадёт один процесс svchost в котором живёт 10 служб, все они станут недоступны. Получить список служб для каждого из активных процессов можно командой
tasklist /svc.На схеме ниже видно, что любой запрос к службе от приложения проходит через SCM. Если служба находится в состоянии "Running", она обязана функцией
SetServiceStatus() периодически сообщать диспетчеру, мол "в Багдаде всё спокойно" и я просто жду команд. Иначе SCM посчитает, что у неё имеются какие-то проблемы, и принудительно удалит из памяти (хотя может перезагрузить). Это один из основных нюансов при программировании служб Windows.3. Процесс службы в сессии(0)
Служба это не просто EXE-файл. Чтобы приложение стало службой, оно должно быть написано по строго определённому протоколу:
1. Уметь принимать команды SCM, типа остановка, пауза, продолжение работы.
2. С периодом не более 30-сек (дефолт) сообщать диспетчеру о своём состоянии.
Значит точкой входа в службу является процедура обратного вызова Main(), которая через
StartServiceCtrlDispatcher() сразу должна зарегистрировать основной поток нашей службы. Суть в том, что внутри одной службы может быть контейнер до 64 дочерних служб, как показано на рис.ниже. При этом SCM каждому выделяет свои потоки Thread. Но ничто не мешает собрать весь перечисленный ниже функционал типа сеть, FS, мониторинг и прочие внутри одного основного потока. То-есть это просто такая архитектура, без навязывания прогерам:У функции
StartServiceCtrlDispatcher() всего 1 параметр - это указатель на таблицу, которая содержит в себе по 2 указателя для описания каждой службы: первый линк на имя, а второй на точку-входа ServiceMain(). Терминальный нуль прихлопывает таблицу. Вот фрагмент, как это реализовано у меня:
C-подобный:
format pe64 gui
include 'win64ax.inc'
include 'equates\services.inc'
entry start
;//----------
.data
dspTable dq srvName ;//<---- Таблица для StartServiceCtrlDispatcher()
dq ServiceMain ;// в данном случае всего 1 запись.
dq 0 ;//<---- терминальный нуль
srvName db 'WiFiService',0
srvHndl dq 0
.....
;//----------
section '.code' code readable executable
start: pop rax
push rax rax
invoke StartServiceCtrlDispatcher,dspTable
xor eax,eax
ret
align 16
proc ServiceMain argNum,argStr ;//<---- Точка входа в службу
mov [argNum],rcx
mov [argStr],rdx
invoke RegisterServiceCtrlHandlerEx,\
srvName,\
ServiceRoutineEx,0 ;//<---- Важно! Линк на обработчик событий
mov [srvHndl],rax
.....
На точке-входа в службу ServiceMain(), происходит уже инициализация прочих компонентов, и наконец регистрация обработчика событий, для которого SCM выделит отдельный поток. Именно здесь нужно через
SetServiceStatus() периодически уведомлять SCM о различных этапах типа SERVICE_START_PENDING (инициализация), после чего переход в состояние RUNNING (активен). Процедуру оповещения лучше оформить как универсальную, т.к. один из её параметров указывает на структуру SERVICE_STATUS аж с 7 полями, и всего 2 динамические (5 остальных не меняются). В общем вот пример, как продолжение кода выше:
C-подобный:
;// Заполняем структуру SERVICE_STATUS основными значениями
;//---------------
mov [srvStatus.dwServiceType], SERVICE_WIN32_OWN_PROCESS ;// Частная служба нашего юзер-процесса
mov [srvStatus.dwControlsAccepted],SERVICE_ACCEPT_STOP ;// Какие команды будем принимать (см.ниже)
mov [srvStatus.dwWin32ExitCode], ERROR_SERVICE_SPECIFIC_ERROR
mov [srvStatus.dwServiceSpecificExitCode],0
mov [srvStatus.dwCheckPoint],0 ;//<--- Если не нуль, значит служба чем-то занята
mov [srvStatus.dwWaitHint],3000 ;//<--- Важно! Период отправки оповещений SCM = макс 3 сек.
stdcall UpdateStatus, SERVICE_START_PENDING, 1 ;// Сообщаем SCM о начале запуска.. (1 = dwCheckPoint)
;// Создаём доп.события для юзер-команд (не нужно перечислять в поле dwControlsAccepted)
;//---------------
invoke CreateEvent, 0, 1, 0, 0 ;// стд.событие остановки службы
mov [hStopEvent], rax
invoke CreateEvent, 0, 1, 0, 0 ;// юзер-чтение из Pipe
mov [hReadEvent], rax
invoke CreateEvent, 0, 1, 0, 0 ;// юзер-запись в Pipe
mov [hWriteEvent],rax
;// Открываем канал Pipe для обмена данными с юзером (пайп создаёт сам софт управления службой)
;//---------------
invoke CreateFile,<'\\.\pipe\WiFiPipe',0>,\
GENERIC_READ + GENERIC_WRITE,\
0,0,OPEN_EXISTING,0,0
mov [hPipe],rax
stdcall UpdateStatus, SERVICE_RUNNING, 0 ;//<---- Служба запущена!
call @ServiceWorkedLoop
ret
endp
Из значимых моментов, выше создаются три объекта синхронизации "Event", по сигналу которых будем реагировать на команды управления от юзер-софта. Контроль этих ивентов осуществляется в колбек-процедуре
ServiceRoutineEx(), адрес которой мы передавали в RegisterServiceCtrlHandlerEx(). На время её работы накладываются жёсткие ограничения, т.к. служб в системе много, а диспетчер один. Поэтому мы просто проверяем код события, и по нему переводим соответствующий ивент в сигнальное состояние. А вот сама обработка события происходит уже в основном потоке службы, а не в потоке SCM, и её макс.длительность определяет значение поля dwWaitHint структуры SERVICE_STATUS.Для отправки кодов-управления в службу используется
ControlService(). Первые 127 кодов забрала себе система, а пользовательские должны начинаться с 128=80h. Здесь нужно отметить, что колбек обычной RegisterServiceCtrlHandler() в упор не видит юзер-коды, и нужно обязательно звать её расширенную версию с суфиксом Ex() в конце. Вот фрагмент:
C-подобный:
;// Обработчик команд SCM (запускается в отдельном потоке)
;//---------------
proc ServiceRoutineEx opCode, dwEventType, lpEventData, lpContext
mov [opCode],rcx
cmp [opCode],SERVICE_CONTROL_STOP
jne @f
invoke SetEvent,[hStopEvent] ;// Сигнал, если команда "стоп"
jmp @endRoutine
@@: cmp [opCode],0x80 ;// Если юзер-код "ReadPipe"
jne @f
invoke SetEvent,[hReadEvent]
jmp @endRoutine
@@: cmp [opCode],0x81 ;// Если это "WritePipe"
jne @endRoutine
invoke SetEvent,[hWriteEvent]
@endRoutine:
stdcall UpdateStatus,0,0 ;// Просто подтверждаем текущий статус
xor eax,eax
ret
endp
Ну и под занавес службе требуется бесконечный цикл, который мы вызывали в хвосте предыдущего фрагмента
call @ServiceWorkedLoop. На входе сразу ждём сигналы от трёх своих ивентов через WaitForMultipleObjects() и если да, то прыжок на метку. Обратите внимание на аргумент(3) функции Wait() - он задаёт число дескрипторов в массиве EventArray. Поскольку проверки следуют от 0 до 2, то важен порядок следования дескрипторов в массиве, который лежит в секции-данных:
C-подобный:
section 'data' data readable writeable
EventArray:
hStopEvent dq ? ;// WAIT_OBJECT_0
hReadEvent dq ?
hWriteEvent dq ?
......
section 'code' code readable executable
......
@ServiceWorkedLoop:
invoke WaitForMultipleObjects,3,EventArray,0,5000
cmp rax, 0 ;// WAIT_OBJECT_0
je @stopService
cmp rax, 1 ;// WAIT_OBJECT_0 + 1
je @readPipe
cmp rax, 2 ;// WAIT_OBJECT_0 + 2
je @writePipe
cmp eax,WAIT_TIMEOUT ;// истёк таймаут 5000 м/сек
jne @ServiceWorkedLoop
invoke Sleep,500 ;// немного разгрузим CPU
jmp @ServiceWorkedLoop
@stopService:
stdcall UpdateStatus,SERVICE_STOP_PENDING,1
invoke ResetEvent, [hStopEvent]
invoke CloseHandle,[hPipe]
stdcall UpdateStatus,SERVICE_STOPPED,0
invoke ExitProcess,0
@readPipe:
invoke ResetEvent,[hReadEvent]
invoke ReadFile,[hPipe],rdBuff,strLen,byteRetn,0
jmp @ServiceWorkedLoop
@writePipe:
invoke ResetEvent,[hWriteEvent]
invoke WriteFile,[hPipe],wrBuff,strLen,byteRetn,0
jmp @ServiceWorkedLoop
4. Программа управления службой в сессии(1)
Как видно из таблицы выше, работу на стороне софта поддерживает довольно внушительная агитбригада WinAPI. На что здесь нужно обратить внимание, это запуск службы функцией
StartService(). Дело в том, что после отправки запроса в SCM, необходимо сделать небольшую паузу типа Sleep(1000), т.к. в отличии от прикладного софта службы не могут стартовать мгновенно и им нужна "раскачка". А в остальном всё по классической схеме (обработка ошибок опущена):
C-подобный:
;// Подготовка службы к запуску..
;//---------------
invoke OpenSCManager,0,0,SC_MANAGER_ALL_ACCESS
mov [hScm],rax
;// Получаем полный путь до бинаря своей службы (если лежит рядом с прожкой)
;//---------------
invoke GetFullPathName,<'WiFiService64.exe',0>,256,buff,rsp
invoke CreateService,[hScm],\
<'WiFiService64',0>,\
<'Частная служба процесса Codeby',0>,\
SERVICE_ALL_ACCESS,\
SERVICE_WIN32_OWN_PROCESS,\
SERVICE_DEMAND_START,\
SERVICE_ERROR_NORMAL,\
buff,0,0,0,0,0
mov [hServ],rax
;// Создаём пайп для обмена данными со службой
;//---------------
invoke CreateNamedPipe,<'\\.\pipe\WiFiPipe',0>,\
PIPE_ACCESS_DUPLEX,\
PIPE_TYPE_MESSAGE,\
1,1024,1024,0,0
mov [hPipe],rax
;// Запуск службы с обязательной паузой после
;//---------------
invoke StartService,[hServ],0,0
invoke Sleep,1000
;// Теперь можно отправить любую команду
;//---------------
invoke ControlService,[hServ],\
0x81,\ ;// юзер-код "WritePipe"
sStatus
......
;// Обработчик "WM_CLOSE/COMMAND" в диалоговой процедуре окна
;//---------------
@command:
.if [wparam] = BN_CLICKED shl 16 + IDCANCEL
invoke ControlService,[hServ],SERVICE_CONTROL_STOP,sStatus
invoke DeleteService,[hServ]
invoke CloseServiceHandle,[hServ]
invoke CloseServiceHandle,[hScm]
invoke CloseHandle,[hPipe]
invoke Sleep,500
jmp @close
.endif
jmp @next
@close: invoke EndDialog,[hwnddlg],0
@next: xor eax,eax
ret
endp
Основная проблема при написании служб - это их отладка. В сети предлагают различные методы типа "юзай WinDbg" и прочие, но все они мутные и лично у меня не работают. Поэтому приходится отлавливать блох в слепую, а результат проверять например в программе "ProcessHacker". Эта крутая тулза выдаст всю необходимую инфу о службе, включая просмотр свойств по Enter. Как видим наша служба исправно запустилась и находится с состоянии Running, ожидая от нас команд.
Обратите внимание с какими правами она запустилась - это LocalSystem, что является псевдонимом SYSTEM, т.е. наивысшие права, которых нет даже у админа системы. Как уже упоминалось ранее, это может представлять уязвимость, однако в частных случаях требуется именно System, например для декрипта паролей функцией
CryptUnprotectData(), о чём пойдёт речь далее.А вообще права задаются в аргументе lpServiceStartName функции
CreateService() - это указатель на строку вида "NT AUTHORITY\LocalService". Если в этом аргументе нуль, то в дефолте назначается именно System.4.1. Поиск и чтение паролей Wi-Fi
Если кто забыл, вся эта заваруха со службами нужна была для декрипта паролей Wi-Fi на локальной машине. Забегая вперёд скажу, что с основным функционалом придётся повозиться - это работа с файлами, и нудный поиск в них текстовых строк (без этого никак). В общем зайдём из далека..
В Win-XP настройки сети Wi-Fi прописывались в следующей ветке реестра.
Здесь каждый интерфейс представлен уникальным GUID, и все его настройки хранятся в значении "ActiveSettings". HKLM\Software\Microsoft\WZCSVC\Parameters\Interfaces\{GUID}
Но начиная с Vista майки отказались от реестра - теперь все параметры хранятся в файле конфигов XML по пути:
C:\ProgramData\Microsoft\Wlansvc\Profiles\Interfaces\{GUID}\Random-GUID.xml
В каждом профиле беспроводной сети хранится инфа о названии сети Wi-Fi, параметрах безопасности (аутентификация, шифрование), и собственно сам зашифрованный пароль. Вот типичное содержимое одного из таких файлов:
XML:
<?xml version="1.0"?>
<WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
<name>COMNET_16</name>
<SSIDConfig>
<SSID>
<hex>434F4D4E45545F3136</hex>
<name>COMNET_16</name>
</SSID>
</SSIDConfig>
<connectionType>ESS</connectionType>
<connectionMode>manual</connectionMode>
<MSM>
<security>
<authEncryption>
<authentication>WPA2PSK</authentication>
<encryption>AES</encryption>
<useOneX>false</useOneX>
</authEncryption>
<sharedKey>
<keyType>passPhrase</keyType>
<protected>true</protected>
<keyMaterial>01000000D08C9DDF0115D1118C7A00C04FC297EB01000000</keyMaterial>
</sharedKey>
</security>
</MSM>
<MacRandomization xmlns="http://www.microsoft.com/networking/WLAN/profile/v3">
<enableRandomization>false</enableRandomization>
<randomizationSeed>1038085264</randomizationSeed>
</MacRandomization>
</WLANProfile>
Дадим определение основным узлам этого xml-файла:
1. Имя сети Wi-Fi прописывается в разделе <SSID>, которое хранится как в ASCII (здесь COMNET_16), так и в HEX-формате.
2. В узле <authEncryption> найдём методы аутентификации (здесь WPA2PSK) и шифрования (почти всегда AES).
3. Инфа о пароле лежит в узле <sharedKey> - значение <protected> указывает, зашифрован пароль(true), или хранится в открытом виде(false).
4. Сам пасс зарыт в узле <keyMaterial> и это то, что нам предстоит вытащить из xml, и передать его в нашу службу.
5. В некоторых роутерах есть опция рандома МАС-адреса, тогда можно заглянуть в узел <MacRandomization> (здесь enable=false, и других я не встречал).
На резонный вопрос "зачем передавать готовый HEX-пасс службе" можно ответить, что он шифруется функцией
CryptProtectData() из либы Crypt32.dll, при этом ни соль, ни какой-либо доп.ключ не используются, что на первый взгяд делает декрипт простым. Однако защита в том, что само шифрование производится одной из системных служб под учётной записью именно System, так-что даже под админом мы не сможем его расшифровать. Тут у нас всего 2 варианта - или внедрять шелл через удалённый поток в системный процесс Lsass.exe (метод довольно рискованный, т.к. любая ошибка может привести к краху всей системы), или-же просто создать свою службу в контексте System, что собственно мы и выбрали.Имейте в виду, что крипт/декрипт должен осуществляться на одной машине, т.е. нельзя утащить чужой пароль, и пытаться расшифровать его на своём компьютере. Это механизм защиты DPAPI, который при шифровании под капотом формирует сессионный ключ текущей машины (имя юзера, GUID сеанса, и прочее), после чего сохраняет эту инфу прямо внутри зашифрованных данных. Если в момент расшифровки эти сведения не совпадут, то получим прокол.
Проблемы при работе с xml-файлом:
1. Поиск самого файла по адресу: C:\ProgramData\Microsoft\Wlansvc\Profiles\Interfaces\{GUID}\Random-GUID.xml
Ладно до папки ..\Interface у нас есть постоянный путь, а вложенную нужно вычислять, т.к. ей назначается произвольное имя {GUID}, впрочем как и самому файлу. Значит придётся через
FindFirstFile() найти имя папки, и вручную добавить его к известной строке ..\Interface (конкатенация). После этого повторить запрос FindFirstFile() уже для xml-файла. Только теперь его можно будет прочитать ReadFile() предварительно выделив память HeapAlloc().2. Если всё ок, приступаем к поиску нужных узлов внутри xml.
Здесь вычислив общий размер файла
GetFileSize(), от начала и до конца ищем символ(<) и через repe cmpsb сравниваем строки, заранее определив её длину. Готовые API использовать нельзя, т.к. в xml-строках нет терминальных нулей. Поскольку придётся искать разные строки для вывода их в окно, я написал универсальную процедуру. На входе она ожидает имя и длину строки для поиска:
C-подобный:
.data
strSsid db 04,'name' ;// первый байт длина
strPass db 11,'keyMaterial'
strAuth db 14,'authentication'
strCrypt db 10,'encryption'
strRnd db 19,'enableRandomization'
strSeed db 17,'randomizationSeed'
.code
;//----------------------------------------
;// Процедура поиска заданной строки в XML
;//----------------------------------------
align 16
proc ParseWiFiXml lpName,strLen
mov [lpName],rcx
mov [strLen],rdx
;// Очистить приёмный буфер
xor eax,eax
mov ecx,256/8
mov edi,buff
rep stosq
;// Парсим файл датабазы *.XML
mov rsi,[fBuff] ;// адрес файла от HeapAlloc()
mov rdx,[fSize] ;// его длина GetFileSize()
@@: cmp byte[rsi],'<'
je @testStr
inc rsi
dec rdx
jnz @b
jmp @endParse
;// Сравнить строки !
@testStr: mov rcx,[strLen]
mov rdi,[lpName]
inc rsi
repe cmpsb
or ecx,ecx
jnz @b
;// Если нашли, скопировать в буфер для вывода
inc rsi
mov rdi,buff
@@: lodsb
cmp al,'<' ;// конец строки?
je @endParse
stosb
jmp @b
@endParse:
ret
endp
;//----------- Вызов процедуры -------------------
.....
movzx eax,byte[strSsid] ;// первый байт в строке = размер
mov ebx,strSsid+1 ;// ..и далее линк на саму строку
stdcall ParseWiFiXml,ebx,eax
invoke SetDlgItemText,[hwnddlg],ID_Ssid,buff
movzx eax,byte[strAuth]
mov ebx,strAuth+1
stdcall ParseWiFiXml,ebx,eax
invoke SetDlgItemText,[hwnddlg],ID_Auth,buff
... etc ...
3. Пароль в xml-файле хранится в виде HEX-строки, в то время как функция расшифровки
CryptUnprotectData() ожидает бинарные данные в структуре DATA_BLOB. Значит получаем длину HEX-строки lstrlen(), и переводим строку произвольной длины в число по такой схеме:
C-подобный:
;//-----------------------------------------
;// Процедура преобразования строки в число
;//-----------------------------------------
align 16
proc StringToHex
invoke lstrlen,buff
shr eax,1
mov word[writeBuff],ax ;// длина
mov esi,buff
mov edi,buff
xor eax,eax
@@: lodsb
or al,al
jz @stop
cmp al,'9'
jbe @01
or al,0x20 ;// A-F в нижний регистр
sub al,'a'
add al,10
jmp @02
@01: sub al,'0'
@02: stosb
jmp @b
@stop: mov esi,buff ;// источник
mov edi,writeBuff+2 ;// приёмник
movzx ecx,word[writeBuff]
@@: lodsw
xchg ah,al
shl al,4
shr ax,4
stosb
loop @b
ret
endp
Полный исходник положу в скрепку, а результат его работы выглядит так. Поскольку у меня нет бука под рукой (а только стационар с инетом по кабелю), для тестов я нашёл несколько xml с чужих машин, поэтому в окне "Pass" служба сообщила об ошибке декрипта пароля (причина обсуждалась выше). Но главное вся эта конструкция исправно воркает, и я вытащил из файла наиболее информативные строки.
5. Заключение
В скрепку положил исходник + готовый файл для тестов (запускать правой клавишей от админа). Семпл сырой и толком не протестированный. Поэтому при желании можете допилить его сами. В принципе подправить нужно будет только один аргумент в функции декрипта службы. Если коротко, то
CryptUnprotectData() использует мастер-ключи, которые могут быть привязаны или к конкретной учётной записи (флаг 0), или к конкретному компьютеру (флаг 4). Этот флаг указывается в предпоследнем аргументе функции, поэтому если код вернёт ошибку, нужно попробовать оба варианта в службе:
C-подобный:
;// Вариант 1: для текущего пользователя
invoke CryptUnprotectData, DataIn, 0, 0, 0, 0, 0, DataOut
;// Вариант 2: для локальной машины (скорее всего нужен)
invoke CryptUnprotectData, DataIn, 0, 0, 0, 0, 4, DataOut
Ссылки по теме:
• Описание сервисных функций от майков
Ссылка скрыта от гостей
• Про учётную запись System
Ссылка скрыта от гостей
• Детали DPAPI
Ссылка скрыта от гостей
• Софт для декрипта паролей (внедрение в lsass.exe)
Ссылка скрыта от гостей