这里我们提到的静态语言 —— 指的是「原生静态语言」,现代高级语言的确以各种方式简化了编程 —— 但同时也会导致访问原生静态接口越来越困难。但是 aardio 打破了这个束缚,aardio 虽然是一个动态语言,但同时支持原生静态类型开发,可以方便地访问原生接口和代码,这带来极大的好处。
今天说个具体一点的例子:现在我们提出一个需求,我们要读取另外一个进程的启动命令行参数,一个典型的应用就是窗口探测器。例如你的桌面突然弹出了一个广告窗口,这个广告非常鸡贼地让你难以查觉他到底来自哪个程序,这时你用一般的窗口探测程序是检测不到启动路径的,因为有些狡猾的广告会用管理权限启动。
你可能会有疑问,为什么用管理权限启动,却没有出现用于确认的警告窗口呢?!其实要跳过这个警告获取管理权限是非常容易的。aardio 中有一个库 sys.runAsTask 就可以实现这样的效果,下面是开源软件 wubiLex 设置开机启动的源代码:
var task = sys.runAsTask('wubiLex( 五笔助手 ) 启动任务','用于微软五笔的辅助工具。');
if(winform.chkEnableSystemRun.checked){
task.register('/tray');
}
else {
task.delete();
}
如果有兴趣可以去看一下 sys.runAsTask 的源代码,aardio 标准库都是开源的。
回到我们的主题,在 aardio 中有一个窗口探测器,现在大家可能会明白为什么这个窗口探测器需要以管理权限启动了。
我们在 aardio 工具中双击「窗口探测器」,然后拖动左下角的
process.wmi(pid).CommandLine
你可能会说你不就是用了库吗,但实际上 process.wmi 这个库的关键只有一句:
com.wmi.getProperties('SELECT * FROM Win32_Process WHERE Handle = @id',,{id=pid});
另外 com.wmi 这个库里也没有几句代码。
当然我们今天要讲的不是 WMI 或者 COM,我们不讲这种 aardio 存在压倒性压势的方案。我们今天要讲的是 C++才拥有压倒性优势的原生解决方案:从进程的 PEB 里读取启动命令行,我们先看一下 C++代码,有点长:
//GetCmdLine.h:
#pragma once
#include 'stdafx.h'
// NtQueryInformationProcess for pure 32 and 64-bit processes
typedef NTSTATUS (NTAPI *_NtQueryInformationProcess)(
IN HANDLE ProcessHandle,
ULONG ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
typedef NTSTATUS (NTAPI *_NtReadVirtualMemory)(
IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
OUT PVOID Buffer,
IN SIZE_T Size,
OUT PSIZE_T NumberOfBytesRead);
// NtQueryInformationProcess for 32-bit process on WOW64
typedef NTSTATUS (NTAPI *_NtWow64ReadVirtualMemory64)(
IN HANDLE ProcessHandle,
IN PVOID64 BaseAddress,
OUT PVOID Buffer,
IN ULONG64 Size,
OUT PULONG64 NumberOfBytesRead);
// PROCESS_BASIC_INFORMATION for pure 32 and 64-bit processes
typedef struct _PROCESS_BASIC_INFORMATION {
PVOID Reserved1;
PVOID PebBaseAddress;
PVOID Reserved2[2];
ULONG_PTR UniqueProcessId;
PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;
// PROCESS_BASIC_INFORMATION for 32-bit process on WOW64
// The definition is quite funky, as we just lazily doubled sizes to match offsets...
typedef struct _PROCESS_BASIC_INFORMATION_WOW64 {
PVOID Reserved1[2];
PVOID64 PebBaseAddress;
PVOID Reserved2[4];
ULONG_PTR UniqueProcessId[2];
PVOID Reserved3[2];
} PROCESS_BASIC_INFORMATION_WOW64;
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef struct _UNICODE_STRING_WOW64 {
USHORT Length;
USHORT MaximumLength;
PVOID64 Buffer;
} UNICODE_STRING_WOW64;
//GetCmdLine.cpp:
#include 'stdafx.h'
#include 'GetCmdLine.h'
int _tmain(int argc, _TCHAR* argv[])
{
if (argc < 2)
{
printf('Format is GetCmdLine <process id>\n');
return 0;
}
// get process identifier
DWORD dwId = _wtoi(argv[1]);
// open the process
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwId);
DWORD err = 0;
if (hProcess == NULL)
{
printf('OpenProcess %u failed\n', dwId);
err = GetLastError();
return -1;
}
// determine if 64 or 32-bit processor
SYSTEM_INFO si;
GetNativeSystemInfo(&si);
// determine if this process is running on WOW64
BOOL wow;
IsWow64Process(GetCurrentProcess(), &wow);
// use WinDbg 'dt ntdll!_PEB' command and search for ProcessParameters offset to find the truth out
DWORD ProcessParametersOffset = si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ? 0x20 : 0x10;
DWORD CommandLineOffset = si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ? 0x70 : 0x40;
// read basic info to get ProcessParameters address, we only need the beginning of PEB
DWORD pebSize = ProcessParametersOffset + 8;
PBYTE peb = (PBYTE)malloc(pebSize);
ZeroMemory(peb, pebSize);
// read basic info to get CommandLine address, we only need the beginning of ProcessParameters
DWORD ppSize = CommandLineOffset + 16;
PBYTE pp = (PBYTE)malloc(ppSize);
ZeroMemory(pp, ppSize);
PWSTR cmdLine;
if (wow)
{
// we're running as a 32-bit process in a 64-bit OS
PROCESS_BASIC_INFORMATION_WOW64 pbi;
ZeroMemory(&pbi, sizeof(pbi));
// get process information from 64-bit world
_NtQueryInformationProcess query = (_NtQueryInformationProcess)GetProcAddress(GetModuleHandleA('ntdll.dll'), 'NtWow64QueryInformationProcess64');
err = query(hProcess, 0, &pbi, sizeof(pbi), NULL);
if (err != 0)
{
printf('NtWow64QueryInformationProcess64 failed\n');
CloseHandle(hProcess);
return -1;
}
// read PEB from 64-bit address space
_NtWow64ReadVirtualMemory64 read = (_NtWow64ReadVirtualMemory64)GetProcAddress(GetModuleHandleA('ntdll.dll'), 'NtWow64ReadVirtualMemory64');
err = read(hProcess, pbi.PebBaseAddress, peb, pebSize, NULL);
if (err != 0)
{
printf('NtWow64ReadVirtualMemory64 PEB failed\n');
CloseHandle(hProcess);
return -1;
}
// read ProcessParameters from 64-bit address space
// PBYTE* parameters = (PBYTE*)*(LPVOID*)(peb + ProcessParametersOffset); // address in remote process address space
PVOID64 parameters = (PVOID64) * ((PVOID64*)(peb + ProcessParametersOffset)); // corrected 64-bit address, see comments
err = read(hProcess, parameters, pp, ppSize, NULL);
if (err != 0)
{
printf('NtWow64ReadVirtualMemory64 Parameters failed\n');
CloseHandle(hProcess);
return -1;
}
// read CommandLine
UNICODE_STRING_WOW64* pCommandLine = (UNICODE_STRING_WOW64*)(pp + CommandLineOffset);
cmdLine = (PWSTR)malloc(pCommandLine->MaximumLength);
err = read(hProcess, pCommandLine->Buffer, cmdLine, pCommandLine->MaximumLength, NULL);
if (err != 0)
{
printf('NtWow64ReadVirtualMemory64 Parameters failed\n');
CloseHandle(hProcess);
return -1;
}
}
else
{
// we're running as a 32-bit process in a 32-bit OS, or as a 64-bit process in a 64-bit OS
PROCESS_BASIC_INFORMATION pbi;
ZeroMemory(&pbi, sizeof(pbi));
// get process information
_NtQueryInformationProcess query = (_NtQueryInformationProcess)GetProcAddress(GetModuleHandleA('ntdll.dll'), 'NtQueryInformationProcess');
err = query(hProcess, 0, &pbi, sizeof(pbi), NULL);
if (err != 0)
{
printf('NtQueryInformationProcess failed\n');
CloseHandle(hProcess);
return -1;
}
// read PEB
if (!ReadProcessMemory(hProcess, pbi.PebBaseAddress, peb, pebSize, NULL))
{
printf('ReadProcessMemory PEB failed\n');
CloseHandle(hProcess);
return -1;
}
// read ProcessParameters
PBYTE* parameters = (PBYTE*)*(LPVOID*)(peb + ProcessParametersOffset); // address in remote process adress space
if (!ReadProcessMemory(hProcess, parameters, pp, ppSize, NULL))
{
printf('ReadProcessMemory Parameters failed\n');
CloseHandle(hProcess);
return -1;
}
// read CommandLine
UNICODE_STRING* pCommandLine = (UNICODE_STRING*)(pp + CommandLineOffset);
cmdLine = (PWSTR)malloc(pCommandLine->MaximumLength);
if (!ReadProcessMemory(hProcess, pCommandLine->Buffer, cmdLine, pCommandLine->MaximumLength, NULL))
{
printf('ReadProcessMemory Parameters failed\n');
CloseHandle(hProcess);
return -1;
}
}
printf('%S\n', cmdLine);
return 0;
}
你可以用你熟悉的动态语言尝试实现一下上面的功能?!你可以任意地去找可以使用的库,流行的编程语言你能轻松找到几GB,或者几TB的库。不过我猜你大概率会放弃。
好吧,让我们尝试用 aardio 实现上面的功能,同样做到兼容 32 位、64位目标进程,从进程 PEB 里读取外部进程的启动命令行,aardio 真的只要几句代码就可以做到:
import process;
var prcs = process(pid,0x1410);
var pebAddr = prcs.getInfo().pebBaseAddress;
var cmdline;
if(prcs.isX64()){
var param = prcs.readStruct(pebAddr+0x20,{LONG addr})
cmdline = prcs.readStruct(param.addr + 0x70,{WORD length;WORD maxLength;LONG buffer;})
}
else {
var param = prcs.readStruct(pebAddr+0x10,{INT addr})
cmdline = prcs.readStruct(param.addr + 0x40,{WORD length;WORD maxLength;INT buffer;})
}
var str = prcs.readStringUtf16(cmdline.buffer,cmdline.length/2)
有人可能又要说了,你不又是用了库吗?!你首先要明白,C++ 代码一样需要库,仅仅是声明 API 头文件的代码量都是惊人的。而且操作原生接口和指针,C++ 更有优势。我上面引用的 process,本身的源代码也并不多( 这个库也是用纯 aardio 代码编写 )。
这段代码我放到了标准库 process.cmdline 里面,用起来可以更简单,例如窗口探测器里就是这样调用:
winform.editPath.text = process.cmdline(pid);
今天举了一个很简单的例子,让大家了解 aardio 这样混合开发的模式具体可以节省我们多少时间和代码。
编程小白有一个常见的误解,觉得用 C++ 这样复杂的语言写的程序就一定很强大很稳定并一定有更好的可靠性,而 aardio 这样的编程语言一定是不太靠谱的,事实恰恰相反,用 aardio 写的程序通常有更少的 BUG,更加的稳定,更加可控。这很容易理解,如果把 aardio 比作自行车,C++ 比作开飞机,大家觉得一般的普通人,开哪个更不容易出事故呢?!
如果非要杠自行车只能做点小事,跑趟菜市场也一定要开着飞机去 —— 这是非常愚蠢的。
联系客服