Virtual Card and Simulator manual
使用说明
Virtual card软件仿真是指在pc上使用vc等开发工具,配合Snooper脚本工具,能够快速进行功能调试一种工程搭建方法。
使用者通过一些简单设置,即可将同一套代码共享在两个工程下(例如一个vc工程,一个keil工程),这样代码的逻辑完全一致,绝大多数问题都可以在vc环境下进行调试,效率比在keil下调试能提高几倍到几十倍,并可实现一些在真实环境下很难复现的场景。
本手册中涉及IO_Algo.dll(32位),IO_Algo64.dll(64位)和vcardio.h(也可能是其他名字,如testos_io.h),以及根据dll不同版本,配套的其他头文件(如当前提供了rsa算法)。
本手册中涉及的软件是Snooper taoism 5软件0.0.6.8版及以上版本。
Virtual Card and Simulator manual
拷贝IO_Algo.dll或IO_Algo64.dll和头文件到工程目录下,并加入工程
一个极简单的cos代码,前述Testos_Main.cpp内容
将代码实现编写好后,将IO_Algo.dll或IO_Algo64.dll拷贝到exe文件夹,运行程序
此处使用vs2022为示例



运行结果如下







|
// TODO: 在此添加额外的初始化代码 { // -------- 虚拟卡修改处 001 -- 调用mapfile,这个是可选的,本示例中用来初始化一个flash空间,并加载对应的dll函数 extern void mapfile( void ); mapfile();
extern pdaemon daemon;
// -------- 虚拟卡修改处 002 -- 9999 是端口号,启动收发数据线程 DWORD id; CreateThread( NULL, 0, daemon, (LPVOID)9999, 0, &id );
SetTimer( 9999, 1000, 0 ); }
|

