【ProVerif学习笔记】5:理解验证后的输出

ProVerif的输出比较长,格式如下:
在这里插入图片描述

其中 [ E q u a t i o n s ] [Equations] [Equations]给出了涉及的等式的内部表示。 [ P r o c e s s ] [Process] [Process]给出了将所有的进程宏展开之后的进程,在进程里面会看到一些标号为 { n } \{n\} {n}的地方,这只是为了方便引用。 [ Q u e r y ] [Query] [Query]部分就是开始评估用户给出的性质查询(对应输入文件中的query),也就是试图证明违反性质的状态是不可达的,然后在 [ G o a l ] [Goal] [Goal]这一行就会显示目标性质是可达的还是不可达的。接着就会给出推导的 [ A t t a c k   d e r i v a t i o n ] [Attack \ derivation] [Attack derivation]和构建的 [ A t t a c k   t r a c e ] [Attack \ trace] [Attack trace]。最后会给出验证结果的摘要,如果要裁剪输出到只剩验证结果,可以直接用proverif filename.pv | grep "RES"

1 结果

首先要区分 A t t a c k   D e r i v a t i o n Attack \ Derivation Attack Derivation A t t a c k   T r a c e Attack \ Trace Attack Trace这两个概念。前者是解释攻击者为了破坏性质而必须采取的行动,因为ProVerif内部表示中使用了抽象,所以可能会出现一些实际根本没法出现的情况,比如要求某个动作重复多次,但是那个动作实际上从不重复执行。后者则是和书写的进程紧密相关的,是能够在输入文件所给出的进程中执行的trace。

ProVerif可以输出三种结果:

  • R E S U L T   [ Q u e r y ]   i s   t r u e RESULT \ [Query] \ is \ true RESULT [Query] is true:性质被证实了,即没有攻击发生,所以不会输出 [ A t t a c k   d e r i v a t i o n ] [Attack \ derivation] [Attack derivation] [ A t t a c k   t r a c e ] [Attack \ trace] [Attack trace]
  • R E S U L T   [ Q u e r y ]   i s   f a l s e RESULT \ [Query] \ is \ false RESULT [Query] is false:性质被证伪了,两个都会输出,但是主要关注真正靠谱的 [ A t t a c k   t r a c e ] [Attack \ trace] [Attack trace]就行。
  • R E S U L T   [ Q u e r y ]   c a n n o t   b e   p r o v e d RESULT \ [Query] \ cannot \ be \ proved RESULT [Query] cannot be proved:性质证明不了,这种时候可以更关注 [ A t t a c k   d e r i v a t i o n ] [Attack \ derivation] [Attack derivation],观察它说的攻击来源有时候可以手动构建一个攻击trace。

2 示例:握手协议中的输出

下载的文档下有握手协议的完整例子,执行第一版握手协议,即:

proverif docs/ex_handshake_annotated.pv

看到最下面的验证结果总结:

--------------------------------------------------------------
Verification summary:

Query not attacker(s[]) is false.

Query event(termClient(x_2,y_1)) ==> event(acceptsServer(x_2,y_1)) is false.

Query inj-event(termServer(x_2)) ==> inj-event(acceptsClient(x_2)) is true.

--------------------------------------------------------------

这说明消息 s s s会被攻击,从 B B B A A A的认证性质可以被破坏,从 A A A B B B的认证性质是保持的。

接下来尝试重构这个攻击,观察 q u e r y   a t t a c k e r ( s ) . \bold{query} \ attacker(s). query attacker(s).的输出,如下:

Process 0 (that is, the initial process):
{1}new skA: skey;
{2}new skB: sskey;
{3}let pkA: pkey = pk(skA) in
{4}out(c, pkA);
{5}let pkB: spkey = spk(skB) in
{6}out(c, pkB);
(
    {7}!
    {8}let skA_1: skey = skA in
    {9}out(c, pkA);
    {10}in(c, x: bitstring);
    {11}let y: bitstring = adec(x,skA_1) in
    {12}let (=pkB,k: key) = checksign(y,pkB) in
    {13}event acceptsClient(k);
    {14}out(c, senc(s,k));
    {15}event termClient(k,pkA)
) | (
    {16}!
    {17}let skB_1: sskey = skB in
    {18}in(c, pkX: pkey);
    {19}new k_1: key;
    {20}event acceptsServer(k_1,pkX);
    {21}out(c, aenc(sign((pkB,k_1),skB_1),pkX));
    {22}in(c, x_1: bitstring);
    {23}let z: bitstring = sdec(x_1,k_1) in
    {24}if (pkX = pkA) then
    {25}event termServer(k_1)
)

