管道控制Telnet

Posted rlee063

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了管道控制Telnet相关的知识,希望对你有一定的参考价值。

前言

上一篇随笔介绍了如何通过管道控制一个程序,但是这一套在控制telnet的时候意外失效了,CreateProcess执行之后telnet闪退了。

问题分析

不修改标准输入输出的话CreateProcess之后程序能被正常执行,可以判断是修改了标准输入输出句柄导致的。为了得到更多信息,用IDA打开telnet分析,找出使程序闪退的语句。

此处有图

mian函数的主体逻辑如上,程序最开始调用了GetStdHandle(),并且通过返回值判断是否退出程序,这很关键

//Retrieves a handle to the specified standard device (standard input, standard output, or standard error).
HANDLE WINAPI GetStdHandle(
  _In_ DWORD nStdHandle
);

函数返回当前的标准输入输出句柄,由于我们已经用管道替换了,所以返回的应该是我们管道的句柄,通过写一个测试程序可以测试到返回的句柄确实是我们的管道句柄。
所以程序并不在最开始的判断处退出。
为了更快定位到出错的地方,用GetExitCodeProcess()获得程序返回的值。(然而后来发现这里直接测试下一个函数更快)

//Retrieves the termination status of the specified process.
BOOL WINAPI GetExitCodeProcess(
  _In_  HANDLE  hProcess,
  _Out_ LPDWORD lpExitCode
);

程序运行中就返回259,其余的数值就是程序退出时的返回值,telnet的错误返回值是-1。回到程序代码,在main中返回-1的话只剩下GetConsoleScreenBufferInfo()函数返回0这一种可能,当然程序在其他地方也可以通过exit(-1)来使程序退出并且返回-1,在IDA中搜索exit并没有发现其他可疑的地方,大胆猜测就是这儿了好吧。写一个测试程序发现果然GetConsoleScreenBufferInfo()不认传进去的管道句柄。

//
Retrieves information about the specified console screen buffer.
BOOL WINAPI GetConsoleScreenBufferInfo(
  _In_  HANDLE                      hConsoleOutput,
  _Out_ PCONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo
);

在函数的介绍页面上并没有看见为什么传管道句柄不行,但是在writeConsole()的介绍里发现writeConsole()在传入的句柄是重定向到其他文件的句柄时会失败,猜测就是这样,因为telnet采用对console的write和read一系列操作,而cmd采用的是writeFile类似的操作。

WriteConsole fails if it is used with a standard handle that is redirected to a file. If an application processes multilingual output that can be redirected, determine whether the output handle is a console handle (one method is to call the GetConsoleMode function and check whether it succeeds). If the handle is a console handle, call WriteConsole. If the handle is not a console handle, the output is redirected and you should call WriteFile to perform the I/O. Be sure to prefix a Unicode plain text file with a byte order mark. For more information, see Using Byte Order Marks.

同时也说,可以通过CreateFile返回标准输入输出句柄,于是

解决方案1

大致思路呢就是不再创建管道,而通过CreateFile创建能够返回标准输入输出句柄的文件来控制telnet,其中主要解决两个问题:1)让telnet不会因为该句柄而拒绝 2)能够通过句柄来控制

让telnet接受

最开始设置telnet的句柄为CreateFile返回的句柄的时候,惊人的被拒绝了,没理由啊,在测试程序里都能接受,难道程序是被调用的就不行了?后来偶然发现在CreateProcess的时候添加一个flag CREATE_NEW_CONSOLE 就可以了。

这个地方其实没有搞懂为什么,也为后面埋下伏笔.

控制

似乎不能用WriteFile和ReadFile来使用标准的返回的句柄,但是查询MSDN可以发现可以通过writeConsoleInput()和ReadOutputBufferCharacters()来实现写入和读出管道类似的功能。ReadOutputBufferCharacters()亲自测试确实能够返回,但是在测试writeConsoleInput()之前陷入了沉思。

很奇怪我明明给被调用程序传的是新建的句柄,但是它还是会在控制程序的console上抢输入输出。MSDN上说如果不新建console就是会抢输入输出,但是我新建console就不接受我的句柄,好像事情变得困难了起来,秉承避重就轻的思想,我放弃了这一条路

有兴趣的话可以按照我这一条路实现下去,或者你对console有见解,知道我的问题出在哪里也可以告诉我。

解决方案2

(这条路才是老师想让我们走的路吧喂)仍旧是传入管道句柄通过管道控制,通过HOOK WIN32 API的方式,将基于console的操作变为基于file就好了。
实现的方法有很多,我给出一个思路:1)hook所有与write和read相关的api,重定向到管道上去 2)hook getStdHandle让他返回createFile新建的句柄用来应付其他console相关的函数,但是由于write和read需要获取管道句柄,但管道句柄通过getStdHandle返回,所以根据调用getStdHandle的次序返回不同的Handle。搜索发现telnet中只有最开始调用了getStdHandle,那么很简单,第二次以后函数正常工作,第一次第二次就调用createFile创建。
emmm说起来很简单,但是想这个逻辑还是花了一点时间,比如最开始想要找到存放handle的全局变量。
hook工具使用

