近来准备搞搞usb, 翻出我的正版开发板, 启动正版visualgdb, 插上盗版v9, 工程向导识别不到开发板无法下一步. 于是我换了一下最新版本的7.58c驱动, 打开准备升级一下固件来着, 好家伙, 直接弹出一个好家伙, 大意是,
"现在连接的探头是个克隆版jlink, 在克隆硬件上用我们的软件既不合理又不合法, 请联系我们并附上截图."
虽然后面我发现工程向导从openocd里面间接驱动jlink才能下一步并且成功调试, 可这个segger的提示深深的打动了我.
众所周知, 假货宝上的v9已经不知道出过多少版本了, 大的小的, 带壳的裸板的, 都是包最新驱动, 还真没遇到segger检测到的情况, 去问了卖家和程序员一样的回答”我这里好好的”,
得, 网上搜也搜不到信息, 估计是因为clone提示文字也是全新的, 以前好像出错提示是defective, 新的提示是clone.
卖家不管, 我自己折腾, 首先我怀疑是不是那个签名问题, 于是找了下jlink_x64.dll弹框的地方的函数, 发现没有call它的调用, 只有一个传参引用. 不死心, 开x64dbg跟了一下, 是从线程调用来的.
那么再看这个传参引用的位置, x64dbg用animate trace记录了一下会发现他在检测和比较逗号分隔的特性字符串, 比较到特定的字符串”RDI”就跳转到将这个弹窗函数入参的分支了. 如果将字符串比较的jz都给跳过, 则不会弹窗.
回到IDA调试和整理下这个函数, 首先我们要摸清这个逗号分隔的字符串哪来的, 是从一个0x80字节的缓冲区统计来的, 每0x10开头是ascii的部分加到字符串里.
如果是做过山寨版的朋友可能就知道了, 这信息是在0800BF20开始的地方, 而0800BF00处是序列号.
通过整理和调试, 得出的这个新版驱动的弹窗分支条件依次如下
0序列号不能为黑名单里面的那几个. 此条不重要, 因为没人去用那些特殊序列号.
1 新型号不能内置GDBFull, 有了直接报错. 所有型号不能内置RDDI, 有了直接报错.
2 硬件版本v9~v11并且序列号开头为26,5,82的, 和版本号v1并且序列号开头为80的, 不许内置JFlash或RDI特性.
看到这里聪明的小伙伴可能要问了,这些不都是专门针对盗版的吗? 但随着我在网上和闲鱼搜集正版的序列号, 我发现26开头是edu的特征, 而5开头是base版的特征, 80开头是edu mini的特征. 这里说的开头就是第8,9位的数字.
山寨版的序号那可就五花八门了, 有很多直接-1(4294967295). 在搜索中我还看到小窍门原版edu并 通过addfeature指令增加jflash和rdi特性的, 还在论坛看到了Segger去年成立中国部门, 坛友表示担忧, 还有最近原版自己加feature的坛友被报告clone的帖子.
略作思考我觉得我破案了, 这个崭新的中国部门怕是读了论坛的帖子后, 把这个当作成果汇报上去了, 然后segger程序员一琢磨, 来个根据型号限制功能, 可齐活了, 这一砖头主要砸到了买正版edu并且加了内置特性的, 盗版序列号-1或者瞎写的都没被误伤.
我们可以选择补dll, 拆机重刷, 但我选择了最程序正义的一种: 让jlink自己去掉feature.
此篇文章也就是从一个和嵌入式开发关系不大的视角上展示如何利用基础推理能力来拨开云雾得报大仇, 阅读只需要有一定的调试经验. 不需要做漏洞分析. 好了, 闲话不多说, 我们正式就顺着这个Feature字符串来摸. 因为新版驱动的commander不支持AddFeature指令了, 我在老版JlinkARM.dll搜索发现AddFeature命令附近有一个ClearFeatures.
这个命令也是非公开的, 和AddFeature,ChangeSN一样的流程, 执行后会把现有ota的Features区域全部修改为0, 发送更新ots信息请求让设备去更新. 但我测试了一下设备上的固件却无法成功的把GDBFull或者JFlash字样给修改为00.
通过查阅STM32的flash编程手册PM0059, 明确说可以将非0的bit改为0, 不需要擦除再改写.
再看看固件更新ots有啥限制. 固件怎么来呢, 可以从JLinkARM.dll解. 老版本的方法大家都知道了吧, 新版本7.2后厂家给一部分固件加了压缩, 我就用另外思路写了个工具解压它. 可以参考附件.
固件中对客户端发过去的新内容检查也是检查没有出现0变1的bit, 然后就送入内存中的函数来修改flash了.
看了下内存中的这个函数, 写的歪七扭八的, 除了加了个跳过写入FF功能, 没有会导致非FF不擦写的bug. 编程手册上每次写入都要拉高一次PG,它给简化为设置一次, 循环写入了. 估计其实不需要.
话说其实修改flash的代码完全没必要放在内存, 因为要修改的目标地址是sector2, 和执行的都不在一个sector, 而且它末尾还调用了flash里面的memcmp函数判断写入是否成功, 白隔离了.
修改里面没有额外的判断了, 没有什么猫腻, 感觉是STM32本身不允许非全1的被改写? 也就是说ST资料写错了?
抛开这个不谈, 我们要还原已经包含ascii的features区域, 势必要重刷该区域所在的sector并且写入擦除前的内容, 这也是flash修改的常规操作了, Features位于0800BF20, 所以要擦掉08008000~0800BFFF对应的sector2.
那么原本更新bf00的代码为啥不去做擦除重写呢? 实际这个函数根据最后一个参数是有擦除sector功能的. 估计是厂家设计的时候模拟的单次OTP, 不擦硬改, 所以一旦不是FF了就不能再次写入.
如果不大改函数读出全部再回写, 08008000~0800B6FF这13.75k的内容就会变FF. (后记: 实际变了也无所谓, 应该这段是空的), 所以我选择写一段代码来完成读出/擦除/写回操作. 要测试我们写的代码是否工作正常, 一定得在真实环境下调试, 强迫症半仙表示, 还得是由原版的bootloader引导的. 接下来我们分析Bootloader.
Bootloader怎么来呢, 可以从正版里面提取. 可以搜索thxlp的”人人都可以提取V9的Bootloader一文”.
IDE当然选用了IAR, 因为经过分析, JLinkV9的固件和Bootloader都是IAR编译的, 最新的758c固件是IAR 6.40.5编译的, bootloader也应该是6.40.x编译. 当然了我们用IAR 7.x 8.x都是可以的, segger用的还是stdperiph库, 我们也可以用LL/HAL开发. 只要搞清bootloader怎么才肯加载我们刷入的app”分区”就可以了.
bootloader自身和信息存储被官方安排在08010000之前, app分区从08010000开始到0803FFFF, Vector为08010000, 可用内存是20000008开始到20020000, 这些在工程选项里面可以设置.
经过分析bootloader引导时候检查内存20000000处不是0x12344321和app分区的08010210处是不是” J-Link V9”, 还有08010000起的2FFFE字节的crc是不是和固件末尾的2字节crc相等. 这个crc的算法是CRC-16/KERMIT.
正好IAR的链接器有嵌入checksum功能, 经过苦苦搜索, 我并没有找到怎么用他的自定义CRC生成KERMIT校验的选项来. 或许我应该写个小工具自己来postbuild里面修改out文件?
就在我放弃并且补丁了bootloader来调试后, 经过一顿尝试, 我找到了这个组合
"
按照这个选项设置, 链接时候生成的crc便可通过bootloader的校验.
当然不想填充的话, 那补bootloader也是可以的, 位置如下
ROM:08000E7C 03 D1 BNE locret_8000E86 ; patch to nop!
注意看我源码里的main.c,
#pragma location=0x08010210
__root const char fwversion[] = "J-Link V9 "
这段可以让指定地址出现这个字串, 满足bootloader对此处检测.
我们把bootloader加入到raw binary image里面, 设置好符号, 段名, 然后在icf加入定位指令指示它放置到08000000位置, 这样我们调试app时候, 就会将app和bootloader一起烧录并调试了.
然后我们还要知道的是因为bootloader本身已经完成了主频初始化和对应的flash延迟配置, 我们这里不用重复初始化. 如果是STM32CubeMX建立的模板, 不要调用SystemClock_Config. 可以参考我提供的工程, 下载调试可以看到bootloader放行了我们程序, 并且成功的断在了main函数处.
接下来我们还要完成一个额外的功能才可以安心的往正版里面刷, 我们的代码执行完自身代码后, 需要返回刷机状态, 这样才可以继续刷回原版固件. 不然每次都进我们固件, 回不去了.
通过分析固件和bootloader, 只需要我们将20000000处置为12344321这个魔法数值, 然后芯片复位就可以. bootloader引导过程检测到这个标志就会进入恢复状态, 恢复状态官方工具连上去就会刷固件进去.
经过少量的调试, 按照编程手册做好了擦除功能, 重刷后dump可以验证我们每次烧进去时候序列号和feature在重启前就被清掉了, 同时我们还保留了B700开始的0x800大小的签名.
万里长城剩下最后一步, 怎么把固件给刷进去, 这也难不倒我们, 首先我们看原版固件里面进入刷机状态代码, 也就是设置vectorbase和重启代码, 发现是在06号指令里面调用的, 也就是cmd_06_update_firmware, 然后会给主机回应1并且复位让bootloader进入恢复状态.
嵌入式开发的内容不是我们的重点, 看代码说明就可以.
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2021-12-12 10:14
被曾半仙编辑
,原因: