Skip to main content

JLink V9 Bootloader

v9里面大家最关心的就是bootloader了吧?

[https://www.amobbs.com/thread-5653964-1-1.html]


  论坛上有很多前辈都给出了各种办法把这个bootloader弄出来,可是都是片言只语,所以这里我整理一下大概的两个办法。
首先是个最简单的办法,不用拆机,没有风险,原理很简单,很多年前论坛上的大牛就发现并公布了这个办法,不过据说后来论坛浮云过一回,资料丢了。
这个办法利用的是jlink自带的一个命令,这个命令能读取jlink自身的内存,我们只是需要用这个命令把bootloader部分的内容读取出来就可以了。

在进入这个具体命令之前,我们来看一下jlink的操纵方法,比较普遍的做法是调用jlinkarm.dll公开的接口,再有sdk的情况下,调用这些接口并不麻烦,
但是如果没有sdk的话,c/c++语言要调用这些接口显得特别的麻烦,所以这里我们使用更为底层的办法越过jlink的dll,直接和jlink的驱动打交道。

首先,我们需要找到系统里面的jlink这个设备

  1. GUID classGuid = {0x54654E76, 0xdcf7, 0x4a7f, 0x87, 0x8A, 0x4E, 0x8F, 0x0CA, 0x0A, 0x0CC, 0x9A};
  2. auto devInfoSet = SetupDiGetClassDevsW(&classGuid, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
用上面的代码先找到jlink的class

然后我们需要枚举这个devInfoSet里面的全部成员,依次取得各种信息,最终拿到jlink驱动提供的设备路径

  1. SP_DEVICE_INTERFACE_DATA interfaceData = {0};
  2. interfaceData.cbSize = sizeof(interfaceData);
  3. for(DWORD i = 0; ; i ++)
  4. {
  5.     if(!SetupDiEnumDeviceInterfaces(devInfoSet, nullptr, &classGuid, i, &interfaceData))
  6.         break;

  7.     DWORD requiredSize = 0;
  8.     SetupDiGetDeviceInterfaceDetailW(devInfoSet, &interfaceData, nullptr, 0, &requiredSize, nullptr);
  9.     void* tempBuffer = new uint8_t[requiredSize];
  10.     PSP_DEVICE_INTERFACE_DETAIL_DATA interfaceDetailData = static_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(tempBuffer);
  11.     interfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
  12.     if(!SetupDiGetDeviceInterfaceDetailW(devInfoSet, &interfaceData, interfaceDetailData, requiredSize, &requiredSize, nullptr))
  13.         continue;

到了这里interfaceDetailData->DevicePath这个里面就是jlink的设备路径,我们打开它

  1.     HANDLE deviceFile = CreateFileW(interfaceDetailData->DevicePath, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
  2.     if(deviceFile == INVALID_HANDLE_VALUE)
  3.         continue;
这个打开的文件句柄主要是给jlink发送一些控制命令,真正读写的是需要打开另外两个句柄的,pipe00用来读,pipe01用来写,我们也打开它们

  1.     wchar_t pipeFileName[1024] = {0};
  2.     wcscpy_s(pipeFileName, interfaceDetailData->DevicePath);
  3.     wcscat_s(pipeFileName, L"\\pipe00");
  4.     HANDLE readPileFile = CreateFileW(pipeFileName, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
  5.     if(readPileFile == INVALID_HANDLE_VALUE)
  6.         continue;

  7.     wcscpy_s(pipeFileName, interfaceDetailData->DevicePath);
  8.     wcscat_s(pipeFileName, L"\\pipe01");
  9.     HANDLE writePileFile = CreateFileW(pipeFileName, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
  10.     if(writePileFile == INVALID_HANDLE_VALUE)
  11.         continue;
到了这里我们拿到了三个句柄,接下来我们就可以用这三个句柄来操控jlink了

首先介绍一下jlink的各种协议,这个协议有一部分公开了在它官网上,还有一个部分没有公开,大家可以去官网看看那个公开的文档,这里简单介绍一下。
jlink的协议比较简单,当我们需要做某件事情的时候,就往jlink发送一个命令过去,jlink解析我们发送的命令,处理,并返回一个结果(也有许多命令不返回结果)
我们发送给jlink的命令通过WriteFile函数调用写入到pipe01里面,然后通过读取pipe00获得命令结果。
jlink的数据使用stream的模式读写,在满足一定的延时要求的情况下,我们可以将一个命令分成几截写入,也可以按批次读取结果,下面我们就用一个简单的命令来举个例子。
这个命令用来获取jlink的固件版本,是一个公开的命令。
首先我们发送单字节的01到jlink
jlink会返回0x72个字节的内容给我们,前面两个字节是le格式的长度=0x70,表示这之后还要0x70个字节,这0x70个字节就是真正的内容。

  1. bool jlinkCommandReadFirmwareVersion(HANDLE readPipeFile, HANDLE writePipeFile, void* dataBuffer)
  2. {
  3.             uint8_t commandBuffer[1] = {0x01};
  4.             uint16_t leftLength = 0;
  5.             if(!jlinkSendCommand(readPipeFile, writePipeFile, commandBuffer, sizeof(commandBuffer), &leftLength, sizeof(leftLength)))
  6.                         return false;

  7.             return jlinkContinueReadResult(readPipeFile, dataBuffer, leftLength);
  8. }
这里使用了两个函数,这两个函数下面会介绍,先看看这个函数的内容。
首先我们调用jlinkSendCommand发送1个字节的0x01给jlink,并读回两个字节的内容,这两个字节的内容=0x70,也就是剩余的数据大小,然后我们调用jlinkContinueReadResult把剩下的内容读全了。
注意,上面说过jlink使用的是stream模式,这就意味着,如果我没有未读完的数据还在jlink的缓冲区里面,那么这些内容会出现在下一条命令的返回结果里面,这里要特别小心。

  1. bool jlinkSendCommand(HANDLE readPipeFile, HANDLE writePipeFile, void const* commandBuffer, uint32_t commandLength, void* resultBuffer, uint32_t resultHeaderLength)
  2. {
  3.      if(!WriteFile(writePipeFile, commandBuffer, commandLength, nullptr, nullptr))
  4.         return false;

  5.     if(!resultHeaderLength)
  6.         return true;

  7.         return !!ReadFile(readPipeFile, resultBuffer, resultHeaderLength, nullptr, nullptr);
  8. }

  9. bool jlinkContinueReadResult(HANDLE readPipeFile, void* resultBuffer, uint32_t resultLength)
  10. {
  11.         return !!ReadFile(readPipeFile, resultBuffer, resultLength, nullptr, nullptr);
  12. }
这两个函数非常简单,一目了然。

有了这些辅助函数,我们来看下一个命令。

  1. bool jlinkCommandReadEmulatorMemory(HANDLE readPipeFile, HANDLE writePipeFile, uint32_t address, uint32_t length, void* dataBuffer)
  2. {
  3.     uint8_t commandBuffer[9] =
  4.     {
  5.          0xfe,
  6.          static_cast<uint8_t>(address), static_cast<uint8_t>(address >> 8), static_cast<uint8_t>(address >> 16), static_cast<uint8_t>(address >> 24),
  7.          static_cast<uint8_t>(length), static_cast<uint8_t>(length >> 8), static_cast<uint8_t>(length >> 16), static_cast<uint8_t>(length >> 24),
  8.     };

  9.     return jlinkSendCommand(readPipeFile, writePipeFile, commandBuffer, sizeof(commandBuffer), dataBuffer, length);
  10. }
这就是关键命令了,id=0xfe,读取jlink自身的内存区域,命令id之后是4个字节的地址,然后是4个字节的长度,都是le格式。
在使用这个命令的时候要注意,jlink有缓冲区大小限制,我们不能一次发送太多的数据到jlink,我们也不能一次读取太多的数据,这个缓冲区限制大小是64k,我们也可以使用下面两个函数获取这个值。

  1. uint32_t jlinkGetReadBufferSize(HANDLE deviceFile)
  2. {
  3.             uint32_t readBufferSize = 0;
  4.             jlinkDeviceControl(deviceFile, 0x220460, nullptr, 0, &readBufferSize, sizeof(readBufferSize));
  5.             return readBufferSize;
  6. }

  7. uint32_t jlinkGetWriteBufferSize(HANDLE deviceFile)
  8. {
  9.             uint32_t writeBufferSize = 0;
  10.             jlinkDeviceControl(deviceFile, 0x220464, nullptr, 0, &writeBufferSize, sizeof(writeBufferSize));
  11.             return writeBufferSize;
  12. }
而jlinkDeviceControl这个函数只是一个简单的封装

  1. bool jlinkDeviceControl(HANDLE deviceFile, uint32_t controlCode, void* inputBuffer, uint32_t inputSize, void* outputBuffer, uint32_t outputSize, uint32_t* actualSize = nullptr)
  2. {
  3.             DWORD resultSize = 0;
  4.             if(!DeviceIoControl(deviceFile, controlCode, inputBuffer, sizeof(inputSize), outputBuffer, sizeof(outputSize), &resultSize, nullptr))
  5.                         return false;

  6.             if(actualSize)
  7.                         *actualSize = static_cast<uint32_t>(resultSize);

  8.             return true;
  9. }
函数都全了,接下来就进入主题了。
首先我们知道jlink的bootloader占用的是0x08000000到0x08010000之间的64k内容,
其中从0x0800b700开始到0x0800c000的部分占据0x900个字节,jlink管他称为ots,大概是什么one time section的缩写?
这部分放的大约有3个东西,序列号,license,然后还有一段签名,这个贴的最后有部分ots的介绍。
这部分jlink并没有提供擦出功能,只能写入,你只能把某个位从1变成0,不能反过来(所以才叫one time?)
然后是从0x0800c000开始到0x0800c900同样也是0x900个字节的内容,这个部分jlink管他叫config data,
这部分jlink能够擦出,并且能用jlink自带的jlinkconfig.exe修改。
他里面主要放的是一些配置信息,比如昵称,是否打开虚拟串口,是否使用jlink给目标板供电什么的。

我们在读取bootloader的时候完全可以跳过ots和config部分,读取前面的0xb700个字节就可以了(实际上bootloader只有不到0x4000个字节)

  1.         uint8_t bootloader[0xb700] = {0};
  2.         jlinkCommandReadEmulatorMemory(readPipeFile, writePipeFile, 0x08000000, sizeof(bootloader), bootloader);
就是这么简单,我们就拿到了bootloader。

但是不要着急,我们跳过了ots和config,这两个部分也挺重要的。
如果我们只是希望把挂掉的jlink救活,并且我们的jlink本身就是正版(正版会丢固件吗?我有个盗版丢了),那我们也需要把ots也读出来,
但是不要把ots发给其他人,ots里面有唯一的设备签名。
把坏掉的jlink擦除掉,然后把这个bootloader刷回去,通电,jlink能识别这个设备,即使这个设备里面只有bootloader,jlink会显示出bootloader的版本来。
大约是“J-Link V9 compiled Oct 12 2012 BTL”这样的。
然后我们可以使用jlinkconfig.exe从新写入固件,并不需要自己手动去jlink的dll里面导出固件再手动往里面刷。

如果我们想做个一摸一样的复制品(序列号也原样复制),那也把ots读出来。

如果我们想干坏事,批量生产一波,那么还需要了解一下ots这个东西。
ots有两个部分,从0xb700开始的0x100字节是一段数字签名,很不幸我这盗版jlink里面全是ff,正版用户可以看看这256个字节的数字签名。
jlink使用的是salt长度为4的rsa-pss算法来生成这段签名的。至于rsa-pss算法大家可以google一下。
签名的原始数据长度16个字节,前面4个字节是序列号,后面12个字节是stm32提供的设备唯一id。
rsa算法长度2048,很不幸现在没啥希望能算出他的私钥,所以大部分(全部?)的盗版这个签名部分都是0xff吧。
这段签名即使全是0xff也不影响jlink的功能,只是能用这个签名判断出是否是正版jlink,判断办法如下。

  1. bool jlinkCommandVerifySignature(HANDLE readPipeFile, HANDLE writePipeFile)
  2. {
  3.             uint8_t commandBuffer[] =
  4.             {
  5.                         0x18,
  6.                         0x01,
  7.                         0x01, 0x00, 0x00, 0x00,
  8.                         0x00,
  9.                         0x00,
  10.             };

  11.             int32_t isValid                                                                                                                = 0;
  12.             if(!jlinkSendCommand(readPipeFile, writePipeFile, commandBuffer, sizeof(commandBuffer), &isValid, sizeof(isValid)))
  13.                     return false;

  14.             return isValid > 0;
  15. }
这个函数返回true的时候,那签名就是合法的,否则就是非法的,我大致看了看jlink的dll,似乎并没有使用这个判断?
签名这个0x100个字节我们先放一边(不放一边也没办法,2048位的rsa束手无策的)
rsa的e是常客0x10001,n在下面

  1. ROM:08011C28                 DCD 0x4FFF1729
  2. ROM:08011C2C                 DCD 0xAD96D829
  3. ROM:08011C30                 DCD 0xCD9F0C6A
  4. ROM:08011C34                 DCD 0x444F49FD
  5. ROM:08011C38                 DCD 3236562632
  6. ROM:08011C3C                 DCD 2651048264
  7. ROM:08011C40                 DCD 2156799988
  8. ROM:08011C44                 DCD 1002538269
  9. ROM:08011C48                 DCD 1414572176
  10. ROM:08011C4C                 DCD 728261039
  11. ROM:08011C50                 DCD 195953012
  12. ROM:08011C54                 DCD 4092138938
  13. ROM:08011C58                 DCD 3035786873
  14. ROM:08011C5C                 DCD 1754605398
  15. ROM:08011C60                 DCD 3394821355
  16. ROM:08011C64                 DCD 3852065468
  17. ROM:08011C68                 DCD 1379916164
  18. ROM:08011C6C                 DCD 2955657565
  19. ROM:08011C70                 DCD 3891065497
  20. ROM:08011C74                 DCD 372041464
  21. ROM:08011C78                 DCD 1715106254
  22. ROM:08011C7C                 DCD 3832064334
  23. ROM:08011C80                 DCD 254910677
  24. ROM:08011C84                 DCD 2322701057
  25. ROM:08011C88                 DCD 1330054993
  26. ROM:08011C8C                 DCD 3621432991
  27. ROM:08011C90                 DCD 0xE870EC79
  28. ROM:08011C94                 DCD 0x56C9D464
  29. ROM:08011C98                 DCD 0xA786970A
  30. ROM:08011C9C                 DCD 0x15A58D01
  31. ROM:08011CA0                 DCD 0x3481F0D4
  32. ROM:08011CA4                 DCD 0x371B4738
  33. ROM:08011CA8                 DCD 0xA1CF85E4
  34. ROM:08011CAC                 DCD 0xEFC0BA1B
  35. ROM:08011CB0                 DCD 0x512F550A
  36. ROM:08011CB4                 DCD 0xA2719983
  37. ROM:08011CB8                 DCD 0xCAFE135
  38. ROM:08011CBC                 DCD 0xC87FC0B1
  39. ROM:08011CC0                 DCD 0x35028880
  40. ROM:08011CC4                 DCD 0xAB5DE12
  41. ROM:08011CC8                 DCD 0xC791BF33
  42. ROM:08011CCC                 DCD 0xD38E90E4
  43. ROM:08011CD0                 DCD 0x93B510C5
  44. ROM:08011CD4                 DCD 0xC47DEB52
  45. ROM:08011CD8                 DCD 0xA359C991
  46. ROM:08011CDC                 DCD 0xB6C37DD8
  47. ROM:08011CE0                 DCD 0xDE7F258D
  48. ROM:08011CE4                 DCD 0xF5C6215B
  49. ROM:08011CE8                 DCD 0xC2DF133D
  50. ROM:08011CEC                 DCD 0x3B92601C
  51. ROM:08011CF0                 DCD 0x24B2416
  52. ROM:08011CF4                 DCD 0xB669CCD2
  53. ROM:08011CF8                 DCD 0x73477502
  54. ROM:08011CFC                 DCD 0x847F9D58
  55. ROM:08011D00                 DCD 0x69D5387F
  56. ROM:08011D04                 DCD 0x2CC6592A
  57. ROM:08011D08                 DCD 0x1BDCC656
  58. ROM:08011D0C                 DCD 0x5F4959FE
  59. ROM:08011D10                 DCD 0x745CB3ED
  60. ROM:08011D14                 DCD 0x60087B7D
  61. ROM:08011D18                 DCD 0xBC8436B4
  62. ROM:08011D1C                 DCD 0x6A0C76C7
  63. ROM:08011D20                 DCD 0x1B99A01F
  64. ROM:08011D24                 DCD 0xAE87F498
其实知道了也没啥用。

接下来我们来看看序列号,他位于bf00,这个序列号用的地方有两个,一个自然就是序列号,另外一个是硬件版本,是的你没有看错。
硬件版本是用序列号来计算的,首先把序列号除以100000,得到的商再除以10,得到的余数如果大于等于8则取2,得到的就是子版本号。
比如我这个盗版设备,sn=59101308,他的硬件版本显示为9.10
sn先除以100000,商591,再除以10,余1,所以就是9.10
当序列号这个位置4个字节都是ff的时候,我们可以使用exec setsn=xxx来设置一个序列号,如果序列号已经有值了,这个命令就不能用了。

然后就是license,他从bf20开始,每个license占16个字节,他们就是普通的ascii字符串,包括结尾的0。
这个部分可以使用exec addfeature来添加license。

所以如果数字签名本身就是无效的话,那么ots部分我们可以完全不用管他,全部擦除成ff,然后用jlink的命令写入需要的值就可以了。
当然jlink也提供了两个命令来更新ots,这里介绍一个简单的,他只是更新bf00开始的0x100个字节,这个区域里面包括序列号和license。

  1. int32_t jlinkCommandWriteOneTimeSettings(HANDLE readPipeFile, HANDLE writePipeFile, void const* dataBuffer)
  2. {
  3.             uint8_t commandBuffer[0x10d] = {0x13};
  4.             uint32_t tempValue = crc32(0xffffffff, static_cast<uint8_t const*>(dataBuffer), 0x100) ^ 0xffffffff;
  5.             commandBuffer[0x101] = static_cast<uint8_t>(tempValue);
  6.             commandBuffer[0x102] = static_cast<uint8_t>(tempValue >> 8);
  7.             commandBuffer[0x103] = static_cast<uint8_t>(tempValue >> 16);
  8.             commandBuffer[0x104] = static_cast<uint8_t>(tempValue >> 24);
  9.             commandBuffer[0x105] = 0x49;
  10.             commandBuffer[0x106] = 0x44;
  11.             commandBuffer[0x107] = 0x53;
  12.             commandBuffer[0x108] = 0x45;
  13.             commandBuffer[0x109] = 0x47;
  14.             commandBuffer[0x10a] = 0x47;
  15.             commandBuffer[0x10b] = 0x45;
  16.             commandBuffer[0x10c] = 0x52;
  17.             memcpy(commandBuffer + 1, dataBuffer, 0x100);
  18.             if(!jlinkSendCommand(readPipeFile, writePipeFile, commandBuffer, sizeof(commandBuffer), &tempValue, sizeof(tempValue)))
  19.                     return -1;

  20.             return static_cast<int32_t>(tempValue);
  21. }
发送过去的命令长度0x10d,第一个字节是命令id=0x13,接下里0x100个字节就是新的ots数据,然后是4个字节的crc32(根据crc32实现的默认初始值不同,我们可能需要调整0xffffffff这个值为0),接下来8个字节是个常量,他等于IDSEGGER的ascii码。
这个命令返回一个错误代码,如果成功写入了,返回的是0,否则返回一个负值。
注意,这个ots并不是我们想改成什么样子就能改成什么样子的,我们发送过去的0x100字节的新数据必须满足三个条件。
第一,如果原来的ots里面已经有一个非ffffffff的序列号了,那么我们的对应的序列号必须要和原始序列号相同,也就是说我们只有一次机会把序列号从0xffffffff改成其他的。
第二,我们发送过去的0x100数据里面的license不能是空的,具体的说就是0x100的ots数据的偏移量0x20这个地方不能是0
第三,我们只能把原始数据里面是1的位变成0,不能反过来。
如果我们发送过去的数据不满足这三个条件,jlink都会返回错误。
至于原来的bf00这部分的数据是什么,我们可以使用上面那个jlinkCommandReadEmulatorMemory函数来读取,或者可以使用下面这个专属命令。

  1. bool jlinkCommandReadOneTimeSettings(HANDLE readPipeFile, HANDLE writePipeFile, void* dataBuffer)
  2. {
  3.             uint8_t commandBuffer[1] = {0xe6};
  4.             return jlinkSendCommand(readPipeFile, writePipeFile, commandBuffer, sizeof(commandBuffer), dataBuffer, 0x100);
  5. }
这个命令他只能读取固定的0x100个字节的数据(bf00到c000)

当然也有进阶的读取和更新全部0x900数据的命令,但是用处不大(我们盗版用户也没办法生成新的数字签名),这里就不多介绍了,感兴趣的朋友可以自己逆向一下jink的固件
当前版本的固件大约是这样的

  1. ROM:080114CC                 DCD EMU_CMD_01_VERSION+1
  2. ROM:080114D0                 DCD EMU_CMD_02_RESET_TRST+1
  3. ROM:080114D4                 DCD EMU_CMD_03_RESET_TARGET+1
  4. ROM:080114D8                 DCD EM_CMD_04_GET_INFO+1
  5. ROM:080114DC                 DCD EMU_CMD_05_SET_SPEED+1
  6. ROM:080114E0                 DCD EMU_CMD_06_UPDATE_FIRMWARE+1
  7. ROM:080114E4                 DCD EMU_CMD_07_GET_STATE+1
  8. ROM:080114E8                 DCD EMU_CMD_08_SET_KS_POWER+1
  9. ROM:080114EC                 DCD EMU_CMD_09_REGISTER_UNREGISTER+1
  10. ROM:080114F0                 DCD EMU_CMD_0A_INDICATORS+1
  11. ROM:080114F4                 DCD EMU_CMD_0B_PERMIT+1
  12. ROM:080114F8                 DCD EMU_CMD_0C_PCODE+1
  13. ROM:080114FC                 DCD EMU_CMD_0D_PROT_VERSION+1
  14. ROM:08011500                 DCD EMU_CMD_0E_SET_EMU_OPTION+1
  15. ROM:08011504                 DCD EMU_CMD_UNSUPPORTED+1
  16. ROM:08011508                 DCD EMU_CMD_UNSUPPORTED+1
  17. ROM:0801150C                 DCD EMU_CMD_11_MERGE_COMMANDS+1
  18. ROM:08011510                 DCD EMU_CMD_12_UPDATE_CONFIG_DATA_C000_C100+1
  19. ROM:08011514                 DCD EMU_CMD_13_UPDATE_CONFIG_DATA_B700_BF00+1
  20. ROM:08011518                 DCD EMU_CMD_UNSUPPORTED+1
  21. ROM:0801151C                 DCD EMU_CMD_15_SPI+1
  22. ROM:08011520                 DCD EMU_CMD_16_UPDATE_CONFIG_DATA+1
  23. ROM:08011524                 DCD EMU_CMD_17_HANDLE_C2+1
  24. ROM:08011528                 DCD EMU_CMD_18_+1
  25. ROM:0801152C                 DCD EMU_CMD_19_+1
  26. ROM:08011530                 DCD EMU_CMD_UNSUPPORTED+1
  27. ROM:08011534                 DCD EMU_CMD_UNSUPPORTED+1
  28. ROM:08011538                 DCD EMU_CMD_UNSUPPORTED+1
  29. ROM:0801153C                 DCD EMU_CMD_UNSUPPORTED+1
  30. ROM:08011540                 DCD EMU_CMD_UNSUPPORTED+1
  31. ROM:08011544                 DCD EMU_CMD_UNSUPPORTED+1
  32. ROM:08011548                 DCD EMU_CMD_UNSUPPORTED+1
  33. ROM:0801154C                 DCD EMU_CMD_C0_GET_SPEEDS+1
  34. ROM:08011550                 DCD EMU_CMD_C1_GET_HW_INFO+1
  35. ROM:08011554                 DCD EMU_CMD_C2_GET_COUNTERS+1
  36. ROM:08011558                 DCD EMU_CMD_C3_TEST_NET_SPEED+1
  37. ROM:0801155C                 DCD EMU_CMD_C4_CPU2_SET_CONFIG+1
  38. ROM:08011560                 DCD EMU_CMD_C5_CPU2_EXEC_CMD+1
  39. ROM:08011564                 DCD EMU_CMD_C6_GET_CPU2_CAPS+1
  40. ROM:08011568                 DCD EMU_CMD_C7_SELECT_IF+1
  41. ROM:0801156C                 DCD EMU_CMD_C8_HW_CLOCK+1
  42. ROM:08011570                 DCD EMU_CMD_C9_HW_TMS0+1
  43. ROM:08011574                 DCD EMU_CMD_CA_HW_TMS1+1
  44. ROM:08011578                 DCD EMU_CMD_CB_HW_DATA0+1
  45. ROM:0801157C                 DCD EMU_CMD_CC_HW_DATA1+1
  46. ROM:08011580                 DCD EMU_CMD_CD_HW_JTAG+1
  47. ROM:08011584                 DCD EMU_CMD_CE_HW_JTAG2+1
  48. ROM:08011588                 DCD EMU_CMD_CF_HW_JTAG3+1
  49. ROM:0801158C                 DCD EMU_CMD_D0_HW_RELEASE_RESET_STOP_EX+1
  50. ROM:08011590                 DCD EMU_CMD_D1_HW_RELEASE_RESET_STOP_TIMED+1
  51. ROM:08011594                 DCD EMU_CMD_UNSUPPORTED+1
  52. ROM:08011598                 DCD EMU_CMD_UNSUPPORTED+1
  53. ROM:0801159C                 DCD EMU_CMD_D4_GET_MAX_MEM_BLOCK+1
  54. ROM:080115A0                 DCD EMU_CMD_D5_HW_JTAG_WRITE+1
  55. ROM:080115A4                 DCD EMU_CMD_D6_HW_JTAG_GET_RESULT+1
  56. ROM:080115A8                 DCD EMU_CMD_UNSUPPORTED+1
  57. ROM:080115AC                 DCD EMU_CMD_UNSUPPORTED+1
  58. ROM:080115B0                 DCD EMU_CMD_UNSUPPORTED+1
  59. ROM:080115B4                 DCD EMU_CMD_DA_HW_TCK0+1
  60. ROM:080115B8                 DCD EMU_CMD_DB_HW_TCK1+1
  61. ROM:080115BC                 DCD EMU_CMD_DC_HW_RESET0+1
  62. ROM:080115C0                 DCD EMU_CMD_DD_HW_RESET1+1
  63. ROM:080115C4                 DCD EMU_CMD_DE_HW_TRST0+1
  64. ROM:080115C8                 DCD EMU_CMD_DF_HW_TRST1+1
  65. ROM:080115CC                 DCD EMU_CMD_E0_FINE_WRITE_READ+1
  66. ROM:080115D0                 DCD EMU_CMD_E1_CDC_EXEC+1
  67. ROM:080115D4                 DCD EMU_CMD_UNSUPPORTED+1
  68. ROM:080115D8                 DCD EMU_CMD_UNSUPPORTED+1
  69. ROM:080115DC                 DCD EMU_CMD_UNSUPPORTED+1
  70. ROM:080115E0                 DCD EMU_CMD_E5_GET_CPU2_CAPS_DLL_VERSION+1
  71. ROM:080115E4                 DCD EMU_CMD_E6_READ_CONFIG_DATA_BF00_C000+1
  72. ROM:080115E8                 DCD EMU_CMD_E7_SYNC_KS_POWER_FROM_CONFIG_DATA+1
  73. ROM:080115EC                 DCD EMU_CMD_E8_GET_CAPS+1
  74. ROM:080115F0                 DCD EMU_CMD_E9_GET_CPU_CAPS+1
  75. ROM:080115F4                 DCD EMU_CMD_EA_EXEC_CPU_CMD+1
  76. ROM:080115F8                 DCD EMU_CMD_EB_SWO+1
  77. ROM:080115FC                 DCD EMU_CMD_UNSUPPORTED+1
  78. ROM:08011600                 DCD EMU_CMD_ED_GET_CAPS_EX+1
  79. ROM:08011604                 DCD EMU_CMD_UNSUPPORTED+1
  80. ROM:08011608                 DCD EMU_CMD_UNSUPPORTED+1
  81. ROM:0801160C                 DCD EMU_CMD_F0_GET_HW_VERSION+1
  82. ROM:08011610                 DCD EMU_CMD_F1_WRITE_DCC+1
  83. ROM:08011614                 DCD EMU_CMD_F2_READ_CONFIG_C000_C100+1
  84. ROM:08011618                 DCD EMU_CMD_F3_WRITE_CONFIG_DUMMY+1
  85. ROM:0801161C                 DCD EMU_CMD_F4_WRITE_MEM+1
  86. ROM:08011620                 DCD EMU_CMD_F5_READ_MEM+1
  87. ROM:08011624                 DCD EMU_CMD_F6_MEASURE_RTCK_REACT+1
  88. ROM:08011628                 DCD EMU_CMD_F7_WRITE_MEM_ARM79+1
  89. ROM:0801162C                 DCD EMU_CMD_F8_READ_MEM_ARM79+1
  90. ROM:08011630                 DCD EMU_CMD_UNSUPPORTED+1
  91. ROM:08011634                 DCD EMU_CMD_FA_READ_DCC+1
  92. ROM:08011638                 DCD WRITE_DCC_EX+1
  93. ROM:0801163C                 DCD EMU_CMD_UNSUPPORTED+1
  94. ROM:08011640                 DCD EMU_CMD_UNSUPPORTED+1
  95. ROM:08011644                 DCD EMU_CMD_FE_READ_EMU_MEM+1
  96. ROM:08011648                 DCD EMU_CMD_UNSUPPORTED+1
有了这些信息,大家可以救活自己死掉的jlink,也可以自己做一个精简版的jlink(光是bootloader也许不够?还需要原理图?)
顺便多一句,网上能搜索到那个原理图根我手上的这个盗版有两个地方不一样,lm324那里,我这个盗版运放2是个5v的电压跟随器模式,运放1是个同步放大器模式(外围电阻并没焊接),
从固件里面看可以通过在启动的时候将PC13拉低来启用运放1,我不知道正版这个部分是怎么样子的。


接下来我们来看一个麻烦一点的办法,这个有风险要拆机,我的bootloader就是用这个办法弄出来的。
这也是论坛上各位大牛早就研究过的办法,就是写一段木马进去把bootloader用串口的办法发送出来。
这里我们使用一个相对安全的办法来dump出bootloader。
我们在jlink的固件里面找到一段不使用代码,插入我们的一段小程序进去,然后把启动向量指向我们的小程序,
这段小程序在上电的时候先通过读取某gpio管教的电平高低来决定继续执行还是跳转到原始jlink的启动向量。
这样一来,我们的这段小程序不会影响到jlink本身的功能,jlink可以正常使用,只有在上电的时候我们短接某个管脚,才会进入dump模式。
只要我们的程序在判读管脚高低电平的时候没有出错,即使接下里的代码里面有问题,也没关系,因为jlink是可以正常使用的,我们可以修改再来过。

因为我们的代码要和jlink的代码共存,所以,我们需要使用汇编语言来生成这段代码,这样能做到比较短小,毕竟jlink里面废代码并不多。

首先我们需要找到“废代码”的位置,这个挺容易的,中断表里面通常有很多中断都是不使用的,对应的中断处理函数的代码都是一个死循环。
我们可以直接覆盖掉这些中断处理函数,因为他们本身就不会被触发,为了安全我们可以保留一个这样的函数,并把其他的中断向量都指向到这个函数上,这里我偷懒并没有这样做。

那么这段废代吗在什么地方呢?就在0802cff0的地方,我们可以看到这里都是一堆的jump指令,好了,覆盖他们

                                    ldr                r0, =0x40023830
                                    ldr                r1, [r0]
                                    orr                r1, r1, #7
                                    str                r1, [r0]                                // enable GPIOA, GPIOB, GPIOC

                                    ldr                r5, =0x40020400
                                    ldr                r0, [r5]
                                    bic                r0, r0, #0xf00000
                                    orr                r0, r0, #0x200000
                                    str                r0, [r5]                                // PB10 = af, PB11 = input

                                    add                r2, r5, #0x10                        // read PB11
                                    ldr                r0, [r2]
                                    tst                r0, #0x800                                // if PB11 is high then jmp to jlink
                                    bne                finished

                                    add                r4, r5, #0x24
                                    ldr                r0, [r4]
                                    bic                r0, r0, #0xf00
                                    orr                r0, r0, #0x700                        // sete PB10 as af7 = USART3_TX
                                    str                r0, [r4]

                                    ldr                r0, =0x40023840                        // enable USART3 clock
                                    ldr                r1, [r0]
                                    orr                r1, r1, #0x40800                // enable USART3 + WWDG
                                    str                r1, [r0]

                                    ldr                r4, =0x40004800                        // r4 = USART_SR
                                    add                r5, r4, 0x0c                        // r5 = USART_CR
                                    ldr                r0, [r5]
                                    bic                r0, #0x2000                                // UE = 0, disable USART first
                                    str                r0, [r5]

                                    mov                r0, #0x0008                                // TE = 1, enable tx
                                    str                r0, [r5]
                                    add                r3, r4, #0x10                        // r3 = USART_CR2
                                    mov                r0, #0
                                    str                r0, [r3]

                                    add                r3, r4, #0x08                        // r3 = USART_BRR
                                    mov                r0, #0x104                                // 30Mhz / 115200 / 16 = 16.25
                                    str                r0, [r3]

                                    ldr                r0, [r5]
                                    orr                r0, #0x2000                                // UE = 1, enable USART
                                    str                r0, [r5]

                                    add                r5, r4, #4                                // r5 = USART_DR

                                    ldr                r6, =0x40002c00                        // r6 = WWDG_CR
                                    mov                r7, #0x7f

                    dump_start:
                                    ldr                r1, =0x08000000                        // r1 = current address
                                   mov                r2, #0x10000                        // r2 = left count
               
                    wait_txe:
                                    ldr                r0, [r4]
                                    tst                r0, #0x80                                // test TXE
                                    beq                wait_txe

                                    str                r7, [r6]                                // reload watchdog
                                    ldrb              r0, [r1], #1                        // load current byte and output to USART3
                                    str                r0, [r5]

                    wait_tc:
                                    ldr                r0, [r4]
                                    tst                r0, #0x40                                // wait TC
                                    beq                wait_tc

                                    subs               r2, r2, 1
                                    bne                wait_txe

                                    b                   dump_start

                     finished:
                                    ldr                pc, =0x0802cf61    // jump to jlink's reset handler

代码简单直接,就不多描述了,我是用的时arm-gcc编译器,大家也许需要做做移植工作才能在别的编译器下面编译。
这里面也许唯一需要说的就是串口的波特率计算,怎么知道当前的时钟频率究竟是多少。
当然也可以不管三七二十一初始化一趟RCC,不过这样会增加许多的代码量,
我这里比较偷懒,首先假定时钟频率是16MHz,然后按照115200的波特率算出一个值来,
接着让它开始dump,然后接入一个能自动分析波特率的逻辑分析仪,逻辑分析仪能汇报一个不太准确的波特率,
没关系,用这个不太准确的波特率回推出一个不太准确的时钟频率,我这里算出来是个30.13MHz,那么显然实际的时钟就是30MHz
然后在修改成30MHz的参数实际的跑一下,顺利拿到bootloader。

拿到bootloader以后,我转为逆向jlink固件,才发现有更简单的办法读取bootloader,于是这个代码也就用处不大了。
==================================================================================
题外话,也许大家有兴趣逆向jlink的固件?我这里提供一些简单的经验。
jlink使用的时他们自己家的实时操作系统embOS作为底层,这个embOS有源代码,但是需要买,我肯定买不起,不过好在他们家提供试用版。
大家可以下载试用版然后用ida打开,即使没有源代码,但是也有头文件,这里有几个结构定义,然后还有库文件,虽然库文件也是只能分析汇编,但是库文件能看到函数名字!
这样大家能分析出很多库代码,能将上层逻辑和操作系统代码剥离开,清晰不少。

然后还有一个,这个固件里面的初始化数据经过压缩,也不知道是谁家的编译器(ti家的编译器有这个压缩功能,不过我没详细看过),大家还需要一份代码来解压缩。

  1. uint8_t const* in8 = 0x0802D2BC;
  2. uint8_t* out8 = 0x20000008;
  3. while(out8 < 0x20000008 + 0x814)
  4. {
  5.     uint32_t c  = *in8 ++;
  6.     uint32_t a = c & 3;
  7.     if(!a)
  8.         a = 3 + static_cast<uint32_t>(*in8 ++);

  9.     uint32_t b = c >> 4;
  10.     if(b == 0x0f)
  11.         b = 0xf + static_cast<uint32_t>(*in8 ++);

  12.     while(-- a)
  13.         *out8 ++ = *in8 ++;

  14.     if(b)
  15.     {
  16.         uint32_t e = *in8 ++;
  17.         uint32_t d = (c >> 2) & 0x03;
  18.         if(d == 3)
  19.             d = *in8 ++;

  20.         d = (d << 8) + e;
  21.         uint8_t* back8 = out8 - d;
  22.         for(uint32_t j = 0; j < b + 2; j ++)
  23.             *out8 ++ = *back8 ++;
  24.         }
  25.     }
也很简单的,当前这个版本,压缩之前的代码在0802D2BC,长度0x668,解开到20000008,长度0x814
解开的数据可以用ida再加载进去。方便分析。



>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  1. //gcc usb.c `pkg-config libusb-1.0 --libs --cflags` -o usb
  2. #include <errno.h>
  3. #include <signal.h>
  4. #include <string.h>
  5. #include <stdio.h>
  6. #include <stdbool.h>
  7. #include <stdlib.h>
  8. #include <stdint.h>
  9. #include <unistd.h>
  10. #include <sys/select.h>
  11. #include <termios.h>

  12. #include <libusb.h>

  13. #define EP_DATA_IN        (0x3|LIBUSB_ENDPOINT_IN)
  14. #define EP_DATA_OUT       (0x3|LIBUSB_ENDPOINT_OUT)

  15. static struct libusb_device_handle *devh = NULL;
  16. static struct libusb_transfer *recv_bulk_transfer = NULL;

  17. bool jlinkSendCommand(  void const* commandBuffer, uint32_t commandLength, void* resultBuffer, uint32_t resultHeaderLength)
  18. {
  19.     int transferred, r;
  20.     r = libusb_bulk_transfer(devh, EP_DATA_OUT, commandBuffer, commandLength, &transferred, 200);
  21.      // if(!WriteFile( commandBuffer, commandLength, nullptr, nullptr))
  22.      //    return false;

  23.     if(!resultHeaderLength)
  24.         return true;

  25.     // return !!ReadFile( resultBuffer, resultHeaderLength, nullptr, nullptr);
  26.     return 0==libusb_bulk_transfer(devh, EP_DATA_IN, resultBuffer, resultHeaderLength, &transferred, 200);
  27. }

  28. bool jlinkContinueReadResult(void* resultBuffer, uint32_t resultLength)
  29. {
  30.     int transferred, r;
  31.     // return !!ReadFile( resultBuffer, resultLength, nullptr, nullptr);
  32.     return 0==libusb_bulk_transfer(devh, EP_DATA_IN, resultBuffer, resultLength, &transferred, 200);
  33. }
  34. bool jlinkCommandReadFirmwareVersion(void* dataBuffer)
  35. {
  36.     uint8_t commandBuffer[1] = {0x01};
  37.     uint16_t leftLength = 0;
  38.     if(!jlinkSendCommand(  commandBuffer, sizeof(commandBuffer), &leftLength, sizeof(leftLength)))
  39.                 return false;

  40.     return jlinkContinueReadResult( dataBuffer, leftLength);
  41. }
  42. bool jlinkCommandReadEmulatorMemory(uint32_t address, uint32_t length, void* dataBuffer)
  43. {
  44.     uint8_t commandBuffer[9] =
  45.     {
  46.          0xfe,
  47.          (uint8_t)(address), (uint8_t)(address >> 8), (uint8_t)(address >> 16), (uint8_t)(address >> 24),
  48.          (uint8_t)(length), (uint8_t)(length >> 8), (uint8_t)(length >> 16), (uint8_t)(length >> 24),
  49.     };

  50.     return jlinkSendCommand(  commandBuffer, sizeof(commandBuffer), dataBuffer, length);
  51. }
  52. int main(int argc, char **argv)
  53. {
  54.     int r = 1;

  55.     r = libusb_init(NULL);
  56.     if (r < 0) {
  57.         fprintf(stderr, "failed to initialise libusb\n");
  58.         exit(1);
  59.     }

  60.     devh = libusb_open_device_with_vid_pid(NULL, 0x1366, 0x0105);
  61.     if (devh == NULL) {
  62.         fprintf(stderr, "Could not find/open device\n");
  63.         goto out;
  64.     }

  65.     r = libusb_claim_interface(devh, 2);
  66.     if (r < 0) {
  67.         fprintf(stderr, "usb_claim_interface error %d\n", r);
  68.         goto out;
  69.     }
  70.     printf("claimed interface\n");

  71.     char version[128];
  72.     bool ret = jlinkCommandReadFirmwareVersion(version);
  73.     printf("ver[%d] %s\n", ret, version);

  74.     uint8_t bootloader[0xb700] = {0};
  75.     ret = jlinkCommandReadEmulatorMemory( 0x08000000, sizeof(bootloader), bootloader);
  76.     printf("read[%d]\n", ret);

  77.     FILE* fBld = fopen("bootloader.bin", "wb");
  78.     fwrite(bootloader, 1, sizeof(bootloader), fBld);
  79.     fclose(fBld);

  80. out_release:
  81.     libusb_release_interface(devh, 2);
  82. out:
  83.     libusb_close(devh);
  84.     libusb_exit(NULL);
  85.     return r >= 0 ? r : -r;
  86. }

Popular posts from this blog

Installing Debian on QNAP HS-210, TS-21x and TS-22x devices

QNAP's original firmware has a lot of bloatware. If you want to has a lean NAS with more choices and controls on what are running inside the box, here is a webpage to install Debian, my favorite distro, into TS-212p. [https://www.cyrius.com/debian/kirkwood/qnap/ts-219/install/] Overview In a nutshell, the installation of Debian on your QNAP HS-210, TS-21x or TS-22x works like this: you use the QNAP firmware to write a Debian installer image to flash. When you restart your device, Debian installer starts and allows you to login via SSH to perform the installation. Debian will be installed to disk and a Debian kernel will be put in flash that will start Debian from disk. If you follow this procedure, Debian 9 (stretch) will be installed to your SATA disk and the QNAP firmware on disk and in flash will be replaced with Debian. Debian does not install a web interface to configure your machine, although it's possible to install such software. If this is not what you want, pleas...

Build Marvell ARMv5 toolchain with Crosstool-NG

Notes: under Ubuntu 16.04   0. Prepeare - install required tools 1. Install crosstool-ng # mkdir -p scratch/ct-ng; cd scratch/ct-ng # wget http://crosstool-ng.org/download/crosstool-ng/crosstool-ng-1.18.0.tar.bz2 # tar xf crosstool-ng-1.18.0.tar.bz2 # cd crosstool-ng-1.18.0 # ./configure --prefix=`pwd`/crosstool # make && make install # export PATH="${PATH}:`pwd`/crosstool/bin" # ct-ng help This is crosstool-NG version 1.18.0 2. Configure marvell ARMv5 toolchain # mkdir -p ~/scratch/marvell; cd ~/scratch/marvell # ct-ng list-samples  [G.X]   arm-cortex_a15-linux-gnueabi  [G..]   arm-cortex_a8-linux-gnueabi  [G..]   arm-davinci-linux-gnueabi  [G..]   arm-unknown-eabi  [G..]   arm-unknown-linux-gnueabi  [G.X]   arm-unknown-linux-uclibcgnueabi  [G..]   x86_64-unknown-linux-gnu  [G..]   x86_64-unknown-linux-uclibc  [G.X]   x86_64-unknown-mingw3...

Recovery mode of QNAP HS-210, TS-21x and TS-22x devices

If, somehow, hard drive becomes unreadable, use the method below to restore installation[ https://www.cyrius.com/debian/kirkwood/qnap/ts-219/recovery/] Recovery mode of QNAP HS-210, TS-21x and TS-22x devices QNAP HS-210, TS-21x and TS-22x devices have a recovery mode that can be used when there is a problem with your installation of Debian that renders your device unbootable. The system recovery mode allows you write a recovery image to flash via the network using the TFTP protocol. This pages describes how how to create recovery images and how to use the recovery mode. As an alternative to the instructions on this page, you can use a  Live CD provided by QNAP . Creating recovery images In order to create a recovery image for your QNAP TS-21x/TS-22x, you have to take an exact copy of your flash memory. That is, the recovery image consists of the following parts of your flash in this order:  mtd0 ,  mtd4 ,  mtd5 ,  mtd1 ,  mtd2 ,  mtd3 . ...