На заметку Исследование флага /P у svchost.exe


X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 125
Репутация
8 243
Перевод:

Привет!

В этом посте мы рассмотрим загадочный флаг “/P” у svchost.exe. Кратко: флаг P применяет различные политики: DynamicCodePolicy, BinarySignaturePolicy и ExtensionPolicy.
Для начала, что такое svchost.exe? Согласно Википедии, “svchost.exe (Service Host или SvcHost) — это системный процесс, который может содержать от одной до множества служб Windows в семействах операционных систем Windows NT”. Это по сути загрузчик ваших различных служб.

Прежде чем углубиться в svchost с помощью Cutter (или вашего любимого дизассемблера/декомпилятора), хорошо бы заранее иметь PDB-файл исполняемого файла. PDB означает “Program DataBase” и хранит отладочную информацию исполняемого файла. Вы можете получить его с помощью PDBDownloader.exe (который можно скачать здесь: ). Microsoft предоставляет доступ к некоторой отладочной информации для своих различных компонентов, чтобы вы могли отлаживать их с помощью Windbg или импортировать их в IDA, что круто. Важно отметить, что вы не получите доступ ко всей отладочной информации. Только к символам, которые Microsoft сделала публичными.

Теперь можно переходить к интересной части. Итак, этот статический анализ был выполнен с помощью Cutter (GUI для radare2). Заранее у вас есть возможность выбрать PDB-файл для загрузки при выборе расширенного анализа. Если вы этого не сделали, не беспокойтесь, вы можете добавить его позже.

Мы сначала попадаем в точку входа нашего исполняемого файла. Вы можете увидеть некоторые общие функции для PE исполняемого файла. Многие из них — это просто шаблонные функции, такие как инициализация аргументов, куки безопасности и так далее. Мы можем перейти и посмотреть на wmain.

1717063065612.png


В функции wmain нет ничего особо интересного. Единственная функция, которая может быть интересной, это InitializeSvcHostLib. Остальные, похоже, регистрируют новую службу, устанавливают таймер и ждут его.

1717063109976.png


Теперь это становится намного интереснее. Мы видим несколько вызовов, но что кажется важным для нашей цели, так это вызов GetCommandLineW, за которым следует BuildCommandOption. Безусловно, BuildCommandOption должна нам помочь.

1717063153843.png


BuildCommandOption гораздо более насыщенная, чем другие функции, поэтому нам нужно разбирать её по частям. Из предыдущей функции мы видим, что передаваемый ей параметр является возвращаемым значением GetCommandLineW, что согласно является командной строкой для текущего процесса (что неудивительно для функции с таким названием). Теперь, следуя за параметром внутри BuildCommandOption, мы можем увидеть что-то интересное.

1717063207428.png


Параметр копируется с помощью memcopy в “ppiVar5 + 0xf”, а ppiVar5 - это результат HeapAlloc. Из этих двух элементов можно сделать вывод, что ppiVar5 является структурой или объектом. Поэтому мы можем попытаться создать структуру в меню типов. Кроме того, при взгляде на конец функции видно, что ppiVar5 возвращается.

Так как я ленивый, я не буду проводить полный реверс-инжиниринг функции. Примерно посмотрев на функцию, можно заметить несколько интересных вещей. Мы видим множество операций с знакомыми значениями, такими как “0x20” (пробел), “0x2d” (минус) или “0x50” (буква P). Обычно можно было бы ожидать найти switch case или несколько “if else if”, сравнивающих символ с различными шестнадцатеричными значениями. Но здесь этого нет. Странно, но мы находим вычитание, за которым следует сравнение с 0. В конце концов, это всё же сравнение, но не такое простое, как можно было бы ожидать.

1717063290984.png


Из-за этих значений можно предположить, что iVar2 (который происходит от piVar8) указывает на символ из командной строки, и этот цикл его разбирает.
При более внимательном рассмотрении этого цикла и особенно действия, выполняемого при нахождении флага, можно увидеть, что наш ppiVar5 модифицируется (по крайней мере, изменяются различные поля ppiVar5). Вот различные случаи:
  • если установлен K (0x4B): *(ppiVar5 + 2) = 1
  • иначе, если установлен S (0x53): ppiVar5 не изменяется, счетчик увеличивается
  • иначе, если установлен P (0x50): *(ppiVar5 + 0x5c) = 1
Мы знаем, что поля ppiVar5 устанавливаются в зависимости от различных флагов, которые мы указываем в наших командных строках. Официально известно, что K — это группа служб, а S — служба из этой группы. Но информации о P нет. В конце этой функции наш ppiVar5 представляет собой структуру, выглядящую так:

