proxmark3 與 chameleon mini 是兩個常見的 研究 mifare tag 的工具,雖然 mifare 已經被研究十幾年了,但現在還是一直被大量的使用著,像是門禁卡等等的,這裡就不講 mifare 內部的資料結構以及缺失,想研究的話可以參考這篇 Legitimate-reader-only attack on MIFARE Classic。
由於最近幾年小米手環 nfc 版本比較容易入手以及使用,裡面其實可以模擬 5 張 mifare 卡,使用上相當方便。曾經有一陣子搭輕軌捷運上下班進出社區是可以只靠手環就可以的。
而根據使用的情景,可能需要找出 mifare 裡的 keys,這時候 proxmark3 與 chameleon mini 就可以派上用場。本來使用這兩個工具上都是可以的,不過覺得步驟有點繁瑣,有些也沒有 release 版本了,所以這次好好研究如何自動化甚至可以在 android 手機上直接使用。
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 直接連呢?
可以可以的,反正本來就會對 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 某張 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]
...
因為最終是想要在 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:
...
事情上得到 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 也是相當多版本,一樣容易便宜買到的也是對岸修改版,而 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')