• C/C++ 通用ShellCode的编写与调用


    首先,我们的ShellCode代码需要自定位,因为我们的代码并不是一个完整的EXE可执行程序,他没有导入表无法定位到当前系统中每个函数的虚拟地址,所以我们直接获取到Kernel32.dll的基地址,里面的GetProcAddr这个函数,获取的方式有很多,第一种是暴力搜索,第二种通过遍历进程的TEB结构来实现,我们使用第二种方式尝试,一旦获取到该函数,就可以动态的调用任何想要的函数了。

    获取DLL模块基地址

    首先打开WinDbg加载符号链接文件,输入 srv*https://www.blib.cn/symbols

    1.首先FS寄存器里面存储的是TEB结构,TEB是线程环境快,里面的PET。

    TEB的偏移位置30h处,存放的是PEB线程环境快。

    接着解析一下 dt _peb 0026b000 里面的0C字段是LDR,一个指向_PEB_LDR_DATA的结构数组。

    PEB_LDR_DATA 结构体偏移位置为 0x1c 的地方存放着指向模块初始化链表的头指针 InInitializationOrderModuleList,该指针指向了一个双向链表。

    模块初始化链表 InInitializationOrderModuleList 中按顺序存放着PE装入运行时初始化模块的信息,第一个链表节点是 ntdll.dll,第二个链表结点就是kernel32.dll可以先看看 InInitializationOrderModuleList 中的内容。

    上图中的 004e3278 保存的是第一个链表节点的指针,通过dd 004e3278解析这个结点,可发现如下地址0x773a0000就是ntdll.dll的基地址,而 004e3b20 则是下一个模块的指针

    继续跟随 004e3b20 跟进后的76a90000就是kernel32.dll的基地址,而下一个地址的指针则是004e3760以此类推来遍历。

    最后我们通过!peb命令来验证一下,如下会发现第一个对上了,这里的kerlel32.dll其实是kernelbase.dll 这个dll是转向dll中转到kernel32.dll中,64位系统特有的。

    通过上方的调试我们可得到公式,接着通过编写一段汇编代码来实现自动的遍历出 kernel32.dl 的基址。

    include windows.inc
    include kernel32.inc
    includelib kerbcli.lib
    assume fs:nothing
    
    .code
    	main PROC
    		xor eax,eax
    		xor edx,edx
    		mov eax,fs:[30h]           ; 得到PEB结构地址
    		mov eax,[eax + 0ch]        ; 得到PEB_LDR_DATA结构地址
    		mov esi,[eax + 1ch]        ; 得到 InInitializationOrderModuleList
    		lodsd                      ; 得到KERNEL32.DLL所在LDR_MODULE结构的
    		mov eax,[eax]              ; Windows 7 以上要将这里打开
    		mov edx,[eax + 8h]         ; 得到BaseAddress,既Kernel32.dll基址
    		ret
    	main ENDP
    END main
    

    通过使用C语言也可以实现拿到Kernel32的基地址.

    #include <windows.h>
    #include <stdio.h>
    
    int main(int argc, char * argv[])
    {
    	DWORD *PEB = NULL;
    	DWORD *Ldr = NULL;
    	DWORD *Init = NULL;
    	DWORD *Kernel32 = NULL;
    
    	__asm
    	{
    		mov eax, fs:[0x30]
    		mov PEB,eax
    	}
    	printf("得到PEB指针 = %x \n", PEB);
    
    	Ldr = *(DWORD **)((unsigned char *)PEB + 0x0c);
    	printf("得到LDR结构指针 = %x \n", Ldr);
    
    	Init = *(DWORD **)((unsigned char *)Ldr + 0x1c);
    	printf("得到InInitializationOrderModuleList结构指针 = %x \n", Init);
    
    
    	Kernel32 = *(DWORD **)((unsigned char *)Init + 0x08);
    	printf("得到Kernel32的基地址 = %x \n", Kernel32);
    
    	system("pause");
    	return 0;
    }
    

    获得镜像基地址: 我们来扩展一个知识点,首先我们这次想要获得镜像基地址,如何解析结构?

    首先镜像基地址,在PEB结构中,我们先来获取到其偏移地址。

    此时我们知道TEB结构中 指向 PEB,则 0026b000

    接着来解析TEB结构,只需要执行 dt _PEB 0026b000 即可得到该地址。

    直接汇编实现,也非常简单,如下。


    枚举进程模块

    1.我们来拓展一个知识点,通过PEB/TEB找到自身进程的所有载入模块数据,首先获取 TEB,也就是线程环境块。在编程的时候,TEB 始终保存在寄存器 FS 中。

    先来得到LDR结构:Ldr = *( ( DWORD ** )( ( unsigned char * )PEB + 0x0c ) );

    先找到TEB

    然后再找到PEB结构 偏移为 0x30 从该命令的输出可以看出,PEB 结构体的地址位于 TEB 结构体偏移0x30 的位置

    找到了PEB也就可以找到_PEB_LDR_DATA结构 其位于 PEB 偏移 0c的位置上。

    Ldr = *( ( DWORD ** )( ( unsigned char * )PEB + 0x0c ) );

    从输出结果可以看出,LDR 在 PEB 结构体偏移的 0x0C 处,该地址保存的地址是 0x77bf0c40 通过该地址来解析 LDR 结构体。

    WinDBG 输出如下内容:

    Flink = *( ( DWORD ** )( ( unsigned char * )Ldr + 0x14 ) );

    位于LDR偏移14的位置就是InLoadOrderModuleList其所指向的就是模块名称表。

    现在来手动遍历第一条链表,输入命令 0x4e3370

    链表偏移 0x18 的位置是模块的映射地址 ImageBase;
    链表偏移 0x28 的位置是模块的路径及名称的地址;
    链表偏移 0x30 的位置是模块名称的地址。

    的确是模块的名称,遍历下一条链表的信息,004e3268 保存着下一个链表结构,依次遍历就是了。

    我们找到下一个链表位置,然后同样的方法来验证一下。

    没错了吧,下一个是 ntdll.dll

    这个链表结构其实访问 InMemoryOrderModuleList 也可以得到,这两个都指向同一片区域 例如第二个 0x4e3378

    解析一下看看 0x4e3378 一致。

    第二个是ntdll.dll

    上面介绍的结构,是微软保留结构,只能从网上找到一个结构定义,然后自行看着解析就好了。

    typedef struct _LDR_DATA_TABLE_ENTRY {
    	PVOID Reserved1[2];
    	LIST_ENTRY InMemoryOrderLinks;
    	PVOID Reserved2[2];
    	PVOID DllBase;
    	PVOID EntryPoint;
    	PVOID Reserved3;
    	UNICODE_STRING FullDllName;
    	BYTE Reserved4[8];
    	PVOID Reserved5[3];
    	union {
    	ULONG CheckSum;
    	PVOID Reserved6;
    	};
    	ULONG TimeDateStamp;
    } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
    

    Flink = *((DWORD **)((unsigned char *)Ldr + 0x14));

    枚举模块的方法就是:得到TEB -> PEB ->LDR ->遍历。

    #include <Windows.h>
    #include <stdio.h>
    
    int main(int argc, char* argv[])
    {
    	DWORD *PEB = NULL, *Ldr = NULL, *Flink = NULL, *p = NULL;
    	DWORD *BaseAddress = NULL, *FullDllName = NULL,*Ba = NULL;
    
    	__asm
    	{
    		mov eax, fs:[0x30]
    		mov PEB, eax
    	}
    
    	Ldr = *((DWORD **)((unsigned char *)PEB + 0x0c));
    	Flink = *((DWORD **)((unsigned char *)Ldr + 0x14));
    	p = Flink;
    
    	p = *((DWORD **)p);
    	while (Flink != p)
    	{
    		BaseAddress = *((DWORD **)((unsigned char *)p + 0x10));
    		FullDllName = *((DWORD **)((unsigned char *)p + 0x20));
    
    	if (BaseAddress == 0)
    		break;
    
    	printf("镜像基址 = %08x \n --> 模块路径 = %S \n", BaseAddress, (unsigned char *)FullDllName);
    
    		p = *((DWORD **)p);
    	}
    	system("pause");
    	return 0;
    }
    

    上方的 0x10 与 0x20 对应的就是地址结构与路径名称。

    BaseAddress = *((DWORD **)((unsigned char *)p + 0x10));
    FullDllName = *((DWORD **)((unsigned char *)p + 0x20));

    将0x10改为 0x24

    或改为 0x28

    换成0x18 和 0x28 运行看看,获取到的就是文件名称。

    BaseAddress = *((DWORD **)((unsigned char *)p + 0x18));
    FullDllName = *((DWORD **)((unsigned char *)p + 0x28));

    对照解析结果,观察,就明白了。


    进程模块隐藏

    一维指针的骚操作:

    #include <stdio.h>
    #include <Windows.h>
    
    int main(int argc, char * argv[])
    {
    	DWORD *PEB = (DWORD *)0x401000;
    	DWORD *LDR;
    	DWORD *BaseAddr = NULL;
    
    	printf("PEB = %x \t &PEB = %x \n", PEB,&PEB);
    	LDR = (DWORD *)&PEB;
    	printf("PEB = %x \t LDR = %x PEB Value = %x \n", LDR,&LDR,*LDR);
    	printf("-------------------------------------------------------------- \n");
    
    	printf("(unsigned char *)PEB = %x \n", (unsigned char *)PEB);
    	printf("(unsigned char *)PEB + 0xc) = %x \n", (unsigned char *)PEB + 0xc);
    	printf("取出内存中的值: %x \n", (DWORD **)((unsigned char *)PEB + 0xc));
    	printf("取出其地址中的值:%x \n", *(DWORD **)((unsigned char *)PEB + 0xc));
    
    	printf("-------------------------------------------------------------- \n");
    
    	DWORD Hmodule = 0x401000;
    
    	// 设置指针指向
    	BaseAddr = (DWORD *)0x401000;
    
    	// 设置指针中的数值
    	*BaseAddr = 0x1000;
    
    	if (BaseAddr == (DWORD *)Hmodule)
    	{
    		printf("BaseAddr = %x \t BaseAddr = %d \t Hmodule = %x \n", BaseAddr,*BaseAddr,Hmodule);
    	}
    
    	printf("-------------------------------------------------------------- \n");
    	int Array[] = {1,2,3,4,5,6,7,8,9};
    	DWORD *Flink;
    	DWORD *ptr;
    
    	Flink = *(DWORD **)((unsigned char *)Array);
    	ptr = Flink;
    	printf("%x \n", ptr);
    
    	for (int x = 0; x < 9; x++)
    	{
    		printf("遍历元素: %d \n", *(DWORD **)((unsigned char *)Array + ( x*4 )));
    	}
    
    	// 反向输出
    	for (int x = 0; x < 9; x++)
    	{
    		Flink = *(DWORD **)((unsigned char *)Array + (x * 4));
    		Link = *(DWORD **)((unsigned char *)Array + ((9 - x - 1) * 4));
    		printf("%d --> %d \n", Flink, Link);
    	}
    
    	system("pause");
    	return 0;
    }
    
    #include <stdio.h>
    #include <Windows.h>
    
    int main(int argc, char * argv[])
    {
    	int Array[] = {1,2,3,4,5,6,7,8,9,10};
    
    	DWORD *PEB = (DWORD *)Array;
    	DWORD *Ldr = *((DWORD **)((DWORD *)Array + 1));
    	printf("Ldr = > %x Value =  %d \n", &Ldr,Ldr);
    
    	// 基本的取值
    	printf("PEB = %x &PEB = %x \n", PEB, &PEB);
    	Ldr = (DWORD *)&PEB;
    	printf("LDR = %x &LDR = %x *LDR = %x \n", Ldr, &Ldr, *Ldr);
    	printf("(unsigned char *)PEB = %x \n", (unsigned char *)PEB);
    	printf("(unsigned char *)PEB + 4 = %x \n", (unsigned char *)PEB + 4);
    	printf("取出第一个元素内存地址: %x \n", (DWORD **)((unsigned char *)PEB + 4));
    	printf("取出其中的值: %d \n", *(DWORD **)((unsigned char *)PEB + 4));
    
    	// 取值与替换值
    	DWORD ref = (DWORD)*((unsigned char *)Array + 4);  // 取出第2个值
    	*((DWORD *)(Array + 1)) = 10;                      // 替换数组中第二个值
    	printf("取出的值: %d 替换后: %d \n", ref, *((DWORD *)(Array + 1)));
    
    	// 正向遍历元素
    	for (int x = 0; x < 10; x++)
    	{
    		DWORD ref1 = *((DWORD *)((unsigned char *)Array + (x * 4)));
    		printf("正向元素输出: %d \n", ref1);
    	}
    
    	// 反向输出元素
    	DWORD *Frist = NULL;
    	DWORD *Last = NULL;
    	for (int x = 0; x < 10; x++)
    	{
    		Frist = *(DWORD **)((DWORD *)Array + x);
    		Last = (DWORD *)((DWORD *)Array + (9 - x));
    		printf("反向输出: %d --> %d \n", Frist, *Last);
    	}
    
    	system("pause");
    	return 0;
    }
    

    二维指针应用:

    #include <stdio.h>
    #include <Windows.h>
    
    int main(int argc, char * argv[])
    {
    	DWORD Array[] = { 1, 2, 3, 4, 5 };
    	DWORD *ArrayPtr[] = { &Array[0], &Array[1], &Array[2], &Array[3], &Array[4] };
    	DWORD *PEB;
    
    	// 这三种方式均可定位二级数组
    	PEB = *((DWORD **)((DWORD *)ArrayPtr + 1));
    	printf("%x %d \n", PEB,*PEB);
    
    	PEB = *((DWORD **)((DWORD *)(ArrayPtr + 1)));
    	printf("%x %d \n", PEB, *PEB);
    
    	PEB = *(DWORD **)((unsigned char *)(ArrayPtr) + (1*4));
    	printf("%x %d \n", PEB, *PEB);
    
    	PEB = *(DWORD **)ArrayPtr;
    	printf("得到ArrayPtr地址: %x --> 得到Array元素地址: %x --> 得到元素值: %x \n", &PEB,PEB,*PEB);
    
    	// 二级元素赋值操作
    	printf("得到第一个指针地址: %x \n", (DWORD)*(DWORD **)ArrayPtr);
    	printf("得到第二个指针地址: %x --> 数据: %d \n", (*((DWORD **)ArrayPtr) + 1), *(*((DWORD **)ArrayPtr) + 1));
    
    	printf("原始数据为: %x \n", *ArrayPtr[1]);
    	*((DWORD *)(ArrayPtr + 1)) = (DWORD)*(DWORD **)ArrayPtr;
    	printf("更改数据为: %x \n", *ArrayPtr[1]);
    
    	printf("原始指针数据为: %x \n", *ArrayPtr[1]);
    	**((DWORD **)(ArrayPtr + 1)) = (DWORD)*(DWORD **)ArrayPtr;
    	printf("更改指针数据为: %x \n", *ArrayPtr[1]);
    
    	for (int x = 0; x < 5; x++)
    	{
    		printf("地址: %x --> 数据: %d \n", (*((DWORD **)ArrayPtr) + x), *(*((DWORD **)ArrayPtr) + x));
    	}
    
    	system("pause");
    	return 0;
    }
    

    三层指针遍历:

    #include <stdio.h>
    #include <Windows.h>
    
    int main(int argc, char * argv[])
    {
    	DWORD Array[] = { 1, 2, 3, 4, 5 };
    	DWORD *ArrayPtr[] = { &Array[0], &Array[1], &Array[2], &Array[3], &Array[4] };
    	DWORD *ArrayPtrS[] = { ArrayPtr[0], ArrayPtr[1], ArrayPtr[2], ArrayPtr[3], ArrayPtr[4] };
    
    	// 输出三级指针中的数据
    	DWORD *PtrA = (DWORD *)((DWORD **)((DWORD ***)ArrayPtrS));
    	printf("获取到ArrayPtr[0]地址 = %x \t 获取到Array[0]地址 = %x \n", PtrA,*PtrA);
    
    	DWORD *PtrB = (DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1)));
    	printf("获取到ArrayPtr[1]地址 = %x \t 获取到Array[1]地址 = %x \n", PtrB, *PtrB);
    
    	DWORD PtrC = *((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1))));
    	printf("获取到里面的数值: %d \n", *(DWORD *)PtrC);
    
    	// 三级指针
    	// 遍历指针数据
    	printf("ArrayPtrS => ");
    	for (int x = 0; x < 5; x++)
    		printf("0x%x ", &ArrayPtrS[x]);
    	printf("\n");
    	printf("ArrayPtr => ");
    	for (int x = 0; x < 5; x++)
    		printf("0x%x ", ArrayPtr[x]);
    	printf("\n");
    
    	// 输出特定指针
    	DWORD **PtrAA = *((DWORD ***)(ArrayPtrS)+1);
    	printf("ArrayPtr[1] => %x \n", PtrAA);
    
    	DWORD *PtrBB = ((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1))));
    	printf("ArrayPtrS => %x \n", PtrBB);
    
    	DWORD Ref = *((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1))));
    	printf("%x  \n", Ref);
    
    	DWORD Ref = *(DWORD *) (*((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1)))));
    	printf("原始数值 ArrayPtrS[1] = %x \n", Ref);
    
    	system("pause");
    	return 0;
    }
    

    模块隐藏方法:

    #include <Windows.h>
    #include <stdio.h>
    
    int main(int argc, char* argv[])
    {
    	DWORD *PEB = NULL,*Ldr = NULL,*Flink = NULL,*p = NULL,
    		*BaseAddress = NULL,*FullDllName = NULL;
    	__asm
    	{
    		mov eax, fs:[0x30]
    		mov PEB, eax
    	}
    	HMODULE hMod = GetModuleHandle("kernel32.dll");
    	Ldr = *((DWORD **)((unsigned char *)PEB + 0x0c));
    	Flink = *((DWORD **)((unsigned char *)Ldr + 0x0c));
    	p = Flink;
    	do
    	{
    		BaseAddress = *((DWORD **)((unsigned char *)p + 0x18));
    		FullDllName = *((DWORD **)((unsigned char *)p + 0x28));
    
    		if (BaseAddress == (DWORD *)hMod)
    		{
    			**((DWORD **)(p + 1)) = (DWORD)*((DWORD **)p);
    			*(*((DWORD **)p) + 1) = (DWORD)*((DWORD **)(p + 1));
    			break;
    		}
    		p = *((DWORD **)p);
    	} while (Flink != p);
    	return 0;
    }
    

    循环枚举Kernel32.dll 中模块地址

    #include <stdio.h>
    #include <Windows.h>
    
    int main(int argc, char * argv[])
    {
    	int a;
    	__asm
    	{
    		mov ebx, dword ptr fs : [0x30]
    			mov ecx, dword ptr[ebx + 0xc]
    			mov ecx, dword ptr[ecx + 0x1c]
    			mov ecx, [ecx]
    			mov edx, [ecx + 0x8]; kernelbase.dll
    
    			mov eax, [edx+0x3c]
    			mov ecx, [edx + eax + 0x78]
    			add ecx,edx
    			mov ebx, [ecx+0x20]
    			add ebx,edx
    			xor edi,edi
    		s1:
    			inc edi
    			mov esi, [ebx+edi*4]
    			add esi,edx
    
    			cmp esi,edx
    			je no
    			loop s1
    		no:
    			xor eax,eax
    	}
    	system("pause");
    	return 0;
    }
    

    生成shellcode并自动提取:

    #include <stdio.h>
    #include <Windows.h>
    
    int main(int argc, char * argv[])
    {
    
    	DWORD Start, End, Len;
    	goto GetShellCode;
    	__asm
    	{
    	ShellCodeStart:
    		mov ebx, dword ptr fs : [0x30]
    			mov ecx, dword ptr[ebx + 0xc]
    			mov ecx, dword ptr[ecx + 0x1c]
    			mov ecx, [ecx]
    			mov edx, [ecx + 0x8]; kernelbase.dll
    
    			mov eax, [edx + 0x3c]
    			mov ecx, [edx + eax + 0x78]
    			add ecx, edx
    			mov ebx, [ecx + 0x20]
    			add ebx, edx
    			xor edi, edi
    		s1 :
    		inc edi
    			mov esi, [ebx + edi * 4]
    			add esi, edx
    
    			cmp esi, edx
    			je no
    			loop s1
    		no :
    		xor eax, eax
    	ShellCodeEnd:
    	}
    
    
    GetShellCode:
    	__asm
    	{
    		mov Start, offset ShellCodeStart
    		mov End, offset ShellCodeEnd
    	}
    
    	Len = End - Start;
    	unsigned char *newBuffer = new unsigned char[Len + 1024];
    
    	memset(newBuffer, 0, Len + 1024);
    	memcpy(newBuffer, (unsigned char *)Start, Len);
    
    	FILE *fp = fopen("c://shellcode.txt", "wb+");
    
    	//fwrite(newBuffer, Len, 1, fp);
    	//_fcloseall();
    
    	fwrite("unsigned char Buffer[] = {", 22, 1, fp);
    	for (int x = 0; x < Len; x++)
    	{
    		if (x % 16 == 0)
    			fwrite("\r\n", 2, 1, fp);
    		fprintf(fp, "0x%02x,", newBuffer[x]);
    	}
    	fwrite("\n};", 2, 1, fp);
    	_fcloseall();
    
    	system("pause");
    	return 0;
    }
    

    运行后自动生成shellcode.txt文本。

    通过上方代码生成二进制shellcode.bin文件,然后将其动态读入内存,并执行即可.

    #include <stdio.h>
    #include <Windows.h>
    
    int main(int argc, char * argv[])
    {
    	HANDLE fp;
    	unsigned char * fBuffer;
    	DWORD fSize, dwSize;
    
    	fp = CreateFile(L"c://shellcode.bin", GENERIC_READ, FILE_SHARE_READ, NULL,
    		OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    
    	fSize = GetFileSize(fp, 0);
    	fBuffer = (unsigned char *)VirtualAlloc(NULL, fSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    	ReadFile(fp, fBuffer, fSize, &dwSize, 0);
    	CloseHandle(fp);
    
    	__asm
    	{
    		mov eax,fBuffer
    		push eax
    		ret
    		int 3
    	}
    	return 0;
    }
    

    ShellCode注入进程:

    #include <stdio.h>
    #include <windows.h>
    
    unsigned char ShellCode[] = "shellcode代码";
    
    BOOL InjectShellCode(int Pid)
    {
    	HANDLE Handle, remoteThread;
    	PVOID remoteBuffer;
    
    	Handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
    
    	remoteBuffer = VirtualAllocEx(Handle, NULL, sizeof(ShellCode), (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
    	WriteProcessMemory(Handle, remoteBuffer, ShellCode, sizeof(ShellCode), NULL);
    	remoteThread = CreateRemoteThread(Handle, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0, NULL);
    	CloseHandle(Handle);
    }
    
    
    int main(int argc, char *argv[])
    {
    	InjectShellCode(1024);
    	return 0;
    }
    
  • 相关阅读:
    ShoneSharp语言(S#)的设计和使用介绍系列(10)— 富家子弟“语句“不炫富
    ShoneSharp语言(S#)的设计和使用介绍系列(9)— 一等公民“函数“爱炫巧
    ShoneSharp语言(S#)的设计和使用介绍系列(8)— 最炫“公式”风
    ShoneSharp语言(S#)的设计和使用介绍系列(1)— 开篇
    ShoneSharp语言(S#)软件更新13.7版
    ShoneSharp语言(S#)软件更新13.6版
    ShoneSharp语言(S#)的设计和使用介绍系列(7)— 布尔Bool及对象Object
    ShoneSharp语言(S#)的设计和使用介绍系列(6)— 字符串String
    自然语言处理系列-4条件随机场(CRF)及其tensorflow实现
    【NLP】老司机带你入门自然语言处理
  • 原文地址:https://www.cnblogs.com/LyShark/p/13718524.html
Copyright © 2020-2023  润新知