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