aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--imsi-change/.gitignore3
-rw-r--r--imsi-change/LICENSE202
-rw-r--r--imsi-change/Makefile71
-rw-r--r--imsi-change/README.md26
-rw-r--r--imsi-change/applet-project.mk51
-rw-r--r--imsi-change/src/org/osmocom/IMSIChange/Bytes.java82
-rwxr-xr-ximsi-change/src/org/osmocom/IMSIChange/IMSIChange.java176
-rw-r--r--imsi-change/src/org/osmocom/IMSIChange/MobileIdentity.java152
-rw-r--r--imsi-change/src/org/osmocom/IMSIChange/Test.java75
9 files changed, 838 insertions, 0 deletions
diff --git a/imsi-change/.gitignore b/imsi-change/.gitignore
new file mode 100644
index 0000000..5150de5
--- /dev/null
+++ b/imsi-change/.gitignore
@@ -0,0 +1,3 @@
+build/
+test/
+.sim-keys
diff --git a/imsi-change/LICENSE b/imsi-change/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/imsi-change/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/imsi-change/Makefile b/imsi-change/Makefile
new file mode 100644
index 0000000..9656b05
--- /dev/null
+++ b/imsi-change/Makefile
@@ -0,0 +1,71 @@
+SIMTOOLS_DIR = ../../sim-tools
+
+APPLET_AID = 0xd0:0x70:0x02:0xca:0x44:0x90:0x01:0x01
+APPLET_NAME = org.osmocom.IMSIChange.IMSIChange
+PACKAGE_AID = 0xd0:0x70:0x02:0xCA:0x44:0x90:0x01
+PACKAGE_NAME = org.osmocom.IMSIChange
+PACKAGE_VERSION = 1.0
+
+SOURCES = \
+ src/org/osmocom/IMSIChange/Bytes.java \
+ src/org/osmocom/IMSIChange/MobileIdentity.java \
+ src/org/osmocom/IMSIChange/IMSIChange.java \
+ $(NULL)
+
+CAP_FILE = build/javacard/org/osmocom/IMSIChange/javacard/IMSIChange.cap
+
+include ./applet-project.mk
+
+.PHONY: flash
+flash: classes
+ $(eval MODULE_AID := $(shell echo $(APPLET_AID) | sed 's/0x//g' | sed 's/\://g'))
+ $(eval INSTANCE_AID := $(shell echo $(APPLET_AID) | sed 's/0x//g' | sed 's/\://g'))
+ . $$PWD/.sim-keys && $(SIMTOOLS_DIR)/bin/shadysim \
+ --pcsc \
+ -l $(CAP_FILE) \
+ -i $(CAP_FILE) \
+ --enable-sim-toolkit \
+ --access-domain=00 \
+ --module-aid $(MODULE_AID) \
+ --instance-aid $(INSTANCE_AID) \
+ --nonvolatile-memory-required 0100 \
+ --volatile-memory-for-install 0100 \
+ --max-menu-entry-text 21 \
+ --max-menu-entries 01 \
+ --kic "$$KIC1" \
+ --kid "$$KID1"
+
+.PHONY: remove
+remove:
+ . $$PWD/.sim-keys && $(SIMTOOLS_DIR)/bin/shadysim \
+ --pcsc \
+ -d "$$(echo $(PACKAGE_AID) | sed 's/0x//g' | sed 's/\://g')" \
+ --kic "$$KIC1" \
+ --kid "$$KID1"
+
+.PHONY: list
+list:
+ . $$PWD/.sim-keys && $(SIMTOOLS_DIR)/bin/shadysim \
+ --pcsc \
+ --list-applets \
+ --kic "$$KIC1" \
+ --kid "$$KID1"
+
+.PHONY: delete
+delete: remove
+
+.PHONY: reflash
+reflash:
+ $(MAKE) remove
+ $(MAKE) flash
+
+.PHONY: test
+test:
+ mkdir -p ./test/classes
+ javac -target 1.1 -source 1.3 -classpath test/classes -g -d ./test/classes src/org/osmocom/IMSIChange/Bytes.java
+ javac -target 1.1 -source 1.3 -classpath test/classes -g -d ./test/classes src/org/osmocom/IMSIChange/MobileIdentity.java
+ javac -target 1.1 -source 1.3 -classpath test/classes -g -d ./test/classes src/org/osmocom/IMSIChange/Test.java
+ java -classpath test/classes org.osmocom.IMSIChange.Test
+
+.PHONY: check
+check: test
diff --git a/imsi-change/README.md b/imsi-change/README.md
new file mode 100644
index 0000000..99d1a55
--- /dev/null
+++ b/imsi-change/README.md
@@ -0,0 +1,26 @@
+# IMSI change SIM applet
+
+Display and change the IMSI of the SIM. This is a standalone version of a debug
+feature in the more complex IMSI Pseudonymization applet. To be used as example
+code to build other applets.
+
+### How to flash
+
+```
+$ cp .sim-keys.example .sim-keys
+$ nvim .sim-keys # adjust KIC1, KID1
+$ make flash
+```
+
+Before flashing a second time, remove the sim applet:
+
+```
+$ make remove
+```
+
+### Related
+
+* [IMSI Pseudonymization](https://osmocom.org/projects/imsi-pseudo/wiki)
+* [Shadysimply in Osmocom wiki](https://osmocom.org/projects/cellular-infrastructure/wiki/Shadysimpy)
+
+
diff --git a/imsi-change/applet-project.mk b/imsi-change/applet-project.mk
new file mode 100644
index 0000000..982e768
--- /dev/null
+++ b/imsi-change/applet-project.mk
@@ -0,0 +1,51 @@
+BUILD_DIR = ./build
+BUILD_CLASSES_DIR = $(BUILD_DIR)/classes
+BUILD_JAVACARD_DIR = $(BUILD_DIR)/javacard
+JAVACARD_SDK_DIR ?= $(SIMTOOLS_DIR)/javacard
+JAVACARD_EXPORT_DIR ?= $(JAVACARD_SDK_DIR)/api21_export_files
+ifdef COMSPEC
+ CLASSPATH = $(JAVACARD_SDK_DIR)/lib/api21.jar;$(JAVACARD_SDK_DIR)/lib/sim.jar
+else
+ CLASSPATH = $(JAVACARD_SDK_DIR)/lib/api21.jar:$(JAVACARD_SDK_DIR)/lib/sim.jar
+endif
+JFLAGS = -target 1.1 -source 1.3 -g -d $(BUILD_CLASSES_DIR) -classpath "$(BUILD_CLASSES_DIR):$(CLASSPATH)"
+JAVA ?= java
+JC ?= javac
+
+.SUFFIXES: .java .class
+.java.class:
+ @mkdir -p $(BUILD_CLASSES_DIR)
+ @mkdir -p $(BUILD_JAVACARD_DIR)
+ $(JC) $(JFLAGS) $*.java
+
+.PHONY: jar
+jar: classes
+ $(JAVA) -jar $(JAVACARD_SDK_DIR)/bin/converter.jar \
+ -d $(BUILD_JAVACARD_DIR) \
+ -classdir $(BUILD_CLASSES_DIR) \
+ -exportpath $(JAVACARD_EXPORT_DIR) \
+ -applet $(APPLET_AID) $(APPLET_NAME) \
+ $(PACKAGE_NAME) $(PACKAGE_AID) $(PACKAGE_VERSION)
+
+default: jar
+
+classes: $(SOURCES:.java=.class)
+
+clean:
+ $(RM) -rf $(BUILD_DIR)
+
+install:
+ $(eval CAP_FILE := $(shell find $(BUILD_JAVACARD_DIR) -name *.cap))
+ $(eval MODULE_AID := $(shell echo $(APPLET_AID) | sed 's/0x//g' | sed 's/\://g'))
+ $(eval INSTANCE_AID := $(shell echo $(APPLET_AID) | sed 's/0x//g' | sed 's/\://g'))
+ $(SIMTOOLS_DIR)/bin/shadysim \
+ $(SHADYSIM_OPTIONS) \
+ -l $(CAP_FILE) \
+ -i $(CAP_FILE) \
+ --enable-sim-toolkit \
+ --module-aid $(MODULE_AID) \
+ --instance-aid $(INSTANCE_AID) \
+ --nonvolatile-memory-required 0100 \
+ --volatile-memory-for-install 0100 \
+ --max-menu-entry-text 10 \
+ --max-menu-entries 01
diff --git a/imsi-change/src/org/osmocom/IMSIChange/Bytes.java b/imsi-change/src/org/osmocom/IMSIChange/Bytes.java
new file mode 100644
index 0000000..f4f2505
--- /dev/null
+++ b/imsi-change/src/org/osmocom/IMSIChange/Bytes.java
@@ -0,0 +1,82 @@
+/* Copyright 2020 sysmocom s.f.m.c. GmbH
+ * SPDX-License-Identifier: Apache-2.0 */
+package org.osmocom.IMSIChange;
+
+public class Bytes {
+ public static byte nibble2hex(byte nibble)
+ {
+ nibble = (byte)(nibble & 0xf);
+ if (nibble < 0xa)
+ return (byte)('0' + nibble);
+ else
+ return (byte)('a' + nibble - 0xa);
+ }
+
+ public static byte[] hexdump(byte data[])
+ {
+ byte res[] = new byte[(byte)(data.length*2)];
+ for (byte i = 0; i < data.length; i++) {
+ res[(byte)(i*2)] = nibble2hex((byte)(data[i] >> 4));
+ res[(byte)(i*2 + 1)] = nibble2hex(data[i]);
+ }
+ return res;
+ }
+
+ public static boolean equals(byte a[], byte b[])
+ {
+ if (a.length != b.length)
+ return false;
+ for (short i = 0; i < (short)a.length; i++) {
+ if (a[i] != b[i])
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean isDigit(byte digits[])
+ {
+ for (short i = 0; i < (short)digits.length; i++) {
+ if (digits[i] < '0' || digits[i] > '9')
+ return false;
+ }
+ return true;
+ }
+
+ public static byte[] toStr(byte byte_nr)
+ {
+ byte str[];
+ short nr = byte_nr;
+ byte d;
+ byte l = 0;
+ if (nr < 0) {
+ l = 1;
+ nr = (short)-nr;
+ }
+
+ if (nr > 99) {
+ l += 3;
+ d = 100;
+ }
+ else if (nr > 9) {
+ l += 2;
+ d = 10;
+ }
+ else {
+ str = new byte[1];
+ l += 1;
+ d = 1;
+ }
+
+ byte i = 0;
+ str = new byte[l];
+ if (byte_nr < 0)
+ str[i++] = '-';
+
+ while (d > 0) {
+ str[i++] = (byte)('0' + (nr / d));
+ nr %= d;
+ d /= 10;
+ }
+ return str;
+ }
+}
diff --git a/imsi-change/src/org/osmocom/IMSIChange/IMSIChange.java b/imsi-change/src/org/osmocom/IMSIChange/IMSIChange.java
new file mode 100755
index 0000000..6135596
--- /dev/null
+++ b/imsi-change/src/org/osmocom/IMSIChange/IMSIChange.java
@@ -0,0 +1,176 @@
+/* Copyright 2020 sysmocom s.f.m.c. GmbH
+ * SPDX-License-Identifier: Apache-2.0 */
+package org.osmocom.IMSIChange;
+import org.osmocom.IMSIChange.MobileIdentity;
+
+import sim.access.*;
+import sim.toolkit.*;
+import javacard.framework.*;
+
+public class IMSIChange extends Applet implements ToolkitInterface, ToolkitConstants {
+ // DON'T DECLARE USELESS INSTANCE VARIABLES! They get saved to the EEPROM,
+ // which has a limited number of write cycles.
+
+ private byte STKServicesMenuId;
+ private SIMView gsmFile;
+
+ /* Main menu */
+ private static final byte[] changeIMSI = {'C', 'h', 'a', 'n', 'g', 'e', ' ', 'I', 'M', 'S', 'I'};
+ private static final byte[] invalidIMSI = {'I', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'I', 'M', 'S', 'I'};
+ private static final byte[] noChange = {'N', 'o', ' ', 'c', 'h', 'a', 'n', 'g', 'e'};
+ private static final byte[] changed = {'I', 'M', 'S', 'I', ' ', 'c', 'h', 'a', 'n', 'g', 'e', 'd', '!'};
+
+ private IMSIChange() {
+ gsmFile = SIMSystem.getTheSIMView();
+
+ ToolkitRegistry reg = ToolkitRegistry.getEntry();
+ STKServicesMenuId = reg.initMenuEntry(changeIMSI, (short)0, (short)changeIMSI.length,
+ PRO_CMD_SELECT_ITEM, false, (byte)0, (short)0);
+ }
+
+ public static void install(byte[] bArray, short bOffset, byte bLength) {
+ IMSIChange applet = new IMSIChange();
+ applet.register();
+ }
+
+ public void process(APDU arg0) throws ISOException {
+ if (selectingApplet())
+ return;
+ }
+
+ public void processToolkit(byte event) throws ToolkitException {
+ EnvelopeHandler envHdlr = EnvelopeHandler.getTheHandler();
+
+ if (event == EVENT_MENU_SELECTION) {
+ byte selectedItemId = envHdlr.getItemIdentifier();
+
+ if (selectedItemId == STKServicesMenuId) {
+ byte prevIMSI_mi[] = readIMSI();
+ byte prevIMSI_str[] = MobileIdentity.mi2str(prevIMSI_mi);
+ promptIMSI(prevIMSI_str);
+ }
+ }
+ }
+
+ private void showMsg(byte[] msg) {
+ ProactiveHandler proHdlr = ProactiveHandler.getTheHandler();
+ proHdlr.initDisplayText((byte)0, DCS_8_BIT_DATA, msg, (short)0, (short)(msg.length));
+ proHdlr.send();
+ }
+
+ private byte[] getResponse()
+ {
+ ProactiveResponseHandler rspHdlr = ProactiveResponseHandler.getTheHandler();
+ byte[] resp = new byte[rspHdlr.getTextStringLength()];
+ rspHdlr.copyTextString(resp, (short)0);
+ return resp;
+ }
+
+ private byte[] prompt(byte[] msg, byte[] prefillVal, short minLen, short maxLen) {
+ /* if maxLen < 1, the applet crashes */
+ if (maxLen < 1)
+ maxLen = 1;
+
+ ProactiveHandler proHdlr = ProactiveHandler.getTheHandler();
+ proHdlr.initGetInput((byte)0, DCS_8_BIT_DATA, msg, (short)0, (short)(msg.length), minLen, maxLen);
+ if (prefillVal != null && prefillVal.length > 0) {
+ /* appendTLV() expects the first byte to be some header before the actual text.
+ * At first I thought it was the value's length, but turned out to only work for lengths under 8...
+ * In the end I reversed the value 4 from the first byte read by rspHdlr.copyValue() for
+ * TAG_TEXT_STRING fields. As long as we write 4 into the first byte, things just work out,
+ * apparently.
+ * This is the appendTLV() variant that writes one byte ahead of writing an array: */
+ proHdlr.appendTLV((byte)(TAG_DEFAULT_TEXT), (byte)4, prefillVal, (short)0,
+ (short)(prefillVal.length));
+ }
+ proHdlr.send();
+
+ return getResponse();
+ }
+
+ private void showError(short code) {
+ byte[] msg = {'E', '?', '?'};
+ msg[1] = (byte)('0' + code / 10);
+ msg[2] = (byte)('0' + code % 10);
+ showMsg(msg);
+ }
+
+ private void promptIMSI(byte prevIMSI_str[])
+ {
+ byte newIMSI_str[] = prevIMSI_str;
+
+ try {
+ newIMSI_str = prompt(changeIMSI, newIMSI_str, (short)0, (short)15);
+ } catch (Exception e) {
+ showError((short)40);
+ return;
+ }
+
+ if (newIMSI_str.length < 6 || newIMSI_str.length > 15
+ || !Bytes.isDigit(newIMSI_str)) {
+ showMsg(invalidIMSI);
+ return;
+ }
+
+ if (Bytes.equals(newIMSI_str, prevIMSI_str)) {
+ showMsg(noChange);
+ return;
+ }
+
+ byte mi[];
+ try {
+ /* The IMSI file should be 9 bytes long, even if the IMSI is shorter */
+ mi = MobileIdentity.str2mi(newIMSI_str, MobileIdentity.MI_IMSI, (byte)9);
+ writeIMSI(mi);
+ showMsg(changed);
+ refreshIMSI();
+ } catch (Exception e) {
+ showError((short)42);
+ }
+ }
+
+ private byte[] readIMSI()
+ {
+ gsmFile.select((short) SIMView.FID_DF_GSM);
+ gsmFile.select((short) SIMView.FID_EF_IMSI);
+ byte[] IMSI = new byte[9];
+ gsmFile.readBinary((short)0, IMSI, (short)0, (short)9);
+ return IMSI;
+ }
+
+ private void writeIMSI(byte mi[]) throws Exception
+ {
+ if (mi.length != 9)
+ throw new Exception();
+ gsmFile.select((short) SIMView.FID_DF_GSM);
+ gsmFile.select((short) SIMView.FID_EF_IMSI);
+ gsmFile.updateBinary((short)0, mi, (short)0, (short)mi.length);
+ }
+
+ /*
+ * - command qualifiers for REFRESH,
+ * ETSI TS 101 267 / 3GPP TS 11.14 chapter 12.6 "Command details":
+ * '00' =SIM Initialization and Full File Change Notification;
+ * '01' = File Change Notification;
+ * '02' = SIM Initialization and File Change Notification;
+ * '03' = SIM Initialization;
+ * '04' = SIM Reset;
+ * '05' to 'FF' = reserved values.
+ */
+ public static final byte SIM_REFRESH_SIM_INIT_FULL_FILE_CHANGE = 0x00;
+ public static final byte SIM_REFRESH_FILE_CHANGE = 0x01;
+ public static final byte SIM_REFRESH_SIM_INIT_FILE_CHANGE = 0x02;
+ public static final byte SIM_REFRESH_SIM_INIT = 0x03;
+ public static final byte SIM_REFRESH_SIM_RESET = 0x04;
+
+ /* Run the Proactive SIM REFRESH command for the FID_EF_IMSI. */
+ private void refreshIMSI()
+ {
+ /* See ETSI TS 101 267 / 3GPP TS 11.14 section 6.4.7.1 "EF IMSI changing procedure":
+ * Valid qualifiers are SIM_REFRESH_SIM_INIT_FILE_CHANGE and SIM_REFRESH_SIM_INIT_FULL_FILE_CHANGE.
+ */
+ ProactiveHandler proHdlr = ProactiveHandler.getTheHandler();
+ proHdlr.init((byte)PRO_CMD_REFRESH, SIM_REFRESH_SIM_INIT_FULL_FILE_CHANGE, DEV_ID_ME);
+ proHdlr.send();
+ }
+}
diff --git a/imsi-change/src/org/osmocom/IMSIChange/MobileIdentity.java b/imsi-change/src/org/osmocom/IMSIChange/MobileIdentity.java
new file mode 100644
index 0000000..54fa2b0
--- /dev/null
+++ b/imsi-change/src/org/osmocom/IMSIChange/MobileIdentity.java
@@ -0,0 +1,152 @@
+/* Copyright 2020 sysmocom s.f.m.c. GmbH
+ * SPDX-License-Identifier: Apache-2.0 */
+package org.osmocom.IMSIChange;
+
+public class MobileIdentity {
+ public static final byte MI_IMSI = 1;
+
+ /* Convert BCD-encoded digit into printable character
+ * \param[in] bcd A single BCD-encoded digit
+ * \returns single printable character
+ */
+ public static byte bcd2char(byte bcd)
+ {
+ if (bcd < 0xa)
+ return (byte)('0' + bcd);
+ else
+ return (byte)('A' + (bcd - 0xa));
+ }
+
+ /* Convert BCD to string.
+ * The given nibble offsets are interpreted in BCD order, i.e. nibble 0 is bcd[0] & 0xf, nibble 1 is bcd[0] >> 4, nibble
+ * 3 is bcd[1] & 0xf, etc..
+ * \param[out] dst Output byte array.
+ * \param[in] dst_ofs Where to start writing in dst.
+ * \param[in] dst_len How many bytes are available at dst_ofs.
+ * \param[in] bcd Binary coded data buffer.
+ * \param[in] start_nibble Offset to start from, in nibbles.
+ * \param[in] end_nibble Offset to stop before, in nibbles.
+ * \param[in] allow_hex If false, return false if there are digits other than 0-9.
+ * \returns true on success, false otherwise
+ */
+ public static boolean bcd2str(byte dst[], byte dst_ofs, byte dst_len,
+ byte bcd[], byte start_nibble, byte end_nibble, boolean allow_hex)
+ {
+ byte nibble_i;
+ byte dst_i = dst_ofs;
+ byte dst_end = (byte)(dst_ofs + dst_len);
+ boolean rc = true;
+
+ for (nibble_i = start_nibble; nibble_i < end_nibble && dst_i < dst_end; nibble_i++, dst_i++) {
+ byte nibble = bcd[(byte)nibble_i >> 1];
+ if ((nibble_i & 1) != 0)
+ nibble >>= 4;
+ nibble &= 0xf;
+
+ if (!allow_hex && nibble > 9)
+ rc = false;
+
+ dst[dst_i] = bcd2char(nibble);
+ }
+
+ return rc;
+ }
+
+ public static byte[] mi2str(byte mi[])
+ {
+ /* The IMSI byte array by example:
+ * 08 99 10 07 00 00 10 74 90
+ *
+ * This is encoded according to 3GPP TS 24.008 10.5.1.4 Mobile
+ * Identity, short the Mobile Identity IEI:
+ *
+ * 08 length for the following MI, in bytes.
+ * 9 = 0b1001
+ * 1 = odd nr of digits
+ * 001 = MI type = IMSI
+ * 9 first IMSI digit (BCD)
+ * 0 second digit
+ * 1 third
+ * ...
+ * 0 14th digit
+ * 9 15th and last digit
+ *
+ * If the IMSI had an even number of digits:
+ *
+ * 08 98 10 07 00 00 10 74 f0
+ *
+ * 08 length for the following MI, in bytes.
+ * 8 = 0b0001
+ * 0 = even nr of digits
+ * 001 = MI type = IMSI
+ * 9 first IMSI digit
+ * 0 second digit
+ * 1 third
+ * ...
+ * 0 14th and last digit
+ * f filler
+ */
+ byte bytelen = mi[0];
+ byte mi_type = (byte)(mi[1] & 0xf);
+ boolean odd_nr_of_digits = ((mi_type & 0x08) != 0);
+ byte start_nibble = 2 + 1; // 2 to skip the bytelen, 1 to skip the mi_type
+ byte end_nibble = (byte)(2 + bytelen * 2 - (odd_nr_of_digits ? 0 : 1));
+ byte str[] = new byte[end_nibble - start_nibble];
+ bcd2str(str, (byte)0, (byte)str.length, mi, start_nibble, end_nibble, true);
+ return str;
+ }
+
+ public static byte char2bcd(byte c)
+ {
+ if (c >= '0' && c <= '9')
+ return (byte)(c - '0');
+ else if (c >= 'A' && c <= 'F')
+ return (byte)(0xa + (c - 'A'));
+ else if (c >= 'a' && c <= 'f')
+ return (byte)(0xa + (c - 'a'));
+ else
+ return 0;
+ }
+
+ public static byte[] str2mi(byte str[], byte mi_type, byte min_buflen)
+ {
+ boolean odd_digits = ((str.length & 1) != 0);
+ /* 1 nibble of mi_type.
+ * str.length nibbles of MI BCD.
+ */
+ byte mi_nibbles = (byte)(1 + str.length);
+ byte mi_bytes = (byte)(mi_nibbles / 2 + ((mi_nibbles & 1) != 0? 1 : 0));
+ /* 1 byte of total MI length in bytes, plus the MI nibbles */
+ byte buflen = (byte)(1 + mi_bytes);
+ /* Fill up with 0xff to the requested buffer size */
+ if (buflen < min_buflen)
+ buflen = min_buflen;
+ byte buf[] = new byte[buflen];
+
+ for (byte i = 0; i < buf.length; i++)
+ buf[i] = (byte)0xff;
+
+ /* 1 byte of following MI length in bytes */
+ buf[0] = mi_bytes;
+
+ /* first MI byte: low nibble has the MI type and odd/even indicator bit,
+ * high nibble has the first BCD digit.
+ */
+ mi_type = (byte)(mi_type & 0x07);
+ if (odd_digits)
+ mi_type |= 0x08;
+ buf[1] = (byte)((char2bcd(str[0]) << 4) + mi_type);
+
+ /* fill in the remaining MI nibbles */
+ byte str_i = 1;
+ for (byte mi_i = 1; mi_i < mi_bytes; mi_i++) {
+ byte data = char2bcd(str[str_i++]);
+ if (str_i < str.length)
+ data |= char2bcd(str[str_i++]) << 4;
+ else
+ data |= 0xf0;
+ buf[1 + mi_i] = data;
+ }
+ return buf;
+ }
+}
diff --git a/imsi-change/src/org/osmocom/IMSIChange/Test.java b/imsi-change/src/org/osmocom/IMSIChange/Test.java
new file mode 100644
index 0000000..89905b8
--- /dev/null
+++ b/imsi-change/src/org/osmocom/IMSIChange/Test.java
@@ -0,0 +1,75 @@
+/* Copyright 2020 sysmocom s.f.m.c. GmbH
+ * SPDX-License-Identifier: Apache-2.0 */
+package org.osmocom.IMSIChange;
+import org.osmocom.IMSIChange.*;
+
+public class Test {
+ private static byte nibble2hex(byte nibble)
+ {
+ nibble = (byte)(nibble & 0xf);
+ if (nibble < 0xa)
+ return (byte)('0' + nibble);
+ else
+ return (byte)('a' + nibble - 0xa);
+ }
+
+ private static byte[] hexdump(byte data[])
+ {
+ byte res[] = new byte[(byte)(data.length*2)];
+ for (byte i = 0; i < data.length; i++) {
+ res[(byte)(i*2)] = nibble2hex((byte)(data[i] >> 4));
+ res[(byte)(i*2 + 1)] = nibble2hex(data[i]);
+ }
+ return res;
+ }
+
+ private static String hexdumpStr(byte data[])
+ {
+ return new String(hexdump(data));
+ }
+
+ private static final String[] imsis = {
+ "123456",
+ "1234567",
+ "12345678",
+ "123456789",
+ "1234567890",
+ "12345678901",
+ "123456789012",
+ "1234567890123",
+ "12345678901234",
+ "123456789012345",
+ "1234567890123456",
+ };
+
+ private static void test_str2mi2str()
+ {
+ for (int i = 0; i < imsis.length; i++) {
+ byte str[] = imsis[i].getBytes();
+ byte mi[] = MobileIdentity.str2mi(str, MobileIdentity.MI_IMSI, (byte)9);
+ byte str_from_mi[] = MobileIdentity.mi2str(mi);
+ System.out.print("IMSI " + new String(str) + " --> MI " + hexdumpStr(mi) + " --> IMSI "
+ + new String(str_from_mi));
+ if (Bytes.equals(str, str_from_mi))
+ System.out.println(" (ok)");
+ else
+ System.out.println(" ERROR!");
+ }
+ }
+
+ private static void test_toStr()
+ {
+ byte nr = -128;
+ while (true) {
+ System.out.println("" + nr + " = '" + new String(Bytes.toStr(nr)) + "'");
+ if (nr == 127)
+ break;
+ nr++;
+ }
+ }
+
+ public static void main(String args[]){
+ test_str2mi2str();
+ test_toStr();
+ }
+}