段刚-关于软件保护的若干忠告

来自《加密与解密》第17章
1. 尽量自行开发保护机制,不要依赖任何非自行开发的代码。
在不影响效率的情况下,可以用虚拟机保护软件处理需要保护的核心代码

2.不要依赖壳的保护。加密壳都能被解开或者脱壳。现在许多加密壳转向
虚拟机加密方向就是利用了这一特点。如果时间允许且有相应的技术能力,
可以设计自己的加壳压缩方法。如果利用现成的加壳工具,最好不要选择
流行的工具。保护强度与流行程度成反比。越是流行的工具,就越有可能
由于广泛深入地研究而有了通用的脱壳/解密办法。

3.增加对软件自身完整性的检查,包括对磁盘文件和内存映像的检查,以防止
有人修改程序,进而达到破解的目的。

4. 不要采用一目了然的名字来命令函数和文件,例如
IsLicenseVersion key.data 等。
所有与软件加密相关的字符串都不能以明文的形式直接存放在可执行文件中。
这些字符串最好是动态生成的。

5. 给用户的提示信息越少越好。因为任何蛛丝马迹都可能导致解密者直接
到达核心代码出。 例如,发现破解企图后,不要立即向用户发挥提示信息,
可以在系统的某个地方做一个记号,经过一段随机时间后使软件停止工作。
或者让软件“装作”正常工作,但在所处理的数据中加入一些”垃圾”.

6. 将注册码和安装时间记录在多个地方。

7. 检查注册信息和时间的代码越分散越好。
不要调用同一个函数 或者 判断同一个全局标志。
如果这样做,只要修改一个地方,其他地方就都破解了。

8. 不要通过一些众所周知的API来获取系统时间。可以通过读取关键系统
文件的修改时间来获取系统时间的相关信息。

9. 如果有可能,应采用连网检查注册码的方法,而且数据在网上传输时要加密。

10. 编程时在软件嵌入反跟踪代码,以提高安全性。

11. 在检查注册信息时插入大量无用的运算以误导解密者,并在检查出错误的注册信息后
加入延时机制。

12. 为软件保护增加一定的随机性。例如,除了在启动时检查注册码,还可以在
软件运行的某个时刻随机检查注册码。随机值还可以很好地防范那些模拟工具的解密
,例如软件狗模拟程序。

13. 如果采用注册码的保护方式,最好是一机一码,也就是注册码与机器特征相关。
这样,一台机器上的注册码就无法在另外一台机器上使用。
机器特征要从一些不可修改 的值上获取。也要防止API被hook

14. 如果试用版与正式版是独立的版本,可试用版本软件不具有某项功能,
则不要只禁用相关菜单,而要彻底删除相关代码,是编译后的程序中根本没有
相关的功能代码。

15. 如果软件中包含驱动程序,则最好将保护嗲吗放在驱动程序中。
驱动程序在访问系统资源时受到的限制比普通应用程序少得多,这也给软件设计者
提供了发挥的余地。

16. 如果采用keyfile的保护方式,则keyfile的体积不能太小。可以将其结构设计
得复杂一些,在程序的不同位置,对keyfie的不同部分进行复杂的运算和检查。

17. 自己设计的检查注册信息的算法不能过于简单,最好采用成熟的密码学算法。

设计加密方案时 应该多从解密的角度考虑,这样才能比较合理地运用各种技术。
当然,任何加密方案都无法达到完美的程序。
因此,在设计时,要考虑其他方面的平衡。

ssh密钥类型

ssh -Q key

ssh-ed25519
ssh-ed25519-cert-v01@openssh.com
sk-ssh-ed25519@openssh.com
sk-ssh-ed25519-cert-v01@openssh.com
ecdsa-sha2-nistp256
ecdsa-sha2-nistp256-cert-v01@openssh.com
ecdsa-sha2-nistp384
ecdsa-sha2-nistp384-cert-v01@openssh.com
ecdsa-sha2-nistp521
ecdsa-sha2-nistp521-cert-v01@openssh.com
sk-ecdsa-sha2-nistp256@openssh.com
sk-ecdsa-sha2-nistp256-cert-v01@openssh.com
ssh-dss
ssh-dss-cert-v01@openssh.com
ssh-rsa
ssh-rsa-cert-v01@openssh.com

查询的是key的类型,不是 签名算法的类型
ssh-rsa 都是 rsa的key,但是有三种hash 签名, 分别是 默认的 SHA-1, SHA-256 (rsa-sha2-256) 和 SHA-512 (rsa-sha2-512)
它们key的类型都是 rsa, 但签名算法 不一样。

