When We Disco 的 BeatShow 演示
最近被这首歌洗脑了,忍不住又录了个 demo. 现在业余时间一直在完善这个项目,主要功能基本都完成了,还剩下一些收尾工作,应该很快就能公开提供下载了。


在优酷上观看: https://v.youku.com/v_show/id_XNDg0ODgwMTMyMA==.html
在 bilibili 上观看: https://www.bilibili.com/video/BV1N64y1F73D
当前语言: 中文 (简体)
CH552 官方 USB HID 示例程序中的一处 bug
最近在开发 BeatShow 设备的固件恢复功能时发现,使用 CH552 实现的 USB HID 设备总是获取不到设备名称,而 STM32F072 的就没问题。即使将所有 USB 描述符都改成一样的,问题也仍然存在。

调试 PC 端程序看到底层调用的 HidD_GetProductString() 函数执行结果是成功的,只是返回的字符串长度为 0, 所以就得从硬件下手了。

经过抓包,发现对于 CH552 设备,除了设备枚举过程中的正常 GET_DESCRIPTOR 请求,在 PC 端程序调用 HidD_GetProductString() 时系统又发送了额外的 GET_DESCRIPTOR 请求。而且这些请求的返回数据长度只有 2 字节,明显是不正常的。

CH552 设备枚举过程中正常的 GET_DESCRIPTOR 请求, wLength = 0x00FF = 255:


CH552 设备完成枚举后的额外 GET_DESCRIPTOR 请求, wLength = 0x0102 = 258:


STM32 设备在完成枚举后则没有这些额外的 GET_DESCRIPTOR 请求:


对于为什么系统只对 CH552 设备发送了这些额外的 GET_DESCRIPTOR 请求,经过一番搜索没有找到答案。但显然 CH552 的程序在这块的处理上有 bug。

CH552 实现的 bootloader 是以 WCH 官方的 USB HID 示例程序 CompatibilityHID.C 为基础,逐步重构和修改而来的。这个示例中有一段处理 SETUP 事物的代码是这样的:

