工作经验:用于UDS 27服务安全访问的DLL文件开发和调用方法
0 前言
0.1 学习思路
这次学习开发DLL的经历,可分解为以下子任务:
- 学习ECU的安全算法逻辑:项目中Level01(2/4字节种子密钥)是简单位移、异或操作,Level03使用了AES-128算法,并进行了非对称加密(16字节种子密钥);
- 确认DLL函数的接口形式:需要结合自己使用的测试工具、调用方法、调用环境的编译位数等实际需求;
- 通过代码实现安全算法;
- 开发DLL的编译器使用:配置器设置、编译头、Library和DLL共同封装等;
- Python环境调用、测试工具调用。
0.2 声明
- 为了保护项目信息安全,本文章中的安全访问访问算法已经进行模糊处理,或者是我自己编纂的。
- 通常测试工具会要求DLL使用标准接口类型,编写测试脚本调用的使用场景,可以按照自己的实际需求开发接口。如果是程序调用,可用的封装手段很多,可以考虑是否有必要开发DLL文件。
- 由于本人通常不使用Vector设备,所以没有考虑CANoe场景的使用。之后有机会接触到VN设备,会考虑更新文章。
- 开发环境是Visual Studio 2019
1 DLL文件简介
DLL(Dynamic Link Library)文件是微软Windows操作系统中的一种实现共享库概念的文件格式。DLL可以用来封装特定功能的代码,使得这些代码可以在不同的程序之间复用。例如,在汽车诊断领域,DLL文件可以用来实现安全算法,处理密钥生成等任务。
2 接口形式
2.1 介绍
本质上,DLL文件只要明确接口信息就可以进行调用。用于通过UDS $27安全访问的DLL文件,函数接口也完全可以进行自定义。但由于大部分总线分析工具、测试软件的某些模块(图形界面类型的模块)仅支持CANoe定义的2种接口形式,所以开发时应该优先考虑如下图的接口形式。
2.2 接口说明
// CANoe Form 1
long diagGenerateKeyFromSeed(
byte seedArray[], // 种子数组
dword seedArraySize, // 种子长度
dword securityLevel, // 需要解锁的安全访问等级
char variant[], // variant描述
char ipOption[], // 可选参数,调用时如果不使用则必须在此处给出空字符串""
byte keyArray[], // 密钥数组
dword maxKeyArraySize, // 密钥最大长度
dword& keyActualSizeOut // 密钥实际输出
)
// CANoe Form 2
long DiagGenerateKeyFromSeed(
char ecuQualifier[] // 限定符:在诊断配置对话框中为相应诊断描述设置的 ECU 或目标的限定符。
byte seedArray[], //
dword seedArraySize, //
dword securityLevel, //
char variant[], //
char option[], //
byte keyArray[], //
dword maxKeyArraySize, //
dword& keyActualSizeOut //
)
2.3 接口代码
此部分是我使用的函数接口,在调用DLL文件时也会使用。
// C++编译函数接口
unsigned int GenerateKeyEx(
const unsigned char* ipSeedArray, //
unsigned int iSeedArraySize, //
const unsigned int iSecurityLevel, //
const char* ipVariant, //
unsigned char* iopKeyArray, //
unsigned int iMaxKeyArraySize, //
unsigned int& oActualKeyArraySize //
)
3 代码示例
工程结构: 使用VS提供的动态链接库(DLL)模板,删除了freamwork.h、pch.h、pch.cpp,保留DLL的入口文件dllmain.cpp。
头文件:
// (.h)
#pragma once
#include <stdint.h>
#include <windows.h>
#include "mbedtls/算法名.h" // 引用需要的算法library, 注意需要同DLL一起导出
#define ECU_KEY 0x00000001 // ECU KEY:此安全算法中会用到ECU KEY值
#define ECU_KEY_LOW 0x0001 // ECU KEY低2字节
extern "C" unsigned int __declspec(dllexport) GenerateKeyEx( // 声明导出的函数
const unsigned char* ipSeedArray,
unsigned int iSeedArraySize,
const unsigned int iSecurityLevel,
const char* ipVariant,
unsigned char* iopKeyArray,
unsigned int iMaxKeyArraySize,
unsigned int& oActualKeyArraySize
);
源文件:
// (.cpp)
#include "头文件名.h" // 引用头文件
unsigned int GenerateKeyEx(
const unsigned char* ipSeedArray, //
unsigned int iSeedArraySize, //
const unsigned int iSecurityLevel, //
const char* ipVariant, //
unsigned char* iopKeyArray, //
unsigned int iMaxKeyArraySize, //
unsigned int& oActualKeyArraySize //
)
{
if(iSecurityLevel == 1){ // Level 01: 先通过安全访问等级来划分
if(iSeedArraySize == 4){ // $67 01 返回4字节Seed
uint32_t seed = ((uint32_t)ipSeedArray[0]<<24) | (ipSeedArray[1]<<16) | (ipSeedArray[2]<<8) | ipSeedArray[3];
uint_t Key_Result = (seed >> 1) ^ seed; // 此处安全算法为作者编纂,为位移和异或操纵
uint_t Result = Key_Result^ECU_KEY; // 此处安全算法为作者编纂,为位移和异或操纵
iopKeyArray[0] = (Result>>24) & 0xFF; // 填充iopKeyArray
iopKeyArray[1] = (Result>>16) & 0xFF;
iopKeyArray[2] = (Result>>8) & 0xFF;
iopKeyArray[0] = Result & 0xFF;
oActualKeyArraySize = 4; // 赋值长度
return 0; // 需要返回0或1: return 0->调用DLL成功;return 1->调用DLL失败
}
if(iSeedArraySize == 2){ // $67 01 返回2字节Seed
// 此处省略 2字节种子密钥的计算代码
oActualKeyArraySize = 2;
return 0;
}
}
if(iSecurityLevel == 3){ // Level 03
// 此处省略 Level 03 的密钥计算代码,具体算法查看请参考mbedtls说明
oActualKeyArraySize = 16;
return 0;
}
}
4 编译说明
- 在配置管理器,选择正确的平台。32位环境调用,选择WIN32;64位环境调用,选择x64。
- 如需使用外部算法文件,则按照如下操作,将引用的算法文件同DLL一起导出。
链接头文件:解决方案管理器 → 解决方案(右键)→ 属性 → C/C++ → 常规 → 附加包含目录 → 编辑,选择外部文件"…mbedtls-develpment\include"文件夹。
链接源文件:解决方案管理器 → 解决方案(右键)→ 属性 → 链接器 → 常规 → 附加库目录 → 编辑,选择外部文件"…mbedtls-develpment\library"文件夹。
注意:点击“应用”后,再确认。 - 不使用预编译头文件“pch.h”:解决方案管理器 → 解决方案(右键)→ 属性 → C/C++ → 常规 → 预编译头部,选择不使用预编译头。
5 调用示例
// 使用python语言调用DLL文件计算安全访问密钥
import ctypes
from ctypes import *
dll_path = r'D:\testForDll.dll'
dll = ctypes.cdll.LoadLibrary(dll_path)
seedResponse = [0x67, 0x01, 0x10, 0x20, 0x30, 0x40] # 测试工具采集的诊断响应报文
seed_list = seedResponse[2:] # 提取种子信息
seed_ubyte_array = (c_ubyte * len(seed_list))() # 构造seed_ubyte_array
for i in range(len(seed_list)):
seed_ubyte_array[i] = seed_list[i]
key_ubyte_array = (c_ubyte * len(seed_list))() # 构造key_ubyte_array
key_array_size = ctypes.c_byte() # 构造key_array_size
try: # 调用DLL文件中的密钥生成函数
dll.GenerateKeyEx(seed_ubyte_array, len(seed_list), 1, "description", key_ubyte_array, 1024, ctypes.pointer(key_array_size))
except:
print("dll调用异常")
key_list = key_ubyte_array[0:int(key_array_size.value)]
print(key_list) # 验证成功后,可进一步构造keyRequest报文