管道控制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] = { '\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;
}
posted @ 2018-04-02 22:44  CHOLASLEE  阅读(493)  评论(0编辑  收藏  举报