DIY编程器网

标题: Xeltek适配器克隆保护机制 [打印本页]

作者: liyf    时间: 2023-8-14 08:49
标题: Xeltek适配器克隆保护机制
该文为自动翻译,很多文字表述可能有问题,请自己理会,后面附件原文可自行下载

Xeltek适配器克隆保护机制
条件
这个故事始于我们需要阅读/写入TSOP56外壳中的Spansion S29GL512闪存芯片。在此之前,我们已经成功运行了Xeltek Superpro 500p。但不幸的是,它安装了48针套接字,即使使用适配器也无法做到这一点。当然,在这个程序的PC程序中,这种芯片不支持。
我们决定,是时候升级了,并购买了同一制造商型号的Superpro 6100,它使用不同套接字的可更换适配器。我们还单独购买了一个必要的适配器型号CX1011,带有TSOP56套接字(与编码器一起有一个带有ZIF48套接字的适配器)。

我们同意将程序员的程序称为软件,因为“程序员的程序”听起来不太美观。
在完成所有这些之后,我们从带有适配器的程序员中构建了一个设计器,并试图开始使用它。


但是,在软件中选择了所需的芯片后,程序员多次浸泡,并在其显示屏上显示了一个神秘的铭文“错误代码:1D”。该软件还抱怨说,该适配器不适合该芯片的编程。怎么会这样?适配器与程序员成功结婚,芯片与套接字,但邪恶的制造商决定,这是不够的,用户,除了新的程序员,一定要购买“新”适配器DX1011。顺便说一句,在制造商的网站上,他们想要大约300个常青。来吧,在我们的情况下,我们错误地买了它,尽管我们不想买“新的”。但有人可能会把它从旧的程序员那里保存下来,有人可能会想自己制作它。这篇文章正是为了这个观众。
在编写本材料时,我们并不打算教读者如何使用在这条道路上使用的各种软件和硬件。因此,让我们不要用所有这些工具的复杂性来描述文章,把它放在幕后。但如果有人对细节感兴趣,我们很乐意对此发表评论。
在这里,我们只需引用免责声明,其含义是作者对您使用本文材料的任何行为不承担任何责任。换言之,只有你自己才能对自己的行为负责,你可以通过熟悉这些材料来激励自己。
初步检查
对适配器的审查显示,它有一个八条腿的芯片,上面写着AE801,当然在互联网上没有文档。该芯片在上面CX1011适配器照片的右角清晰可见。
不过,还是找到了一些关于这个芯片的信息。在Xeltek的FAQ中,有一个问题被问到:“Superpro6000和6100有什么区别?“CX系列适配器(用于6000程序员)和DX系列适配器(用于6100程序员)是完全相同的,除了ID芯片,该芯片在DX系列适配器中使用高级保护。这是关于什么保护:适配器应该保护谁或什么?当然是你和我!
这种保护类似于现在在打印机墨盒中普遍使用的保护,以避免重新填充,而是购买新的墨盒。但这里的情况有点不同。你用你的血买了一个廉价的适配器,希望你能永远使用它。不是在这里!制造商对此非常谨慎,就像创造者一样,决定人为地限制适配器的寿命。为了证明这一点,软件中检测到以下重要消息:
— «Socket adapter end-of-life is near. Please prepare to replace with anew one.»
— «Adaptor usage beyond its cycle life, please change.»
你喜欢这个珍珠:“适配器的寿命即将结束”?特别是“replace with a new one”这句话,是销售部门的一个很好的耳语。
不知何故,在Xeltek网站上的适配器页面上,没有关于他们命运的信息!借用一句关于山羊和手风琴的著名谚语,我想说:“你为什么要保护适配器?”
但是,不管怎样,读/写Flash内存芯片的需要(我必须说,我们已经忘记了这一点,专注于其他一切)和不可抗拒的愿望,以任何方式减轻不幸的,注定不可避免的死亡的“病”适配器的命运,使我们继续前进。
治疗准备
我们同意将运行软件的PC称为主机。
为了在主机和程序员之间交换数据,使用USB接口和多个USB通道(管道)。在一个USB通道上,主机向程序员发送命令,在另一个通道上向程序员发送数据,然后从程序员接收数据。
需要确定AE801和程序员之间交换数据的接口。在这里,制造商并没有刻意使用旧的好UART,速度约为10500 bodA801芯片,基本上是一个八条腿的微控制器,脚14连接到“地面”,脚8提供+5V电源,脚3用于UART双向数据传输,脚7用作CS阳性,脚6提供4 MHz的时钟频率。在我们所知道的八条腿微控制器中,Atmel系列Attiny254585是最适合这种钉十字架的。然而,SOIC8的外壳比需要的要宽一些。
在研究适配器上的AE801和程序员之间的数据交换时,有一个有趣的发现:当您在软件中选择任何芯片时,甚至在适配器上选择AE801芯片之前,就会在适配器的3脚上出现数据包。事实证明,在开始与适配器芯片通信之前,程序员正在与其他人通信。后来发现,它安装了相同的AE801芯片,与适配器中的AE801相同的双向数据总线。它服务于相同的目的-防止克隆,但现在它是程序员自己。
然后,主机上的软件被修改,以保存它和程序员之间的数据交换过程。从日志中可以清楚地看出,什么都不清楚。数据交换以某种方式被加密并被垃圾吵闹。这可能就是“更好的保护”。但有一点是显而易见的-适配器直接以文本形式告诉程序员他的模型名称和他的年龄,以便可以及时将其发送到Xeltek认为值得休息的地方。其余的都是垃圾。
进一步的研究表明,防御算法是由一个人写的,与这个主题相距甚远。所谓的“强化保护”经不起任何批评。
然而,在日志中无法理解额头,很明显,现在必须移动到程序员本身。它的电子填充物看起来像一个薄片蛋糕,由许多独立的电路板连接在一起,支架和连接器。

