前言
上一篇随笔介绍了如何通过管道控制一个程序,但是这一套在控制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;}