推荐使用 Ed25519 和 RSA2048, RSA3072, RSA4096

ECDSA 据说可能存在 美国国家安全局的后门
DSA 在 数学上已经证明不可靠

rust jni对Java异常的处理

在CPP中,可以调用 ExceptionOccurred 和 ExceptionCheck 来检查处理异常

jthrowable flag = env->ExceptionOccurred();
{
   /* Handle exception here or free up any resources held
      Exception remains pending until control returns back
      to the Java code.
   */
   return;
}

jboolean flag = env->ExceptionCheck();
if (flag) {
   /* Handle exception here or free up any resources held
      Exception remains pending until control returns back
      to the Java code.
   */
   env->ExceptionClear();



   return;
}

在 rust 的 jni 这个crate里, 上述两个函数,被转换成了 exception_occurred 和 exception_check

在rust调用 call_static_method 或者 call_method, 都不会执行异常处理,需要你自己 处理它们

let result = je.call_static_method(cls, "renderTo", "(II[B)V", &[
    JValue::from(width),
    JValue::from(height),
    JValue::from(rgbs2),
])?;
je.exception_check()?;

在调用 可能抛出Java异常的JNI函数后,总是调用 ExceptionCheck 或 ExceptionOccurred 来检查下异常。
总是检查从 JNI函数返回的值。

rust ipv6 udp reverse echo server and client

server
监听在 udp4 和 6 的端口, buf大小为2048, 超过的会丢弃。
将接受到的 内容 反转后 发回给客户端

use std::net::UdpSocket;


fn main() -> std::io::Result<()> {
    //let socket = UdpSocket::bind("0.0.0.0:2000")?; // for UDP4
    let socket = UdpSocket::bind("[::]:2000")?;  // for UDP4/6
    let mut buf = [0; 2048];

    loop {
        // Receives a single datagram message on the socket.
        // If `buf` is too small to hold
        // the message, it will be cut off.
        let (amt, src) = socket.recv_from(&mut buf)?;

        // Redeclare `buf` as slice of the received data
	// and send data back to origin.
        let buf = &mut buf[..amt];

        //reverse
        buf.reverse();

        socket.send_to(buf, &src)?;
    }
}

client

use std::io::{self, BufRead};
use std::net::UdpSocket;
use std::env;
use std::str;

fn main() -> std::io::Result<()> {

    let args: Vec = env::args().collect();
    if args.len() < 2 {
        println!("Usage {} hostname", args[0]);
        std::process::exit(1);
    }
    let hostname = &args[1];

    let socket = UdpSocket::bind("[::]:0")?;  // for UDP4/6
    
    socket.connect(hostname.to_string() + &":2000").expect("couldn't connect to address");

    // from https://stackoverflow.com/questions/30186037/how-can-i-read-a-single-line-from-stdin
    let stdin = io::stdin();
    for line in stdin.lock().lines() {
        let line = line.unwrap();
        println!("Line read from stdin '{}'", line);
        if &line == "BYE" {
            break;
        }

        //socket.send_to(line.as_bytes(), hostname.to_string() + &":2000").expect("Error on send");
        socket.send(line.as_bytes()).expect("Error on send");

        let mut buf = [0; 2048];
        let (amt, _src) = socket.recv_from(&mut buf)?;

        let echo = str::from_utf8(&buf[..amt]).unwrap();
        println!("Echo {}", echo);
    }
    
    Ok(())
}

udp的connect 是可以直接接受 “hostname:port” 字符串形式的参数的, 它会自动进行DNS解析

可以互通的python client

import socket 
import sys 
import argparse 
 
data_payload = 2048 
 
def echo_client(server, port): 
    """ A simple echo client """ 
    # Create a UDP socket 
    sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 
 
    server_address = (server, port) 
    print ("Connecting to %s port %s" % server_address) 
    message = 'This is the message.  It will be repeated.' 
 
    try: 
 
        # Send data 
        message = "Test message. This will be echoed" 
        print ("Sending= %s" % message) 
        sent = sock.sendto(message.encode('utf-8'), server_address) 
 
        # Receive response 
        data, server = sock.recvfrom(data_payload) 
        print ("received= %s" % data) 
 
    finally: 
        print ("Closing connection to the server") 
        sock.close() 
 