C:
struct cmd_parsed {
    int64_t field_0;
    int64_t field_1;
    char k_flag;
    char gap_0[75];
    char p_flag;
    char gap_1[27];
    char *argv;
};

Итак, теперь мы можем немного отступить и вернуться к InitializeSvcHostLib. Мы видим, что возвращаемое значение BuildCommandOption — это iVar4, и оно передается в CallPerInstanceInitFunctions, так что это наша следующая остановка.

Первое, что мы видим, это то, что плагин Ghidra для Cutter не смог декомпилировать эту функцию, поэтому нам нужно полагаться на другой плагин r2dec и дизассемблирование. Второе — это то, что ни одно из полей cmd_parsed, установленных в BuildCommandOption, не используется, а вместо этого используются совершенно новые... Хм, странно. Возможно, пора снова сделать шаг назад и взглянуть на наш код.

В InitializeSvcHostLib мы видим, что указатель на объект, возвращаемый BuildCommandOption (то есть в rax), сразу копируется в rbx. Теперь, если мы немного проследим за rbx, мы увидим, что его содержимое копируется в rdx сразу после BuildServiceArray. Это означает, что это на самом деле параметр, который Ghidra не распознала.

1717064008582.png


Давайте углубимся в BuildServiceArray. Изменив функцию для использования соглашения о вызовах Microsoft (передача параметров в rcx, rdx, r8 и r9, как описано здесь: ), мы можем проследить за param2, который является rdx (нашим объектом cmd_parsed).

1717063489523.png


Мы не задержимся надолго в этой функции, потому что param2 быстро используется функцией ReadPerInstanceRegistryParameters в качестве третьего параметра.

Из этой новой функции видно, что другие поля устанавливаются и используются из нашей основной структуры в зависимости от различных значений, запрашиваемых из реестра по ключу Software\Microsoft\Windows NT\CurrentVersion\Svchost, таких как CoInitializeSecurityParam или CoInitializeSecurityAllowLowBox. Но что более важно, мы видим, что эта функция обращается к нашему полю, установленному флагом P.

1717063584154.png


Как вы можете видеть, если флаг установлен и дескриптор ключа реестра равен 0 (iStack664 содержит дескриптор ключа реестра), то *(ppiVar5 + 0x60) устанавливается. Интересно отметить, в каких еще случаях это поле изменяется. Позже оно устанавливается, если существует значение DynamicCodePolicy. Это интересно. Так что, возможно, наш флаг P из командной строки применяет DynamicCodePolicy?

При следующем упоминании uVar17 (содержащего значение нашего флага P) мы видим, что поле *(ppiVar5 + 0x64) устанавливается в 1. И это поле также устанавливается, когда существует BinarySignaturePolicy.

1717063694698.png


И последнее, для *(ppiVar5 + 0x68), которое устанавливается, когда установлена политика ExtensionPointsPolicy.

1717063637849.png


Что это означает для нас? Это означает, что если флаг P установлен, то различные поля, связанные с DynamicCodePolicy, BinarySignaturePolicy и ExtensionPolicy, устанавливаются в 1. Если это не так, то их значения зависят от значений, найденных в ключе реестра. В конце этой функции наша структура должна выглядеть так:

C:
struct cmd_parsed {
    int64_t field_0;
    int64_t field_1;
    char k_flag;
    char gap_0;
    char gap_1;
    char gap_2;
    char gap_3;
    char gap_4;
    char gap_5;
    char gap_6;
    int64_t lpValue_reg;
    int64_t field_3;
    int64_t field_4;
    int64_t field_5;
    int64_t field_6;
    int64_t field_7;
    int64_t field_8;
    int64_t field_9;
    char gap_7;
    char gap_8;
    char gap_9;
    char gap_9_0;
    char p_flag;
    char gap_10;
    char gap_11;
    char gap_12;
    char DynamicCodePolicy_flag;
    char gap_13;
    char gap_14;
    char gap_15;
    char BinarySignaturePolicy_flag;
    char gap_16;
    char gap_17;
    char gap_18;
    char ExtensionPointsPolicy_flag;
    char gap_19;
    char gap_20;
    char gap_21;
    char gap_22;
    char gap_23;
    char gap_24;
    char gap_25;
    char gap_26;
    char gap_27;
    char gap_28;
    char gap_29;
    char gap_30;
    char gap_31;
    char gap_32;
    char gap_33;
    char *argv;
};

Наконец, мы можем выйти из этой функции. BuildServiceTable не обращается к нашей структуре, а CallPerInstanceInitFunctions — да. При анализе её и применении различных смещений полей к rbx, мы видим, что они проверяются и затем вызывается SetProcessMitigationPolicy, что подтверждает, что эти поля устанавливают различные политики смягчения! И это конец нашего путешествия в svchost!

1717063814661.png
 
Верх Низ