贴代码咯

control

#include<iostream>
#include<Windows.h>
#include<cstdio>
#include<detours.h>
using namespace std;
bool createShell(HANDLE * readH, HANDLE * writeH, PROCESS_INFORMATION * pi) {
    SECURITY_ATTRIBUTES sa = { 0 };
    sa.nLength = sizeof(sa);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;

    HANDLE rh1, wh1, rh2, wh2;
    if (!CreatePipe(&rh1, &wh1, &sa, 1024)) {//outputpipe
        return false;
    }
    if (!CreatePipe(&rh2, &wh2, &sa, 1024)) {//input pipe
        return false;
    }
    *readH = rh1;
    *writeH = wh2;

    STARTUPINFOA  si;
    memset(&si, 0, sizeof(si));
    si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
    si.wShowWindow = SW_HIDE;
    si.hStdInput = rh2;//rh2
    si.hStdOutput = wh1;//wh1
    si.hStdError = wh1;

    if (!DetourCreateProcessWithDllA(NULL, "C:\\Users\\hasee\\Desktop\\tellnet\\telnet.exe", NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS|CREATE_NEW_CONSOLE, NULL, NULL, &si, pi, "D:\\Visual_studio_test\\VirusExcercise\\Fortelnet\\detoursTest3\\Debug\\detoursTest3.dll", NULL)) {
        cout << "CreateProcess error!" << endl;
        return false;
    }
    return true;
}

bool printMsg(HANDLE readH) {
    WCHAR output[1024] = { \0 };
    CHAR outputA[1024] = { \0 };
    unsigned long n;

    for (int i = 0; i < 10; i++) {
        while (1) {
            if (!PeekNamedPipe(readH, NULL, 0, NULL, &n, NULL)) {
                cout << "cannot get msg" << endl;
                return false;
            }
            if (n == 0) 
            {
                break;
            }
            if (!ReadFile(readH, output, n, &n, NULL)) {
                cout << "cannot read file" << endl;
                return false;
            }
            output[n] = NULL;
            WideCharToMultiByte(CP_ACP, 0, output, -1, outputA, n, NULL, FALSE);
            cout << outputA ;
        }
        Sleep(100);
    }
    return 1;
}

int main() {
    HANDLE input = GetStdHandle(STD_INPUT_HANDLE);
    HANDLE readH, writeH;
    CHAR output[1024];

    output[1023] = \0;
    u_long n2;
    PROCESS_INFORMATION pi;
    if (!createShell(&readH, &writeH, &pi)) {
        system("pause");
        return 1;
    }
    printMsg(readH);
    WCHAR * cmd = new WCHAR[1024];
    u_long n;
    int Ecode;
    COORD a;
    a.X = 0;
    a.Y = 0;

    WCHAR *tempB[1024];

    while (1) {
        ReadConsoleW(input, cmd, 1024, &n2, NULL);//ReadConsole
        WriteFile(writeH, cmd, n2*2, &n, NULL);//write pipe
        if (!printMsg(readH)) {
            TerminateProcess(pi.hProcess, 0);
            system("pause");
            return 1;
        }
    }
    system("pause");
    return 0;
}

dll

#include "stdafx.h"
#include <Windows.h>
#include <WinBase.h>
#include <detours.h>

HANDLE(WINAPI* OLD_GetStdHandle)(DWORD nStdHandle) = GetStdHandle;

BOOL(WINAPI * OLD_WriteConsoleW)(HANDLE  hConsoleOutput, const VOID *lpBuffer, DWORD   nNumberOfCharsToWrite, LPDWORD lpNumberOfCharsWritten, LPVOID  lpReserved) = WriteConsoleW;

BOOL(WINAPI * OLD_ReadConsoleInputW)(HANDLE hConsoleInput, PINPUT_RECORD lpBuffer, DWORD nLength, LPDWORD lpNumberOfEventsRead) = ReadConsoleInputW;

BOOL(WINAPI * OLD_ReadConsoleInputA)(HANDLE hConsoleInput, PINPUT_RECORD lpBuffer, DWORD nLength, LPDWORD lpNumberOfEventsRead) = ReadConsoleInputA;

BOOL(WINAPI * OLD_ReadConsoleW)(HANDLE  hConsoleInput, LPVOID  lpBuffer, DWORD   nNumberOfCharsToRead, LPDWORD lpNumberOfCharsRead, PCONSOLE_READCONSOLE_CONTROL  pInputControl) = ReadConsoleW;

