PDA

Просмотр полной версии : Пишем службу для расшифровки паролей Wi-Fi


Marylin
24.03.2026, 17:50
https://forum.antichat.xyz/attachments/4951250/1774388229121.png

В штате буквально всех современных ОС имеется служебный персонал теневого фронта - в Windows его назвали службы "Services". Здесь мы рассмотрим интерфейс программирования их на ассемблере fasm, а так-же способы обмена данными с пользовательским приложением. В качестве практического руководства с нуля напишем софт, который позволит расшифровать пароли Wi-Fi на текущей машине (актуально для буков).

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/ (https://forum.antichat.xyz/threads/579421/)

2. ДиспетчерSCM - Service ControlManager

Управлением служб занимается "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.

https://forum.antichat.xyz/attachments/4951250/img_ba634cf68b.png

3. Процесс службы в сессии(0)

Служба это не просто EXE-файл. Чтобы приложение стало службой, оно должно быть написано по строго определённому протоколу:

1. Уметь принимать команды SCM, типа остановка, пауза, продолжение работы.
2. С периодом не более 30-сек (дефолт) сообщать диспетчеру о своём состоянии.

https://forum.antichat.xyz/attachments/4951250/img_2c2d2ee92a.png
Значит точкой входа в службу является процедура обратного вызова Main(), которая через

StartServiceCtrlDispatcher()

сразу должна зарегистрировать основной поток нашей службы. Суть в том, что внутри одной службы может быть контейнер до 64 дочерних служб, как показано на рис.ниже. При этом SCM каждому выделяет свои потоки Thread. Но ничто не мешает собрать весь перечисленный ниже функционал типа сеть, FS, мониторинг и прочие внутри одного основного потока. То-есть это просто такая архитектура, без навязывания прогерам:

https://forum.antichat.xyz/attachments/4951250/img_4dda2fe758.png
У функции

StartServiceCtrlDispatcher()

всего 1 параметр - это указатель на таблицу, которая содержит в себе по 2 указателя для описания каждой службы: первый линк на имя, а второй на точку-входа ServiceMain(). Терминальный нуль прихлопывает таблицу. Вот фрагмент, как это реализовано у меня:

C-подобный:



format pe64 gui
include
'win64ax.inc'
include
'equates\services.inc'
entry start
;
//----------
.
data
dspTable dq srvName
;
//
,
\
GENERIC_READ
+
GENERIC_WRITE
,
\
0
,
0
,
OPEN_EXISTING
,
0
,
0
mov
[
hPipe
]
,
rax
stdcall UpdateStatus
,
SERVICE_RUNNING
,
0
;
//
,
256
,
buff
,
rsp
invoke CreateService
,
[
hScm
]
,
\

,
\

,
\
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_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, ожидая от нас команд.

https://forum.antichat.xyz/attachments/4951250/img_8ba1c02cbc.png
Обратите внимание с какими правами она запустилась - это LocalSystem, что является псевдонимом SYSTEM, т.е. наивысшие права, которых нет даже у админа системы. Как уже упоминалось ранее, это может представлять уязвимость, однако в частных случаях требуется именно System, например для декрипта паролей функцией

CryptUnprotectData()

, о чём пойдёт речь далее.

А вообще права задаются в аргументе lpServiceStartName функции

CreateService()

- это указатель на строку вида "NT AUTHORITY\LocalService". Если в этом аргументе нуль, то в дефолте назначается именно System.

https://forum.antichat.xyz/attachments/4951250/img_f7e50c6d3c.png

4.1. Поиск и чтение паролейWi-Fi

Если кто забыл, вся эта заваруха со службами нужна была для декрипта паролей Wi-Fi на локальной машине. Забегая вперёд скажу, что с основным функционалом придётся повозиться - это работа с файлами, и нудный поиск в них текстовых строк (без этого никак). В общем зайдём из далека..

В Win-XP настройки сети Wi-Fi прописывались в следующей ветке реестра.
Здесь каждый интерфейс представлен уникальным GUID, и все его настройки хранятся в значении "ActiveSettings". HKLM\Software\Microsoft\WZCSVC\Parameters\Interfac es\{GUID}

Но начиная с Vista майки отказались от реестра - теперь все параметры хранятся в файле конфигов XML по пути:
C:\ProgramData\Microsoft\Wlansvc\Profiles\Interfac es\{GUID}\Random-GUID.xml

В каждом профиле беспроводной сети хранится инфа о названии сети Wi-Fi, параметрах безопасности (аутентификация, шифрование), и собственно сам зашифрованный пароль. Вот типичное содержимое одного из таких файлов:

XML:




COMNET_16

434F4D4E45545F3136

COMNET_16

ESS

manual

WPA2PSK

AES

false

passPhrase

true

01000000D08C9DDF0115D1118C7A00C04FC297EB01000000

false

1038085264



Дадим определение основным узлам этого xml-файла:

1. Имя сети Wi-Fi прописывается в разделе , которое хранится как в ASCII (здесь COMNET_16), так и в HEX-формате.

2. В узле найдём методы аутентификации (здесь WPA2PSK) и шифрования (почти всегда AES).

3. Инфа о пароле лежит в узле - значение указывает, зашифрован пароль(true), или хранится в открытом виде(false).

4. Сам пасс зарыт в узле и это то, что нам предстоит вытащить из xml, и передать его в нашу службу.

5. В некоторых роутерах есть опция рандома МАС-адреса, тогда можно заглянуть в узел (здесь enable=false, и других я не встречал).

На резонный вопрос "зачем передавать готовый HEX-пасс службе" можно ответить, что он шифруется функцией

CryptProtectData()

из либы Crypt32.dll, при этом ни соль, ни какой-либо доп.ключ не используются, что на первый взгяд делает декрипт простым. Однако защита в том, что само шифрование производится одной из системных служб под учётной записью именно System, так-что даже под админом мы не сможем его расшифровать. Тут у нас всего 2 варианта - или внедрять шелл через удалённый поток в системный процесс Lsass.exe (метод довольно рискованный, т.к. любая ошибка может привести к краху всей системы), или-же просто создать свою службу в контексте System, что собственно мы и выбрали.

Имейте в виду, что крипт/декрипт должен осуществляться на одной машине, т.е. нельзя утащить чужой пароль, и пытаться расшифровать его на своём компьютере. Это механизм защиты DPAPI, который при шифровании под капотом формирует сессионный ключ текущей машины (имя юзера, GUID сеанса, и прочее), после чего сохраняет эту инфу прямо внутри зашифрованных данных. Если в момент расшифровки эти сведения не совпадут, то получим прокол.

Проблемы при работе с xml-файлом:

1. Поиск самого файла по адресу: C:\ProgramData\Microsoft\Wlansvc\Profiles\Interfac es\{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" служба сообщила об ошибке декрипта пароля (причина обсуждалась выше). Но главное вся эта конструкция исправно воркает, и я вытащил из файла наиболее информативные строки.

https://forum.antichat.xyz/attachments/4951250/img_6699b90106.png
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


Ссылки по теме:

• Описание сервисных функций от майков
Service Functions - Win32 apps

• Про учётную запись System
Учетная запись LocalSystem

• Детали DPAPI
Секреты DPAPI

• Софт для декрипта паролей (внедрение в lsass.exe)
DataProtectionDecryptor - Decrypt DPAPI (Data Protection API) data of Windows