Proxmark3 Automation and Chameleon Mini CLI client

miband nfc with proxmark3 and chameleon mini

proxmark3chameleon mini 是兩個常見的 研究 mifare tag 的工具,雖然 mifare 已經被研究十幾年了,但現在還是一直被大量的使用著,像是門禁卡等等的,這裡就不講 mifare 內部的資料結構以及缺失,想研究的話可以參考這篇 Legitimate-reader-only attack on MIFARE Classic
由於最近幾年小米手環 nfc 版本比較容易入手以及使用,裡面其實可以模擬 5 張 mifare 卡,使用上相當方便。曾經有一陣子搭輕軌捷運上下班進出社區是可以只靠手環就可以的。

而根據使用的情景,可能需要找出 mifare 裡的 keys,這時候 proxmark3 與 chameleon mini 就可以派上用場。本來使用這兩個工具上都是可以的,不過覺得步驟有點繁瑣,有些也沒有 release 版本了,所以這次好好研究如何自動化甚至可以在 android 手機上直接使用。

chameleon mini command line interface

chameleon mini 其實有好多版本,現在還有 chameleon tiny pro 不但更小巧精緻還有藍牙和 android app 喔。不過這裡還是以可以便宜容易買到的對岸修改版為主,也可能被稱為 revE rebooted 版,重點就是裡面的 command line 都需要加個 MY 結尾的那種。
由於本身不使用 windows,所以 iceman fork 的 GUI 也就沒有跑起來用用看,而是另外裝了用 go 寫的 chamgo-qt,也是可正常操作的,雖然不清楚裡面如何溝通的,但可以達到想要的功能 (mfkey32v2)。
但是 chamgo-qt 年久失修了,當時放出在 dropbox 的 prebuilt 的 binaries 都已經不見,要重新編譯覺得好麻煩啊,而且原本 GUI 使用起來有點多此一舉,還得帶一台電腦出去,那為什麼不研究一下如何用 CLI 直接連呢?

talk chameleon mini with pyserial

可以可以的,反正本來就會對 chameleon mini 敲 command 了,直接找了 python 的 pyserial 對 device node 試試看吧,這個部分並沒有什麼太大問題的,而且使用 pyserial 還有其他好處,待會會提到。

def main():
    with serial.Serial('/dev/tty.usbmodem1101', 115200) as ser:
        print(f'[  ] {ser.name}')
        print(f'[->] helpmy')
        ser.write(b'helpmy\r\n')
        l = ser.readline()
        print(f'[<-] {l.decode().strip()}')
        l = ser.readline()
        print(f'[<-] {l.decode().strip()}')
# 100:OK
# Chameleon-new-1.0

chameleon mini M_DETECTION mode