if __name__ == '__main__': 
    parser = argparse.ArgumentParser(description='Socket Server Example') 
    parser.add_argument('--port', action="store", dest="port", type=int, required=True) 
    parser.add_argument('--server', action="store", dest="server", type=str, required=True)     
    given_args = parser.parse_args()  
    port = given_args.port
    server = given_args.server
    
    echo_client(server, port) 

服务端

import socket 
import sys 
import argparse 
 
host = '::' 
data_payload = 2048 

 
def echo_server(port): 
    """ A simple echo server """ 

    # Create a UDP socket 
    sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 
 
    # Bind the socket to the port 
    server_address = (host, port) 
    print ("Starting up echo server on %s port %s" % server_address) 
 
    sock.bind(server_address) 
 
    while True: 
        print ("Waiting to receive message from client") 
        data, address = sock.recvfrom(data_payload) 
     
        print ("received %s bytes from %s" % (len(data), address)) 
        print ("Data: %s" %data) 
     
        if data: 
            data = data[::-1]  # reverse
            sent = sock.sendto(data, address) 
            print ("sent %s bytes back to %s" % (sent, address)) 
 
 
if __name__ == '__main__': 

    parser = argparse.ArgumentParser(description='Socket Server Example')
    parser.add_argument('--port', action="store", dest="port", type=int, required=True) 
    given_args = parser.parse_args()  
    port = given_args.port 

    echo_server(port) 

nicegui生命周期事件

底层是FastAPI
这些事件来自于 fastapi

from datetime import datetime
from nicegui import app, ui

dt = datetime.now()

def handle_connection():
    global dt
    dt = datetime.now()

app.on_connect(handle_connection)

label = ui.label()
ui.timer(1, lambda: label.set_text(f'Last new connection: {dt:%H:%M:%S}'))

ui.run()

可以注册coroutine或者 function来处理这些事件:
app.on_startup NiceGUI app启动或者重启
app.on_shutdown 关闭或者重启
app.on_connect 当客户端连接上来时 (带一个可选参数 nicegui.Client)
app.on_disconnect 客户端 断开时 (nicegui.Client)
app.on_exception 发生异常时 (可选参数 exception)
当应用关闭时,所有正在执行的任务 都会自动取消

Apple Watch IMEI信息

2022年中国制造 Watch SE 45mm 型号 A2856

IMEI:  35469797 0465568
       35469797 0179617
EID:   89049032007108883000131977530047
2022年产 Watch SE 2代  型号 A2856
35469797 0465568
89049032007108883000131977530047

2020年 apple s4 44mm apple认证的翻新产品

35868009 0053312
89049032004008883000021439339504

2018年 S3

IMEI: 359447080303639
EID:  89049032003008883....

2021年生产 Watch SE 一代,型号A2356

35295111 1930317
89049032006008883000150793755265

2022年产 Watch SE 一代 型号 A2355

35294211 3249152
89049032006008883000150902912566

2022年生产 Watch S7 45mm 型号 A2478

350813505808446
89049032007008883000092511582246

2021年 中国制造 S7 45mm 型号 A2478

35822763 1920813
89049032007008883000089533994683

2022年 中国制造 A2858 S8 45mm

355387177587922
89049032007108883000131950747183

2019年 S4 44mm

358682090250328
89049032004008883000021408341214

2022年中国制造 A2858 S8 45mm

355387172600175
89049032007108883000134610222276

2021年中国制造 S7 45mm A2478

350031570934266
89049032007008883000086710808422

2022年 中国制造 S8 41mm A2857

350950934806334
89049032007108883000134604480264

2022年 中国制造 A2859 Ultra 49mm

356881867653613
89049032007108883000134614178518

2020年 S6 44mm

89049032003008882400000391479138
35296811 9284075
35292581 1928407

在Android中用Kotlin协程将callback转换为同步调用

lifecycleScope
viewModelScope

applicationScope

coroutineScope

GlobalScope

的区别

使用一个和应用生命周期一样长的自定义的CoroutineScope解决这个问题,自定义CoroutineScope的优势在于可以使用自定义的CoroutineExceptionHandler和自定义的thread pool(可以定制一个完全符合自己需求的CoroutineContext)。

我们使用applicationScope来命名这个自定义的CoroutineScope,并且它必须包含一个SupervisorJob(),这样任何子coroutine的执行失败不会影响到该scope下其他的子coroutines,也不会导致applicationScope本身的退出。

class MyApplication : Application() {
  // No need to cancel this scope as it'll be torn down with the process
  val applicationScope = CoroutineScope(SupervisorJob() + otherConfig)
}


