本文最后更新于 2025年6月8日 下午
软件安全复习/知识总结
考试前的复习,顺便回顾一下知识。
[TOC]
杂七杂八的基础概述知识
中午状态不佳,先总一下基本常识
软件安全面临的安全威胁:软件自身的安全(软件漏洞)、恶意代码、软件侵权
软件漏洞通常被认为是软件生命周期中与安全相关的设计错误、编码缺陷、运行故障等
恶意代码:软件或代码片段,如二进制执行文件、脚本语言代码、宏代码或是寄生在其他代码或启动扇区中的一段指令。示例:计算机病毒、特洛伊木马、僵尸网络、网络钓鱼
信息安全的基本属性
保密性、完整性、可用性、可认证性、授权、可审计性、不可否认性、可控性、可存活性
软件安全
软件安全是信息安全保障的重要内容,漏洞分析是降低系统脆弱性的最有效方法,软件安全防护围绕漏洞消除展开
软件漏洞
漏洞是存在于信息系统、系统安全过程、内部控制或实现过程中的可被威胁源攻击或触发的弱点
漏洞是信息系统自身具有的弱点或缺陷,环境通常是特定的
软件漏洞涉及设计错误、编码缺陷及运行故障
软件漏洞的特点 :持久性、时效性、广泛性、具体性、可利用性、隐蔽性
基于漏洞成因的分类 :内存破坏类、逻辑错误类、输入验证类、设计错误类和配置错误类
软件安全开发模型
减小攻击面:最小权限原则
考点:实施
课本119页就是ppt的这几点。课本63页是栈溢出保护(GS)、DEP(NX)、aslr、safeSEH等机制。
web相关知识 ssrf(服务器端请求伪造) SSRF 服务端请求伪造是一个 web 漏洞,它允许攻击者诱导服务端程序向攻击者选择的任何地址发起 HTTP 请求。
eg:baisc ssrf:后端服务的URL请求地址包含在了前端,可以抓包修改为本地访问;pikachu:利用file协议获取本地信息,dict进行端口腿内侧,
os top10
失效的访问控制:没做权限校验,手工改参数
加密机制失效:数据库明文、短哈希、base64传递密码
注入:sql注入等
不安全设计:登录接口未限流、使用问题与答案的形式来证明身份
安全配置错误:未修改默认账户密码、默认配置和默认文件未删除、敏感文件泄露、服务器将详细的报错信息返回给用户
自带缺陷和过时的组件:如复现的那个cve,设定随机数种子来产生图片
身份识别和验证错误:应用会话超时、弱口令等
软件和数据完整性故障:固件更新没有签名
安全日志和监控故障:没有对系统进行日志监控或记录
服务端请求伪造:注意:csrf是客户端伪造;ssrf才是服务端请求伪造。扫描内部服务器端口、访问敏感文件、滥用内部服务进行恶意代码的执行
攻击点:输入框、搜索框、上传接口、登录口、后台地址等
cve的实例:
CVE-2023-42820 是 JumpServer 开源堡垒机中的一个高危漏洞,其核心问题在于随机数种子泄露,导致未授权攻击者可预测密码重置 Token,进而接管未启用多因子认证(MFA)的用户账户。属于6
django-simple-captcha的视图生成了验证码,它将生成的验证码存储的url以hashkey的形式传给用户,这个key被用作随机数种子。(word报告,截个图得了)
windows下的溢出攻击 缓冲区溢出 栈溢出及相关
栈的栈帧及函数调用过程
异常处理SEH结构,该结构存放于栈中,可以通过涉及特定的一处数据,将异常函数的入口覆盖为shellcode的跳转指令,使程序通过发生异常来执行shellcode
堆溢出-无保护下-dword shoot 任意地址写
将bk改为目标地址-4*2(linux,32位下),fd改为要改的内容
node->bk->fd = node ->fd
课上练习–根据调试内容画出windows下的堆块情况(ppt看着怪怪的,自己调一次)(无比怀念linux下用pwndbg直接出来)
源码:
第一次heapalloc后,h1位于0x8204b0
第二次,h2位于0x8204C0
后续依次,从B0到00,共六个块
啊,里面有int3给我直接中断了,重新来,这次的堆基址是0x550000,同样从04B0开始,补一张创建完的图
free掉h1后
free掉h3、h5后
再执行一次alloc后
欸?先进后出?怎么依稀记得堆是先进先出来着,搜了搜,以下两图截自堆漏洞挖掘中bins的单向链表、双向链表存储结构_mb6128aabee41d4的技术博客_51CTO博客
同时回忆了一下,fastbin是往前插入的,每次都往bin的表头里插入,再使用的时候也是从表头开始
而且windows的堆管理机制与linux不同,问了ai,ai说单个子桶是后进先出。
浅画一下3个堆块都free后的内容,以及修改(linux的32位需要减去8,因为->fd的索引是加了0x8,不知道windows,老师讲的是目标地址,就先这么写吧,晚上调试改一下值试试)
动调改值尝试:由于原始代码把1从空闲块中取出,所以改1
这里有空地址,写在这里方便看
改了这里
执行完之后发现没有申请这个块,不知道申请到哪儿了,重来一次,换个地址改,可能那里不可写,直接改成堆上的一个地址
改为19ff38
并没有实现,应该是有保护机制的。。
假设检测fd->bk=p = bk->fd,且对应与linux一样加了pre_size、size
报错了。。。假设不加,再试试
修改,目标19ff50
还是检测到了。
暂且这样吧,有时间有精力的时候再去看看windows的堆分配
另:回顾老师的ppt,发现这同一个程序,老师的heap_size=8的时候分配了0x20字节,我这里的调试是0x10字节。。。。。
甚至老师ppt的那个链表的添加也是从小到大往队尾填的(也就是老师那个会是先进先出)
ok版本不同这么大区别,只能具体情况看着调试的具体分析了。
windows下的保护机制
GS保护(即linux下的canary)
DEP保护(linu下的nx保护,限制了执行的权限)
ASLR(地址随机化)
SafeSEH(检测seh对应的函数地址是否被修改):其实现原理是,编译器在链接生成二进制IMAGE时,把所有合法的异常处理函数的地址解析出来制成一张安全的SEH表,保存在程序的IMAGE数据块里面,当程序调用异常处理函数时会将函数地址与安全SEH表中的地址进行匹配,检查调用的异常处理函数是否位于该表中。
恶意代码分析基础 计算机的启动与PE 自bios芯片开始,从主引导记录读取,启动硬盘,找到激活分区,启动操作系统(内核装载、windows分区激活)
pe基本信息:
导入表:导入函数地址表(iat)、导入函数目录表(it)
导出表:根据编号、函数名字查找返回地址
恶意代码
恶意代码可能是独立的PE文件,亦可以不是,可能有导入表,也可能没有。如果没有,就先找到kernel32.dll的载入地址,然后找到GetProcAddree函数,在用它找到LoadLibraryA函数,从而载入其他链接库的函数。(寻找可以采用弹栈、MZ、PE来找,这个逆向工程小结有;或者查询peb,这个后续写)
弹栈找法:
栈顶是kernel32的一个函数的地址
基于PEB的搜索 fs:[0xc]寻址到ldr表,里面有dll库加载的链表,通常+0x14,从第二个里找,下图是结构
这是具体运行时显示的值
内存位置的第一个是当前执行的程序
索引到的项-8+02c是库的名字,一般第三个是kernel32.dll库
流程 找到kernel32载入地址后,用GetProcAddress这个字符串,从kernel32导出表用名字逐项比对,找到GetProcAddress的地址,然后用它得到LoadLibraryA地址,从而实现任意dll、任意函数的加载
比对过程的一部分:(里面的明文是根据上下文自己命名的,ppt里给出的那些不足以定位,但是问了edi、esi、ecx分别是什么)
分别是:导出表中的一个函数名、储存的GetProcAddress字符串的地址、字符串的长度
重定位: call下一个地址,然后pop出来,以此定位当前运行到的地址
shellcode的执行 写入的shellcode,可以经过哈希值来调用函数,或者异或加密,然后解密后写到VirtualAlloc创建的可读可写可执行空间,跳转过去执行。
VirtualProtect可以将内存的读写权限进行修改;跳转执行可以用CreatThread函数创建线程,然后将函数执行地址设置为分配的内存空间的起始地址。
shellocde的生成:
实现 Kernel32 基址定位,并通过函数名 hash 值,遍历导出表找到 GetProcAddress 函数入口地址,利用 GetProcAddress 函数得到 LoadLibraryA 函数入口地址,从而调用需要用到的 DLL,如user32.DLL,调用其中的MessageBox显示相关信息。
静态分析 静态分析:反病毒引擎搜索、恶意代码的指纹哈希、ASCII 和 Unicode 字符串(如ip 地址、网址、提示信息、硬编码的互斥量等)、导入表的一些函数(如 io 读写函数,ReadFile、WriteFile;监控的 hook 函数 SetWindowsHookEx;Advapi32.dll 库对注册表的敏感操作)、节大小的分配异常(如 size of raw data 为 0 或远小于分配的 visual size)、节大小的异常名称(特殊的自行命名的节)、资源节可能隐藏了恶意程序与驱动。
学会看汇编的循环、选择等 恶意代码通过dll来执行
方法:将恶意代码保存到dll,从而加载到其他进程中;使用widows dll交互,使用第三方dll
实施:恶意代码通过创建一个新进程,或修改一个已存在的进程,来执行当前程序之外的代码。CreateProcess函数创建进程;或用CreateThread来加载恶意dll
加载新的恶意DLL:
CreateThread时将起始地址设置为Loadlibrary的地址。
传递给CreateThread的参数是要被加载库的名字。新的DLL被加载到这个进程的内存中, 然后DllMain被调用。
恶意代码经常使用互斥量来使得一次只运行恶意代码的一个实例,经常用硬编码来作为互斥量的名字
恶意代码会创建服务来使得持久化运行,常使用win32_share_process类型的服务来执行恶意代码
恶意行为
使用下载器和启动器,下载恶意文件、执行恶意文件
URLDownloadtoFileA和WinExec,来下载并运行新的恶意代码。
后门:反向连接,创建套接字连接远程服务器并与cmd.exe标准流绑定,用隐藏窗口的方式创建cmd程序
窃取行为:窃取登录凭证、记录键盘击键行为、获取口令哈希。。。采用hook技术,当捕获到一些信息(入键盘输入),就执行自定义的监控函数;可以通过hook钩取键盘消息,然后执行恶意dll来执行(dll载入后会执行初始化函数,这里可以写入执行恶意行为)
特洛伊木马: 通过修改函数入口点,跳转到恶意代码,再跳转回去(就是补丁的恶意用法)
dll默认载入顺序: 当前目录、系统目录、windows目录、path环境变量的目录。注册表KnownDLL保护了一些DLL,强制这些DLL的加载路径。
IAT HOOK: 本质上是修改了修改IAT表中函数的入口地址,使得原本执行的函数不被执行,而是执行hook后的自定义函数
过程:找到模块基址、找到iat表、找到函数、修改权限为可写、修改为自定义函数地址、恢复权限
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 #include "stdafx.h" #include <stdio.h> #include <windows.h> #include <stdlib.h> #include <Tlhelp32.h.> DWORD g_dwIATHookFlag=0 ; DWORD g_dwOldAddr; DWORD g_dwNewAddr; BOOL SetIATHook (DWORD dwOldAddr,DWORD dwNewAddr) { BOOL bFlag =FALSE; DWORD dwImageBase=0 ; PDWORD pFuncAddr=NULL ; PIMAGE_NT_HEADERS pNtHeader =NULL ; PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor=NULL ; DWORD dwOldProtect=0 ; dwImageBase=(DWORD)::GetModuleHandle(NULL ); pNtHeader=(PIMAGE_NT_HEADERS)(dwImageBase+((PIMAGE_DOS_HEADER)dwImageBase)->e_lfanew); pImportDescriptor=(PIMAGE_IMPORT_DESCRIPTOR)(dwImageBase+pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); while (pImportDescriptor->FirstThunk!=0 && bFlag == FALSE) { pFuncAddr=(PDWORD)(dwImageBase+pImportDescriptor->FirstThunk); while (*pFuncAddr) { if (dwOldAddr == *pFuncAddr) { VirtualProtect(pFuncAddr,sizeof (DWORD),PAGE_READWRITE,&dwOldProtect); *pFuncAddr=dwNewAddr; VirtualProtect(pFuncAddr,sizeof (DWORD),dwOldProtect,0 ); bFlag=TRUE; break ; } pFuncAddr=(PDWORD)((DWORD)pFuncAddr+sizeof (DWORD)); } pImportDescriptor=(PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pImportDescriptor+sizeof (IMAGE_IMPORT_DESCRIPTOR)); } g_dwOldAddr=dwOldAddr; g_dwNewAddr=dwNewAddr; g_dwIATHookFlag=1 ; return bFlag; } BOOL UnIATHook () { BOOL bFlag =FALSE; DWORD dwImageBase=0 ; PDWORD pFuncAddr=NULL ; PIMAGE_NT_HEADERS pNtHeader=NULL ; PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor =NULL ; DWORD dwOldProtect =0 ; if (!g_dwIATHookFlag) { OutputDebugString("UnIATHook失败:尚未进行IAT HOOK!" ); return bFlag; } dwImageBase=(DWORD)::GetModuleHandle(NULL ); pNtHeader=(PIMAGE_NT_HEADERS)(dwImageBase+((PIMAGE_DOS_HEADER)dwImageBase)->e_lfanew); pImportDescriptor=(PIMAGE_IMPORT_DESCRIPTOR)(dwImageBase+pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); while (pImportDescriptor->FirstThunk!=0 && bFlag == FALSE) { pFuncAddr=(PDWORD)(dwImageBase+pImportDescriptor->FirstThunk); while (*pFuncAddr) { if (g_dwNewAddr == *pFuncAddr) { VirtualProtect(pFuncAddr,sizeof (DWORD),PAGE_READWRITE,&dwOldProtect); *pFuncAddr=g_dwNewAddr; VirtualProtect(pFuncAddr,sizeof (DWORD),dwOldProtect,0 ); bFlag=TRUE; break ; } pFuncAddr++; } pImportDescriptor =(PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pImportDescriptor+sizeof (IMAGE_IMPORT_DESCRIPTOR)); } g_dwOldAddr=0 ; g_dwNewAddr=0 ; g_dwIATHookFlag=0 ; return bFlag; }int WINAPI MyMessageBox (HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType) { char lpNewText[]="修改后的内容" ; typedef int (WINAPI *PFNMESSAGEBOX) (HWND,LPCSTR,LPCSTR,UINT) ; int ret=((PFNMESSAGEBOX)g_dwOldAddr)(hWnd,lpNewText,lpCaption,uType); return ret; } DWORD WINAPI ThreadProc (LPVOID lParam) { DWORD pOldFuncAddr=(DWORD)GetProcAddress(LoadLibrary("user32.dll" ),"MessageBoxA" ); if (!g_dwIATHookFlag) { SetIATHook(pOldFuncAddr,(DWORD)MyMessageBox); } else { UnIATHook(); } return 0 ; } BOOL APIENTRY DllMain ( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: CreateThread(NULL ,0 ,(LPTHREAD_START_ROUTINE)ThreadProc,NULL ,0 ,NULL ); break ; case DLL_THREAD_ATTACH: break ; case DLL_THREAD_DETACH: break ; case DLL_PROCESS_DETACH: break ; } return TRUE; }
dll注入: 在另外一个进程分配创建线程函数,地址为LoadLibrary地址,参数为载入恶意dll。
OpenProcess获取进程句柄,计算dll路径长度,为目标进程分配内存,拷贝dll路径到目标内存,获取目标进程的模块地址与LoadLibraryA地址,创建远程线程执行dll注入。
OpenProcess:打开现有进程对象并获取其句柄
VirtualAllocEx:在指定进程的虚拟地址空间中分配内存区域
WriteProcessMemory:向指定进程的内存区域写入数据
GetModuleHandleA:获取已加载模块(DLL)的句柄
GetProcAddress:从DLL模块中获取导出函数的地址
LoadLibrary:将DLL加载到调用进程的地址空间
CreateRemoteThread:在另一个进程的地址空间中创建线程
源码:
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 #include <stdio.h> #include <windows.h> #include <stdlib.h> #include <Tlhelp32.h.> bool AdjustProcessTokenPrivilege () { LUID luidTmp; HANDLE hToken; TOKEN_PRIVILEGES tkp; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) return false ; if (!LookupPrivilegeValue(NULL , SE_DEBUG_NAME, &luidTmp)) { CloseHandle(hToken); return FALSE; } tkp.PrivilegeCount = 1 ; tkp.Privileges[0 ].Luid = luidTmp; tkp.Privileges[0 ].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof (tkp), NULL , NULL )) { CloseHandle(hToken); return FALSE; } return true ; } DWORD GetPid (char *szName) { HANDLE hprocessSnap=NULL ; PROCESSENTRY32 pe32 ={0 }; hprocessSnap =CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0 ); if (hprocessSnap == (HANDLE)-1 ){return 0 ;} pe32.dwSize=sizeof (PROCESSENTRY32); if (Process32First(hprocessSnap,&pe32)) { do { if (!strcmp (szName,pe32.szExeFile)) return (int )pe32.th32ProcessID; }while (Process32Next(hprocessSnap,&pe32)); } else CloseHandle(hprocessSnap); return 0 ; } BOOL LoadDll (DWORD dwProcessID,char *szDllPathName) { BOOL bRet; HANDLE hProcess; HANDLE hThread; DWORD dwLength; DWORD dwLoadAddr; LPVOID lpAllocAddr; HMODULE hModule; bRet=0 ; dwLoadAddr=0 ; hProcess =0 ; if (!AdjustProcessTokenPrivilege()) { printf ("提权失败\n" ); return 0 ; } hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwProcessID); if (hProcess==NULL ) { printf ("OPENPROCESS Error ! \n" ); return FALSE; } dwLength =strlen (szDllPathName)+1 ; lpAllocAddr=VirtualAllocEx(hProcess,NULL ,dwLength,MEM_COMMIT,PAGE_READWRITE); if (lpAllocAddr==NULL ) { printf ("VIRTUALALLOCEX Error ! \n" ); GetLastError(); CloseHandle(hProcess); return FALSE; } bRet = WriteProcessMemory(hProcess,lpAllocAddr,szDllPathName,dwLength,NULL ); if (!bRet) { printf ("WriteProcessMemory Error ! \n" ); GetLastError(); CloseHandle(hProcess); return FALSE; } hModule =GetModuleHandle("kernel32.dll" ); if (!hModule) { printf ("GetModuleHandle Error !\n" ); GetLastError(); CloseHandle(hProcess); return FALSE; } dwLoadAddr=(DWORD)GetProcAddress(hModule,"LoadLibraryA" ); if (!dwLoadAddr) { printf ("GetProcAddress Error !\n" ); GetLastError(); CloseHandle(hProcess); CloseHandle(hModule); return FALSE; } hThread =CreateRemoteThread(hProcess,NULL ,0 ,(LPTHREAD_START_ROUTINE)dwLoadAddr,lpAllocAddr,0 ,NULL ); if (!hThread) { printf ("CreatRemoteTread Error !\n" ); GetLastError(); CloseHandle(hProcess); CloseHandle(hModule); return FALSE; } CloseHandle(hProcess); return TRUE; }int main () { LoadDll(GetPid("crackme.exe" ),"e:\\iatm.dll" ); return 0 ; }
Inline Hook: 本质是修改函数的开头,将其跳转到恶意代码,然后可以选择修复破坏的开头,跳转回原本的函数。详解在实验2,有时间的话复制过来。把源码丢过来了。
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 <windows.h> #include <stdio.h> #include <iostream> BYTE JmpOriginal[5 ] = { 0xE9 , 0 , 0 , 0 , 0 }; BYTE OldCode[5 ] = { 0 }; FARPROC MessageBoxAddress; void * Trampoline; int WINAPI MyMessageBoxA (HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) { printf ("MessageBoxA 已经被Hook\n" ); int ret = ((int (WINAPI*)(HWND, LPCTSTR, LPCTSTR, UINT))Trampoline)(hWnd, lpText, lpCaption, uType); return ret; }void InlineHook () { HMODULE hModule_User32 = LoadLibraryA("user32.dll" ); MessageBoxAddress = GetProcAddress(hModule_User32, "MessageBoxA" ); DWORD JmpAddress = (DWORD)MyMessageBoxA - (DWORD)MessageBoxAddress - 5 ; memcpy (&JmpOriginal[1 ], &JmpAddress, 4 ); ReadProcessMemory(GetCurrentProcess(), MessageBoxAddress, OldCode, 5 , NULL ); Trampoline = VirtualAlloc(NULL , 10 , MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); memcpy (Trampoline, OldCode, 5 ); DWORD jmpBackAddr = (DWORD)MessageBoxAddress + 5 - (DWORD)Trampoline - 5 - 5 ; memcpy ((void *)((DWORD)Trampoline + 5 ), &JmpOriginal[0 ], 5 ); memcpy ((void *)((DWORD)Trampoline + 6 ), &jmpBackAddr, 4 ); DWORD dwOldProtect; VirtualProtect(MessageBoxAddress, 5 , PAGE_EXECUTE_READWRITE, &dwOldProtect); WriteProcessMemory(GetCurrentProcess(), MessageBoxAddress, &JmpOriginal[0 ], 5 , NULL ); VirtualProtect(MessageBoxAddress, 5 , dwOldProtect, &dwOldProtect); }void main () { InlineHook(); MessageBoxA(NULL , "Hello World" , "Title" , MB_OK);
脚本木马
启发式分析是一种通过检查代码中可疑属性来检测病毒的方法; 脚本木马:编写简单、易于免杀以及难以封堵。
可以动态调试js代码,发现其特征:如被转码过的url、发送的网络流量
查杀:对样本哈希运算、病毒内部提取特征码(会结合偏移)
免杀:修改偏移。
动态启发查杀:通过分析程序指令出现的顺序,或者特定的组合情况以及所调用的函数及其参数等属于恶意行为特征,来判断目标程序是不是病毒程序。如加入启动项、复制到系统目录、删除自身;
启发特征的编写其实就是可疑字符串的选取与匹配的工作。
解密引擎可以去除混淆,将编码提取为字符,从而得到解密的脚本文件及其特征。