其中一个主板(我们称之为“主板”)包含Atmel AT91SAM9G20ARM微控制器、SDRAM、带有固件的SPI闪存、带有编码器型号及其序列号的AE801 ID芯片、控制器USB芯片和许多其他芯片。

另一个电路板上安装了Xilinx芯片组,该芯片组与其他晶体管芯片组一起在读/写过程中控制连接到程序员的适配器上的芯片脚。

微控制器的所有ARM固件都包含在外部SPI闪存芯片中,这当然也无助于增强保护。加载程序通常隐藏在微控制器的内部内存中,无法读取,外部固件或更新被加密。但Xeltek的开发人员决定走另一条路,用专有的A801芯片,让ARM固件一团糟。这是我们接下来做的事情。
这个想法是对程序员的“USB”算法文件进行反汇编,然后在一个ARM IDE中构建一个项目,然后通过jTag,在汇编程序上有一个源代码,学习程序员在与AE801芯片通信方面的工作。
在主板上,JTAG连接器下面有一个空间。为了便于调试,JTAG连接器安装在软件的塑料外壳上,并以扁平的短线连接到主板。

这是一个开发工具包。

由于我们有一个JTAG OLIMEXARM-USB-OCD,从一些旧项目中幸存下来,ARM IDE的选择落在了IAR嵌入式工作台上,因为它支持通过GDB服务器进行调试。然而,配置这个GDB服务器是一个非常复杂的任务。从汇编源代码中构建的项目开始加载到程序员的SDRAM中,并有可能对其进行调试,这花了一些时间。
一旦一切都从调试器下运行,一些最“有趣”的算法方法被重写为C,以便更容易理解。因此,逐渐积累的信息使我们能够理解程序员的工作机制,下面将以稍微简化的形式描述。
当程序启动电源时,ARM微控制器启动其内置引导加载程序(条件为零),该引导加载程序将SPI Flash中的另一个引导加载程序(称为第一个引导加载程序)加载到其内部RAM中,将RAM重置到0地址,并将控制权交给第一个引导加载程序。启动后,第一个引导加载程序在ARM SDRAM中配置控制器,从而将外部RAM连接到0x20000000地址,并从相同的SPI闪存重写另一个引导加载程序(称为第二个引导加载程序),并将控制权移交给它。第二个引导加载程序配置控制器的外部USB芯片以与主机通信,在LCD上显示带有程序员名称和固件版本的字符串屏幕,与自己的AE801 ID芯片通信,最终进入主机命令处理循环。
为了处理每种类型的内存芯片,为ARM微控制器创建了一个固件,称为算法。算法文件具有USB扩展名,并存储在安装程序的“algox”目录中的主机上。“lib”目录包含Xilinx芯片的固件和扩展名为“wls”的文件,其中包含有关所需算法是否与每个支持的芯片匹配的信息,该芯片所需的适配器类型,以及每个芯片的一些附加参数。例如,电源电压、编程电压等。
在软件中选择任何芯片后,必须确保程序员安装了相应的适配器。为了做到这一点,主机向程序员发送一个请求命令,请求其中安装的适配器类型。程序员从适配器中请求AE801芯片类型,并将特殊生成的响应发送回主机。主机上的软件将“WLS”文件中的适配器类型与响应中的适配器类型进行比较,如果它们匹配,则将另一个引导加载程序(称为第三个引导加载程序)发送到地址0x20020000的程序员,该文件位于“bin”目录中。在将控制权移交给第三个引导加载程序后,主机使用它在地址0x20000000处下载相应的算法(USB文件),这是Xilinx固件,并最终将控制权移交给下载的算法。在对硬件进行了一些调整后,算法再次进入主机命令处理循环。中间的第三个加载程序只需要每次将工作算法加载到同一地址0x20000000。
当您在软件中选择不同的芯片时,从主机请求适配器类型开始,重复上述整个过程。
一旦加载到编程程序中的算法进入主机命令处理循环,所有进一步的操作都只由这些命令激活。换句话说,主机成为主机,程序员成为从属设备。适配器中的AE801芯片在向其7(CS)脚进料后,从适配器中导出,并在向其6脚进料后,从适配器中导出4 MHz的指令。AE801芯片的3支脚最初配置为接收来自程序员的命令。在发送下一个命令后,程序员将数据线配置为接收,AE801将其脚3分别发送给程序员并将数据发送给程序员。因此,适配器中的AE801和程序员之间交换数据。主板上的内部AE801和程序员之间的数据交换以相同的方式进行。
治疗
     因此,我们直接讨论了DX系列适配器的保护机制。

