libpeconv
libpeconv copied to clipboard
How to pass arguments to main payload in run-PE (or any program written by libPeConv)?
Issue: I have tried to pass arguments to a payload loaded by libpeconv but it wasn't possible directly. So i decided to go a little bit deeper and modified some stack related parts of the code, it was successful but this method is heavily relied on Non-standard methods and requires more or less complicated modifications on the main code.
Is there any possible ongoing features that are not yet published or any other methods that i could use for this matter?
I've looked into this, you can't simply do EntryPoint(argc, argv), This happens due to the fact that normal PEs entry point is not actually int main(...) but rather int64_t crt_main() which is responsible of calling and _p___argc and __p___argv or __p___wargv and passing them to the real main function after setting up the CRT. seems like there is no straightforward way to approach this but the solution i went with was hooking the real main
here's a quick demo:
constexpr inline ptrdiff_t RealMainOffset = ...;
inline peconv::PatchBackup PayloadMainBackup;
const wchar_t* NewArgv[] = { L"....exe", L"--version" };
constexpr auto NewArgc = sizeof(NewArgv) / sizeof(NewArgv[0]);
inline auto RealEntryPoint = (int(*)(int , const wchar_t** , const char**))nullptr;
static int PayloadMainHook(int argc, const wchar_t** argv, const char** envp)
{
PayloadMainBackup.applyBackup();
argc = NewArgc;
argv = NewArgv;
return RealEntryPoint(argc, argv, envp);
}
int main()
{
// Load the PE normally
// ...
auto EntryPointOffset = peconv::get_entry_point_rva((BYTE*)PayloadModuleBase);
RealEntryPoint = (decltype(RealEntryPoint))(PayloadModuleBase + RealMainOffset);
peconv::redirect_to_local(RealEntryPoint, &PayloadMainHook, &PayloadMainBackup);
auto EntryPoint = (int(*)())(PayloadModuleBase + EntryPointOffset);
return EntryPoint();
}
This for sure isn't convenient for a library, The way the library can approach this imo is doing some simple dynamic analysis on the crt main to find the real main and hook it, i think this is the best way instead of editing the args pushed to the host process.
You need to hook GetCommandLineW & GetCommandLineA functions; CRT uses them for initialize the globals: __p___argc, __p___argv and __p___wargv:
Btw, some programs prefer not to use CRT, but to parse arguments via
GetCommandLineA/W manually.
Sometimes you will also have to patch peb->ProcessParameters->CommandLine, because this is the field that Kernelbase uses to get information about command-line arguments and then issue them to GetCommandLineA/W, so that the programs can use peb->ProcessParameters to get information about command-line arguments
#include <Windows.h>
#include <peconv.h>
#include <string>
#include <cstdint>
std::string gCmdLineA;
std::wstring gCmdLineW;
static const uint8_t pe[] = {
//...
};
LPWSTR
WINAPI
hGetCommandLineW(
VOID
) {
return gCmdLineW.data();
}
LPSTR
WINAPI
hGetCommandLineA(
VOID
) {
return gCmdLineA.data();
}
int main() {
wchar_t selfFileNameW[MAX_PATH]{};
char selfFileNameA[MAX_PATH]{};
if (GetModuleFileNameW(NULL, selfFileNameW, MAX_PATH)) {
gCmdLineW.push_back(L'"');
gCmdLineW.append(selfFileNameW, wcslen(selfFileNameW)); // The first arg is always quoted application path
gCmdLineW.push_back(L'"');
gCmdLineW += L" NewArg1 NewArg2"; // New arguments
gCmdLineW.push_back(L'\0'); // Null terminator
std::wcout << gCmdLineW << std::endl;
}
if (GetModuleFileNameA(NULL, selfFileNameA, MAX_PATH)) {
gCmdLineA.push_back('"');
gCmdLineA.append(selfFileNameA, strlen(selfFileNameA)); // The first arg is always quoted application path
gCmdLineA.push_back('"');
gCmdLineA += " NewArg1 NewArg2"; // New arguments
gCmdLineA.push_back('\0'); // Null terminator
std::cout << gCmdLineA << std::endl;
}
peconv::hooking_func_resolver resolver;
resolver.add_hook("GetCommandLineW", reinterpret_cast<FARPROC>(&hGetCommandLineW));
resolver.add_hook("GetCommandLineA", reinterpret_cast<FARPROC>(&hGetCommandLineA));
RtlAcquirePebLock();
PPEB peb = NtCurrentPeb();
RtlInitUnicodeString(&peb->ProcessParameters->CommandLine, gCmdLineW.data()); // Patching PEB
RtlReleasePebLock();
size_t vSize = 0;
BYTE* loadedPe = peconv::load_pe_executable(pe, sizeof(pe), vSize, &resolver);
// ...
}
And with RunPE everything is much simpler: you just need to pass the arguments to CreateProcessA/W directly.