case UIS_TOKEN_SETUP | 0:                       // SETUP 事务
    len = USB_RX_LEN;
    if (len == (sizeof(USB_SETUP_REQ)))
    {
        SetupLen = UsbSetupBuf->wLengthL;
        len = 0;                                // 默认为成功并且上传 0 长度
        SetupReq = UsbSetupBuf->bRequest;
        // ...

可以看到只取了 wLength 的低字节部分 wLengthL 作为 SetupLen, 完全没有使用高字节部分 wLengthH。而另一个 VendorDefinedDev.C 示例程序中,就对高字节部分 wLengthH 做了检查:

case UIS_TOKEN_SETUP | 0:                       // endpoint 0# SETUP
    len = USB_RX_LEN;
    if (len == sizeof(USB_SETUP_REQ)) {         // SETUP 包长度
        SetupLen = UsbSetupBuf->wLengthL;
        if (UsbSetupBuf->wLengthH || SetupLen > 0x7F) SetupLen = 0x7F;      // 限制总长度
        len = 0;                                // 默认为成功并且上传 0 长度
        SetupReqCode = UsbSetupBuf->bRequest;
        // ...

CompositeKM.C 示例程序中也一样检查了 wLengthH:

case UIS_TOKEN_SETUP | 0:                       // SETUP 事务
    len = USB_RX_LEN;
    if (len == (sizeof(USB_SETUP_REQ)))
    {
        SetupLen = UsbSetupBuf->wLengthL;
        if (UsbSetupBuf->wLengthH || SetupLen > 0x7F)
        {
            SetupLen = 0x7F;                    // 限制总长度
        }
        len = 0;                                // 默认为成功并且上传 0 长度
        SetupReq = UsbSetupBuf->bRequest;
        // ...

CompatibilityHID.C 示例程序中原本没有任何字符串描述符,也没有对 GET_DESCRIPTOR 请求的处理代码,相关的内容都是我后添加的。这可能是官方程序中这个 bug 存在了这么长时间的原因。

知道原因后就好修改了,只要在 wLengthH 不为 0 时把 SetupLen 限制到 uint8_t 范围内的最大值 0xFF 就可以了:

case UIS_TOKEN_SETUP | 0:                       // SETUP 事务
    len = USB_RX_LEN;
    if (len == (sizeof(USB_SETUP_REQ)))
    {
        SetupLen = UsbSetupBuf->wLengthL;
        if (UsbSetupBuf->wLengthH != 0)
        {
            SetupLen = 0xFF;                    // 限制总长度
        }
        len = 0;                                // 默认为成功并且上传 0 长度
        SetupReq = UsbSetupBuf->bRequest;
        // ...
当前语言: 中文 (简体)
简单“修正”了一下 AStyle 中的一个重复缩进的问题
GitHub 地址: https://github.com/wudicgi/astyle-modified
效果对比脚本: https://github.com/wudicgi/astyle-modified/tree/master/bin
修改后程序下载: https://github.com/wudicgi/astyle-modified/blob/master/bin/AStyle.exe

1. 发现问题

上周我在对 ffmpeg 的 transcoding.c 示例程序进行代码风格美化时,发现 AStyle 在很多处不需要改动的地方添加了额外的缩进:

// 处理前
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
        args, NULL, filter_graph);

// 处理后
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
                args, NULL, filter_graph);

其实以前也遇到过这个问题,但这次出现得比较集中、比较多,就准备解决一下了。打开 AStyle 的 VS2017 项目进行调试,发现对于我使用的选项

indent=spaces=4
indent-after-parens
indent-continuation=2

AStyle 在遇到赋值运算符 '=' 时为后续行添加 2 级缩进,而当继续处理遇到左括号 '(' 时又添加了 2 级缩进,于是后续行就拥有了多余的 2 级缩进。

2. 修改问题

我使用了一个简单粗暴的方式进行修改,就是在 ASBeautifier 类的 registerContinuationIndent() 方法中添加一个条件判断,使它在已经因为 '=' 添加了后续行缩进的情况下,不再因为其后出现的第一个 '(' 再添加缩进。

void ASBeautifier::registerContinuationIndent(const string& line, int i, int spaceIndentCount_,
                                              int tabIncrementIn, int minIndent, bool updateParenStack)
{
    assert(>= -1);
    int remainingCharNum = line.length() - i;
    int nextNonWSChar = getNextProgramCharDistance(line, i);

    // if indent is around the last char in the line OR indent-after-paren is requested,
    // indent with the continuation indent
    if (nextNonWSChar == remainingCharNum || shouldIndentAfterParen)
    {
        // added by Wudi
        bool noDuplicatedIndentForFirstParen = updateParenStack // current indentation is for opening paren '('
                && !continuationIndentStack->empty()            // previously indented for an assignment '='
                && parenIndentStack->empty()                    // current '(' is the first opening paren needs to add indentation
                && (!= 0);                                    // current '(' must not be the first char, otherwise there is no '=' ahead

        int previousIndent = spaceIndentCount_;
        if (!continuationIndentStack->empty())
            previousIndent = continuationIndentStack->back();

        int currIndent = continuationIndent * indentLength + previousIndent;

        // added by Wudi
        if (noDuplicatedIndentForFirstParen) {
            currIndent = previousIndent;
        }

        if (currIndent > maxContinuationIndent && line[i] != '{')
            currIndent = indentLength * 2 + spaceIndentCount_;
        continuationIndentStack->emplace_back(currIndent);
        if (updateParenStack)
            parenIndentStack->emplace_back(previousIndent);
        return;
    }

    // ...

3. 修改前后效果对比

对同样的一段代码进行处理,原版 AStyle 的输出为:

int ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
                args, NULL, filter_graph);

int (*enc_func_2)(AVCodecContext *, AVPacket *, const AVFrame *, int *) = (ifmt_ctx->streams[stream_index]->codecpar->codec_type ==
                AVMEDIA_TYPE_VIDEO) ? avcodec_encode_video2 : avcodec_encode_audio2;

ret = func(a, b, another_func(1, 2,
                        3),
                c, d);

修改后版本的输出为:

int ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
        args, NULL, filter_graph);

int (*enc_func_2)(AVCodecContext *, AVPacket *, const AVFrame *, int *) = (ifmt_ctx->streams[stream_index]->codecpar->codec_type ==
        AVMEDIA_TYPE_VIDEO) ? avcodec_encode_video2 : avcodec_encode_audio2;

ret = func(a, b, another_func(1, 2,
                3),
        c, d);

4. 修改的副作用

标题中的“修正”带引号是因为这并不一定是个 bug, 同时现在所做的这个修改会有一些副作用。对于一些极端情况的代码,例如:

int ret = func(a, b, another_func(1, 2,
3, 4, 5),
c, d)
/ 123;

原版 AStyle 能保留所有缩进等级,输出结果为:

int ret = func(a, b, another_func(1, 2,
                        3, 4, 5),
                c, d)
        / 123;

而该修改版本的输出为:

int ret = func(a, b, another_func(1, 2,
                3, 4, 5),
        c, d)
        / 123;

最外层在缩进等级上没有与内层区分开来。但像这样的代码并不常见,所以为方便自己日常使用,“修正”一下还是有必要的。

附: AStyle 原版链接

AStyle 的官方项目地址为 http://astyle.sourceforge.net/
当前语言: 中文 (简体)
开源一个剪贴板文本自动处理工具 Clipboard Auto Processor
这个工具最开始是 2016 年时写了个很粗糙的版本,只有自己一直在用。今年业余抽时间整理了一下代码,把原来设计的基础功能都补充完整了,感觉可以发一个 1.0 版本了。目标用户主要是程序员,可以自己编写脚本把平时一些重复性的文本处理操作自动化。

GitHub 地址: https://github.com/wudicgi/clipboard-auto-processor

考虑到 GitHub 上的图片经常加载不出来,把 readme 的所有内容直接贴这儿了。

1. 简介

Clipboard Auto Processor 是一个剪贴板文本自动处理工具。不同于一般的剪贴板增强软件只是管理历史内容,该工具可以自动调用任何脚本,来自动处理剪贴板中的内容。



脚本可以使用 PHP, Python 或 JavaScript 等任何您熟悉的语言来编写,只需实现从原始文本到所需结果的转换即可,其余工作均由 Clipboard Auto Processor 完成。

2. 使用方法

下载最新的 release 版本,解压到任意有写权限的位置,通常只要不放在 Windows, Program Files 或 C 盘根目录等路径下就可以。

首次运行 ClipboardAutoProcessor.exe 主程序时,程序会根据当前系统语言创建 config.ini 配置文件。之后可以修改该文件来设置脚本解释路径和显示字体等选项。

您可以手动创建快捷方式,放置到开始菜单中或桌面上。但是更推荐使用 Launchy, WoxKeypirinha 等快速启动工具,实现随时快速打开使用。



3. 示例脚本

转换文件路径列表中的反斜杠 (查看 JS, PHP, Python 脚本)



调整从 PDF 文档所复制文本的格式 (查看 PHP 脚本)



十六进制字符串到数组定义 (查看 PHP 脚本)



AStyle 代码风格美化 (查看 INI 配置文件)


当前语言: 中文 (简体) · also available in: English
已经成功将 Spleeter 引入到了 BeatShow 的音频处理过程中
经过两个周末的努力,已经成功将 Spleeter 引入到 BeatShow 中,用于音频文件的预处理。现在像《东风破》这样节奏比较慢,人声分离度不是很高的音乐也可以处理了。


在优酷上观看: https://v.youku.com/v_show/id_XNDc2ODIyOTM1Mg==.html

不过要看效果的话,还是得节奏欢快一点的音乐才好看。现在相比之前,人声和乐器轨的效果更好了一些。


在优酷上观看: https://v.youku.com/v_show/id_XNDc2ODIzMDY1Ng==.html
当前语言: 中文 (简体)
发现了一个开源的人声和乐器音轨分离软件 Spleeter
用郭德纲的话说就是“晴天霹雳,kucha一声”,看看这《双截棍》的分离效果。


在优酷上观看: https://v.youku.com/v_show/id_XNDc2ODEyMjM4NA==.html

我每隔一段时间就会搜一遍音乐节拍识别、人声和乐器分离以及小型灯光秀设备相关的东西,这次搜出大问题了。去年 11 月,一法国公司开源了 spleeter 程序及已训练好的模型,效果和售价 1000 多刀的 RX7 软件中的 Music Rebalance 功能一样好。考虑之后把这个类库作为前置处理环节引入 BeatShow 了,这样流行音乐就也能处理了。
当前语言: 中文 (简体)
FFmpeg 的 Windows build 现在有了 LGPL 版本

当前语言: 中文 (简体)
为开源的 C# USB 库 Device.Net 添加了根据名称过滤设备的功能
修改后 Device.Net 库的 Github 地址:
https://github.com/wudicgi/Device.Net-display-name-filtering-enhanced

2020-06-07 添加:
今天在 BeatShow 中完善 USB 设备管理相关代码时,发现之前所修改的版本获取设备的 DisplayName 不太方便, IsDefinitionMatch() 方法中也没有检查 DisplayName 是否匹配,今天把这两个问题都改了。
当前语言: 中文 (简体) · also available in: English
BeatShow 的一种新的硬件形式
新硬件,名字还没确定。这个设备是 USB 供电+通信的,Win7 以上的系统免驱。


在优酷上观看: https://v.youku.com/v_show/id_XNDc2ODI4MjkxNg==.html
当前语言: 中文 (简体)
ATSHA204A 简明使用手册 (1/2: 配置项和常用命令)
本文共分 2 部分:

前言

在 BeatShow 硬件中,我需要使用 ATSHA204A 为 ESP8266 和 STM32 的固件提供一定的验证功能,以尽量保护硬件不被克隆。当然,我没有使用 MCU 验证 ATSHA204A 的这种方式,而是选择在通信过程中,由 PC 端来验证 ATSHA204A. 使用这种方式的好处在于, PC 端软件是可以通过混淆、虚拟化、加壳等手段来增加破解难度的,而且随着软件升级还可以修改验证时使用的数据,灵活度更高一些。

确定使用方式后,我本身其实只需要在生产阶段向 ATSHA204A 写入一些预先计算好的密钥值,然后在使用阶段由 PC 端软件通过指令交互来验证这些密钥值是否正确就可以了。但是 ATSHA204A 本身的功能比较多,配置也比较灵活,直接看 Atmel (当然被收购之后应该说 Microchip 了) 的 datasheet 容易被绕晕。网上能搜到的文章感觉写得也不够清楚,或者比较散,可能还不如直接看 datasheet. 因此我在阅读 datasheet 时专门梳理了一些我认为比较重要的概念和细节,并经过整理形成了本篇《ATSHA204A 简明使用手册》。

AT88CK590 评估板

Microchip 有一个 AT88CK590 评估套件 (Evaluation Kit), 可以用于评估 ATSHA204A, ATAES132A 和 ATECC508A 这三颗芯片。同时还可以自己打开外壳,在尾部的焊盘焊上插针,外接芯片使用。



有了这个评估套件后,就可以使用官方的 ACES CE 软件了,否则找不到设备的话是进入不了主界面的。



ATSHA204A 中的几个区域

Configuration Zone

Configuration Zone 是芯片内 EEPROM 中的一块存储配置信息的区域,共 88 bytes。整个 Configuration Zone 中的数据都是一直可读的,而且除了开头部分的 SN, RevNum 和 I2CEnable 不可修改外,其他数据在 Configuration Zone 未被锁定时是可写的。



OTP Zone

OTP Zone 是 EEPROM 中的一块 32 * 2 = 64 bytes 大小的区域。在 Configuration Zone 已锁定,但 OTP Zone 未锁定时可写。



Data Zone

Data Zone 是 EEPROM 中的一块 32 * 16 = 512 bytes 大小的区域。



TempKey

TempKey 是芯片内 SRAM 中的一块区域,数据长度为 32 bytes, 此外还有一些像 SlotID, SourceFlag, Valid 这样的一些状态数据。用于存储 Nonce 和 GenDig 命令的结果。



区域锁定

在通过 Lock 命令写入 LockConfig 来锁定 Configuration Zone 前,OTP Zone 即不能读也不能写。只有在锁定 Configuration Zone 后,但还未通过 Lock 命令写入 LockValue 来锁定 OTP Zone 前,OTP Zone 可以通过 Write 命令写入。

ATSHA204A 的常用命令

ATSHA204A 共有 14 个命令,如下 (粗体: 较常用灰色: 不推荐使用):

  • CheckMac -- 检查 MAC 并返回 bool 结果,不推荐使用
  • DeriveKey -- 从 target key 或 parent key 分散出一个 target key
  • DevRev -- 获取 4 字节的芯片修订编号,软件不应该使用这个值
  • GenDig -- 从一个随机或输入的种子和一个 key 生成一个数据保护摘要
  • HMAC -- 计算 HMAC, 无兼容需求时不如使用 MAC 命令
  • Lock -- 锁定 Configuration Zone 或 Data and OTP Zone
  • MAC -- 使用 key 和其他内部数据计算一个 SHA-256 值,作为 MAC 用于验证
  • Nonce -- 生成一个 32 字节的随机数 (返回),和一个 nonce (内部存储于 TempKey, 不返回)
  • Pause -- 暂停,MCU 只接一个 ATSHA204A 时用不到
  • Random -- 生成一个 32 字节的随机数,不推荐使用,没有 Nonce 命令 TempKey 保密的作用
  • Read -- 读取 EEPROM 中的数据
  • SHA -- 计算 SHA-256 值,完全可以在 MCU 中算
  • UpdateUltra -- 用于更新配置区域中的 UserExtra 和 Selector 字节,一般不用
  • Write -- 写入数据到 EEPROM

不推荐使用的命令后边就不再介绍了,本文主要针对硬件防克隆这一应用介绍常用的几个命令。

使用阶段的常用命令

(符号约定: a || b 表示 a 与 b 相连接,'12EF' 中的 12, EF 为十六进制数据 0x12, 0xEF.)

使用阶段的常用命令有 Nonce, MAC, GenDig 和 DeriveKey.

Nonce

Nonce 是 ATSHA204A 的 datasheet 中随处可见的一个词,根据 Wiktionary 上的解释,"The cryptography sense is commonly said to be a contraction of number used once, although this is probably incorrect." 把 nonce 理解成只使用一次的随机数在 ATSHA204A 中是非常恰当的,比使用 random number 多了只使用一次的含义。

Nonce 命令会生成一个 nonce, 以便在后续的 GenDig, MAC, HMAC, Read 或 Write 命令中使用。生成结果 (nonce) 会存储在 TempKey 中,同时将生成过程中产生的随机数 RandOut 返回。

Mode 参数的低 2 位指定 nonce 的生成方式:
0b00 生成一个新的随机数,将其和命令数据 NumIn 合并后,结果存储在 TempKey 中。在产生随机数前如果有必要则自动更新随机数发生器的 seed 值(推荐的方式,安全度高)。
RandOut = 新生成的随机数 (32 bytes)
TempKey = SHA256(RandOut || NumIn (20 bytes) || ...)
0b01 同 0b00, 除了生成随机数前不更新随机数发生器的 seed 值,直接使用原有的 seed 值。
RandOut = 新生成的随机数 (32 bytes)
TempKey = SHA256(RandOut || NumIn (20 bytes) || ...)
0b11 透传模式,直接将命令数据 NumIn 写入到 TempKey 中。
RandOut = '00'
TempKey = NumIn

MAC

使用芯片内存储的 key 或其他数据和命令数据 Challenge 计算一个 SHA-256 值,作为 MAC 返回。

Response = SHA256(
    SlotID 指定的内部 key 或 TempKey ||
    命令数据 Challenge 或 TempKey (推荐) ||
    ... ||
    (OTP 的前 11 bytes) 或 (OTP 的前 8 bytes || '000000') 或 (11 bytes '00') ||
    (SN 调整过顺序的所有 9 bytes) 或 (SN 的最后 1 byte || 4 bytes '00' || SN 的前 2 bytes || 2 bytes '00')
)

GenDig

生成摘要,使用内部存储的数据 (配置区域数据,OTP 数据,或 key 值) 和 TempKey 的当前内容计算一个 SHA-256 值,并将其更新到 TempKey 中。

执行该命令可以为需要使用 TempKey 值的命令 (如 MAC 和 DeriveKey 命令) 引入额外的参与运算的数据。

如果 Zone 参数为 0x02 (Data), 且SlotConfig<SlotID>.CheckOnly 位为 1, 则摘要按如下内容计算:

TempKey = SHA256(
    SlotID 指定的内部 key ||
    命令数据 OtherData ||
    (SN 的最后 1 byte || SN 的前 2 bytes) ||
    ... ||
    TempKey
)

否则按如下方式计算:

TempKey = SHA256(
    Config 或 OTP 或 Data.slot 或 TransportKey ||
    ... ||
    (SN 的最后 1 byte || SN 的前 2 bytes) ||
    ... ||
    TempKey
)

DeriveKey

分散密钥,使用当前的一个 key 值 (source key) 和 TempKey 中存储的 nonce 计算一个 SHA-256 值,并将其写入到 target key 的 slot 中。

结果写入到哪个 slot 由命令参数 TargetKey 指定,SlotConfig<TargetKey>.Bit13 必须已被置位。

如果 SlotConfig<TargetKey>.Bit12 为 0, 则参与 hash 计算的 source key 就是 target key (即 key rolling, 密钥滚动操作)。
如果该位为 1, 则 source key 为该 target key 的 parent key, 通过 SlotConfig<TargetKey>.WriteKey 中指定的 slot 号来查找 (即密钥创建操作).

如果 SlotConfig<TargetKey>.Bit15 为 1, 则必须计算并提供命令数据 MAC 用于验证。
MAC = SHA256(ParentKey, Opcode, Param1, Param2, SN<8>, SN<0:1>)
其中 ParentKey 是 SlotConfig<TargetKey>.WriteKey 中指定的 slot 号的 key.

target key = SHA256(
    source key (target key 或 parent key) ||
    ... ||
    (SN 的最后 1 byte || SN 的前 2 bytes) ||
    ... ||
    TempKey
)

生产阶段的常用命令

生产 (数据个人化) 阶段的常用命令有 Write, Read 和 Lock. 待更新下一篇时再补充本部分内容。
当前语言: 中文 (简体)
更多条目: [1]
« 上一页 · 下一页 »