int c = 0;

HANDLE WINAPI NEW_GetStdHandle(DWORD nStdHandle) {
    HANDLE ret = 0;
    if (c < 2 ) {
        SECURITY_ATTRIBUTES sa;
        sa.nLength = sizeof(sa);
        sa.lpSecurityDescriptor = NULL;
        sa.bInheritHandle = TRUE;
        if (nStdHandle == STD_INPUT_HANDLE) {
            ret = CreateFileA("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, &sa, OPEN_EXISTING, NULL, NULL);
        }
        else {
            ret = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, &sa, OPEN_EXISTING, NULL, NULL);
        }
        c++;
        return ret;
    }
    else {
        ret = OLD_GetStdHandle(nStdHandle);
        return ret;
    }
}

BOOL WINAPI NEW_WriteConsoleW(HANDLE  hConsoleOutput, const VOID *lpBuffer, DWORD   nNumberOfCharsToWrite, LPDWORD lpNumberOfCharsWritten, LPVOID  lpReserved) {
    HANDLE wPip = GetStdHandle(STD_OUTPUT_HANDLE);
    WriteFile(wPip, lpBuffer, nNumberOfCharsToWrite*2, lpNumberOfCharsWritten, NULL);
    return TRUE;
}

BOOL WINAPI NEW_ReadConsoleInputW(HANDLE hConsoleInput, PINPUT_RECORD lpBuffer, DWORD nLength, LPDWORD lpNumberOfEventsRead) {
    HANDLE rPip = GetStdHandle(STD_INPUT_HANDLE);
    ReadFile(rPip, MultiBytes, nLength, lpNumberOfEventsRead, NULL);
    *lpNumberOfCharsRead /= 2;
    return TRUE;
}

BOOL WINAPI NEW_ReadConsoleInputA(HANDLE hConsoleInput, PINPUT_RECORD lpBuffer, DWORD nLength, LPDWORD lpNumberOfEventsRead) {
    char wideChar[1024];
    HANDLE rPip = GetStdHandle(STD_INPUT_HANDLE);
    ReadFile(rPip, wideChar, nLength, lpNumberOfEventsRead, NULL);
    DWORD dwNum = MultiByteToWideChar (CP_ACP, 0, wideChar, -1, NULL, 0);
    MultiByteToWideChar(CP_ACP, 0, wideChar, -1, lpBuffer, dwNum);
    *lpNumberOfCharsRead *= 2;
    return TRUE;
}

BOOL WINAPI NEW_ReadConsoleW(HANDLE  hConsoleInput, LPVOID  lpBuffer, DWORD   nNumberOfCharsToRead, LPDWORD lpNumberOfCharsRead, PCONSOLE_READCONSOLE_CONTROL  pInputControl) {
    HANDLE rPip = GetStdHandle(STD_INPUT_HANDLE);
    ReadFile(rPip, lpBuffer, nNumberOfCharsToRead, lpNumberOfCharsRead, NULL);
    *lpNumberOfCharsRead /= 2;
    return TRUE;
}

void Hook() {
    DetourRestoreAfterWith();
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourAttach((PVOID *)&OLD_WriteConsoleW, NEW_WriteConsoleW);
    DetourAttach((PVOID *)&OLD_ReadConsoleInputW, NEW_ReadConsoleInputW);
    DetourAttach((PVOID *)&OLD_ReadConsoleInputA, NEW_ReadConsoleInputA);
    DetourAttach((PVOID *)&OLD_ReadConsoleW, NEW_ReadConsoleW);
    DetourAttach((PVOID *)&OLD_GetStdHandle, NEW_GetStdHandle);
    DetourTransactionCommit();
}

void UnHook() {
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourDetach((PVOID *)&OLD_WriteConsoleW, NEW_WriteConsoleW);
    DetourDetach((PVOID *)&OLD_ReadConsoleInputW, NEW_ReadConsoleInputW);
    DetourDetach((PVOID *)&OLD_ReadConsoleInputA, NEW_ReadConsoleInputA);
    DetourDetach((PVOID *)&OLD_ReadConsoleW, NEW_ReadConsoleW);
    DetourDetach((PVOID *)&OLD_GetStdHandle, NEW_GetStdHandle);
    DetourTransactionCommit();
}


BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        Hook();
        break;
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        UnHook();
        break;
    }
    return TRUE;
}

以上是关于管道控制Telnet的主要内容,如果未能解决你的问题,请参考以下文章

[Go] 通过 17 个简短代码片段,切底弄懂 channel 基础

在 Windows 命令行中使用管道

使用 FFmpeg 通过管道输出视频片段

管道文件和I/O文件用途

r 计算管道的步骤(基本片段)

15种Python片段去优化你的数据科学管道