软件安全复习&re/web

本文最后更新于 2025年6月8日 下午

软件安全复习/知识总结

考试前的复习,顺便回顾一下知识。

[TOC]

杂七杂八的基础概述知识

中午状态不佳,先总一下基本常识

  1. 软件安全面临的安全威胁:软件自身的安全(软件漏洞)、恶意代码、软件侵权
  • 软件漏洞通常被认为是软件生命周期中与安全相关的设计错误、编码缺陷、运行故障等
  • 恶意代码:软件或代码片段,如二进制执行文件、脚本语言代码、宏代码或是寄生在其他代码或启动扇区中的一段指令。示例:计算机病毒、特洛伊木马、僵尸网络、网络钓鱼
  1. 信息安全的基本属性

保密性、完整性、可用性、可认证性、授权、可审计性、不可否认性、可控性、可存活性

  1. 软件安全

软件安全是信息安全保障的重要内容,漏洞分析是降低系统脆弱性的最有效方法,软件安全防护围绕漏洞消除展开

  1. 软件漏洞
  • 漏洞是存在于信息系统、系统安全过程、内部控制或实现过程中的可被威胁源攻击或触发的弱点

  • 漏洞是信息系统自身具有的弱点或缺陷,环境通常是特定的

  • 软件漏洞涉及设计错误、编码缺陷及运行故障

  • 软件漏洞的特点:持久性、时效性、广泛性、具体性、可利用性、隐蔽性

  • 基于漏洞成因的分类:内存破坏类、逻辑错误类、输入验证类、设计错误类和配置错误类

  1. 软件安全开发模型

减小攻击面:最小权限原则

考点:实施

image-20250607214153155

课本119页就是ppt的这几点。课本63页是栈溢出保护(GS)、DEP(NX)、aslr、safeSEH等机制。

web相关知识

ssrf(服务器端请求伪造)

SSRF 服务端请求伪造是一个 web 漏洞,它允许攻击者诱导服务端程序向攻击者选择的任何地址发起 HTTP 请求。

eg:baisc ssrf:后端服务的URL请求地址包含在了前端,可以抓包修改为本地访问;pikachu:利用file协议获取本地信息,dict进行端口腿内侧,

os top10

image-20250607212516273

  1. 失效的访问控制:没做权限校验,手工改参数
  2. 加密机制失效:数据库明文、短哈希、base64传递密码
  3. 注入:sql注入等
  4. 不安全设计:登录接口未限流、使用问题与答案的形式来证明身份
  5. 安全配置错误:未修改默认账户密码、默认配置和默认文件未删除、敏感文件泄露、服务器将详细的报错信息返回给用户
  6. 自带缺陷和过时的组件:如复现的那个cve,设定随机数种子来产生图片
  7. 身份识别和验证错误:应用会话超时、弱口令等
  8. 软件和数据完整性故障:固件更新没有签名
  9. 安全日志和监控故障:没有对系统进行日志监控或记录
  10. 服务端请求伪造:注意:csrf是客户端伪造;ssrf才是服务端请求伪造。扫描内部服务器端口、访问敏感文件、滥用内部服务进行恶意代码的执行

攻击点:输入框、搜索框、上传接口、登录口、后台地址等

cve的实例:

  • CVE-2023-42820 是 JumpServer 开源堡垒机中的一个高危漏洞,其核心问题在于随机数种子泄露,导致未授权攻击者可预测密码重置 Token,进而接管未启用多因子认证(MFA)的用户账户。属于6
  • django-simple-captcha的视图生成了验证码,它将生成的验证码存储的url以hashkey的形式传给用户,这个key被用作随机数种子。(word报告,截个图得了)

image-20250608115749946

windows下的溢出攻击

缓冲区溢出

栈溢出及相关

  1. 栈的栈帧及函数调用过程
  2. 异常处理SEH结构,该结构存放于栈中,可以通过涉及特定的一处数据,将异常函数的入口覆盖为shellcode的跳转指令,使程序通过发生异常来执行shellcode

堆溢出-无保护下-dword shoot

任意地址写

image-20250607160212539