因此,我们直接讨论了DX系列适配器的保护机制。
如前所述,为了避免文章过于详细,我们只提供了所走过的道路的精髓-程序员和AE801 ID芯片之间的数据交换过程的描述。
本描述中使用的术语:
•术语“支腿”指AE801芯片的相应结论。
•字节计数-从1开始。
•每一次,程序员在向芯片发送一定数量的字节后,将数据线翻译为接收,而芯片在接收到数据后,将脚3切换到传输等。因此,这些切换的描述和脚3的提及将不会出现。
·故事以AE801芯片为代表。
RSA
{
  0x0000, 0x0001, 0x03d0, 0x0640, 0x059d, 0x045f, 0x0191, 0x020b, 0x0863, 0x0209, 0x0933, 0x0489, 0x017f, 0x0168, 0x00d8, 0x0717,
  0x0741, 0x0315, 0x040d, 0x0368, 0x0984, 0x084f, 0x06d8, 0x0832, 0x08b2, 0x0602, 0x07aa, 0x06a4, 0x0044, 0x086f, 0x08da, 0x07cd,
  0x067f, 0x099f, 0x0797, 0x0086, 0x0ab4, 0x0088, 0x09e6, 0x07c5, 0x0433, 0x082a, 0x06d4, 0x00d0, 0x06f8, 0x029d, 0x0496, 0x04f5,
  0x0525, 0x06ef, 0x037f, 0x02ad, 0x0606, 0x0133, 0x03b2, 0x0573, 0x0a1d, 0x017d, 0x09ad, 0x0674, 0x05ff, 0x0745, 0x097a, 0x02d9,
  0x0373, 0x02f3, 0x09a6, 0x0402, 0x0969, 0x05be, 0x01c5, 0x0ab7, 0x042c, 0x074c, 0x0965, 0x047d, 0x0989, 0x0a81, 0x00a4, 0x00fd,
  0x03ee, 0x099c, 0x0695, 0x05ee, 0x028d, 0x0435, 0x0243, 0x07df, 0x09d1, 0x07ee, 0x0509, 0x09b9, 0x023f, 0x02c0, 0x06fa, 0x098d,
  0x05d5, 0x0499, 0x0800, 0x016f, 0x0019, 0x0410, 0x010b, 0x01ab, 0x07ea, 0x036f, 0x0094, 0x004e, 0x0a64, 0x06b9, 0x0abe, 0x051a,
  0x0295, 0x0078, 0x0112, 0x06a8, 0x08dd, 0x06e9, 0x04d7, 0x08c3, 0x02e4, 0x00f3, 0x0015, 0x09e7, 0x0967, 0x06e2, 0x0650, 0x0508,
  0x0882, 0x0028, 0x07f3, 0x00b7, 0x03d7, 0x0504, 0x0143, 0x0016, 0x0995, 0x08b5, 0x0437, 0x0763, 0x04c5, 0x0234, 0x04c7, 0x07da,
  0x09bd, 0x027e, 0x051b, 0x01c0, 0x052a, 0x0544, 0x046c, 0x078c, 0x0199, 0x0299, 0x04b6, 0x094a, 0x07d3, 0x053c, 0x0083, 0x017b,
  0x00d6, 0x077f, 0x090b, 0x0475, 0x00ab, 0x09cc, 0x0312, 0x0603, 0x0907, 0x07fa, 0x00b9, 0x0909, 0x0889, 0x0360, 0x0247, 0x00cc,
  0x054c, 0x0213, 0x054e, 0x0a44, 0x0767, 0x07b0, 0x0074, 0x087b, 0x041e, 0x098a, 0x087d, 0x03ab, 0x069c, 0x06cc, 0x0604, 0x095f,
  0x053f, 0x0954, 0x02da, 0x06d1, 0x08f0, 0x09bf, 0x01db, 0x039e, 0x08a8, 0x0ac5, 0x007a, 0x0222, 0x0a8f, 0x042f, 0x0322, 0x01f0,
  0x00e3, 0x00f7, 0x0417, 0x09aa, 0x00fc, 0x077a, 0x04e9, 0x0a21, 0x0278, 0x06f7, 0x07ef, 0x08e7, 0x09cd, 0x04aa, 0x0739, 0x09e3,
  0x0708, 0x0a72, 0x028e, 0x04a6, 0x04c0, 0x021b, 0x081d, 0x05c5, 0x069a, 0x05bc, 0x06ca, 0x00eb, 0x00ec, 0x0a9b, 0x04f7, 0x07a2,
  0x04ec, 0x0338, 0x05b7, 0x0459, 0x043d, 0x02f5, 0x0284, 0x023b, 0x01f5, 0x0979, 0x01c4, 0x027b, 0x0868, 0x043c, 0x0397, 0x048f
};

