zoukankan      html  css  js  c++  java
  • 管道控制Telnet

    前言

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

    问题分析

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

    321

    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工具使用Detours,有关Detours的介绍传送门:detours的简介与简单使用

    贴代码咯

    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] = { '' };
    	CHAR outputA[1024] = { '' };
    	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] = '';
    	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;
    }
    
  • 相关阅读:
    记一道乘法&加法线段树(模版题)
    2021CCPC网络赛(重赛)题解
    Codeforces Round #747 (Div. 2)题解
    F. Mattress Run 题解
    Codeforces Round #744 (Div. 3) G题题解
    AtCoder Beginner Contest 220部分题(G,H)题解
    Educational Codeforces Round 114 (Rated for Div. 2)题解
    Codeforces Global Round 16题解
    Educational Codeforces Round 113 (Rated for Div. 2)题解
    AtCoder Beginner Contest 182 F
  • 原文地址:https://www.cnblogs.com/rlee063/p/8698424.html
Copyright © 2011-2022 走看看