将bk改为目标地址-4*2(linux,32位下),fd改为要改的内容

node->bk->fd = node ->fd

课上练习–根据调试内容画出windows下的堆块情况(ppt看着怪怪的,自己调一次)(无比怀念linux下用pwndbg直接出来)

源码:image-20250607165551849

第一次heapalloc后,h1位于0x8204b0

image-20250607170037966

第二次,h2位于0x8204C0

image-20250607170200963

后续依次,从B0到00,共六个块

image-20250607170425193

啊,里面有int3给我直接中断了,重新来,这次的堆基址是0x550000,同样从04B0开始,补一张创建完的图

image-20250607170851478

free掉h1后

image-20250607171746736

free掉h3、h5后

image-20250607171957386

再执行一次alloc后

image-20250607172137686

欸?先进后出?怎么依稀记得堆是先进先出来着,搜了搜,以下两图截自堆漏洞挖掘中bins的单向链表、双向链表存储结构_mb6128aabee41d4的技术博客_51CTO博客

同时回忆了一下,fastbin是往前插入的,每次都往bin的表头里插入,再使用的时候也是从表头开始

image-20250607172743196

image-20250607172758053

而且windows的堆管理机制与linux不同,问了ai,ai说单个子桶是后进先出。

浅画一下3个堆块都free后的内容,以及修改(linux的32位需要减去8,因为->fd的索引是加了0x8,不知道windows,老师讲的是目标地址,就先这么写吧,晚上调试改一下值试试)

image-20250607175521416

动调改值尝试:由于原始代码把1从空闲块中取出,所以改1

这里有空地址,写在这里方便看

image-20250607202719995

改了这里

image-20250607202918105

执行完之后发现没有申请这个块,不知道申请到哪儿了,重来一次,换个地址改,可能那里不可写,直接改成堆上的一个地址

改为19ff38

image-20250607204147408

并没有实现,应该是有保护机制的。。

image-20250607204353193

假设检测fd->bk=p = bk->fd,且对应与linux一样加了pre_size、size

image-20250607210253072

报错了。。。假设不加,再试试

image-20250607210418353

修改,目标19ff50

image-20250607210617726

还是检测到了。

image-20250607210657591

暂且这样吧,有时间有精力的时候再去看看windows的堆分配

另:回顾老师的ppt,发现这同一个程序,老师的heap_size=8的时候分配了0x20字节,我这里的调试是0x10字节。。。。。

甚至老师ppt的那个链表的添加也是从小到大往队尾填的(也就是老师那个会是先进先出)

ok版本不同这么大区别,只能具体情况看着调试的具体分析了。

windows下的保护机制

  1. GS保护(即linux下的canary)
  2. DEP保护(linu下的nx保护,限制了执行的权限)
  3. ASLR(地址随机化)
  4. SafeSEH(检测seh对应的函数地址是否被修改):其实现原理是,编译器在链接生成二进制IMAGE时,把所有合法的异常处理函数的地址解析出来制成一张安全的SEH表,保存在程序的IMAGE数据块里面,当程序调用异常处理函数时会将函数地址与安全SEH表中的地址进行匹配,检查调用的异常处理函数是否位于该表中。

恶意代码分析基础

计算机的启动与PE

自bios芯片开始,从主引导记录读取,启动硬盘,找到激活分区,启动操作系统(内核装载、windows分区激活)

pe基本信息:

image-20250607220743217

导入表:导入函数地址表(iat)、导入函数目录表(it)

导出表:根据编号、函数名字查找返回地址

恶意代码

  • 恶意代码可能是独立的PE文件,亦可以不是,可能有导入表,也可能没有。如果没有,就先找到kernel32.dll的载入地址,然后找到GetProcAddree函数,在用它找到LoadLibraryA函数,从而载入其他链接库的函数。(寻找可以采用弹栈、MZ、PE来找,这个逆向工程小结有;或者查询peb,这个后续写)

弹栈找法:

image-20250607221903251

栈顶是kernel32的一个函数的地址

基于PEB的搜索