我们不需要处理applicationScope的退出,因为它的生命周期和我们的应用一样长,所以我们也不需要持有SupervisorJob()的句柄。我们可以使用applicationScope来启动那些需要和我们App生命周期一样长的coroutines。
对于那些我们不希望被取消执行的coroutines,我们都可以使用applicationScope。
当你创建一个新的Repository实例的时候,就可以传入applicationScope。

suspend
withContext
Dispatchers.IO

调度程序:调度程序帮助协程决定必须在哪个线程上完成工作。
调度程序主要分为三种类型:IO、Default 和 Main。
IO调度程序用于完成网络和磁盘相关的工作。
Default用于执行 CPU 密集型工作。
Main是Android的UI线程。

还有一个在Android上可能不常用
Dispatchers.Unconfined
不限于任何特定线程的协程调度程序。它在当前调用帧中执行协程的初始延续,并让协程在相应挂起函数使用的任何线程中恢复,而无需强制执行任何特定的线程策略。
它不会改变线程。当它启动时,它在启动它的线程上运行。如果恢复,它将在恢复它的线程上运行。

假设有一个使用回调的库

Library.doSomething(object : Listener {
    override fun onSuccess(result: Result) {
    }
    override fun onError(throwable: Throwable) {
    }
})

用协程将它转换为同步函数

suspend fun doSomething(): Result {

    return suspendCoroutine { continuation ->
        Library.doSomething(object : Listener {
            override fun onSuccess(result: Result) {
                continuation.resume(result)
            }
            override fun onError(throwable: Throwable) {
                continuation.resumeWithException(throwable)
            }
        })
    }
}

步骤:
1. 创建一个suspend函数来返回 onSucess的 Result
2. 用 suspendCoroutine返回块 来返回
3. 在onSucess回调中,用 continuation.resume(result) 来恢复
4. 用 continuation.resumeWithException(throwable) 抛出错误

在launch块里使用 创建的suspend函数

launch {
    val result = doSomething()
}

有时候需要用到 suspendCancellableCoroutine, 而不是 suspendCoroutine
假设该库还支持取消任务

val id = Library.doSomething(object : Listener {
    override fun onSuccess(result: Result) {
    }
    override fun onError(throwable: Throwable) {
    }
})
可以用id来取消任务
Library.cancel(id)

转换成

suspend fun doSomething(): Result {
    return suspendCancellableCoroutine { continuation ->
        val id = Library.doSomething(object : Listener {
            override fun onSuccess(result: Result) {
                continuation.resume(result)
            }
            override fun onError(throwable: Throwable) {
                continuation.resumeWithException(throwable)
            }
        })
        continuation.invokeOnCancellation {
            Library.cancel(id)
        }
    }
}

withContext是一个suspend函数

withContext(Dispatchers.Default) {
    delay(2000)
    return@withContext 10
}

withContext不会创建新的协程,它只会改变现有协程的上下文,它是一个suspend函数,
而launch和async创建一个新的协程,它们不是suspend函数。

用async 启动协程,然后用await得到返回结果。

launch{}返回 一个Job并且 不携带任何结果值,
async{}返回一个实例Deferred t,该实例 有一个await()函数, 用来返回协程结果。

launch和async之间的另一个区别在于异常处理。
如果launch块内出现任何异常,如果我们没有处理它,应用程序就会崩溃。
但是,如果async块内出现任何异常,它会存储在结果中Deferred并且不会传递到其他任何地方,除非我们处理它,否则它将被悄悄丢弃。

将Camera API由回调改协程

suspend fun CameraManager.openCamera(cameraId: String): CameraDevice? =
    suspendCoroutine { cont ->
        val callback = object : CameraDevice.StateCallback() {
            override fun onOpened(camera: CameraDevice) {
                cont.resume(camera)
            }

            override fun onDisconnected(camera: CameraDevice) {
                cont.resume(null)
            }

            override fun onError(camera: CameraDevice, error: Int) {
                // assuming that we don't care about the error in this example
                cont.resume(null) 
            }
        }
        openCamera(cameraId, callback, null)
    }

生产者/消费者

val channel = Channel<ByteArray>
send()
receive()

挂起函数应该能够安全地从主线程调用

挂起函数应该是主线程安全的,这意味着,您可以安全地从主线程调用挂起函数。如果某个类在协程中执行长期运行的阻塞操作,那么该类负责使用 withContext 将执行操作移出主线程。这适用于应用中的所有类,无论其属于架构的哪个部分都不例外。