5.从程序员那里得到1个字节,并将其作为标记存储。然后将随机的4字节发送到程序员。程序员得到这4个字节后,就像我们在P.4中一样,使用相同的RSA算法对它们进行加密,并以相同的顺序将生成的8个字节发送给我们(首先是高级字节,然后是每个单词的初级字节)。在获得这8个字节后,我们必须检查程序员是否完成了任务,如果是,我们将向他发送之前收到的令牌。
6.从程序员那里得到1字节,并将其作为标记再次记住。然后在数组中形成buf11-11随机字节,并将其保存以供后续操作。然后巧妙地将这11字节发送到程序员。诀窍是,我们必须向程序员发送88个字节,其中包含我们生成的11个字节,而最小的令牌位用于转换传输的11个字节。将88字节发送到程序员后,将接收到的令牌发送到程序员。
C的超控代码。
byte _mark;                 // Полученный из программатора байт.
byte _buf11[11];      // Массив для 11-и случайных байт.

for( i=0; i<11; i++ )
{
  byte bt = rand();

  _buf11 = bt;
  if( _mark & 1 )
    bt = ~bt;

  // Упаковываем один байт в 8.
  //
  for( j=0; j<8; j++ )
  {
    byte rnd = rand();

    byte msk = ( 1 << j );
    if( bt & msk )
      rnd |= msk;
    else
      rnd &= ~msk;

    // Отправляем байт rnd с упакованным в негоочередным битом в программатор.
    //
    ОтправкаБайта( rnd );
  }
}

// Отправляем впрограмматор маркер.
//
ОтправкаБайта( _mark );

7.从程序员那里得到1个字节,并将133个字节的数组发送到程序员,该数组由四个部分组成。
第一部分是48字节,其中包含适配器类型及其“年龄”的信息,取自本机适配器“DX0001”,其中文本“DX0001”被替换为“DX1011”。
且会有一个认证ID号
{
  'D',  'X', '1', '0', '1', '1', 0xff, 0xff, 0xff, 0xff,  '1',  '6',  '0',  '6',  '2',  '4',
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x50, 0x00, 0xff, 0xff, 0x00, 0x00, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};

