大数跨境
0
0

x64汇编与 shellcode 入门教程 05

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

这是我的系列文章《x64汇编与 shellcode 入门教程》的第五篇,前四篇在这里:

x64汇编与shellcode入门教程 01

x64汇编与 shellcode 入门教程 02

x64汇编与 shellcode 入门教程 03

x64汇编与 shellcode 入门教程 04


在这篇文章中,我们要做如下事情:

顺便说一句,我们的代码不会包含任何NULL值。

  • 定位Kernel32模块并收集PE导出表信息
  • 动态定位GetProcAddress函数
  • 使用GetProcAddress的句柄来定位LoadLibraryA函数的地址
  • 使用GetProcAddress的句柄来定位ExitProcess函数
  • 使用LoadLibraryA函数的句柄加载user32.dll
  • 使用user32.dll的句柄,通过GetProcAddress在user32.dll中定位MessageBoxA的地址
  • 弹出messagebox消息框
  • 调用ExitProcess以正常退出程序

这些内容是我们在前面的系列文章中承诺给读者的。

在深入探讨之前,我想指出在重新学习x64汇编时发现的一点。也许只是我的个人感受,但在查看Windows 11的PE导出表时,名称索引的地址似乎与函数地址是对应的。这样一来,我们就不再需要借助名称索引的地址来获取序号了。我可能搞错了,但我在两台不同的机器上的Windows 11系统上进行了测试,结果都是一样的。我完全跳过了序号查找这一步,直接将从名称查找地址中获取的索引代入函数查找地址,并没有出现任何问题。这值得思考一下。也许这一点应该由比我更了解Windows内部机制的人来进一步详细阐述。先把这个小想法放在一边,我们继续往下说。

今天的代码将是本系列中迄今为止最具挑战性的,所以先提前提醒一下。内容很多,但我相信你能做好。我会借鉴上面的要点,将其分成几个部分来讲解。

序言 - 定位Kernel32并收集PE导出表信息

;nasm -fwin64 [x64findkernel32.asm]
;ld -m i386pep -o x64findkernel32.exe x64findkernel32.obj
BITS 64
SECTION .text
global main
main:
sub rsp, 0x28
and rsp, 0xFFFFFFFFFFFFFFF0
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
xor rcx, rcx                      ; Avoid null bytes from mov edx,[rbx+0x88] by using rcx register to add
add cx, 0x88ff
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 functions for future use

动态定位GetProcAddress

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 
kernel32findfunction:             ; Loop over Export Address Table to find WinApi names
    jecxz FunctionNameNotFound    ; Loop around this function until we find GetProcAddress
    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
    mov r9, qword [rax]           ; R9 = "GetProcA"
    cmp [rbx], r9                 ; Compare first 8 bytes
    jnz kernel32findfunction      ; If not equal, continue loop
    mov r9d, dword [rax + 8]      ; R9 = "ddress"
    cmp [rbx + 8], r9d            ; Compare remaining part
    jz FunctionNameFound          ; If match, function found
 jnz kernel32findfunction
FunctionNameNotFound:
    int3
FunctionNameFound:
    push rcx
    pop r15                       ; getprocaddress position
    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

我真的希望不需要这么多行代码就能动态定位kernel32以及你最初的API,但看起来这种情况已经持续很久了。不过值得庆幸的是,代码的其他部分要短得多,也更容易理解,至少在我看来是这样。

使用GetProcAddress句柄找到LoadLibraryA的地址

; Prepare arguments for getting 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
    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                  ; alignmnent/shadow space adjustments
    mov r15, rax                   ; holds LoadLibraryA!

使用GetProcAddress句柄查找ExitProcess的地址

;getexitprocess
    mov r14, r12                    ; temporary assignment of GetProcess 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 r14                        ; Call GetProcAddress
    add rsp, 0x30
    mov r14, rax                    ; holds ExitProcess!

使用LoadLibraryA句柄定位user32.dll

;locate user32.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, 0x642E323372657375     ; Add "user32.d" string to RAX.
    push rax                        ; Push RAX to stack
    mov rcx, rsp                    ; Move a pointer to User32.dll into RCX.
    sub rsp, 0x30
    call r15                        ; Call LoadLibraryA("user32.dll")
    mov rdi, rax                    ; holds User32.dll address

使用user32.dll句柄和GetProcAddress定位MessageBoxA地址

