关于如何使用高级语言编写ShellCode的一些思考

思考

查了下网络上的通用ShellCode和目前为止出到第四版的《加密与解密》,发现关于ShellCode的编写基本上是在汇编这一个层级编写的。但是使用汇编语言写应该是十分不方便了,联想到C语言实际上是零抽象的高级语言,能不能直接使用C语言来编写ShellCode呢?

如何使用C语言来编写ShellCode那么,仔细想一想我们ShellCode的运行环境和平时的代码的运行环境有什么不一样的地方呢?

其中最大的一个区别就是我们的ShellCode的变量位置是不确定的,因此必须使用地址无关技术来实现。而实际上,C语言的局部变量也是通过ESP+偏移的方式来确定的,并且C的局部变量的地址也是不确定的。所以我们可以通过在一个函数里面编写ShellCode来实现通过高级语言来编写ShellCode。
使用C编写ShellCode时还有什么不能使用的语法呢?需要注意以下几点:

  • 不能使用全局变量:全局变量位于.data段,对于EXE文件来说,一般的变量地址是固定的,但是对于DLL来说位置则根据加载位置不同而改变;
  • 不能进行函数调用:C语言的编译器将函数调用编译成call xxx,对应的机械码为E8 xx xx xx xx,后四个字节是目标地址和指令地址的补码;
  • 不能使用复杂数据,如字符串类型。类似于:If(a==”String is invaild”)这样的语句是不行的,原因在于字符串类型实际上是存储于程序的数据段。而如果想要使用字符串,正确的方式应该是:
char Buffer[10000];
	Buffer[0] = 'I';
	Buffer[1] = 'm';
	Buffer[2] = '\0';

Debug版代码和Release版代码的区别

Debug版是为了尽可能尽早的找到出错代码的位置,这意味着编译器会在编译过程中插入检测代码。让我们看如下的一段代码:

void test() {
	int a = 1;
	int* b = &a;
}

在Debug版会编译出如下的代码:

void test() {
001F20F0 55                   push        ebp  
001F20F1 8B EC                mov         ebp,esp  
001F20F3 81 EC D8 00 00 00    sub         esp,0D8h  
001F20F9 53                   push        ebx  
001F20FA 56                   push        esi  
001F20FB 57                   push        edi  
001F20FC 8D BD 28 FF FF FF    lea         edi,[ebp-0D8h]  
001F2102 B9 36 00 00 00       mov         ecx,36h  
001F2107 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
001F210C F3 AB                rep stos    dword ptr es:[edi]  
001F210E B9 56 D0 1F 00       mov         ecx,offset _9793C0DE_源@cpp (01FD056h)  
001F2113 E8 62 F7 FE FF       call        @__CheckForDebuggerJustMyCode@4 (01E187Ah)  
	int a = 1;
001F2118 C7 45 F8 01 00 00 00 mov         dword ptr [a],1  
	int* b = &a;
001F211F 8D 45 F8             lea         eax,[a]  
001F2122 89 45 EC             mov         dword ptr [b],eax  
}
001F2125 52                   push        edx  
001F2126 8B CD                mov         ecx,ebp  
001F2128 50                   push        eax  
001F2129 8D 15 4C 21 1F 00    lea         edx,ds:[1F214Ch]  
001F212F E8 E0 F3 FE FF       call        @_RTC_CheckStackVars@8 (01E1514h)  
001F2134 58                   pop         eax  
001F2135 5A                   pop         edx  
001F2136 5F                   pop         edi  
001F2137 5E                   pop         esi  
001F2138 5B                   pop         ebx  
001F2139 81 C4 D8 00 00 00    add         esp,0D8h  
001F213F 3B EC                cmp         ebp,esp  
001F2141 E8 D2 F4 FE FF       call        __RTC_CheckEsp (01E1618h)  
001F2146 8B E5                mov         esp,ebp  
001F2148 5D                   pop         ebp  
001F2149 C3                   ret

其中有三条call指令,这三条指令都是为了检查栈溢出的,但是违反了不能再ShellCode中使用Call指令的原则,所以这样的代码是不正确的。也就是说必须使用release版的代码。

实例

这里是一个示例,用于获取GetProcAddr函数的地址。

#include"InlineFunctions.h"
#include<stdio.h>
#include<Windows.h>
typedef HANDLE (WINAPI*PGetFunction)(_In_ HMODULE hModule,_In_ LPCSTR lpProcName);
int ShellCode_Srouce() {
	PGetFunction PGetProcAddress = nullptr;
	char* DllBase;
	char* ProcessBase;

	__asm {
		mov ebx, fs: [0x30]         //得到peb结构体的地址
		mov ebx, [ebx + 0xc]       //得到Ldr结构体的地址
		mov ebx, [ebx + 0xc]       //得到ldr.InLoadOrderModuleList.Flink 第一个模块,当前进程
		mov ProcessBase, ebx
		mov ebx, [ebx]             //得到第二个模块地址 ntdll.dll
		mov ebx, [ebx]             //得到第三个模块地址 kernel32.dll
		mov ebx, [ebx + 0x18]        //得到第三个模块地址(kernel32模块的dllbase)      
		mov DllBase, ebx
	}
	IMAGE_DOS_HEADER* DOS_Header = (IMAGE_DOS_HEADER*)DllBase;
	IMAGE_NT_HEADERS* NT_Header = (IMAGE_NT_HEADERS*)(DOS_Header->e_lfanew + (char*)DOS_Header);
	IMAGE_DATA_DIRECTORY* Export_Dir = (IMAGE_DATA_DIRECTORY*)&(NT_Header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]);
	IMAGE_EXPORT_DIRECTORY* Export = (IMAGE_EXPORT_DIRECTORY*)(Export_Dir->VirtualAddress + DllBase);
	for (int i = 0; i < Export->NumberOfNames; i++)
	{
		DWORD* FunctionNameRVA = (DWORD*)(DllBase + (DWORD)(Export->AddressOfNames + i * sizeof(DWORD)));
		char* FunctionName = DllBase + *FunctionNameRVA;
		if (FunctionName[0] != 'G')//这样做是为了使数据嵌入到指令中
			continue;
		if (FunctionName[1] != 'e')
			continue;
		if (FunctionName[2] != 't')
			continue;
		if (FunctionName[3] != 'P')
			continue;
		if (FunctionName[4] != 'r')
			continue;
		if (FunctionName[5] != 'o')
			continue;
		if (FunctionName[6] != 'c')
			continue;
		if (FunctionName[7] != 'A')
			continue;
		if (FunctionName[8] != 'd')
			continue;
		if (FunctionName[9] != 'd')
			continue;
		if (FunctionName[10] != 'r')
			continue;
		if (FunctionName[11] != 'e')
			continue;
		if (FunctionName[12] != 's')
			continue;
		if (FunctionName[13] != 's')
			continue;
		if (FunctionName[14] != '\0')
			continue;
		WORD Offset = *((WORD*)(Export->AddressOfNameOrdinals + DllBase) + i);
		DWORD FunctionRVA = *((DWORD*)(DllBase + Export->AddressOfFunctions) + Offset);
		PGetProcAddress = (PGetFunction)(DllBase + FunctionRVA);
		break;
	}
	return;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值