打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
动静态混合语言到底节省了多少代码

这里我们提到的静态语言 —— 指的是「原生静态语言」,现代高级语言的确以各种方式简化了编程 —— 但同时也会导致访问原生静态接口越来越困难。但是 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 工具中双击「窗口探测器」,然后拖动左下角的 

到目标窗口上,就可以查看窗口有关的信息。在窗口探测器中就可以看到窗口的启动命令行(包含启动文件路径)。

获取一个程序的启动路径有很多方法,最简单的方法可能是使用 WMI,当然,在其他语言里你看到用这个方案的并不多,更多的你可能会看到他们在抱怨调用 WMI 很复杂,他们宁愿用更麻烦的方法 - 例如从进程 PEB 中去获取。但实际上 aardio 调用 WMI 非常方便,例如获取其他进程的启动命令行只要一句代码:

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 processestypedef 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 WOW64typedef 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 processestypedef 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++ 比作开飞机,大家觉得一般的普通人,开哪个更不容易出事故呢?!

如果非要杠自行车只能做点小事,跑趟菜市场也一定要开着飞机去 —— 这是非常愚蠢的。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
查找父进程,进程的PEB 进程是否被调试 NtQueryInformationProcess
自删除程序的研究及实现
windows 下的自动更新
windows下写的shell脚本,到linux下无法使用的问题
WindowsAPI详解——TerminateProcess 终止|杀死其它进程
Windows核心编程:DLL注入和API拦截
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服