![](/img/trans.png)
[英]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.