• 管道控制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;
    }
    
  • 相关阅读:
    开发者入门必读:最值得看的十大机器学习公开课
    ansible 文件模块,很实用
    前端之Bootstrap框架
    47考题整理
    前端之jQuery
    前端之BOM和DOM
    python补充之进制转换、exec、eval、compile
    JavaScript
    css(2)
    前端之form表单与css(1)
  • 原文地址:https://www.cnblogs.com/rlee063/p/8698424.html
Copyright © 2020-2023  润新知