--  Process 1 (that is, process 0, with let moved downwards):
{1}new skA: skey;
{2}new skB: sskey;
{3}let pkA: pkey = pk(skA) in
{4}out(c, pkA);
{5}let pkB: spkey = spk(skB) in
{6}out(c, pkB);
(
    {7}!
    {9}out(c, pkA);
    {10}in(c, x: bitstring);
    {8}let skA_1: skey = skA in
    {11}let y: bitstring = adec(x,skA_1) in
    {12}let (=pkB,k: key) = checksign(y,pkB) in
    {13}event acceptsClient(k);
    {14}out(c, senc(s,k));
    {15}event termClient(k,pkA)
) | (
    {16}!
    {18}in(c, pkX: pkey);
    {19}new k_1: key;
    {20}event acceptsServer(k_1,pkX);
    {17}let skB_1: sskey = skB in
    {21}out(c, aenc(sign((pkB,k_1),skB_1),pkX));
    {22}in(c, x_1: bitstring);
    {23}let z: bitstring = sdec(x_1,k_1) in
    {24}if (pkX = pkA) then
    {25}event termServer(k_1)
)

-- Query not attacker(s[]) in process 1.
Translating the process into Horn clauses...
Completing...
Starting query not attacker(s[])
goal reachable: attacker(s[])

Derivation:
Abbreviations:
k_2 = k_1[pkX = pk(sk),!1 = @sid]

1. The attacker has some term sk.
attacker(sk).

2. By 1, the attacker may know sk.
Using the function pk the attacker may obtain pk(sk).
attacker(pk(sk)).

3. The message pk(sk) that the attacker may have by 2 may be received at input {18}.
So the message aenc(sign((spk(skB[]),k_2),skB[]),pk(sk)) may be sent to the attacker at output {21}.
attacker(aenc(sign((spk(skB[]),k_2),skB[]),pk(sk))).

4. By 3, the attacker may know aenc(sign((spk(skB[]),k_2),skB[]),pk(sk)).
By 1, the attacker may know sk.
Using the function adec the attacker may obtain sign((spk(skB[]),k_2),skB[]).
attacker(sign((spk(skB[]),k_2),skB[])).

5. By 4, the attacker may know sign((spk(skB[]),k_2),skB[]).
Using the function getmess the attacker may obtain (spk(skB[]),k_2).
attacker((spk(skB[]),k_2)).

6. By 5, the attacker may know (spk(skB[]),k_2).
Using the function 2-proj-2-tuple the attacker may obtain k_2.
attacker(k_2).

7. The message pk(skA[]) may be sent to the attacker at output {4}.
attacker(pk(skA[])).

8. By 4, the attacker may know sign((spk(skB[]),k_2),skB[]).
By 7, the attacker may know pk(skA[]).
Using the function aenc the attacker may obtain aenc(sign((spk(skB[]),k_2),skB[]),pk(skA[])).
attacker(aenc(sign((spk(skB[]),k_2),skB[]),pk(skA[]))).

9. The message aenc(sign((spk(skB[]),k_2),skB[]),pk(skA[])) that the attacker may have by 8 may be received at input {10}.
So the message senc(s[],k_2) may be sent to the attacker at output {14}.
attacker(senc(s[],k_2)).

10. By 9, the attacker may know senc(s[],k_2).
By 6, the attacker may know k_2.
Using the function sdec the attacker may obtain s[].
attacker(s[]).

11. By 10, attacker(s[]).
The goal is reached, represented in the following fact:
attacker(s[]).


A more detailed output of the traces is available with
  set traceDisplay = long.

new skA: skey creating skA_2 at {1}

new skB: sskey creating skB_2 at {2}

out(c, ~M) with ~M = pk(skA_2) at {4}

out(c, ~M_1) with ~M_1 = spk(skB_2) at {6}

out(c, ~M_2) with ~M_2 = pk(skA_2) at {9} in copy a

in(c, pk(a_1)) at {18} in copy a_2

new k_1: key creating k_2 at {19} in copy a_2

event acceptsServer(k_2,pk(a_1)) at {20} in copy a_2

out(c, ~M_3) with ~M_3 = aenc(sign((spk(skB_2),k_2),skB_2),pk(a_1)) at {21} in copy a_2

in(c, aenc(adec(~M_3,a_1),~M)) with aenc(adec(~M_3,a_1),~M) = aenc(sign((spk(skB_2),k_2),skB_2),pk(skA_2)) at {10} in copy a

event acceptsClient(k_2) at {13} in copy a

out(c, ~M_4) with ~M_4 = senc(s,k_2) at {14} in copy a

event termClient(k_2,pk(skA_2)) at {15} in copy a

The attacker has the message sdec(~M_4,2-proj-2-tuple(getmess(adec(~M_3,a_1)))) = s.
A trace has been found.
RESULT not attacker(s[]) is false.

可以分为三部分:

  1. Abbreviations:A more detailed...描述了导致这个攻击的推导过程。
  2. A more detailed...A trace has been found.描述了攻击trace。
  3. 最后的RESULT行给出了验证结果。

