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. 1

目    录... 2

建立一个vc对话框工程... 3

拷贝IO_Algo.dllIO_Algo64.dll和头文件到工程目录下,并加入工程... 7

vcarddlg.cppinclude 相关的头文件... 10

修改OnInitDialog函数,添加如下代码... 10

增加OnTimer函数... 11

增加OnClose函数... 12

编译代码,提示缺少main函数... 13

将真正的工程拷贝到工程文件夹下,并加入工程... 14

编写智能卡逻辑代码... 18

一个极简单的cos代码,前述Testos_Main.cpp内容... 18

将代码实现编写好后,将IO_Algo.dllIO_Algo64.dll拷贝到exe文件夹,运行程序... 27

打开vreader.exe,虚拟一个读卡器,连接虚拟卡。... 28

运行Snooper 或其他智能卡程序... 29

连接成功,效果如下... 29

发送一些指令... 30

读写flash定时器异常现象都可在vc下实现。... 30

 


 

建立一个vc对话框工程

此处使用vs2022为示例

 

 

 

运行结果如下

 

拷贝IO_Algo.dllIO_Algo64.dll和头文件到工程目录下,并加入工程

 

 

vcarddlg.cppinclude 相关的头文件

 

修改OnInitDialog函数,添加如下代码

    // 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 );

    }

 

 

 

增加OnTimer函数

编辑代码

void CvcardDlg::OnTimer(UINT_PTR nIDEvent)

{

    // TODO: 在此添加消息处理程序代码和/或调用默认值

 

    CDialogEx::OnTimer(nIDEvent);

    KillTimer( nIDEvent );

    // main函数也可以是其他函数名,这个main是虚拟卡的主要入口函数

    extern int main(void);

    main();

}

 

 

增加OnClose函数

void CvcardDlg::OnClose()

{

    // TODO: 在此添加消息处理程序代码和/或调用默认值

 

    // -------- 虚拟卡修改处 004 -- 关闭时直接杀死进程,因为收发数据线程没有写退出机制

    TerminateProcess( GetCurrentProcess(), 0 );

}

 

至此虚拟卡的mfc部分修改完成。

编译代码,提示缺少main函数

已启动重新生成...

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 ==========

 

 

编写智能卡逻辑代码

智能卡逻辑代码一般由发送atrats和一个无限循环组成,下面使用atr来简单描述,虚拟卡与真实卡只是io收发不同,其他的逻辑是相同的。

 

一个极简单的cos代码,前述Testos_Main.cpp内容

此代码演示了虚拟一个ram的存储空间,3apdu指令。

 

#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

}

 

 

将代码实现编写好后,将IO_Algo.dllIO_Algo64.dll拷贝到exe文件夹,运行程序

打开vreader.exe,虚拟一个读卡器,连接虚拟卡。

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

运行Snooper 或其他智能卡程序

 

可以看到有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

 

读写flash定时器异常现象都可在vc下实现。