withContext(Dispatchers.IO) {
    ...
}

返回协程中产生的值

val result = runBlocking(CommonPool) {
         suspendingFunctionThatReturnsMyValue()
}

将会一直阻塞,直到结果可用

runBlocking 将运行一个新的协程,并阻塞当前线程,知道协程运行完成。
这个函数,不应该从 协程中调用。
它设计成 桥接 普通的 代码块 到 suspend风格的代码库(也就是协程写的代码库)。
一般用在 main函数, 或者 测试代码中

最好是同 withContext(Dispachedr.IO) 一起用。

因为阻塞主线程不是一个好注意。并且当有多个协程 同 runBlocking唤起 的协程,运行在同一个线程时,会将其他协程也阻塞住。

Kotlin has special ways to deal with it, such as the IO Dispatcher, which will run the code on its own, isolated thread so that it doesn’t disrupt your other coroutines.

北京移动服务密码修改

1. 您可以登录北京移动门户网站办理。当您的客服密码遗忘或丢失时,全球通、动感地带、神州行客户可以办理密码重置业务,相关手续如下:
(1)通话记录验证:验证临时校验码、提供近90天内3天以前的5条通话或短信记录,其中至少有3条是主叫通话记录。验证通过后,系统会提示您自行填写6位或8位密码,填写完成后,系统会为您下发密码重置成功的短信,短信内容包含已重置的新密码。
(2)身份验证:个人客户可验证入网时登记的机主姓名和证件号码办理;单位客户可验证入网时登记的单位名称和单位证件号码,同时还需填写使用人或责任人证件号码办理。验证通过后,系统将通过短信为您发送一个新的6位密码,你可自行修改。
—————————-
2. 短信
(1)密码修改
全球通、动感地带、神州行畅听卡、5元卡客户:发送“MMXG空格原密码空格新密码”至10086修改客服密码。
神州行升级版标准卡、家园卡客户:发送“MMBG空格原密码空格新密码”至10086修改客服密码。
(2)密码重置
北京移动个人客户/单位客户,均可发送“MMCZ空格证件号码空格新密码空格新密码”至10086重新设置客服密码。单位客户办理时,短信指令中的证件号码是客户入网时使用的注册登记证照上的号码,您发送短信后还需按短信提示回复使用人证件号码进行验证。
——————
3. 10086自动台
您可以本机拨打10086按提示修改客服密码。已办理实名制认证的全球通、动感地带、神州行升级版标准卡、畅听卡、家园卡、5元卡客户,还可以本机拨打10086自动台,按提示成功验证身份证后,10086会将系统自动生成的新密码发送到您的本机号码上。
———————

办理限制:
1、每月最后一天19点后无法办理。(不含10086自动台办理密码修改)
2、您通过网站、短信、10086自动台申请密码重置时,同一手机号码在30天内最多可以成功办理2次,通过营业厅办理密码重置没有次数限制,但30天内您通过各渠道办理密码重置的次数将累加计算。
3、您在30天内(从操作当天起向前推算30天)最多只能通过网站渠道办理5次密码重置,达到5次后,将无法再通过网站办理。若您已有2次成功办理的记录,则30天内无法再通过网站办理,此时将不再执行最多5次的规定。
4、目前仅限北京移动全球通以及已实名制的动感地带、神州行升级版标准卡、畅听卡、家园卡、5元卡、无线座机卡客户,在营业厅办理密码修改。

selenium模拟手机浏览器访问网页

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
  

chrome_options = Options()
  

chrome_options.add_argument('--headless=new')
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')

mobile_emulation = {
    "deviceMetrics": { "width": 720, "height": 1280, "pixelRatio": 1 },
    "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 5 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Mobile Safari/537.36" }

chrome_options.add_experimental_option("mobileEmulation", mobile_emulation)    
      

browser = webdriver.Chrome(options=chrome_options)
  

browser.get('https://www.baidu.com/')
  

print(browser.title)
browser.get_screenshot_as_file("capture.png")


browser.close()

还可跟页面交互

CCID接口规范

控制通道(Control pipe)
GET_DESCRIPTOR 和 SET_CONFIGURATION 等标准请求 通过此通道发送。

命令在默认通道上发动,报告信息也在默认通道上返还给主机。
———————–
中断通道处理异步事件
NotifySlotChange (卡插入,移除)
HardwareError (硬件错误)
————————
Bulk-in, Bulk-out通道