第二部分是16个随机字节。
第三部分是22个字节,由Buf11中保存的11个字节组成。
C上的22字节生成码
byte _buf22[22];

const byte _hash[] =
{
  0x58,0x3E,0x09,0x2D,0xFD,0x45,0x68,0x10,0xD5,0x25,0xC7,0xB8,0x28,0x93,0x75,0xDE,
  0x57,0x02,0x1E,0x24,0x36,0xB6,0x3A,0x59,0x11,0x6E,0x58,0x91,0xAE,0x53,0x69,0xDF,
  0x44,0xA4,0x8B,0xFB,0x76,0x91,0x59,0x3C,0x30,0xB9,0xDA,0x21,0xD8,0x05,0xB4,0x16,
  0x4C,0x05,0x78,0x8D,0xB5,0x1C,0x41,0x63,0x4C,0xBE,0xA6,0xCC,0x65,0xB8,0x38,0x1D,
  0xE7,0xC6,0xC9,0x19,0xB7,0x73,0xB2,0x7D,0xCD,0x54,0xDC,0xFE,0x67,0x5E,0x79,0x68,
  0xB8,0x77,0x73,0x37,0xC8,0x56,0xA2,0x4D,0x9B,0x86,0x56,0x3F,0x26,0x39,0xDE,0xF6,
  0xA8,0x13,0xB4,0xBA,0x19,0xDE,0xDF,0x08,0x64,0x2A,0x9F,0xA4,0x3E,0xEE,0x90,0x5B,
  0xF0,0xF3,0xC6,0x5F,0x1F,0x84,0x87,0xA3,0x94,0x0D,0x04,0x92,0xDC,0x3C,0xD0,0x6A,
  0xD6,0x9B,0xA9,0xED,0x02,0xB0,0xB3,0xBB,0xF3,0x17,0x04,0x93,0x8F,0x18,0x22,0x9B,
  0x33,0x0F,0x2A,0x4C,0x72,0x1A,0x0F,0xC2,0x3E,0x4C,0x77,0xAA,0xF2,0x04,0xDC,0x60,
  0x68,0x81,0x7B,0x7C,0x60,0xE7,0xD3,0x61,0x3A,0xDA,0x69,0x4A,0x14,0x5A,0xB7,0x31,
  0x9F,0xB5,0x60,0x61,0xB4,0x2D,0x80,0x10,0xCF,0x16,0x6B,0xF1,0x08,0x81,0xDA,0x12,
  0xA6,0x46,0xF2,0xA2,0x14,0x68,0xAA,0x48,0x94,0x8B,0x9D,0xE3,0xD0,0xFB,0x84,0x74,
  0x1C,0x3C,0x94,0x5A,0x3F,0xF0,0x37,0x8C,0xD9,0x7E,0xA7,0x38,0xA4,0xB5,0xA7,0x25,
  0x65,0x15,0x7F,0xE5,0x3B,0xD1,0x14,0x1E,0xD3,0xA8,0x47,0x2E,0xD8,0xEB,0xB0,0xAE,
  0x4F,0x34,0xF4,0x52,0xC7,0x23,0x9D,0x60,0x98,0x1E,0x2C,0xFC,0xF2,0x96,0xB7,0x83
};

void PrepareBuf22()
{
  byte i, j, bt, bt1, bt2, bt3;

  for( i=0; i<4; i++ )
    _buf22 = rand();
  bt2 = (_buf22[1] ^ _buf22[2] ) & 0xEF;
  bt3 = _buf22[1] ^ _buf22[3];
  _buf22[4] = (byte)((_buf22[0] + _buf22[1] + _buf22[2]) ^ _buf22[3]);
  _buf22[1] ^= ( 1 << (_buf22[2] & 7) );
  bt = _buf22[2];
  bt1 = _buf22[0];
  i = 0;
  while( i < 10 )
  {
    bt += _buf11[i++];
    bt1 += _buf11[i++];
  }
  _buf22[5] = bt;
  _buf22[6] = bt1;
  //----------------------------------------------------------------
  _buf22[7+11] = 0;
  _buf22[7+12] = 0;
  for( i=0; i<10; i++ )
  {
    _buf22[7+11] += _buf11;
    _buf22[7+12] ^= _buf11;
  }

  _buf22[7+11] ^= _buf11[9];
  _buf22[7+12] ^= _buf11[10];
  _buf22[7+13] = (_buf22[7+11] + _buf11[10]) ^ _buf22[7+12];
  _buf22[7+14] = (_buf22[7+13] + _buf11[9]) ^ _buf22[7+12];

  for( i=0; i<15; i++ )
  {
    bt = (_buf22[7+i] ^ _hash[bt2+i] );
    bt = (bt << 2) | (bt >> 6);
    bt ^= bt3;
    _buf22[7+i] = bt;
  }
}