A b b r e v i a t i o n Abbreviation Abbreviation的一开始是给一些项设置一些简短的别名(内部表示)。然后接下来按数字编号的步骤就是具体的 D e r i v a t i o n Derivation Derivation了,每个步骤对应进程和攻击者的一个操作,它包含英语描述和一个形式化的描述,比如 a t t a c k e r   ( M ) \bold{attacker} \ (M) attacker (M)就是在攻击者的知识里是持有项 M M M的。以上面的输出为例按步骤说明:

  • 第1步,攻击者随便拿个自己知道的私钥sk,可以是自己的私钥
  • 第2步,拿这个私钥就能生成公钥pk(sk)
  • 第3步,程序点{18}处可以接收到攻击者发出的公钥,然后在{21}处的信息aenc(sign((spk(skB[]),k_2),skB[]),pk(sk))就能发出去给攻击者,现在攻击者持有这个知识了。注意这里的k_2k_1[pkX = pk(sk),!1 = @sid]的缩写,这表示它是由{19}处的new k_1操作得到,SessionID是1(这是{16}处生成的,即第一次重复)。这里的skB[]不是skB的原因是它是个非输入、没有重复、没有参数的name。
  • 第4步,通过攻击者第3和第1步知道的知识,可以获得sign((spk(skB[]),k_2),skB[])
  • 第5步,通过getmess获取签名的内容(spk(skB[]),k_2)作为知识
  • 第6步,获取到元组中的k_2
  • 第7步,获取A的公钥
  • 第8步,用A的公钥给sign((spk(skB[]),k_2),skB[])加密
  • 第9步,在{10}处这个消息被收到,然后在{14}处将消息senc(s[],k_2)发给了攻击者
  • 第10步,攻击者用k_2解密就得到了s[],第11步说明了一下这个是能被攻击的

设置abbreviateDerivation=false可以禁止使用缩写名称;设置explainDerivation=false来显示内部表示的Horn子句,而不是去显示进程的信息;设置traceDisplay=long可以获得进程的状态等详细知识。

当进程被replication时,可以会在验证结果中看到多个name的副本,形如a_n,名字本身不重要,重要的是后面的数字也相同时才表示使用了同一个会话,不相同时就是不同会话中的。

接下来分析一下输入的trace信息:

  • 前两个new对应创建非对称密钥的过程
  • 接下来的两个out是发送发送生成的非对称公钥,攻击者还将它们存在~M~M_1
  • 接下来的out是客户机在{9}位置的输出
  • 接下来的4行都能看到in copy a_2,这说明它们是同一个会话下的,找一下标号知道是攻击者在和服务器对话,使服务器发了个给对称密钥签名并加密的东西
  • 接下来的4行都能看到in copy a,这说明它们是同一个会话下的,找一下标号知道是攻击者在和客户端对话,使客户端接受了发来的东西,然后用这个对称密钥把信息加密发了出去
  • 最后,攻击者就能获取到信息s[]

在命令行中用-graph 目录或者-html 目录可以把攻击trace的图绘制出来,图总是以一个诚实进程和一个攻击者进程开始,并行的进程就占有多个列,进程的replication用!表示。

如果是公共通道上的输出,图上就是指向攻击者的横线,横线上总是形如 X = M X=M X=M的形式,其中 M M M是消息,而 X X X是攻击者将这个消息存储到里面的变量或者元组。

如果是公共通道上的输入,就是从攻击者出发的横线,横线上总是形如 R = M R=M R=M的形式,其中 R R R是攻击者为了获得发送的信息 M M M而执行的计算,如果两者一样就只显示个 R R R

如果是私有通道上的通信,即是输出消息的进程和输入消息的进程之间的箭头来表示,箭头上标记消息。

nonce和一些和通信无关的步骤是在框里面显示的,步骤的标号就是绿色的{n},如果是攻击的信息是在红框里,具体内容要取决于攻击的类型。
在这里插入图片描述
分析一下上图的trace。

首先诚实的进程创建了两个私钥,然后将生成的对应的公钥发送给了攻击者,攻击者将它们分别存储在~M~M_1中。

然后获得两个进程,一个clientA和一个serverB

然后clientA将自己的公钥发到公共信道上,然后攻击者接收了并存在~M_2里。

然后攻击者向serverB发送自己的公钥pk(a_1)

然后能看到serverB的方框,它创建了一个对称密钥k_2,并且执行了事件acceptsServer

然后serverBaenc(sign((spk(skB_1), k_2), skB_1), pk(a_1))发送到公共信道上,攻击者将其存在~M_3中。

然后攻击者计算等号左边的aenc(adec(~M_3,a_1),~M)),即对信息先解密再加密(注意~M_3M里存的是什么东西),然后就能获得等号右边的aenc(sign((spk(skB_1),k_2),skB_1),pk(skA_1))了,将这个消息发送给clientA

然后clientA执行事件acceptsClients,然后用k_2对保密信息s加密发送到了公共信道,攻击者接收并将其保存在了~M_4中,然后执行事件termClient

最终,攻击者用红框中的操作就能解密拿出保密信息了,所以攻击是可以发生的。

手册里还给出了修正后的协议,在docs/ex_handshake_annotated_fixed.pv,不过没有具体说明。

参考阅读

ProVerif的manual第3.3~3.3.2章节

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值