fs:[0xc]寻址到ldr表,里面有dll库加载的链表,通常+0x14,从第二个里找,下图是结构

image-20250607222108023

这是具体运行时显示的值

image-20250607222328053

内存位置的第一个是当前执行的程序

image-20250607222455114

索引到的项-8+02c是库的名字,一般第三个是kernel32.dll库

image-20250607222632353

流程

找到kernel32载入地址后,用GetProcAddress这个字符串,从kernel32导出表用名字逐项比对,找到GetProcAddress的地址,然后用它得到LoadLibraryA地址,从而实现任意dll、任意函数的加载

比对过程的一部分:(里面的明文是根据上下文自己命名的,ppt里给出的那些不足以定位,但是问了edi、esi、ecx分别是什么)

image-20250607223317018

分别是:导出表中的一个函数名、储存的GetProcAddress字符串的地址、字符串的长度

重定位:

call下一个地址,然后pop出来,以此定位当前运行到的地址

image-20250607223752210

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来执行

  1. 方法:将恶意代码保存到dll,从而加载到其他进程中;使用widows dll交互,使用第三方dll

  2. 实施:恶意代码通过创建一个新进程,或修改一个已存在的进程,来执行当前程序之外的代码。CreateProcess函数创建进程;或用CreateThread来加载恶意dll

加载新的恶意DLL:

CreateThread时将起始地址设置为Loadlibrary的地址。

传递给CreateThread的参数是要被加载库的名字。新的DLL被加载到这个进程的内存中, 然后DllMain被调用。

  1. 恶意代码经常使用互斥量来使得一次只运行恶意代码的一个实例,经常用硬编码来作为互斥量的名字
  2. 恶意代码会创建服务来使得持久化运行,常使用win32_share_process类型的服务来执行恶意代码

恶意行为

  1. 使用下载器和启动器,下载恶意文件、执行恶意文件

  2. URLDownloadtoFileA和WinExec,来下载并运行新的恶意代码。

  3. 后门:反向连接,创建套接字连接远程服务器并与cmd.exe标准流绑定,用隐藏窗口的方式创建cmd程序

  4. 窃取行为:窃取登录凭证、记录键盘击键行为、获取口令哈希。。。采用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; // #HOOK状态 (1 HOOK 0 未HOOK)
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);

