|
楼主 |
发表于 2016-6-26 19:12:32
|
显示全部楼层
v10也出来了,v9也快没人用了?现在发个研究贴不违规吧?
v9里面大家最关心的就是bootloader了吧?
论坛上有很多前辈都给出了各种办法把这个bootloader弄出来,可是都是片言只语,所以这里我整理一下大概的两个办法。
首先是个最简单的办法,不用拆机,没有风险,原理很简单,很多年前论坛上的大牛就发现并公布了这个办法,不过据说后来论坛浮云过一回,资料丢了。
这个办法利用的是jlink自带的一个命令,这个命令能读取jlink自身的内存,我们只是需要用这个命令把bootloader部分的内容读取出来就可以了。
在进入这个具体命令之前,我们来看一下jlink的操纵方法,比较普遍的做法是调用jlinkarm.dll公开的接口,再有sdk的情况下,调用这些接口并不麻烦,
但是如果没有sdk的话,c/c++语言要调用这些接口显得特别的麻烦,所以这里我们使用更为底层的办法越过jlink的dll,直接和jlink的驱动打交道。
首先,我们需要找到系统里面的jlink这个设备
GUID classGuid = {0x54654E76, 0xdcf7, 0x4a7f, 0x87, 0x8A, 0x4E, 0x8F, 0x0CA, 0x0A, 0x0CC, 0x9A};
auto devInfoSet = SetupDiGetClassDevsW(&classGuid, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
复制代码
用上面的代码先找到jlink的class
然后我们需要枚举这个devInfoSet里面的全部成员,依次取得各种信息,最终拿到jlink驱动提供的设备路径
SP_DEVICE_INTERFACE_DATA interfaceData = {0};
interfaceData.cbSize = sizeof(interfaceData);
for(DWORD i = 0; ; i ++)
{
if(!SetupDiEnumDeviceInterfaces(devInfoSet, nullptr, &classGuid, i, &interfaceData))
break;
DWORD requiredSize = 0;
SetupDiGetDeviceInterfaceDetailW(devInfoSet, &interfaceData, nullptr, 0, &requiredSize, nullptr);
void* tempBuffer = new uint8_t[requiredSize];
PSP_DEVICE_INTERFACE_DETAIL_DATA interfaceDetailData = static_cast<SP_DEVICE_INTERFACE_DETAIL_DATA>(tempBuffer);
interfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if(!SetupDiGetDeviceInterfaceDetailW(devInfoSet, &interfaceData, interfaceDetailData, requiredSize, &requiredSize, nullptr))
continue;
复制代码
到了这里interfaceDetailData->DevicePath这个里面就是jlink的设备路径,我们打开它
HANDLE deviceFile = CreateFileW(interfaceDetailData->DevicePath, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if(deviceFile == INVALID_HANDLE_VALUE)
continue;
复制代码
这个打开的文件句柄主要是给jlink发送一些控制命令,真正读写的是需要打开另外两个句柄的,pipe00用来读,pipe01用来写,我们也打开它们
wchar_t pipeFileName[1024] = {0};
wcscpy_s(pipeFileName, interfaceDetailData->DevicePath);
wcscat_s(pipeFileName, L"\\\\pipe00");
HANDLE readPileFile = CreateFileW(pipeFileName, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if(readPileFile == INVALID_HANDLE_VALUE)
continue;
wcscpy_s(pipeFileName, interfaceDetailData->DevicePath);
wcscat_s(pipeFileName, L"\\\\pipe01");
HANDLE writePileFile = CreateFileW(pipeFileName, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if(writePileFile == INVALID_HANDLE_VALUE)
continue;
复制代码
到了这里我们拿到了三个句柄,接下来我们就可以用这三个句柄来操控jlink了
首先介绍一下jlink的各种协议,这个协议有一部分公开了在它官网上,还有一个部分没有公开,大家可以去官网看看那个公开的文档,这里简单介绍一下。
jlink的协议比较简单,当我们需要做某件事情的时候,就往jlink发送一个命令过去,jlink解析我们发送的命令,处理,并返回一个结果(也有许多命令不返回结果)
我们发送给jlink的命令通过WriteFile函数调用写入到pipe01里面,然后通过读取pipe00获得命令结果。
jlink的数据使用stream的模式读写,在满足一定的延时要求的情况下,我们可以将一个命令分成几截写入,也可以按批次读取结果,下面我们就用一个简单的命令来举个例子。
这个命令用来获取jlink的固件版本,是一个公开的命令。
首先我们发送单字节的01到jlink
jlink会返回0x72个字节的内容给我们,前面两个字节是le格式的长度=0x70,表示这之后还要0x70个字节,这0x70个字节就是真正的内容。
bool jlinkCommandReadFirmwareVersion(HANDLE readPipeFile, HANDLE writePipeFile, void* dataBuffer)
{
uint8_t commandBuffer[1] = {0x01};
uint16_t leftLength = 0;
if(!jlinkSendCommand(readPipeFile, writePipeFile, commandBuffer, sizeof(commandBuffer), &leftLength, sizeof(leftLength)))
return false;
return jlinkContinueReadResult(readPipeFile, dataBuffer, leftLength);
}
复制代码
这里使用了两个函数,这两个函数下面会介绍,先看看这个函数的内容。
首先我们调用jlinkSendCommand发送1个字节的0x01给jlink,并读回两个字节的内容,这两个字节的内容=0x70,也就是剩余的数据大小,然后我们调用jlinkContinueReadResult把剩下的内容读全了。
注意,上面说过jlink使用的是stream模式,这就意味着,如果我没有未读完的数据还在jlink的缓冲区里面,那么这些内容会出现在下一条命令的返回结果里面,这里要特别小心。
bool jlinkSendCommand(HANDLE readPipeFile, HANDLE writePipeFile, void const* commandBuffer, uint32_t commandLength, void* resultBuffer, uint32_t resultHeaderLength)
{
if(!WriteFile(writePipeFile, commandBuffer, commandLength, nullptr, nullptr))
return false;
if(!resultHeaderLength)
return true;
return !!ReadFile(readPipeFile, resultBuffer, resultHeaderLength, nullptr, nullptr);
}
bool jlinkContinueReadResult(HANDLE readPipeFile, void* resultBuffer, uint32_t resultLength)
{
return !!ReadFile(readPipeFile, resultBuffer, resultLength, nullptr, nullptr);
}
复制代码
这两个函数非常简单,一目了然。
有了这些辅助函数,我们来看下一个命令。
bool jlinkCommandReadEmulatorMemory(HANDLE readPipeFile, HANDLE writePipeFile, uint32_t address, uint32_t length, void* dataBuffer)
{
uint8_t commandBuffer[9] =
{
0xfe,
static_cast<uint8_t>(address), static_cast<uint8_t>(address >> 8), static_cast<uint8_t>(address >> 16), static_cast<uint8_t>(address >> 24),
static_cast<uint8_t>(length), static_cast<uint8_t>(length >> 8), static_cast<uint8_t>(length >> 16), static_cast<uint8_t>(length >> 24),
};
return jlinkSendCommand(readPipeFile, writePipeFile, commandBuffer, sizeof(commandBuffer), dataBuffer, length);
}
复制代码
这就是关键命令了,id=0xfe,读取jlink自身的内存区域,命令id之后是4个字节的地址,然后是4个字节的长度,都是le格式。
在使用这个命令的时候要注意,jlink有缓冲区大小限制,我们不能一次发送太多的数据到jlink,我们也不能一次读取太多的数据,这个缓冲区限制大小是64k,我们也可以使用下面两个函数获取这个值。
uint32_t jlinkGetReadBufferSize(HANDLE deviceFile)
{
uint32_t readBufferSize = 0;
jlinkDeviceControl(deviceFile, 0x220460, nullptr, 0, &readBufferSize, sizeof(readBufferSize));
return readBufferSize;
}
uint32_t jlinkGetWriteBufferSize(HANDLE deviceFile)
{
uint32_t writeBufferSize = 0;
jlinkDeviceControl(deviceFile, 0x220464, nullptr, 0, &writeBufferSize, sizeof(writeBufferSize));
return writeBufferSize;
}
复制代码
而jlinkDeviceControl这个函数只是一个简单的封装
bool jlinkDeviceControl(HANDLE deviceFile, uint32_t controlCode, void* inputBuffer, uint32_t inputSize, void* outputBuffer, uint32_t outputSize, uint32_t* actualSize = nullptr)
{
DWORD resultSize = 0;
if(!DeviceIoControl(deviceFile, controlCode, inputBuffer, sizeof(inputSize), outputBuffer, sizeof(outputSize), &resultSize, nullptr))
return false;
if(actualSize)
*actualSize = static_cast<uint32_t>(resultSize);
return true;
}
复制代码
函数都全了,接下来就进入主题了。
首先我们知道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个字节)
uint8_t bootloader[0xb700] = {0};
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,判断办法如下。
bool jlinkCommandVerifySignature(HANDLE readPipeFile, HANDLE writePipeFile)
{
uint8_t commandBuffer[] =
{
0x18,
0x01,
0x01, 0x00, 0x00, 0x00,
0x00,
0x00,
};
int32_t isValid = 0;
if(!jlinkSendCommand(readPipeFile, writePipeFile, commandBuffer, sizeof(commandBuffer), &isValid, sizeof(isValid)))
return false;
return isValid > 0;
}
复制代码
这个函数返回true的时候,那签名就是合法的,否则就是非法的,我大致看了看jlink的dll,似乎并没有使用这个判断?
签名这个0x100个字节我们先放一边(不放一边也没办法,2048位的rsa束手无策的)
rsa的e是常客0x10001,n在下面
ROM:08011C28 DCD 0x4FFF1729
ROM:08011C2C DCD 0xAD96D829
ROM:08011C30 DCD 0xCD9F0C6A
ROM:08011C34 DCD 0x444F49FD
ROM:08011C38 DCD 3236562632
ROM:08011C3C DCD 2651048264
ROM:08011C40 DCD 2156799988
ROM:08011C44 DCD 1002538269
ROM:08011C48 DCD 1414572176
ROM:08011C4C DCD 728261039
ROM:08011C50 DCD 195953012
ROM:08011C54 DCD 4092138938
ROM:08011C58 DCD 3035786873
ROM:08011C5C DCD 1754605398
ROM:08011C60 DCD 3394821355
ROM:08011C64 DCD 3852065468
ROM:08011C68 DCD 1379916164
ROM:08011C6C DCD 2955657565
ROM:08011C70 DCD 3891065497
ROM:08011C74 DCD 372041464
ROM:08011C78 DCD 1715106254
ROM:08011C7C DCD 3832064334
ROM:08011C80 DCD 254910677
ROM:08011C84 DCD 2322701057
ROM:08011C88 DCD 1330054993
ROM:08011C8C DCD 3621432991
ROM:08011C90 DCD 0xE870EC79
ROM:08011C94 DCD 0x56C9D464
ROM:08011C98 DCD 0xA786970A
ROM:08011C9C DCD 0x15A58D01
ROM:08011CA0 DCD 0x3481F0D4
ROM:08011CA4 DCD 0x371B4738
ROM:08011CA8 DCD 0xA1CF85E4
ROM:08011CAC DCD 0xEFC0BA1B
ROM:08011CB0 DCD 0x512F550A
ROM:08011CB4 DCD 0xA2719983
ROM:08011CB8 DCD 0xCAFE135
ROM:08011CBC DCD 0xC87FC0B1
ROM:08011CC0 DCD 0x35028880
ROM:08011CC4 DCD 0xAB5DE12
ROM:08011CC8 DCD 0xC791BF33
ROM:08011CCC DCD 0xD38E90E4
ROM:08011CD0 DCD 0x93B510C5
ROM:08011CD4 DCD 0xC47DEB52
ROM:08011CD8 DCD 0xA359C991
ROM:08011CDC DCD 0xB6C37DD8
ROM:08011CE0 DCD 0xDE7F258D
ROM:08011CE4 DCD 0xF5C6215B
ROM:08011CE8 DCD 0xC2DF133D
ROM:08011CEC DCD 0x3B92601C
ROM:08011CF0 DCD 0x24B2416
ROM:08011CF4 DCD 0xB669CCD2
ROM:08011CF8 DCD 0x73477502
ROM:08011CFC DCD 0x847F9D58
ROM:08011D00 DCD 0x69D5387F
ROM:08011D04 DCD 0x2CC6592A
ROM:08011D08 DCD 0x1BDCC656
ROM:08011D0C DCD 0x5F4959FE
ROM:08011D10 DCD 0x745CB3ED
ROM:08011D14 DCD 0x60087B7D
ROM:08011D18 DCD 0xBC8436B4
ROM:08011D1C DCD 0x6A0C76C7
ROM:08011D20 DCD 0x1B99A01F
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。
int32_t jlinkCommandWriteOneTimeSettings(HANDLE readPipeFile, HANDLE writePipeFile, void const* dataBuffer)
{
uint8_t commandBuffer[0x10d] = {0x13};
uint32_t tempValue = crc32(0xffffffff, static_cast<uint8_t const*>(dataBuffer), 0x100) ^ 0xffffffff;
commandBuffer[0x101] = static_cast<uint8_t>(tempValue);
commandBuffer[0x102] = static_cast<uint8_t>(tempValue >> 8);
commandBuffer[0x103] = static_cast<uint8_t>(tempValue >> 16);
commandBuffer[0x104] = static_cast<uint8_t>(tempValue >> 24);
commandBuffer[0x105] = 0x49;
commandBuffer[0x106] = 0x44;
commandBuffer[0x107] = 0x53;
commandBuffer[0x108] = 0x45;
commandBuffer[0x109] = 0x47;
commandBuffer[0x10a] = 0x47;
commandBuffer[0x10b] = 0x45;
commandBuffer[0x10c] = 0x52;
memcpy(commandBuffer + 1, dataBuffer, 0x100);
if(!jlinkSendCommand(readPipeFile, writePipeFile, commandBuffer, sizeof(commandBuffer), &tempValue, sizeof(tempValue)))
return -1;
return static_cast<int32_t>(tempValue);
}
复制代码
发送过去的命令长度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函数来读取,或者可以使用下面这个专属命令。
bool jlinkCommandReadOneTimeSettings(HANDLE readPipeFile, HANDLE writePipeFile, void* dataBuffer)
{
uint8_t commandBuffer[1] = {0xe6};
return jlinkSendCommand(readPipeFile, writePipeFile, commandBuffer, sizeof(commandBuffer), dataBuffer, 0x100);
}
复制代码
这个命令他只能读取固定的0x100个字节的数据(bf00到c000)
当然也有进阶的读取和更新全部0x900数据的命令,但是用处不大(我们盗版用户也没办法生成新的数字签名),这里就不多介绍了,感兴趣的朋友可以自己逆向一下jink的固件
当前版本的固件大约是这样的
ROM:080114CC DCD EMU_CMD_01_VERSION+1
ROM:080114D0 DCD EMU_CMD_02_RESET_TRST+1
ROM:080114D4 DCD EMU_CMD_03_RESET_TARGET+1
ROM:080114D8 DCD EM_CMD_04_GET_INFO+1
ROM:080114DC DCD EMU_CMD_05_SET_SPEED+1
ROM:080114E0 DCD EMU_CMD_06_UPDATE_FIRMWARE+1
ROM:080114E4 DCD EMU_CMD_07_GET_STATE+1
ROM:080114E8 DCD EMU_CMD_08_SET_KS_POWER+1
ROM:080114EC DCD EMU_CMD_09_REGISTER_UNREGISTER+1
ROM:080114F0 DCD EMU_CMD_0A_INDICATORS+1
ROM:080114F4 DCD EMU_CMD_0B_PERMIT+1
ROM:080114F8 DCD EMU_CMD_0C_PCODE+1
ROM:080114FC DCD EMU_CMD_0D_PROT_VERSION+1
ROM:08011500 DCD EMU_CMD_0E_SET_EMU_OPTION+1
ROM:08011504 DCD EMU_CMD_UNSUPPORTED+1
ROM:08011508 DCD EMU_CMD_UNSUPPORTED+1
ROM:0801150C DCD EMU_CMD_11_MERGE_COMMANDS+1
ROM:08011510 DCD EMU_CMD_12_UPDATE_CONFIG_DATA_C000_C100+1
ROM:08011514 DCD EMU_CMD_13_UPDATE_CONFIG_DATA_B700_BF00+1
ROM:08011518 DCD EMU_CMD_UNSUPPORTED+1
ROM:0801151C DCD EMU_CMD_15_SPI+1
ROM:08011520 DCD EMU_CMD_16_UPDATE_CONFIG_DATA+1
ROM:08011524 DCD EMU_CMD_17_HANDLE_C2+1
ROM:08011528 DCD EMU_CMD_18_+1
ROM:0801152C DCD EMU_CMD_19_+1
ROM:08011530 DCD EMU_CMD_UNSUPPORTED+1
ROM:08011534 DCD EMU_CMD_UNSUPPORTED+1
ROM:08011538 DCD EMU_CMD_UNSUPPORTED+1
ROM:0801153C DCD EMU_CMD_UNSUPPORTED+1
ROM:08011540 DCD EMU_CMD_UNSUPPORTED+1
ROM:08011544 DCD EMU_CMD_UNSUPPORTED+1
ROM:08011548 DCD EMU_CMD_UNSUPPORTED+1
ROM:0801154C DCD EMU_CMD_C0_GET_SPEEDS+1
ROM:08011550 DCD EMU_CMD_C1_GET_HW_INFO+1
ROM:08011554 DCD EMU_CMD_C2_GET_COUNTERS+1
ROM:08011558 DCD EMU_CMD_C3_TEST_NET_SPEED+1
ROM:0801155C DCD EMU_CMD_C4_CPU2_SET_CONFIG+1
ROM:08011560 DCD EMU_CMD_C5_CPU2_EXEC_CMD+1
ROM:08011564 DCD EMU_CMD_C6_GET_CPU2_CAPS+1
ROM:08011568 DCD EMU_CMD_C7_SELECT_IF+1
ROM:0801156C DCD EMU_CMD_C8_HW_CLOCK+1
ROM:08011570 DCD EMU_CMD_C9_HW_TMS0+1
ROM:08011574 DCD EMU_CMD_CA_HW_TMS1+1
ROM:08011578 DCD EMU_CMD_CB_HW_DATA0+1
ROM:0801157C DCD EMU_CMD_CC_HW_DATA1+1
ROM:08011580 DCD EMU_CMD_CD_HW_JTAG+1
ROM:08011584 DCD EMU_CMD_CE_HW_JTAG2+1
ROM:08011588 DCD EMU_CMD_CF_HW_JTAG3+1
ROM:0801158C DCD EMU_CMD_D0_HW_RELEASE_RESET_STOP_EX+1
ROM:08011590 DCD EMU_CMD_D1_HW_RELEASE_RESET_STOP_TIMED+1
ROM:08011594 DCD EMU_CMD_UNSUPPORTED+1
ROM:08011598 DCD EMU_CMD_UNSUPPORTED+1
ROM:0801159C DCD EMU_CMD_D4_GET_MAX_MEM_BLOCK+1
ROM:080115A0 DCD EMU_CMD_D5_HW_JTAG_WRITE+1
ROM:080115A4 DCD EMU_CMD_D6_HW_JTAG_GET_RESULT+1
ROM:080115A8 DCD EMU_CMD_UNSUPPORTED+1
ROM:080115AC DCD EMU_CMD_UNSUPPORTED+1
ROM:080115B0 DCD EMU_CMD_UNSUPPORTED+1
ROM:080115B4 DCD EMU_CMD_DA_HW_TCK0+1
ROM:080115B8 DCD EMU_CMD_DB_HW_TCK1+1
ROM:080115BC DCD EMU_CMD_DC_HW_RESET0+1
ROM:080115C0 DCD EMU_CMD_DD_HW_RESET1+1
ROM:080115C4 DCD EMU_CMD_DE_HW_TRST0+1
ROM:080115C8 DCD EMU_CMD_DF_HW_TRST1+1
ROM:080115CC DCD EMU_CMD_E0_FINE_WRITE_READ+1
ROM:080115D0 DCD EMU_CMD_E1_CDC_EXEC+1
ROM:080115D4 DCD EMU_CMD_UNSUPPORTED+1
ROM:080115D8 DCD EMU_CMD_UNSUPPORTED+1
ROM:080115DC DCD EMU_CMD_UNSUPPORTED+1
ROM:080115E0 DCD EMU_CMD_E5_GET_CPU2_CAPS_DLL_VERSION+1
ROM:080115E4 DCD EMU_CMD_E6_READ_CONFIG_DATA_BF00_C000+1
ROM:080115E8 DCD EMU_CMD_E7_SYNC_KS_POWER_FROM_CONFIG_DATA+1
ROM:080115EC DCD EMU_CMD_E8_GET_CAPS+1
ROM:080115F0 DCD EMU_CMD_E9_GET_CPU_CAPS+1
ROM:080115F4 DCD EMU_CMD_EA_EXEC_CPU_CMD+1
ROM:080115F8 DCD EMU_CMD_EB_SWO+1
ROM:080115FC DCD EMU_CMD_UNSUPPORTED+1
ROM:08011600 DCD EMU_CMD_ED_GET_CAPS_EX+1
ROM:08011604 DCD EMU_CMD_UNSUPPORTED+1
ROM:08011608 DCD EMU_CMD_UNSUPPORTED+1
ROM:0801160C DCD EMU_CMD_F0_GET_HW_VERSION+1
ROM:08011610 DCD EMU_CMD_F1_WRITE_DCC+1
ROM:08011614 DCD EMU_CMD_F2_READ_CONFIG_C000_C100+1
ROM:08011618 DCD EMU_CMD_F3_WRITE_CONFIG_DUMMY+1
ROM:0801161C DCD EMU_CMD_F4_WRITE_MEM+1
ROM:08011620 DCD EMU_CMD_F5_READ_MEM+1
ROM:08011624 DCD EMU_CMD_F6_MEASURE_RTCK_REACT+1
ROM:08011628 DCD EMU_CMD_F7_WRITE_MEM_ARM79+1
ROM:0801162C DCD EMU_CMD_F8_READ_MEM_ARM79+1
ROM:08011630 DCD EMU_CMD_UNSUPPORTED+1
ROM:08011634 DCD EMU_CMD_FA_READ_DCC+1
ROM:08011638 DCD WRITE_DCC_EX+1
ROM:0801163C DCD EMU_CMD_UNSUPPORTED+1
ROM:08011640 DCD EMU_CMD_UNSUPPORTED+1
ROM:08011644 DCD EMU_CMD_FE_READ_EMU_MEM+1
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家的编译器有这个压缩功能,不过我没详细看过),大家还需要一份代码来解压缩。
uint8_t const* in8 = 0x0802D2BC;
uint8_t* out8 = 0x20000008;
while(out8 < 0x20000008 + 0x814)
{
uint32_t c = *in8 ++;
uint32_t a = c & 3;
if(!a)
a = 3 + static_cast<uint32_t>(*in8 ++);
uint32_t b = c >> 4;
if(b == 0x0f)
b = 0xf + static_cast<uint32_t>(*in8 ++);
while(-- a)
*out8 ++ = *in8 ++;
if(b)
{
uint32_t e = *in8 ++;
uint32_t d = (c >> 2) & 0x03;
if(d == 3)
d = *in8 ++;
d = (d << 8) + e;
uint8_t* back8 = out8 - d;
for(uint32_t j = 0; j < b + 2; j ++)
*out8 ++ = *back8 ++;
}
}
复制代码
也很简单的,当前这个版本,压缩之前的代码在0802D2BC,长度0x668,解开到20000008,长度0x814
解开的数据可以用ida再加载进去。方便分析。 |
|