-
-
[原创]简单Win32的Shellcode实现框架和解析
-
2022-11-25 15:25
14522
-
[原创]简单Win32的Shellcode实现框架和解析
简单Win32的Shellcode实现框架和解析
本文主要讲述如何快速将函数转为shellcode然后加密存储用于混淆反汇编器。该技巧广泛应用于恶意软件中,作为一次一密或者反分析的方法,在信息不足时可以有效掩盖可执行段。
编写局部SHELLCODE
1 2 3 4 5 6 7 8 9 10 11 | int __stdcall func( int a)
{
if (a > 50 )
{
return a - 50 ;
}
else
{
return a + 50 ;
}
}
|
由于shellcode被编写后将以硬编码的形式被安置在程序内部进行加密、隐藏和压缩等操作,因此其中的诸多引用、布局和跳转需要特别注意。关于shellcode的编写读者可以自行寻找各参考资料进行学习。在此将总结大部分shellcode的编写原则。
变量声明——局部性原理
为了保证shellcode在引用变量时能够准确定位,需要保证shellcode使用的变量被声明、释放在shellcode内部。如果实在需要引用外部变量,请参见任意调用文章。
错误方式
1 2 3 4 5 6 7 | LPSTR const_string = { "This is a very loooooooooooog constant" };
DWORD const_aes_s_box = { 0x63 , 0x7C , 0x77 , 0x7B ...}
void func()
{
/ / use(const_string);
/ / use(const_aes_s_box);
}
|
上述这种声明方式需要保证被声明的变量完全位于shellcode内部,否则在shellcode生成并读出之后,由于代码段的变更,诸多变量的引用地址会发生变动,这时编码在原来shellcode中的引用地址就将失效。
正确方式
1 2 3 4 5 6 7 | void func()
{
LPSTR const_string = { "This is a very loooooooooooog constant" };
DWORD const_aes_s_box = { 0x63 , 0x7C , 0x77 , 0x7B ...}
/ / use(const_string);
/ / use(const_aes_s_box);
}
|
函数调用
调用系统函数
众所周知,诸windows.h文件中的函数都依赖于在windows根目录下的大量dll文件,因此要调用系统函数,需手动载入系统函数。手动载入系统动态链接库需要使用LoadLibrary函数。
LoadLibrary函数是kernal32.dll
的导出函数。要获得此函数,首先需要载入kernal32.dll
。好在绝大多数Windows程序在载入时都需要同时载入这一关键系统动态链接库。根据PEB表可以获得此动态链接库的地址。
找到kernal32.dll位置(存入ebx)
1 2 3 4 5 6 7 8 | xor ecx, ecx ; 置空ecx
mov eax, fs:[ecx + 0x30 ] ; 取出peb,存入eax
mov eax, [eax + 0xc ] ; 取出PEB - >Ldr
mov esi, [eax + 0x14 ] ; 取出PEB - >Ldr.InMemoryOrderModuleList
lodsd ; 数组操作
xchg eax, esi ;
lodsd ; 取出kernal32.dll
mov ebx, [eax + 0x10 ] ; 取出基地址存入ebx
|
获取kernal32.dll的导出表
1 2 3 4 5 6 7 | mov edx, [ebx + 0x3c ] ; 获取任意值的DOS - >e_lfanew
add edx, ebx ; 获取PE头
mov edx, [edx + 0x78 ] ; 获取导出表偏移
add edx, ebx ; 获取导出表
mov esi, [edx + 0x20 ] ;
add esi, ebx ; 导出表名称
xor ecx, ecx ;
|
获取GetProcAddress的位置(存入edx)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Get_Function:
inc ecx ; 递增序数
lodsd ; 获取导出名称表
add eax, ebx ; 获取函数名称,指针存入eax
cmp dword ptr[eax], 0x50746547 ; GetP
jnz Get_Function
cmp dword ptr[eax + 0x4 ], 0x41636f72 ; rocA
jnz Get_Function
cmp dword ptr[eax + 0x8 ], 0x65726464 ; ddre
jnz Get_Function
mov esi, [edx + 0x24 ] ; 获取序数偏移
add esi, ebx ; 将序数表存入esi
mov cx, [esi + ecx * 2 ] ; 获得函数数量
dec ecx
mov esi, [edx + 0x1c ] ; 获得函数表偏移
add esi, ebx ; 存入esi
mov edx, [esi + ecx * 4 ] ;
add edx, ebx ; edx中存入GetProcAddress地址
|
获取LoadLibrary的位置(最终存入eax)
1 2 3 4 5 6 7 8 9 10 | xor ecx, ecx ;
push ebx ; 获取kernel32.dll的基地址
push edx ; GetProcAddress
push ecx ; 0
push 0x41797261 ; aryA
push 0x7262694c ; Libr
push 0x64616f4c ; Load
push esp ; "LoadLibrary"
push ebx ; Kernel32基地址
call edx ; GetProcAddress(LL)
|
任意调用
要实现任意调用,需要事先给出标志性的可控机器码。一种可行的方法是使用__asm关键字配合_emit指令嵌入一小段关键字节,用于寻址。(对于msvc而言)
一种示例方法如下
参考 c++ - Calling a non-exported function in a DLL - Stack Overflow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | typedef void ( * UnExportedFunc)();
/ / ...
void CallUnExportedFunc()
{
/ / This will get the DLL base address (which can vary)
HMODULE hMod = GetModuleHandleA( "My.dll" );
/ / Get module info
MODULEINFO modinfo = { NULL, };
GetModuleInformation( GetCurrentProcess(), hMod, &modinfo, sizeof(modinfo) );
/ / This will search the module for the address of a given signature
DWORD dwAddress = FindPattern(
hMod, modinfo.SizeOfImage,
(PBYTE) "\xC7\x06\x00\x00\x00\x00\x89\x86\x00\x00\x00\x00\x89\x86" ,
"xx????xx????xx"
);
/ / Calculate the acutal address
DWORD_PTR funcAddress = (DWORD_PTR)hMod + dwAddress;
/ / Cast the address to a function poniter
UnExportedFunc func = (UnExportedFunc)funcAddress;
/ / Call the function
func();
}
|
硬编码字节置换
- 部分指令会在硬编码中引入非法字符,例如
mov eax, 0
就会向硬编码中引入0x00
字节,这可能导致在执行诸如memcpy
、memlen
、时出现错误。因此出现这些指令时需要进行一定的置换,例如上述指令更换为xor eax, eax
。但是对于其他不可置换的指令,建议进行局部xor加密,运行时再解密。
- 一部分特殊使用的shellcode,例如字符串类型的shellcode(参考SkyLined/alpha3: Alphanumeric shellcode encoder. (github.com))中不应该出现
\b\n\t
等字符。
调用shellcode
使用下述方式将任意unsigned int
数组转换为函数并执行调用。
1 2 3 4 5 6 7 | typedef BOOL (WINAPI * pFunc)(IN ARGU);
/ / ...
DWORD pflOldProtect;
VirtualProtect(shellcode, sizeof(unsigned char) * strlen(shellcode), 0x40 , &pflOldProtect);
pFunc func = (pFunc)(void * )shellcode;
/ / ...
func(any_argu);
|
其中关键的部分在于需要绕过DEP,因为Windows的保护措施,存储shellcode的位置总是存在于全局变量中,而全局变量的段是不可执行的。因此在执行shellcode之前需要先更改shellcode所在内存区的可执行权限。
另外,在书写内嵌shellcode时需要不断结合反汇编结果,时刻注意编译器的优化是否影响了生成的硬编码。在实战中可能遇到优化器将**__stdcall**
更改为**__fastcall**
的情况,这个时候就需要结合反汇编结果来适当的插入push和mov指令来有效的补足shellcode的错误。
一个模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | typedef BOOL (WINAPI * pFunc)(IN LPSTR); / / 生成函数原型
unsigned char shellcode[] = / / 仅包含最基础的函数序言和结语部分。
/ / 在此处对 0x40 进行异或得到非 0 字段,同时有运行时解密的效果
{
0x15 , 0xcb , 0xac , 0xc3 , 0xac , 0x40 , 0x89 , 0x82 , 0x40 , 0x40
};
DWORD pflOldProtect; / / 存储原来的保护等级
VirtualProtect(shellcode, sizeof(unsigned char) * strlen(shellcode), 0x40 , &pflOldProtect);
/ / 对shellcode位置的内存空间进行权限修正
for ( int i = 0 ; i < strlen(shellcode); i + + ) / / 解密shellcode
/ / 加解密shellcode可以用各种加密算法实现,例如rc4、tea、aes等。在此不表
{
shellcode[i] ^ = 0x40 ;
}
pFunc func = (pFunc)(void * )shellcode; / / 类型转换
func( "hello world!" )
|
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法
最后于 2022-11-25 15:35
被Hedione编辑
,原因: 更改题目和标题样式