主要想要跑的功能首先要將 chameleon mini 某張 tag 設定 M_DETECTION mode,然後靠近 reader 得到 traffic trace。設定 tag, uid, mode 等等也都不是問題,但要如何得到 traffic trace 呢?
曾經找到某篇講解 ref.G 的 pdf,說需要用 XMODEM 溝通取得 binary 回來,不過看看 chamgo-qt 的 source code 好像不是這樣,事實上對應的 source 上有很詳盡的註解,其實有些不知道為什麼,但看懂 go 程式翻譯成 python 就可以了。
每次 detectionmy? 都會回來 218 bytes,組成為 208 encrypted nonce bytes + 2 crc bytes + 8 bytes (100:OK\n\r),而 208 encrypted bytes 需要過一個 123321 的解密,然後前面 4 bytes 會是 emulated tag 的 uid,接著從第 16 byte 開始每 16 bytes 一組,分別存有 key, block, Nt, Nr, Ar 等等的 data,正是需要的這些。

    # decrypt data
    decoded = []
    size = 208
    key = 123321
    dbuf = bytes([(size + key + i - size // key ^ s) % 256
                  for i, s in enumerate(buf[:-10])])
    # uid
    uid = struct.unpack('>I', dbuf[:4])[0]
    print(f'       UID={hex(uid)[2:].upper()}')
    # nonces
    valid_nonces = []
    per_nonce_bytes = struct.calcsize('>ccccIII')
    num_nonces = len(dbuf[16:]) // per_nonce_bytes
    for i in range(num_nonces):
        start_pos = 16 + i * per_nonce_bytes
        nonce = struct.unpack(
            '>ccccIII', dbuf[start_pos: start_pos + per_nonce_bytes])
        # cccc: [key, block, _, _]
        #      A:0x60, B:0x61
        # III: [Nt, Nr, Ar]
        ...

pyserial with tcp bridge

因為最終是想要在 android 手機上直接連 chameleon mini 操作的,不過馬上就遇到了一個問題,不是 rooted 的 android 的話是看不到 usbmodem 的 device node 的,那一般的手機該如何是好呢?這個問題 proxmark3 也會遇到,但是 proxmark3 的 android/termux wiki 有提到可以安裝 TCPUART 解決,而且剛剛使用的 pyserial 其實也支援 RFC 2217 Serial Port Sever 呢!不過 TCPUART 應該只有直接 redirect 而已,總之只要設定好 TCPUART,把 pyserial connect 改成這樣就行了

 with serial.serial_for_url('socket://192.168.226.202:8080', 115200) as ser:
 # with serial.Serial('/dev/tty.usbmodem1101', 115200) as ser:
     ...

compile mfkey32v2

事情上得到 traffic trace 之後,需要兩兩一組丟到 mfkey32v2 執行一下,但是 android 下是不能直接執行 macos m1 編譯的 executable 的,那麼就改一個 CMakeLists.txt 出來,直接在 android/termux 下編一個吧,這樣即使在 android 上也能夠跑完需要的計算了。

cmake_minimum_required(VERSION 3.0)
project(mfkey)
set(CMAKE_PROJECT_NAME mfkey)
set(CMAKE_BUILD_TYPE RELEASE)

## mfkey32v2
add_executable(mfkey32v2 mfkey32v2.c bucketsort.c crapto1/crapto1.c crapto1/crypto1.c)
target_include_directories(mfkey32v2 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

proxmark3 automation

proxmark3 也是相當多版本,一樣容易便宜買到的也是對岸修改版,而 iceman fork 的 proxmark3.rrg 是支援這種版本的硬體的,而 rrg 版有相當多功能介面也夠花俏,使用起來相當舒適。
不過就硬體來說,還是有些功能無法正常工作的,發現只要是讀取 Reader 方面的都會有問題,所以 sniff, sim 等等的都不能用。曾經看到 github issue 說到某版本可以用,特地降版燒進去也是不太能用。
而 proxmark3 本來就可以使用 lua 自行寫 plugin script 自動化命令的,本來也寫了一個 loop 每個 sector 的功能,但一直覺得有點醜,因為不知道如何得到 proxmark3 的 output string,得從 log 去 parse 才行。
最近使用 proxmark3.rrg 版,發現其實可以用 ./pm3 -c 'hf 14a info' 這樣的指令直接操作,不用進入 interactive mode,這樣相當方便啊,直接把原本的 lua 改用 python subprocess 寫一份吧。

    # find block and key type
    outstr = subprocess.check_output([
        'sh', f'{args.pm3}', '-c',
        f'hf mf fchk --1k --emu -k {args.key}'
    ]).strip().decode()
    if 'No keys found' in outstr:
        raise RuntimeError(f'No known key found for the tag')
    olines = subprocess.check_output([
        'sh', f'{args.pm3}', '-c',
        'hf mf ekeyprn'
    ]).strip().decode().split('\n')
    olines = [l for l in olines if f'{args.key}' in l]
    if not any(olines):
        raise RuntimeError(f'No known key found for the tag in emulator')
    block_num = int(olines[0].split('|')[1].strip())
    key_ab = 'A' if f'args.key' in olines[0].split('|')[2] else 'B'
    print(f'Known Key: [{args.key}] at {block_num:02d} {key_ab}')

另外原本會使用 android 上的 MIFARE Classic Tool 做 Write Dump (Clone) 的,不過發現好像沒辦法掌握各個 block 的寫入順序,感覺這種 tag,會先改 key block 的 attribute 再寫 data 再改回來,因此必須按照順序寫入才行。而透過剛剛的 python subprocess 執行./pm3 -c 'hf mf wrbl' 就可以自行決定順序,還可以做出更自動化的步驟。

    for blkid, bdata in enumerate(dumps):
        if blkid in [0]:
            continue
        outstr = subprocess.check_output([
            'sh', f'{args.pm3}', '-c',
            f'hf mf wrbl --blk {blkid} -k FFFFFFFFFFFF -d {bdata}'
        ]).strip().decode()
        if 'fail' in outstr:
            raise RuntimeError(f'Write block {blkid} with FFFFFFFFFFFF failed')

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *