工作经验:用于UDS $27安全访问的DLL文件开发和调用方法

0 前言

0.1 学习思路

这次学习开发DLL的经历,可分解为以下子任务:

  1. 学习ECU的安全算法逻辑:项目中Level01(2/4字节种子密钥)是简单位移、异或操作,Level03使用了AES-128算法,并进行了非对称加密(16字节种子密钥);
  2. 确认DLL函数的接口形式:需要结合自己使用的测试工具、调用方法、调用环境的编译位数等实际需求;
  3. 通过代码实现安全算法;
  4. 开发DLL的编译器使用:配置器设置、编译头、Library和DLL共同封装等;
  5. 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种接口形式,所以开发时应该优先考虑如下图的接口形式。
CANoe Help提供的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 编译说明

  1. 在配置管理器,选择正确的平台。32位环境调用,选择WIN32;64位环境调用,选择x64。
  2. 如需使用外部算法文件,则按照如下操作,将引用的算法文件同DLL一起导出。
    链接头文件:解决方案管理器 → 解决方案(右键)→ 属性 → C/C++ → 常规 → 附加包含目录 → 编辑,选择外部文件"…mbedtls-develpment\include"文件夹。
    链接源文件:解决方案管理器 → 解决方案(右键)→ 属性 → 链接器 → 常规 → 附加库目录 → 编辑,选择外部文件"…mbedtls-develpment\library"文件夹。
    注意:点击“应用”后,再确认。
  3. 不使用预编译头文件“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报文
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值