1 /**
  2  *  ---------
  3  * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
  4  * |#       #|
  5  * |#       #|  Copyright (c) 1999-2009 CardContact Software & System Consulting
  6  * |'##> <##'|  Andreas Schwier, 32429 Minden, Germany (www.cardcontact.de)
  7  *  ---------
  8  *
  9  *  This file is part of OpenSCDP.
 10  *
 11  *  OpenSCDP is free software; you can redistribute it and/or modify
 12  *  it under the terms of the GNU General Public License version 2 as
 13  *  published by the Free Software Foundation.
 14  *
 15  *  OpenSCDP is distributed in the hope that it will be useful,
 16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 18  *  GNU General Public License for more details.
 19  *
 20  *  You should have received a copy of the GNU General Public License
 21  *  along with OpenSCDP; if not, write to the Free Software
 22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 23  *
 24  * @fileoverview Support for Global Platform SCP03 protocol
 25  */
 26 
 27 var APDU = require('scsh/cardsim/APDU').APDU;
 28 
 29 
 30 
 31 /**
 32  * Class implementing support for Global Platform SCP03 secure messaging protocol
 33  *
 34  * @constructor
 35  * @param {Crypto} crypto the crypto provider
 36  * @param {Key} kenc the static secure channel encryption key
 37  * @param {Key} kmac the static secure channel message authentication code key
 38  * @param {Key} kdek the static data encryption key
 39  * @param {ByteString) aid the AID to use for challenge calculation
 40  */
 41 function GPSCP03(crypto, kenc, kmac, kdek, aid) {
 42 	this.crypto = crypto;
 43 	this.kenc = kenc;
 44 	this.kmac = kmac;
 45 	this.kdek = kdek;
 46 	this.chaining = ByteString.valueOf(0, 16);
 47 	this.enccnt = 1;
 48 	this.seclevel = 0;
 49 
 50 	if (aid != undefined) {
 51 		assert(aid instanceof ByteString, "Argument aid must be ByteString");
 52 		this.aid = aid;
 53 	} else {
 54 		this.aid = new ByteString("A000000151000000", HEX);
 55 	}
 56 }
 57 
 58 exports.GPSCP03 = GPSCP03;
 59 
 60 
 61 
 62 /**
 63  * Strip the ISO padding from dechipered cryptogram
 64  *
 65  * @param {ByteString} the plain text with padding
 66  * @type ByteString
 67  * @return the plain text without padding
 68  */
 69 GPSCP03.stripPadding = function(plain) {
 70 	var i = plain.length - 1;
 71 	while ((i > 0) && (plain.byteAt(i) == 0)) {
 72 		i--;
 73 	}
 74 	if (plain.byteAt(i) != 0x80) {
 75 		throw new GPError(module.id, GPError.CRYPTO_FAILED, 0, "Padding check failed");
 76 	}
 77 	return plain.left(i);
 78 }
 79 
 80 
 81 
 82 /**
 83  * Reset the internal state
 84  **/
 85 GPSCP03.prototype.reset = function() {
 86 	this.chaining = ByteString.valueOf(0, 16);
 87 	this.enccnt = 1;
 88 	this.seclevel = 0;
 89 }
 90 
 91 
 92 
 93 /**
 94  * Called by the secure messaging wrapper to wrap the command APDU
 95  */
 96 GPSCP03.prototype.wrap = function(apduToWrap, usageQualifier) {
 97 
 98 	if (this.seclevel & 0x01) {
 99 		var apdu = new APDU(apduToWrap);
100 
101 		if (apdu.hasCData()) {
102 			if (this.seclevel & 0x02) {
103 				var ivinp = ByteString.valueOf(this.enccnt, 16);
104 				var iv = this.crypto.encrypt(this.skenc, Crypto.AES_ECB, ivinp);
105 				var encinp = apdu.getCData().pad(Crypto.ISO9797_METHOD_2_16);
106 
107 				apdu.setCData(this.crypto.encrypt(this.skenc, Crypto.AES_CBC, encinp, iv));
108 			}
109 		} else {
110 			apdu.setCData(new ByteString("", HEX));
111 		}
112 
113 		var bb = new ByteBuffer();
114 		bb.append(this.chaining);
115 
116 		var cla = apdu.getCLA();
117 		if (cla & 0x40) {
118 			// GP Format
119 			cla = cla & 0xC0 | 0x40;
120 			apdu.cla |= 0x40;
121 		} else {
122 			// ISO Format
123 			cla = cla & 0x80 | 0x04;
124 			apdu.cla |= 0x04;
125 		}
126 		bb.append(cla);
127 		bb.append(apdu.getINS());
128 		bb.append(apdu.getP1());
129 		bb.append(apdu.getP2());
130 
131 		var lc = apdu.getCData().length + 8;
132 		if (apdu.isExtendedLength()) {
133 			bb.append(0);
134 			bb.append(ByteString.valueOf(lc, 2));
135 		} else {
136 			bb.append(ByteString.valueOf(lc, 1));
137 		}
138 
139 		bb.append(apdu.getCData());
140 		var mac = this.crypto.sign(this.skmac, Crypto.AES_CMAC, bb.toByteString());
141 
142 		this.chaining = mac;
143 
144 		apdu.setCData(apdu.getCData().concat(mac.left(8)));
145 
146 		if (this.clatransformation) {
147 			apdu.setCLA(this.clatransformation(apdu.getCLA()));
148 		}
149 
150 		apduToWrap = apdu.getCommandAPDU();
151 	}
152 	return apduToWrap;
153 }
154 
155 
156 
157 /**
158  * Called by the secure messaging wrapper to unwrap the response APDU
159  */
160 GPSCP03.prototype.unwrap = function(apduToUnwrap, usageQualifier) {
161 	if ((this.seclevel & 0x10) && (apduToUnwrap.length > 2)) {
162 		var bb = new ByteBuffer();
163 		bb.append(this.chaining);
164 		bb.append(apduToUnwrap.left(apduToUnwrap.length - 10));
165 		bb.append(apduToUnwrap.right(2));
166 
167 		var mac = this.crypto.sign(this.skrmac, Crypto.AES_CMAC, bb.toByteString()).left(8);
168 
169 		if (!mac.equals(apduToUnwrap.bytes(apduToUnwrap.length - 10, 8))) {
170 			throw new GPError(module.id, GPError.CRYPTO_FAILED, 0, "MAC on R-Data failed verification");
171 		}
172 
173 		if ((this.seclevel & 0x20) && (apduToUnwrap.length > 10)) {
174 			var ivinp = ByteString.valueOf(0x80).concat(ByteString.valueOf(this.enccnt, 15));
175 			var iv = this.crypto.encrypt(this.skenc, Crypto.AES_ECB, ivinp);
176 
177 			var plain = this.crypto.decrypt(this.skenc, Crypto.AES_CBC, apduToUnwrap.left(apduToUnwrap.length - 10), iv);
178 			apduToUnwrap = GPSCP03.stripPadding(plain).concat(apduToUnwrap.right(2));
179 		} else {
180 			apduToUnwrap = apduToUnwrap.left(apduToUnwrap.length - 10).concat(apduToUnwrap.right(2));
181 		}
182 	}
183 
184 	this.enccnt++;
185 
186 	return apduToUnwrap;
187 }
188 
189 
190 
191 
192 /**
193  * Counterpart to wrap and meant to be used in a simulator
194  */
195 GPSCP03.prototype.unwrapCommandAPDU = function(apdu) {
196 	if (this.seclevel == 0) {
197 		throw new GPError(module.id, GPError.CRYPTO_FAILED, APDU.SW_CONDOFUSENOTSAT, "No secure messaging active");
198 	}
199 
200 	if (this.seclevel & 0x01) {
201 		if (!apdu.hasCData() || apdu.getCData().length < 8) {
202 			throw new GPError(module.id, GPError.CRYPTO_FAILED, APDU.SW_SMOBJMISSING, "MAC on C-Data missing");
203 		}
204 		var bb = new ByteBuffer();
205 		bb.append(this.chaining);
206 
207 		var cla = apdu.getCLA();
208 		if (cla & 0x40) {
209 			// GP Format
210 			cla = cla & 0xC0 | 0x40;
211 		} else {
212 			// ISO Format
213 			cla = cla & 0x80 | 0x04;
214 		}
215 
216 		bb.append(cla);
217 		bb.append(apdu.getINS());
218 		bb.append(apdu.getP1());
219 		bb.append(apdu.getP2());
220 
221 		var lc = apdu.getCData().length;
222 		if (apdu.isExtendedLength()) {
223 			bb.append(0);
224 			bb.append(ByteString.valueOf(lc, 2));
225 		} else {
226 			bb.append(ByteString.valueOf(lc, 1));
227 		}
228 
229 		bb.append(apdu.getCData().left(lc - 8));
230 
231 		var mac = this.crypto.sign(this.skmac, Crypto.AES_CMAC, bb.toByteString());
232 
233 		if (!mac.left(8).equals(apdu.getCData().right(8))) {
234 			throw new GPError(module.id, GPError.CRYPTO_FAILED, APDU.SW_SECSTATNOTSAT, "MAC on C-Data failed verification");
235 		}
236 
237 		this.chaining = mac;
238 
239 		apdu.setCData(apdu.getCData().left(lc - 8));
240 
241 		if ((this.seclevel & 0x02) && (lc > 8)) {
242 			var ivinp = ByteString.valueOf(this.enccnt, 16);
243 			var iv = this.crypto.encrypt(this.skenc, Crypto.AES_ECB, ivinp);
244 
245 			var plain = this.crypto.decrypt(this.skenc, Crypto.AES_CBC, apdu.getCData(), iv);
246 			apdu.setCData(GPSCP03.stripPadding(plain));
247 		}
248 
249 	}
250 }
251 
252 
253 
254 /**
255  * Counterpart to unwrap and meant to be used in a simulator
256  */
257 GPSCP03.prototype.wrapResponseAPDU = function(apdu) {
258 	if (this.seclevel & 0x10) {
259 		if ((this.seclevel & 0x20) && apdu.hasRData()) {
260 			var ivinp = ByteString.valueOf(0x80).concat(ByteString.valueOf(this.enccnt, 15));
261 			var iv = this.crypto.encrypt(this.skenc, Crypto.AES_ECB, ivinp);
262 			var plain = apdu.getRData().pad(Crypto.ISO9797_METHOD_2_16);
263 
264 			var cryptogram = this.crypto.encrypt(this.skenc, Crypto.AES_CBC, plain, iv);
265 			apdu.setRData(cryptogram);
266 		}
267 
268 		var bb = new ByteBuffer();
269 		bb.append(this.chaining);
270 
271 		bb.append(apdu.getResponseAPDU());
272 
273 		var mac = this.crypto.sign(this.skrmac, Crypto.AES_CMAC, bb.toByteString()).left(8);
274 
275 		apdu.setRData(apdu.hasRData() ? apdu.getRData().concat(mac) : mac);
276 	}
277 
278 	this.enccnt++;
279 }
280 
281 
282 
283 GPSCP03.prototype.setCLATransformation = function(clatransformation) {
284 	assert(typeof(clatransformation) == "function", "Argument clatransformation must be a function");
285 	this.clatransformation = clatransformation;
286 }
287 
288 
289 
290 /**
291  * Issue INITIALIZE UPDATE command to card and parse response
292  *
293  * @param {Card} card the card to use
294  * @param {Number} keyVersion the version of the key to use (0 for default)
295  * @param {Number} keyId the key id to use (usually 0)
296  */
297 GPSCP03.prototype.initializeUpdate = function(card, keyVersion, keyId) {
298 	this.hostChallenge = this.crypto.generateRandom(8);
299 
300 	var cla = 0x80;
301 	if (this.clatransformation) {
302 		cla = this.clatransformation(cla);
303 	}
304 
305 	var rsp = card.sendApdu(cla, 0x50, keyVersion, keyId, this.hostChallenge, 0, [0x9000]);
306 
307 	this.diversificationData = rsp.bytes(0, 10);
308 	this.keyInformation = rsp.bytes(10, 3);
309 	this.cardChallenge = rsp.bytes(13, 8);
310 	this.cardCryptogram = rsp.bytes(21, 8);
311 	if (rsp.length > 29) {
312 		this.sequenceCounter = rsp.bytes(29,3);
313 		var context = this.sequenceCounter.concat(this.aid);
314 		var ref = this.deriveKey(this.kenc, 2, 8, context);
315 		assert(ref.equals(this.cardChallenge), "Pseudo-random card challenge does not match Kenc");
316 	}
317 }
318 
319 
320 
321 /**
322  * Set the sequence number for pseudo-random card challenges
323  *
324  * @param {ByteString} cnt the last used counter value
325  */
326 GPSCP03.prototype.setSequenceCounter = function(cnt) {
327 	assert(cnt instanceof ByteString, "Argument cnt must be ByteString");
328 	assert(cnt.length == 3, "Argument cnt must be 3 bytes long");
329 	this.sequenceCounter = cnt;
330 }
331 
332 
333 
334 /**
335  * Set the key version and protocol parameter
336  *
337  * @param {Number} version the key version indicated in INITIALIZE_UPDATE
338  * @param {Number} parami the i parameter for the SCP03 protocol (Default '00')
339  */
340 GPSCP03.prototype.setKeyInfo = function(version, parami) {
341 	assert(typeof(version) == "number", "Argument version must be of type number");
342 	if (typeof(parami) == "undefined") {
343 		parami = 0;
344 	}
345 	this.keyInformation = ByteString.valueOf(version).concat(ByteString.valueOf(0x03)).concat(ByteString.valueOf(parami));
346 }
347 
348 
349 
350 /**
351  * Determine random or pseudo-random card challenge
352  *
353  */
354 GPSCP03.prototype.determineCardChallenge = function() {
355 	if (typeof(this.sequenceCounter) == "undefined") {
356 		this.cardChallenge = this.crypto.generateRandom(8);
357 	} else {
358 		var cnt = this.sequenceCounter.toUnsigned() + 1;
359 		this.sequenceCounter = ByteString.valueOf(cnt, 3);
360 		var context = this.sequenceCounter.concat(this.aid);
361 		this.cardChallenge = this.deriveKey(this.kenc, 2, 8, context);
362 	}
363 }
364 
365 
366 
367 /**
368  * Handle processing of INITIALIZE UPDATE command
369  *
370  * @param {Number} keyVersion the version of the key to use (0 for default)
371  * @param {Number} keyId the key id to use (usually 0)
372  * @param {ByteString} hostCryptogram the cryptogram calculated at the host
373  */
374 GPSCP03.prototype.handleInitializeUpdate = function(keyVersion, keyId, hostChallenge) {
375 	this.hostChallenge = hostChallenge;
376 	this.determineCardChallenge();
377 
378 	this.diversificationData = new ByteString("0102030405060708090A", HEX);
379 
380 	if (typeof(this.keyInformation) == "undefined") {
381 		this.keyInformation = new ByteString("010300", HEX);
382 	}
383 
384 	this.deriveSessionKeys();
385 	var context = this.hostChallenge.concat(this.cardChallenge);
386 	this.cardCryptogram = this.deriveKey(this.skmac, 0, 8, context);
387 
388 	this.seclevel = 1;
389 	this.chaining = ByteString.valueOf(0, 16);
390 
391 	var bb = new ByteBuffer();
392 	bb.append(this.diversificationData);
393 	bb.append(this.keyInformation);
394 	bb.append(this.cardChallenge);
395 	bb.append(this.cardCryptogram);
396 	if (this.sequenceCounter != undefined) {
397 		bb.append(this.sequenceCounter);
398 	}
399 	return bb.toByteString();
400 }
401 
402 
403 
404 /**
405  * Issue EXTERNAL AUTHENTICATE command to card.
406  *
407  * @param {Card} card the card to use
408  * @param {Number} level the security level (a combination of bits B5 (R-ENC), B4 (R-MAC), B2 (C-ENC) and B1 (C-MAC))
409  * @param {ByteString} hostCryptogram optional parameter, calculated internally if missing
410  */
411 GPSCP03.prototype.externalAuthenticate = function(card, level, hostCryptogram) {
412 	assert(card instanceof Card, "Argument card must be instance of Card");
413 	assert(typeof(level) == "number", "Argument level must be a number");
414 	assert((level & ~0x33) == 0, "Argument level must use only bits B5,B4,B2 or B1");
415 	assert((hostCryptogram == undefined) || (hostCryptogram instanceof ByteString), "hostCryptogram must be missing or ByteString");
416 
417 	if (hostCryptogram == undefined) {
418 		this.calculateHostCryptogram();
419 	} else {
420 		this.hostCryptogram = hostCryptogram;
421 	}
422 	this.seclevel = 1;
423 
424 	card.sendSecMsgApdu(Card.ALL, 0x80, 0x82, level, 0x00, this.hostCryptogram, [0x9000]);
425 	this.seclevel = level;
426 	this.enccnt = 1;
427 }
428 
429 
430 
431 /**
432  * Derive S-ENC, S-MAC and S-RMAC session keys
433  */
434 GPSCP03.prototype.deriveSessionKeys = function() {
435 	var context = this.hostChallenge.concat(this.cardChallenge);
436 
437 	var keysize = this.kmac.getSize() >> 3;
438 
439 	// Derive S-MAC
440 	var ss = this.deriveKey(this.kmac, 6, keysize, context);
441 	this.skmac = new Key();
442 	this.skmac.setComponent(Key.AES, ss);
443 
444 	// Derive S-RMAC
445 	var ss = this.deriveKey(this.kmac, 7, keysize, context);
446 	this.skrmac = new Key();
447 	this.skrmac.setComponent(Key.AES, ss);
448 
449 	// Derive S-ENC
450 	var ss = this.deriveKey(this.kenc, 4, keysize, context);
451 	this.skenc = new Key();
452 	this.skenc.setComponent(Key.AES, ss);
453 }
454 
455 
456 
457 /**
458  * Verify card cryptogram
459  *
460  * @type boolean
461  * @return true if cryptogram is valid
462  */
463 GPSCP03.prototype.verifyCardCryptogram = function() {
464 	var context = this.hostChallenge.concat(this.cardChallenge);
465 
466 	var ss = this.deriveKey(this.skmac, 0, 8, context);
467 	return ss.equals(this.cardCryptogram);
468 }
469 
470 
471 
472 /**
473  * Calculate host cryptogram using the key derive method
474  *
475  * @type ByteString
476  * @return the 8 byte cryptogram
477  */
478 GPSCP03.prototype.calculateHostCryptogram = function() {
479 	var context = this.hostChallenge.concat(this.cardChallenge);
480 
481 	this.hostCryptogram = this.deriveKey(this.skmac, 1, 8, context);
482 	return this.hostCryptogram;
483 }
484 
485 
486 
487 /**
488  * Encrypt key and prepare key block for PUT KEY command
489  *
490  * @param {Key} dek the data encryption key
491  * @param {Key} key the key to wrap
492  * @type ByteString
493  * @return the key type, length, cryptogram and key check value
494  */
495 GPSCP03.prototype.prepareKeyBlock = function(dek, key) {
496 	assert(dek instanceof Key, "Argument dek must be instance of Key");
497 	assert(key instanceof Key, "Argument key must be instance of Key");
498 
499 	var keysize = key.getSize() >> 3;
500 	var ones = new ByteString("01010101010101010101010101010101", HEX);
501 	var kcv = this.crypto.encrypt(key, Crypto.AES_ECB, ones).left(3);
502 
503 	var iv = ByteString.valueOf(0, 16);
504 	var ck = this.crypto.encrypt(dek, Crypto.AES_CBC, key.getComponent(Key.AES), iv);
505 	var ckbin = new ByteBuffer("88", HEX);
506 	ckbin.append(keysize + 1);
507 	ckbin.append(keysize);
508 	ckbin.append(ck);
509 	ckbin.append(3);
510 	ckbin.append(kcv);
511 
512 	return ckbin.toByteString();
513 }
514 
515 
516 
517 /**
518  * Extract a key data field from C-Data in a PUT KEY APDU
519  *
520  * @param {ByteString} keyDataField the list of key data fields
521  * @type ByteString
522  * @return the extracted key data
523  */
524 GPSCP03.parseKeyDataField = function(keyDataField) {
525 	var i = keyDataField.byteAt(1);
526 	assert(i - 1 == keyDataField.byteAt(2), "Field length does not match");
527 	i += 2;
528 	i += keyDataField.byteAt(i) + 1;
529 	return keyDataField.left(i);
530 }
531 
532 
533 
534 /**
535  * Decrypt and validate key blob containing AES key wrapped with DEK
536  *
537  * @param {ByteString} keyblob the wrapped key
538  * @type Key
539  * @return the unwrapped key
540  */
541 GPSCP03.prototype.validateKeyBlock = function(keyblob) {
542 	assert(keyblob instanceof ByteString, "Argument keyblob must be instance of ByteString");
543 
544 	assert(keyblob.byteAt(0) == 0x88, "AES key blob must start with '88'");
545 	var l = keyblob.byteAt(1);
546 	assert(keyblob.length > l + 2, "Length exceeds key blob");
547 	assert(l - 1 == keyblob.byteAt(2), "Field length does not match");
548 
549 	var ck = keyblob.bytes(3, l - 1);
550 	var iv = ByteString.valueOf(0, 16);
551 	var plain = this.crypto.decrypt(this.kdek, Crypto.AES_CBC, ck, iv);
552 
553 	var key = new Key();
554 	key.setComponent(Key.AES, plain);
555 
556 	var ones = new ByteString("01010101010101010101010101010101", HEX);
557 	var kcv = this.crypto.encrypt(key, Crypto.AES_ECB, ones).left(3);
558 
559 	assert(keyblob.right(3).equals(kcv), "KCV does not match");
560 	return key;
561 }
562 
563 
564 
565 /**
566  * Derive a session key or cryptogram
567  *
568  * @param {Key} key the master key
569  * @param {Number} ddc the data derivation constant
570  * @param {Number} size the size of byte of the resulting key or cryptogram
571  * @param {ByteString} context the context (usually hostChallenge || cardChallenge)
572  * @return the derived value
573  * @type ByteString
574  */
575 GPSCP03.prototype.deriveKey = function(key, ddc, size, context) {
576 	assert(key instanceof Key, "Argument key must be instance of Key");
577 	assert(typeof(ddc) == "number", "Argument ddc must be a number");
578 	assert(typeof(size) == "number", "Argument size must be a number");
579 	assert(context instanceof ByteString, "Argument context must be instance of ByteString");
580 
581 	var dd = new ByteBuffer();
582 	var os = size;
583 	var iter = 1;
584 	while (os > 0) {
585 		var dp = new ByteBuffer();
586 		dp.append(ByteString.valueOf(ddc, 12));
587 		dp.append(0);
588 		dp.append(ByteString.valueOf(size << 3, 2));
589 		dp.append(ByteString.valueOf(iter));
590 		dp.append(context);
591 
592 		var mac = this.crypto.sign(key, Crypto.AES_CMAC, dp.toByteString());
593 
594 		dd.append(mac.left(os > mac.length ? mac.length : os));
595 		os -= mac.length;
596 		iter++;
597 	}
598 	return dd.toByteString();
599 }
600 
601 
602 
603 GPSCP03.test = function() {
604 	var crypto = new Crypto();
605 
606 	var kmac = new Key();
607 	kmac.setComponent(Key.AES, new ByteString("404142434445464748494a4b4c4d4e4f", HEX));
608 
609 	var kenc = new Key();
610 	kenc.setComponent(Key.AES, new ByteString("404142434445464748494a4b4c4d4e4f", HEX));
611 
612 	var kdek = new Key();
613 	kdek.setComponent(Key.AES, new ByteString("404142434445464748494a4b4c4d4e4f", HEX));
614 
615 	var card = new Card(_scsh3.reader);
616 
617 	card.sendApdu(0x00, 0xA4, 0x04, 0x00, new ByteString("A000000151000000", HEX));
618 
619 	var scp = new GPSCP03(crypto, kenc, kmac, kdek);
620 
621 	scp.initializeUpdate(card, 0, 0);
622 	scp.deriveSessionKeys();
623 	assert(scp.verifyCardCryptogram());
624 	card.setCredential(scp);
625 	scp.externalAuthenticate(card, 1);
626 
627 	var aid = new ByteString("E82B0601040181C31F0202", HEX);
628 	var cdata = (new ASN1(0x4F, aid)).getBytes();
629 
630 	card.sendSecMsgApdu(Card.ALL, 0x80, 0xE4, 0x00, 0x80, cdata, [0x9000, 0x6A88]);
631 	card.sendSecMsgApdu(Card.ALL, 0x80, 0xE4, 0x00, 0x80, cdata, 0, [0x9000, 0x6A88]);
632 }
633