昨晚夜深人静正好做点Hack的事 于是我破解了我自己某个装置的固件 用于研究 学习
显然 该固件是加密的 一眼兜过去 依稀能在0x200处看到有STM32的向量表形式 既大量重复的数据 而且从重复周期看 显然是一种128位块的ECB加密方式
第一个猜测当然是当前最受欢迎的AES-128加密了 但是其实还有点不对 (后面发现是AES-256) 我还是想通过SWD接口弄下固件再说 ..... 失败了 ICode被保护 禁止获取flash内容
唉 能弄到多少是多少吧 STM32有个漏洞 就是当发生错误或者中断时 SWD接口会暴露出中断向量地址 比如通过openocd给个"reset halt" 那么PC值会被告知 这个值装自于中断向量表 这意味着 中断向量表所在处有一个4byte内容被暴露出来...通过移动中断向量表 产生各种中断 就能4byte 4byte地得到被保护的Flash内容....但是有部分保留的中断号没有办法引发中断 所以 这个方法只能获得大概90%的Flash内容
关于这个漏洞的具体可用的脚本请移步XXXXXXXXXXXXXXXXXX/romanoLT/stm32f1-firmware-extractor
上面的脚本 建议不能破解的地方 选择填入"00BF00BF" (--value 选项) 就是两个NOP 而产生的结果 可以用这个脚本转成bin文件 然后尝试用IDA解 (也可以用--binary选项 应该不用下面的脚本转换 直接输出二进制文件 但我当时没有选这个)
open F,">extr.bin";
binmode F;
while(<>) {
chomp;
if (m/^\s*[0-9A-F]{8}\s*:\s*([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})\s*$/i) {
print F pack('CCCC',hex($4),hex($3),hex($2),hex($1));
}
}
close F;
虽然不是完全破解 但是还是能够发现 原设备有个bootloader 对送过来的固件进行解密再进行IAP 同时甚至还检测MPU ID .... 他的解密有明显的行位移 GF域乘2 异或RoundKey等AES解密特征 而且每块解密13轮半 (无论AES多少 块都是128位 AES-128的话 是9轮半) .... 确定是AES-256-ECB 就差密钥了
不管怎么样 密钥一定存放在某处 尤其在内部RAM中 不管ROM里面怎么操作 大多数情况下 总会在某些时刻在RAM里面形成连续存放的密钥 ... 幸好这个设备还有一个漏洞 DCode没有被保护 于是我在openOcd使用"mdb 0x20000000 20480"命令获取了整个RAM的映像的log
> mdb 0x20000000 20480
0x20000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 64 00 08 00 00 00 00 00 00 00 00
0x20000020: ..... 此处密钥为厂商隐去 .....
0x20000040: 00 00 00 00 00 00 00 00 00 63 00 08 00 00 00 00 02 00 00 00 64 10 00 20 ac 01 00 20 00 00 00 bf
0x20000060: dc 01 00 20 00 10 10 00 00 00 00 00 01 02 03 04 01 02 03 04 06 07 08 09 02 04 06 08 00 00 00 00
0x20000080: 00 f0 03 00 00 02 00 00 f8 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x200000a0: 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0e 00 00 00 00 00 00
0x200000c0: 00 00 00 00 00 00 80 02 02 20 00 00 00 45 46 54 4f 4e 45 58 20 49 41 50 20 46 6c 61 73 68 44 69
0x200000e0: 73 6b 20 20 20 31 2e 30 20 00 80 02 02 21 00 00 00 56 69 6c 74 72 6f 78 2d 49 41 50 20 20 20 46
0x20000100: 6c 61 73 68 20 44 69 73 6b 20 31 2e 30 20 03 00 00 00 00 06 00 00 00 00 00 00 70 00 00 00 00 00
0x20000120: 00 0a 00 00 00 00 00 00 00 00 00 00 00 00 01 f7 00 00 02 00 00 00 00 08 00 00 01 f8 02 00 02 00
0x20000140: 04 00 1f 00 06 00 00 00 01 00 00 00 1a 03 36 00 44 00 38 00 41 00 34 00 35 00 39 00 39 00 35 00
0x20000160: 30 00 35 00 35 00 00 00 00 01 55 00 11 54 00 08 25 1b 00 08 25 1b 00 08 25 1b 00 08 25 1b 00 08
0x20000180: 25 1b 00 08 25 1b 00 08 25 1b 00 08 19 54 00 08 25 1b 00 08 25 1b 00 08 25 1b 00 08 25 1b 00 08
然后再用下面这个perl脚本把上面的文本log 转成了二进制文件
die "Usage : $0 <log file> <out file>\n" unless $#ARGV>=1;
open Fin, "<$ARGV[0]" or die "cannot open '$ARGV[0]' for reading\n";
open Fout, ">$ARGV[1]" or die "cannot open '$ARGV[1]' for writing\n";
binmode Fout;
while(<Fin>) {
chomp;
if (m/^\s*0x[0-9A-F]+\s*:([\s 0-9A-F]*)$/i) {
$t=$1;
while ($t=~m/([0-9A-F][0-9A-F])/ig) {
print Fout pack('C',hex($1));
}
}
}
close Fin;
close Fout;
又再写了一个Python脚本对RAM二进制文件里面的所有32字节 尝试作为密钥碰撞解密 (就是解一小段疑是中断向量的密文 并且如果密钥正确结果应该是0x0800xxxx)
from Crypto.Cipher import AES
import binascii
import sys
def decrypt(key, ctext):
cipher = AES.new(key, AES.MODE_ECB)
return cipher.decrypt(ctext)
if len(sys.argv)<2 :
print('Try to decrypt using dictionary data file')
print('Usage: python', sys.argv[0], '<dictionary file>')
exit(1)
#some cipher text with known decrypted result
data=binascii.unhexlify('E16DC345FAC37AAB741BB44C3EC3D537E16DC345FAC37AAB741BB44C3EC3D537')
#get dictionary data
f=open(sys.argv[1], "rb")
keys=f.read()
f.close()
#some suggest
keys+= b'\x00'*32 + b'\x55'*32 + b'\xAA'*32+ b'\xFF'*32
#for AES-256
nkey=32
n=len(keys)-nkey
i=0
while i<n:
key=keys[i:i+nkey]
dec=decrypt(key, data)
if dec[3]==0x08 and dec[7]==0x08: # vectors of stm32
print ("i:","%04X" % i,' key:',binascii.hexlify(key),' dec:',binascii.hexlify(dec))
if dec[0]>13 and dec[0]<128 and dec[1]>13 and dec[1]<128 and dec[2]>13 and dec[2]<128 and dec[3]>13 and dec[3]<128 and \
dec[4]>13 and dec[4]<128 and dec[5]>13 and dec[5]<128 and dec[6]>13 and dec[6]<128 and dec[7]>13 and dec[7]<128 and \
dec[8]>13 and dec[8]<128 and dec[9]>13 and dec[9]<128 and dec[10]>13 and dec[10]<128 and dec[11]>13 and dec[11]<128 and \
dec[12]>13 and dec[12]<128 and dec[13]>13 and dec[13]<128 and dec[14]>13 and dec[14]<128 and dec[15]>13 and dec[15]<128 :
#maybe printable hex file?
print ("i:","%04X" % i,' key:',binascii.hexlify(key),' dec:',dec)
i+=1
还真碰到死耗子了
好吧 密钥就是厂商名加上大多数中国人最喜欢的数字
然后用openssl进行整个固件文件的解密
openssl enc -d -aes-256-ecb -in rom.14 -out rom.bin -K <密钥> -iv 00000000000000000000000000000000 -nopad -nosalt
然后在IDA里面加载 注意因为有bootloader 加载的地址不是0x8000000 从向量表漏洞获取的不完全flash内容可以看出 明显第二段App从0x8006400开始的 .... 加载后 觉得固件应该是正常解密了 反汇编看起来没有问题
吐槽一下 实际固件只有37k那么大 固件文件却有73k那么长 还超出了它用的STM32f103C8T6能容纳的长度 我开头还以为有啥特别之处 ..... 后来发现 固件文件头里有个参数 取反后是固件实际有用长度 指导解密和IAP 到那么长就停了 ....也就是说 固件文件中 后面一半的内容 都是没用的 纯粹只是为了假装功能很复杂程序很长
就这样了 破解过程应该具有一定的普遍性 只是大家要注意 用于研究学习 不要搞坏事哦
-------------------------------------------
另: 上面的perl代码第一行 <log file>和<out file> 被自动加上了HTML反标签</log file>和</out file> 而且我已经选了"代码"是"perl"而不是其他 这个应该是这个网站的bug 我没法修改 修改了又会自动加上 ... 要用的话 请Copy后自己手动去除
又另 上报之后 这个问题已经被网站的科创程序员修复 已经没这个问题了 速度真快
200字以内,仅用于支线交流,主线讨论请采用回复功能。