CCID命令在BULK_OUT端点 发送
每个命令都会有一个响应。有些响应可以立即得到。
有些响应 需要通过 BULK-IN 端口返回。

发送给指定卡槽的所有命令,都必须是同步发送的。
一个卡槽同一时刻,只能接受一个命令。
如果一个卡槽准备接受一个命令,那么就应该认为它是空闲的。

如果每条命令 都是给不同的空闲卡槽的,那么主机最多可以同时发送 bMaxCCIDBusySlots 条命令给 CCID
主机需要记住哪些slot是繁忙的。
当激活的卡槽数量达到 bMaxCCIDBusySlots时, CCID只能在 控制通道,接受 通用请求 或者 特定类的请求,去 ABORT一个slot

当CCID成功接收到一条给 繁忙卡槽的命令时,它应该报一个 CMD_SLOT_BUSY 错误

为了实现对每条命令的跟踪,主机对 发出的命令,设置一个 唯一的 bSeq 命令标识符。
CCID发回响应时,也要带上同样的 bSeq 标识符,表明 这条命令已经处理完毕。
驱动程序不会发送新的命令给 slot, 直到前面发送命令的 ending response响应被收到。
如果命令有错误或者超时, 那么 会发送 abandon/abort 类特定的命令 给 控制端点。

对于一个 BULK-OUT 命令 消息,可能会产生多个 BULK-IN 响应消息。
For example, the CCID can send a BULK-IN message with a Time Extension status to notify
the host that the ICC has requested more time to process the ICC command and, after a
delay, follow this with a second BULK-IN message with the ICC’s response to the
command. When this happens, both BULK-IN messages have the same bSeq value.

如果Bulk-In 消息的大小 是 MaxPacketSize 的倍数,那么应该在它后面还发一个 Zero Length Packet (ZLP)
ZLP包可以让 CCID设备驱动更有效率, 是一种最佳实践。

———————————————
CCID 发送 RDR_to_PC_NotifySlotChange 到主机, 通知驱动程序, 新插入了卡
收到驱动来的 PC_to_RDR_IccPowerOn 消息, CCID重新激活 IC卡

PC_to_RDR_IccPowerOn         RDR_to_PC_DataBlock
PC_to_RDR_IccPowerOff        RDR_to_PC_SlotStatus
PC_to_RDR_GetSlotStatus      RDR_to_PC_SlotStatus
PC_to_RDR_XfrBlock           RDR_to_PC_DataBlock
PC_to_RDR_GetParameters      RDR_to_PC_Parameters
PC_to_RDR_ResetParameters    RDR_to_PC_Parameters
PC_to_RDR_SetParameters      RDR_to_PC_Parameters
PC_to_RDR_Escape             RDR_to_PC_Escape
PC_to_RDR_IccClock           RDR_to_PC_SlotStatus
PC_to_RDR_T0APDU             RDR_to_PC_SlotStatus
PC_to_RDR_Secure             RDR_to_PC_DataBlock
PC_to_RDR_Mechanical         RDR_to_PC_SlotStatus
PC_to_RDR_Abort              RDR_to_PC_SlotStatus
PC_to_RDR_SetDataRateAndClockFrequency              RDR_to_PC_DataRateAndClockFrequency

CCID_PC_To_RDR Commands

CodeCommandDescriptionEquivalent PC/SC function
62PC_To_RDR_IccPowerOnPower up the card in the slot, and return its ATRSCardConnect
63PC_To_RDR_IccPowerOffPower down the card in the slotSCardDisconnect
65PC_To_RDR_GetSlotStatusRetrieve the slot’s current status and protocol dataSCardStatus
6BPC_To_RDR_EscapeSend a direct command to the deviceSCardControl
6FPC_To_RDR_XfrBlockSend a C-APDU to the card (and return its R-APDU)SCardTransmit

CCID_RDR_To_PC Responses

CodeCommandDescriptionIn response to
80RDR_To_PC_DataBlockR-APDU returned by the card
ATR of the card
PC_To_RDR_XfrBlock (success)
PC_To_RDR_IccPowerOn (success)
81RDR_To_PC_SlotStatusStatus of the slotPC_To_RDR_GetSlotStatus
PC_To_RDR_IccPowerOff
PC_To_RDR_XfrBlock (failure)
PC_To_RDR_IccPowerOn (failure)
83RDR_To_PC_EscapeSend a direct command to the devicePC_To_RDR_Escape