// #遍历IAt表 找到这个函数地址
while(pImportDescriptor->FirstThunk!=0 && bFlag == FALSE)
{
pFuncAddr=(PDWORD)(dwImageBase+pImportDescriptor->FirstThunk);

while(*pFuncAddr)
{
// #找到要HOOK的函数,先修改内存的属性
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;
// #判断是否HOOK
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);

// #遍历IAt表 找到这个函数地址
while(pImportDescriptor->FirstThunk!=0 && bFlag == FALSE)
{
pFuncAddr=(PDWORD)(dwImageBase+pImportDescriptor->FirstThunk);

while(*pFuncAddr)
{
// #找到要HOOK的函数,先修改内存的属性
if(g_dwNewAddr == *pFuncAddr)
{
// #找到被HOOK的函数
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[]="修改后的内容";
// #定义MessageBox函数指针
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");
// #安装或者卸载HOOK
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。

image-20250607231221458

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;

// #0.提升进程为DEBUG权限
if(!AdjustProcessTokenPrivilege())
{ printf("提权失败\n");
return 0;
}

// #1.获取进程句柄
hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwProcessID);
if(hProcess==NULL)
{
printf("OPENPROCESS Error ! \n");
return FALSE;
}
// #2.计算dll路径长度(加上'\0')
dwLength =strlen(szDllPathName)+1;

// #3.在目标进程分配内存
lpAllocAddr=VirtualAllocEx(hProcess,NULL,dwLength,MEM_COMMIT,PAGE_READWRITE);

if(lpAllocAddr==NULL)
{
printf("VIRTUALALLOCEX Error ! \n");
GetLastError();
CloseHandle(hProcess);
return FALSE;
}

// #4.拷贝dll路径名字到目标进程的内存
bRet = WriteProcessMemory(hProcess,lpAllocAddr,szDllPathName,dwLength,NULL);

if(!bRet)
{
printf("WriteProcessMemory Error ! \n");
GetLastError();
CloseHandle(hProcess);
return FALSE;
}
// #5.获取模块地址
hModule =GetModuleHandle("kernel32.dll");
if (!hModule)
{
printf("GetModuleHandle Error !\n");
GetLastError();
CloseHandle(hProcess);
return FALSE;
}
// #6.获取LoadLibraryA 函数地址
dwLoadAddr=(DWORD)GetProcAddress(hModule,"LoadLibraryA");
if (!dwLoadAddr)
{
printf("GetProcAddress Error !\n");
GetLastError();
CloseHandle(hProcess);
CloseHandle(hModule);
return FALSE;
}
// #7.创建远程线程,加载dll
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;
}
// #8.关闭进程句柄
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 }; // 用于跳转到MyMessageBoxA的指令,0xE9代表JMP指令
BYTE OldCode[5] = { 0 }; // 存储原始MessageBoxA的前5个字节
FARPROC MessageBoxAddress; // MessageBoxA的函数地址
void* Trampoline; // 桥接函数地址

// 自定义的MessageBoxA函数
int WINAPI MyMessageBoxA(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
printf("MessageBoxA 已经被Hook\n"); // 打印被Hook的信息

// 使用桥接函数调用原始的MessageBoxA,这里需要类型转换
int ret = ((int (WINAPI*)(HWND, LPCTSTR, LPCTSTR, UINT))Trampoline)(hWnd, lpText, lpCaption, uType);
return ret;
}

void InlineHook()
{
HMODULE hModule_User32 = LoadLibraryA("user32.dll"); // 加载user32.dll模块
MessageBoxAddress = GetProcAddress(hModule_User32, "MessageBoxA"); // 获取MessageBoxA的函数地址

DWORD JmpAddress = (DWORD)MyMessageBoxA - (DWORD)MessageBoxAddress - 5; // 计算跳转到MyMessageBoxA的地址
memcpy(&JmpOriginal[1], &JmpAddress, 4); // 将跳转地址复制到JmpOriginal的第二个字节

ReadProcessMemory(GetCurrentProcess(), MessageBoxAddress, OldCode, 5, NULL); // 读取并保存MessageBoxA的前5个字节

// 分配10个字节的内存空间作为桥接函数
Trampoline = VirtualAlloc(NULL, 10, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(Trampoline, OldCode, 5); // 复制MessageBoxA的前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;
// 修改MessageBoxA的前5个字节的页属性,使其可读可写可执行
VirtualProtect(MessageBoxAddress, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// 替换MessageBoxA的前5个字节为跳转到MyMessageBoxA的指令
WriteProcessMemory(GetCurrentProcess(), MessageBoxAddress, &JmpOriginal[0], 5, NULL);

// 恢复MessageBoxA的前5个字节的原始页属性
VirtualProtect(MessageBoxAddress, 5, dwOldProtect, &dwOldProtect);
}

void main()
{
InlineHook(); // 实施Inline Hook
MessageBoxA(NULL, "Hello World", "Title", MB_OK); // 调用MessageBoxA函数

脚本木马

  1. 启发式分析是一种通过检查代码中可疑属性来检测病毒的方法;
    脚本木马:编写简单、易于免杀以及难以封堵。
  2. 可以动态调试js代码,发现其特征:如被转码过的url、发送的网络流量

查杀:对样本哈希运算、病毒内部提取特征码(会结合偏移)

免杀:修改偏移。

动态启发查杀:通过分析程序指令出现的顺序,或者特定的组合情况以及所调用的函数及其参数等属于恶意行为特征,来判断目标程序是不是病毒程序。如加入启动项、复制到系统目录、删除自身;

启发特征的编写其实就是可疑字符串的选取与匹配的工作。

解密引擎可以去除混淆,将编码提取为字符,从而得到解密的脚本文件及其特征。


软件安全复习&re/web
https://xyyr-c.github.io/2025/06/07/软件安全复习&reweb/
作者
xyyr
发布于
2025年6月7日
许可协议