
[英]Manually loading PE imports results error when parsing PIMAGE_IMPORT_DESCRIPTOR
[英]AllocConsole issue when manually mapping PE + fetching output
我正在从事一个涉及在项目过程中手动映射和执行 PE 的项目。 我使用其代码作为基础的主要项目可以在这里找到: https ://github.com/aaaddress1/RunPE-In-Memory/blob/master/RunPE-In-Memory/RunPEinMemory/RunPEinMemory.cpp
上面的项目编译并运行良好。 在该项目用于运行的 PE 的映射过程中,它将挂钩与处理命令行参数相关的 API,允许用户手动指定参数给映射的 PE,而不是映射的 PE 尝试使用提供给 RunPEinMemory 的参数。 exe(因为这一切都发生在同一个进程中)。
我正在努力的项目与这个基础项目的不同之处在于:
我(已经成功地)连接了 ExitProcess 和 exit() 之类的 API,并将它们重定向到 ExitThread 以防止映射的 PE 结束 RunPEinMemory.exe 进程
我需要最终从别处的映射 PE 发送输出。 为此,我将 stdout/stderr 重定向到我稍后在运行映射的 PE 之前从 RunPEinMemory 中读取的匿名管道。
我在第二个帐户上苦苦挣扎。
我的项目需要编译为 GUI 应用程序 (subsystem=windows) 而不是控制台应用程序 (subsystem=console)。
出现问题的原因是为了操纵标准输入/标准输出/标准错误句柄,需要将控制台附加到进程。 有许多关于此主题的 StackOverflow 帖子,其中介绍了使用 AllocConsole 然后重新打开 std 句柄以将输出重定向到新控制台(或其他地方)。 可以在这里看到关于此事的重要帖子: Redirecting stdout in win32 does not redirect stdout
我已经实现了这段代码,并且可以成功地手动映射/运行 powershell.exe(例如,将“gci”作为参数传递)。 stdout/stderr 被重定向到匿名管道,然后被读取,之后被写入文件(目前用于测试目的)。 这个成功的测试是用我的 project.exe 按预期为 subsystem=windows 编译的。
当我尝试用 cmd.exe 做同样的事情(传递 '/c dir' 作为参数)时,这就分崩离析了。 在这种情况下,输出无法到达 stdout 或 stderr,并且没有可从匿名管道读取的字节。
现在,当我改为将此项目编译为控制台程序 (subsystem=console) 并删除 AllocConsole 调用(因此使用 Windows 为我分配的控制台)时,程序成功。 我注意到 cmd.exe 输出到 stderr,而不是 stdout。 但无论如何,我能够成功将该输出重定向到其中一个匿名管道,读取它,然后将它写到文件中。
这让我相信我在调用 AllocConsole 时遗漏了一些东西,一些后续步骤来完全设置环境,以便某些程序可以成功地将输出发送到 stdout/stderr。 This post mentions about cmd.exe using win32Api's to write to stdout/stderr(与前面链接中提到的其他方法相反),所以我想知道我是否没有成功设置什么时候手动分配控制台: https ://stackoverflow.com/a/66689266/18776214
相关代码如下:
//Allocate console. This line is the one that gets commented out between console/windows test
BOOL suc = AllocConsole();
//Reopen streams after allocating console + disable buffering
FILE* fout;
FILE* ferr;
freopen_s(&fout, "CONOUT$", "r+", stdout);
freopen_s(&ferr, "CONOUT$", "r+", stderr);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
//Open anonymous pipes for stdout and stderr
HANDLE hreadout;
HANDLE hwriteout;
HANDLE hreaderr;
HANDLE hwriteerr;
SECURITY_ATTRIBUTES sao = { sizeof(sao),NULL,TRUE };
SECURITY_ATTRIBUTES sae = { sizeof(sae),NULL,TRUE };
CreatePipe(&hreadout, &hwriteout, &sao, 0);
CreatePipe(&hreaderr, &hwriteerr, &sae, 0);
printf("CreatePipe last error: %d\n", GetLastError());
printf("hreadout is: %p\n", hreadout);
printf("hwriteout is: %p\n", hwriteout);
printf("hreaderr is: %p\n", hreaderr);
printf("hwriterr is: %p\n", hwriteerr);
//Set std_output_handle and std_error_handle to the write-ends of anonymous pipes
SetStdHandle(STD_OUTPUT_HANDLE, hwriteout);
SetStdHandle(STD_ERROR_HANDLE, hwriteerr);
//Convert write-ends of anonymous pipes to file descriptors and use _dup2 to set stdout/stderr to anonymous pipes
int fo = _open_osfhandle((intptr_t)(hwriteout), _O_TEXT);
int fe = _open_osfhandle((intptr_t)(hwriteerr), _O_TEXT);
int res = _dup2(fo, _fileno(fout)); //_fileno(fout)
int res2 = _dup2(fe, _fileno(ferr)); //_fileno(ferr)
printf("fo is: %d\n", fo);
printf("fe is: %d\n", fe);
printf("res is: %d\n", res);
printf("res1 is: %d\n", res2);
//Execute manually mapped PE now that stdout/stderr have been redirected
Sleep(2000);
HANDLE hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)retAddr, 0, 0, 0);
WaitForSingleObject(hThread, 5000);
Sleep(2000);
//Reopen streams again to set stdout/stderr back to console
freopen_s(&fout, "CONOUT$", "r+", stdout);
freopen_s(&ferr, "CONOUT$", "r+", stderr);
//check how much data there is to be read from pipe + allocate buffer
DWORD cbBytesAvailOut;
PeekNamedPipe(hreadout, NULL, NULL, NULL, &cbBytesAvailOut, NULL);
printf("PeekNamedPipe last error: %d\n", GetLastError());
printf("stdout bytes avail is: %d\n", cbBytesAvailOut);
DWORD cbBytesAvailErr;
PeekNamedPipe(hreaderr, NULL, NULL, NULL, &cbBytesAvailErr, NULL);
printf("PeekNamedPipe last error: %d\n", GetLastError());
printf("stderr bytes avail is: %d\n", cbBytesAvailErr);
//Allocate buffer based on number of bytes available to read
wchar_t* pipeBuf = calloc(cbBytesAvailErr + 2, sizeof(wchar_t));
char* convertBuf;
Sleep(2000);
//Currently only reading from a single pipe, edit this block as needed to get data when it exists
//Read from pipe
DWORD bytesRead;
printf("right before readfile!\n");
BOOL fSuccess = ReadFile(hreaderr, pipeBuf, (cbBytesAvailErr + 2) * sizeof(wchar_t), &bytesRead, NULL);
printf("fSuccess is: %d\n", fSuccess);
printf("ReadFile last error: %d\n", GetLastError());
printf("wide string: %ls\n", pipeBuf);
printf("normal string: %s\n", pipeBuf);
printf("bytesread is: %d\n", bytesRead);
//Write buffer out to disk
Sleep(2000);
char* filename = "C:\\Users\\User\\Inline-Execute-PE-main\\Inline-Execute-PE\\x64\\Release\\outfile.txt";
DWORD byteswritten;
HANDLE hFileOut = CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
WriteFile(hFileOut, pipeBuf, bytesRead, &byteswritten, NULL);
所以这两种情况是这样的:
再次使用 powershell.exe 效果很好。 我在想,因为它可能使用与 cmd.exe 不同的方式写入 stdout/stderr。
有人有什么想法吗?
编辑:作为更新,我尝试在 RunPEinMemory 项目将 PE 映射到内存之前调用 AllocConsole()。 这导致 cmd.exe 的输出显示到输出控制台,但它仍未被重定向到管道。 这至少是一个进步,但由于某种原因,cmd.exe 中的 stdout/stderr 似乎仍未链接或连接到整个过程
我终于能够解决这个问题。
问题似乎是当我调用 AllocConsole 并重定向 stdout/stderr 时。
以前的顺序是:
手动映射PE
修复 PE 的 IAT
分配控制台
使用 freopen_s 和 SetStdHandle 重定向标准输出/标准错误
使用 open_osfhandle 和 _dup2 将 Win32 句柄转换为 C std 句柄
CreateThread运行PE
工作顺序是:
分配控制台
使用 freopen_s 和 SetStdHandle 重定向标准输出/标准错误
手动映射PE
修复 PE 的 IAT
使用 open_osfhandle 和 _dup2 将 Win32 句柄转换为 C std 句柄
CreateThread运行PE
所以看起来当映射的 PE 的 IAT 得到修复/设置时,控制台需要已经存在并首先正确设置。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.