; Prepare arguments for GetProcAddress for MessageBoxA:
    mov rcx, rdi                    ; RCX = handle to user32.dll (first argument)
    mov rax, 0x9041786F             ; Load "oxA" into RAX
    shl eax, 0x8                    ; 0000000041786F00
    shr eax, 0x8                    ; 000000000041786F
    push rax
    mov rax, 0x426567617373654D     ; Load "MessageB" into RAX                  
    push rax
    mov rdx, rsp                    ; RDX points to "MessageBoxA" (second argument)
    sub rsp, 0x30
    call r12                        ; Call GetProcAddress
    mov r15, rax                    ; store MessageBoxA

弹出MessageBox消息框

;messageboxfinally: 
    xor rcx, rcx                    ; hWnd = NULL (no owner window)
    mov rax, 0x9090906D             ; m, 0
    shl eax, 24                     ; 000000006D000000
    shr eax, 24                     ; 000000000000006D
    push rax
    mov rax, 0x3374737973743367     ; g3tsyst3
    push rax
    mov rdx, rsp                    ; lpText = pointer to message
    mov r8, rsp                     ; lpCaption = pointer to title
    xor r9d, r9d                    ; uType = MB_OK (OK button only)
    sub rsp, 0x30
    call r15                        ; Call MessageBoxA
    add rsp, 0x30

调用ExitProcess

;exitcleanly:
    xor ecx, ecx
    call r14                        ;ExitProcess

就是这样!好了,现在我们继续获取我们的shellcode(机器码):


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\xe6\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\xd6\x48\x83\xc4\x30\x49\x89\xc6\xb8\x6c"
"\x6c\x90\x90\xc1\xe0\x10\xc1\xe8\x10\x50\x48\xb8\x75\x73\x65\x72\x33\x32\x2e\x64\x50\x48"
"\x89\xe1\x48\x83\xec\x30\x41\xff\xd7\x48\x89\xc7\x48\x89\xf9\xb8\x6f\x78\x41\x90\xc1\xe0"
"\x08\xc1\xe8\x08\x50\x48\xb8\x4d\x65\x73\x73\x61\x67\x65\x42\x50\x48\x89\xe2\x48\x83\xec"
"\x30\x41\xff\xd4\x49\x89\xc7\x48\x31\xc9\xb8\x6d\x90\x90\x90\xc1\xe0\x18\xc1\xe8\x18\x50"
"\x48\xb8\x67\x33\x74\x73\x79\x73\x74\x33\x50\x48\x89\xe2\x49\x89\xe0\x45\x31\xc9\x48\x83"
"\xec\x30\x41\xff\xd7\x48\x83\xc4\x30\x31\xc9\x41\xff\xd6";

将shellcode放到我们的C++程序中:

#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\xe6\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\xd6\x48\x83\xc4\x30\x49\x89\xc6\xb8\x6c"
"\x6c\x90\x90\xc1\xe0\x10\xc1\xe8\x10\x50\x48\xb8\x75\x73\x65\x72\x33\x32\x2e\x64\x50\x48"
"\x89\xe1\x48\x83\xec\x30\x41\xff\xd7\x48\x89\xc7\x48\x89\xf9\xb8\x6f\x78\x41\x90\xc1\xe0"
"\x08\xc1\xe8\x08\x50\x48\xb8\x4d\x65\x73\x73\x61\x67\x65\x42\x50\x48\x89\xe2\x48\x83\xec"
"\x30\x41\xff\xd4\x49\x89\xc7\x48\x31\xc9\xb8\x6d\x90\x90\x90\xc1\xe0\x18\xc1\xe8\x18\x50"
"\x48\xb8\x67\x33\x74\x73\x79\x73\x74\x33\x50\x48\x89\xe2\x49\x89\xe0\x45\x31\xc9\x48\x83"
"\xec\x30\x41\xff\xd7\x48\x83\xc4\x30\x31\xc9\x41\xff\xd6";


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;
}

执行效果:


虽然这看起来像是一段不必要的大量代码,但从整体来看还不算太糟。我在Notepad++中显示的代码总共有132行。如果我们不需要考虑空值的话,代码会少很多。但是,对于加载一个消息框所需的代码量来说,这真的不算多了😸 不过,当所有部分都整合在一起时,那种感觉确实很棒。接下来是套接字和反向shell的内容!请继续关注,感谢阅读,感谢大家对这个系列的支持。



声明

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


推荐阅读

银狐远控问题排查与修复——Viusal Studio集成Google Address Sanitizer排查内存问题
银狐远控代码中差异屏幕bug修复
银狐远程屏幕内存优化方法探究
银狐远程软件bug修复记录 第03篇
银狐远程软件代理映射功能优化思路分享

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