第四部分是47个随机字节。
8.在133字节阵列传输后,芯片必须进入睡眠状态,直到第7条腿上出现下一个活动高水平,否则如果第7条腿是复位信号,芯片将自动进入复位状态。从第2段开始,一切都在重复。
康复病人出院
最终,CX1011适配器被安全地安装起来,并在新的程序员中获得了第二次生命。
我们希望本文的材料将帮助读者安全地恢复其他“旧”适配器,甚至可能生产自己的适配器。

原文
Механизм защиты от клонирования адаптеров X.rar (336.01 KB, 下载次数: 12)

作者: 神6    时间: 2023-8-14 09:13
好文章,值得推荐666666
作者: 神6    时间: 2023-8-14 09:14
好文章,值得推荐。学习了。
作者: ABBA    时间: 2023-8-14 10:12
东西很好,值得学习
作者: ageway    时间: 2023-8-17 08:53

东西很好,值得学习
作者: maithon    时间: 2023-8-17 16:40
这机翻的有点生啊。
作者: fengdongshi    时间: 2023-8-17 18:35
做个记号。拿点银两赚积分,谢谢楼主的无私奉献精神
作者: 138liuxin    时间: 2023-8-17 18:49
https://www.usbjtag.com/phpbb3/viewtopic.php?t=9809 有链接打不开
作者: liyf    时间: 2023-8-21 11:30
查资料看了下,这个验证芯片可能是AKA LKT4200HS或者 Sentinel HL Max Chip
LKT4200HS芯片手册
LKT4200_datasheet.pdf (521.59 KB, 下载次数: 1)
另一个找不到资料,只看到个介绍
Sentinel HL Max - Chip Form Factor
Sentinel HL Max - Chip Form Factor 是保護和授權軟體運行到嵌入式系統上的理想解決方案。授權保護和實施成為產品設計不可分割的一部分,肉眼是完全看不見的。
技術規格
‧業界首創的白盒加密演算法
‧高性能智慧卡晶片提供更快的軟體執行和提升運算效率
‧標準公開金鑰加密演算法 AES 128, ECC 163, RSA 2048
‧超過 64,000 AES 加密金鑰
‧可寫入超過 2000 組金鑰來保護應用程式
‧31 KB 受保護的記憶體
‧微小外形尺寸:8.2 x 5.3 x 2.1 mm
‧免驅動程式
‧可韌體升級
‧允許應用程式植入晶片(AppOnChip)
‧工作溫度:-25˚C 至 85˚C




作者: 胡志峰    时间: 2023-8-21 20:58
大佬。给力,佩服
作者: qiao982    时间: 2023-8-22 19:58
很好的,给力
作者: HiGoodMan    时间: 2023-9-13 16:52
好牛,原文一个字没看懂,还是楼主的话都看懂。
作者: QHBT8678332    时间: 2023-9-14 15:37
东西很好,值得学习
作者: zj53523094    时间: 2023-9-15 11:26
真牛逼,精华贴。只是我这水平还看不懂....
作者: 365969365    时间: 2024-6-1 22:13

谢谢楼主感谢无私分享
作者: dm8510    时间: 2024-6-13 14:51
牛人,支持一下
作者: yiqiuccc    时间: 2024-7-13 20:05
厉害厉害 学习了
作者: DIYQI    时间: 2024-7-22 13:20
这个做工看起来不错
作者: muelfox    时间: 2024-10-15 19:20
谢谢分享, 一切为了银子




欢迎光临 DIY编程器网 (http://diybcq.com/) Powered by Discuz! X3.2