编辑代码
|
void CvcardDlg::OnTimer(UINT_PTR nIDEvent) { // TODO: 在此添加消息处理程序代码和/或调用默认值
CDialogEx::OnTimer(nIDEvent); KillTimer( nIDEvent ); // main函数也可以是其他函数名,这个main是虚拟卡的主要入口函数 extern int main(void); main(); }
|

|
void CvcardDlg::OnClose() { // TODO: 在此添加消息处理程序代码和/或调用默认值
// -------- 虚拟卡修改处 004 -- 关闭时直接杀死进程,因为收发数据线程没有写退出机制 TerminateProcess( GetCurrentProcess(), 0 ); }
|
至此虚拟卡的mfc部分修改完成。
|
已启动重新生成... 1>------ 已启动全部重新生成: 项目: vcard, 配置: Debug x64 ------ 1>pch.cpp 1>vcard.cpp 1>vcardDlg.cpp 1>正在生成代码... 1>vcardDlg.obj : error LNK2019: 无法解析的外部符号 main,函数 "public: void __cdecl CvcardDlg::OnTimer(unsigned __int64)" (?OnTimer@CvcardDlg@@QEAAX_K@Z) 中引用了该符号 |




再次编译
fatal error C1010: 在查找预编译头时遇到意外的文件结尾。是否忘记了向源中添加“#include "pch.h"”?
可以按需设置是否使用预编译头,此处设置不使用

再次编译,按需处理相关的错误或警告,比如下面的警告
error C4996: 'wcscat': This function or variable may be unsafe. Consider using wcscat_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.

直到编译成功
1>------ 已启动生成: 项目: vcard, 配置: Debug x64 ------
1>pch.cpp
1>Testos_Main.cpp
1>_WIN32_WINNT not defined. Defaulting to _WIN32_WINNT_MAXVER (see WinSDKVer.h)
1>vcard.cpp
1>vcardDlg.cpp
1>正在生成代码...
1>vcard.vcxproj -> C:\Users\ vcard\x64\Debug\vcard.exe
1>'pwsh.exe' 不是内部或外部命令,也不是可运行的程序
1>或批处理文件。
========== 版本: 1 成功,0 失败,0 更新,0 跳过 ==========
========== 占用时间 00:07.263 ==========
智能卡逻辑代码一般由发送atr或ats和一个无限循环组成,下面使用atr来简单描述,虚拟卡与真实卡只是io收发不同,其他的逻辑是相同的。
此代码演示了虚拟一个ram的存储空间,3条apdu指令。
|
#ifdef _WIN32 // window必须的一些头文件包含进来------------------------------------ #include <afxwin.h> // MFC 核心组件和标准组件 #include <afxext.h> // MFC 扩展 #include <afxdisp.h> // MFC 自动化类 #ifndef _AFX_NO_OLE_SUPPORT #include <afxdtctl.h> // MFC 对 Internet Explorer 4 公共控件的支持 #endif #ifndef _AFX_NO_AFXCMN_SUPPORT #include <afxcmn.h> // MFC 对 Windows 公共控件的支持 #endif // _AFX_NO_AFXCMN_SUPPORT
#include <afxcontrolbars.h> // 功能区和控件条的 MFC 支持 // window必须的一些头文件包含进来------------------------------------
// 软仿真必须的一些头文件包含进来------------------------------------
#include "..\\include_export\\algo_data_type.h" #include "..\\include_export\\algo_rsa.h" #include "..\\include_export\\testos_io.h" // 软仿真必须的一些头文件包含进来------------------------------------ #else // 真实卡必须的一些头文件包含进来------------------------------------ // 真实卡必须的一些头文件包含进来------------------------------------ #endif
#ifdef _WIN32 pGenerate_RSA_STD_Keypair Generate_RSA_STD_Keypair; pGenerate_RSA_CRT_Keypair Generate_RSA_CRT_Keypair; pRSA_Encrypt RSA_Encrypt ; pRSA_DecryptSTD RSA_DecryptSTD ; pRSA_DecryptCRT RSA_DecryptCRT ; pCalc_RSA_STD_by_PQE Calc_RSA_STD_by_PQE ; pCalc_RSA_CRT_by_NDE Calc_RSA_CRT_by_NDE ; pCalc_RSA_D2_by_PQED Calc_RSA_D2_by_PQED ;
pdaemon daemon; ph2s h2s; preadio readio; pwriteio writeio; psendstr sendstr;
void mapfile( void ) { TCHAR path[ 0x200 ]; GetModuleFileName( NULL, path, sizeof( path ) ); int len = (int)_tcslen( path ); while( '\\' != path[ len ] ) { path[ len-- ] = 0; } _tcscat( path, _T( "TestOs.bin" ) );
if( !PathFileExists( path ) ) { FILE *fp; fp = _tfopen( path, _T( "wb" ) ); unsigned char *buf = new unsigned char[ 1024 * 512 ]; memset( buf, 0xff, 1024 * 512 ); fwrite( buf, 1, 1024 * 512, fp ); fclose( fp ); delete []buf; } HANDLE file = CreateFile( path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
if( INVALID_HANDLE_VALUE == file ) { AfxMessageBox( _T( "create file error" ) ); return; }
HANDLE mapping = CreateFileMapping( file, NULL, PAGE_READWRITE, 0, 1024 * 512, NULL ); if( !mapping ) { AfxMessageBox( _T( "create mapping error" ) ); return; }
void *flash = MapViewOfFileEx( mapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0, (LPVOID)0x10000000 );
{ // load rsa algo GetModuleFileName( NULL, path, sizeof( path ) ); int len = (int)_tcslen( path ); while( '\\' != path[ len ] ) { path[ len-- ] = 0; } #ifdef _WIN64 _tcscat( path, _T( "IO_Algo64.dll" ) ); #else _tcscat( path, _T( "IO_Algo.dll" ) ); #endif
HMODULE g____dll_module;
g____dll_module = LoadLibrary( path );
if( NULL != g____dll_module ) { Generate_RSA_STD_Keypair = ( pGenerate_RSA_STD_Keypair )GetProcAddress( g____dll_module, "Generate_RSA_STD_Keypair" ); Generate_RSA_CRT_Keypair = ( pGenerate_RSA_CRT_Keypair )GetProcAddress( g____dll_module, "Generate_RSA_CRT_Keypair" ); RSA_Encrypt = ( pRSA_Encrypt )GetProcAddress( g____dll_module, "RSA_Encrypt" ); RSA_DecryptSTD = ( pRSA_DecryptSTD )GetProcAddress( g____dll_module, "RSA_DecryptSTD" ); RSA_DecryptCRT = ( pRSA_DecryptCRT )GetProcAddress( g____dll_module, "RSA_DecryptCRT" ); Calc_RSA_STD_by_PQE = ( pCalc_RSA_STD_by_PQE )GetProcAddress( g____dll_module, "Calc_RSA_STD_by_PQE" ); Calc_RSA_CRT_by_NDE = ( pCalc_RSA_CRT_by_NDE )GetProcAddress( g____dll_module, "Calc_RSA_CRT_by_NDE" ); Calc_RSA_D2_by_PQED = ( pCalc_RSA_D2_by_PQED )GetProcAddress( g____dll_module, "Calc_RSA_D2_by_PQED" );
daemon = ( pdaemon )GetProcAddress( g____dll_module, "daemon" ); h2s = ( ph2s )GetProcAddress( g____dll_module, "h2s" ); readio = ( preadio )GetProcAddress( g____dll_module, "readio" ); writeio = ( pwriteio )GetProcAddress( g____dll_module, "writeio" ); sendstr = ( psendstr )GetProcAddress( g____dll_module, "sendstr" ); } else { #ifdef _WIN64 AfxMessageBox( _T( "加载软仿真的通讯库 IO_Algo64.dll 失败" ) ); #else AfxMessageBox( _T( "加载软仿真的通讯库 IO_Algo.dll 失败" ) ); #endif TerminateProcess( GetCurrentProcess(), 0 ); return; } }
return; } #endif
int atr_count = 0; unsigned char in_buf[ 0x200 ]; unsigned char out_buf[ 0x200 ];
#define INS_GET_CHALLENGE_____84 (0x84) #define INS_INT_AUTH__________88 (0x88) #define INS_SLEEP_____________33 (0x33)
u8 RecvByte( void ) { #ifdef WIN32 return readio(); #else // device code here return 0; #endif }
void SendByte( u8 c ) { #ifdef WIN32 writeio( c ); #else // device code here #endif }
void Recv( unsigned char *buf, int len ) { int i; for( i = 0; i < len; i++ ) { buf[ i ] = RecvByte(); } }
void SendIns( u8 ins ) { SendByte( ins ); }
void SendSw( u16 sw ) { SendByte( (sw >> 8) & 0xff ); SendByte( sw & 0xff ); }
void Wrong6700_Length( void ) { SendSw( 0x6700 ); }
void Wrong6E00_cla( void ) { SendSw( 0x6e00 ); }
void Wrong6D00_Ins( void ) { SendSw( 0x6d00 ); }
void Wrong6A86_P1P2( void ) { SendSw( 0x6a86 ); }
void RecvApduHead( void ) { Recv( in_buf, 5 ); }
void SetIncommingAndReceive( u8 ins, u8 len ) { SendIns( ins ); Recv( in_buf + 5, len ); }
void cmd_InitAuth( void ) { if( 0x08 != in_buf[ 4 ] ) { Wrong6700_Length(); return; }
SetIncommingAndReceive( in_buf[ 1 ], in_buf[ 4 ] );
SendSw( 0x9000 ); }
void cmd_GetChallenge( void ) { u16 len = in_buf[ 4 ]; if( 0x00 == len ) { len = 0x100; }
SendIns( in_buf[ 1 ] );
int i; for( i = 0; i < len; i++ ) { SendByte( 0x77 ); }
SendSw( 0x9000 ); }
void cmd_Sleep( void ) { if( 0x02 != in_buf[ 4 ] ) { Wrong6700_Length(); return; }
SetIncommingAndReceive( in_buf[ 1 ], in_buf[ 4 ] );
unsigned short t = ((in_buf[ 5 ] << 8) | in_buf[ 6 ]);
#ifdef _WIN32 Sleep( t ); #endif
SendSw( 0x9000 ); }
void main_init( void ) { atr_count++; unsigned char t[ 50 ]; t[ 0 ] = 0x3b; t[ 1 ] = 0x04; t[ 2 ] = (unsigned char)( atr_count >> 24 ); t[ 3 ] = (unsigned char)( atr_count >> 16 ); t[ 4 ] = (unsigned char)( atr_count >> 8 ); t[ 5 ] = (unsigned char)( atr_count >> 0 );
#ifdef WIN32 char e[ 0x40 ]; h2s( t, 6, e ); sendstr( e ); #else // device code here #endif }
void main_loop( void ) { do { RecvApduHead(); u8 ins = in_buf[ 1 ]; switch( ins ) { case INS_INT_AUTH__________88: cmd_InitAuth(); break; case INS_GET_CHALLENGE_____84: cmd_GetChallenge(); break; case INS_SLEEP_____________33: cmd_Sleep(); break; default: Wrong6D00_Ins(); break; } } while ( 1 ); }
void main( void ) { #ifdef WIN32 BEGIN:
try { main_loop(); } catch( ... ) { // send atr,这里也可以调用真正的组织atr函数 main_init();
goto BEGIN; } #else main_init(); main_loop(); #endif }
|


虚拟读卡器的使用方法,请参考相关文档。

可以看到有vreader.exe虚拟出来的读卡器
//--JAVACOS Virtual Contactless Reader 1
//Protocol T0
//Reset--Card Status:The card has been reset and specific communication protocols have been established.
//reset
3B0400000002
clear
reset
0084000008
0088000006 112233445566
//Reset--Card Status:The card has been reset and specific communication protocols have been established.
//reset
3B0400000005
//Get Challenge (defined by ISO/IEC 7816-4)
0084000008
7777777777777777
9000
//Internal Authenticate (defined by ISO/IEC 7816-4)
0088000006 112233445566
6700