-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #26 from n4o847/add_attacker
攻撃文字列生成
- Loading branch information
Showing
6 changed files
with
245 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import { State } from './state'; | ||
import { Char, DFA, PrunedNFA, StronglyConnectedComponentGraph } from './types'; | ||
|
||
export class Attacker { | ||
private pnfa: PrunedNFA; | ||
private dfa: DFA; | ||
private nullChar: string; | ||
|
||
constructor(pnfa: PrunedNFA, dfa: DFA) { | ||
this.pnfa = pnfa; | ||
this.dfa = dfa; | ||
this.nullChar = this.getCharForNull(); | ||
} | ||
|
||
/** | ||
* EDA 構造を見つけ、それに対する攻撃文字列を生成する。 | ||
*/ | ||
findExponentialAttack( | ||
scc: StronglyConnectedComponentGraph, | ||
table: Map<State, [State, State]>, | ||
): string | null { | ||
for (const source of scc.stateList) { | ||
const [sourceLeft, sourceRight] = table.get(source)!; | ||
|
||
if (sourceLeft !== sourceRight) { | ||
continue; | ||
} | ||
|
||
// 多重自己ループを検出 | ||
for (const char of scc.alphabet) { | ||
const selfLoops = scc.transitions | ||
.get(source, char) | ||
.filter((q) => q === source); | ||
|
||
if (selfLoops.length >= 2) { | ||
let attack = ''; | ||
attack += this.getPathString(this.pnfa.initialStateSet, sourceLeft); | ||
{ | ||
const loop = char ?? this.nullChar; | ||
attack += loop.repeat(20); | ||
} | ||
attack += this.getSuffix(sourceLeft); | ||
return attack; | ||
} | ||
} | ||
|
||
// (q1, q1) → (q2, q3) → (q1, q1) where q2 ≠ q3 を検出 | ||
for (const viaPair of scc.stateList) { | ||
const [viaLeft, viaRight] = table.get(viaPair)!; | ||
|
||
if (viaLeft !== viaRight && viaLeft !== sourceLeft) { | ||
let attack = ''; | ||
attack += this.getPathString(this.pnfa.initialStateSet, sourceLeft); | ||
{ | ||
let loop = ''; | ||
loop += this.getPathString(sourceLeft, viaLeft); | ||
loop += this.getPathString(viaLeft, sourceLeft); | ||
attack += loop.repeat(20); | ||
} | ||
attack += this.getSuffix(sourceLeft); | ||
return attack; | ||
} | ||
} | ||
} | ||
|
||
// EDA 検出なし | ||
return null; | ||
} | ||
|
||
/** | ||
* IDA 構造を見つけ、それに対する攻撃文字列を生成する。 | ||
*/ | ||
findPolynomialAttack( | ||
scc: StronglyConnectedComponentGraph, | ||
table: Map<State, [State, State, State]>, | ||
): string | null { | ||
// (lq, lq, rq) と (lq, rq, rq) が存在するか | ||
for (const [lq, cq, rq] of scc.stateList.map((s) => table.get(s)!)) { | ||
if (lq === cq && scc.stateList.includes(State.fromTriple([lq, rq, rq]))) { | ||
let attack = ''; | ||
attack += this.getPathString(this.pnfa.initialStateSet, lq); | ||
{ | ||
const loop = this.getPathString(lq, rq); | ||
attack += loop.repeat(20); | ||
} | ||
attack += this.getSuffix(rq); | ||
return attack; | ||
} | ||
} | ||
|
||
// IDA 検出なし | ||
return null; | ||
} | ||
|
||
/** | ||
* null に対応する文字を探す。 | ||
*/ | ||
private getCharForNull(): string { | ||
for (let c = 0x00; ; c++) { | ||
const s = String.fromCharCode(c); | ||
if (!this.pnfa.alphabet.has(s)) { | ||
return s; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* ある状態からある状態まで遷移する文字列を探す。 | ||
*/ | ||
private getPathString( | ||
source: State | Set<State>, | ||
destination: State, | ||
): string { | ||
return findPath(this.pnfa, source, destination) | ||
.map((char) => char ?? this.nullChar) | ||
.join(''); | ||
} | ||
|
||
/** | ||
* EDA/IDA 構造からバックトラックの起こるような状態へ遷移する⽂字列を探す。 | ||
*/ | ||
private getSuffix(source: State): string { | ||
return findPath( | ||
this.dfa, | ||
this.dfa.initialState, | ||
this.pnfa.table.get(source)![1], | ||
) | ||
.map((char) => char ?? this.nullChar) | ||
.reverse() | ||
.join(''); | ||
} | ||
} | ||
|
||
/** | ||
* オートマトン上である状態からある状態までの経路 (Char の配列) を幅優先探索する。 | ||
*/ | ||
function findPath( | ||
automaton: PrunedNFA | DFA, | ||
source: State | Set<State>, | ||
destination: State, | ||
): Char[] { | ||
/** その状態にどの状態からどの遷移でたどり着けるか */ | ||
const referrer = new Map<State, [source: State, char: Char]>(); | ||
|
||
const queue: State[] = []; | ||
|
||
const sourceSet = new Set(source instanceof Set ? source : [source]); | ||
|
||
queue.push(...sourceSet); | ||
|
||
while (queue.length !== 0) { | ||
const q = queue.shift()!; | ||
if (q === destination) { | ||
break; | ||
} | ||
for (const char of automaton.alphabet) { | ||
for (const d of automaton.transitions.get(q, char)) { | ||
if (referrer.has(d)) { | ||
continue; | ||
} | ||
referrer.set(d, [q, char]); | ||
queue.push(d); | ||
} | ||
} | ||
} | ||
|
||
const path: Char[] = []; | ||
|
||
for (let q = destination; !sourceSet.has(q); ) { | ||
const [prevState, char] = referrer.get(q)!; | ||
path.push(char); | ||
q = prevState; | ||
} | ||
path.reverse(); | ||
|
||
return path; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,26 @@ | ||
import { buildStronglyConnectedComponents } from './scc'; | ||
import { DirectProductGraph, Message } from './types'; | ||
import { PrunedNFA, DFA, DirectProductGraph, Message } from './types'; | ||
import { Attacker } from './attack'; | ||
|
||
/** | ||
* 強連結成分を一つ一つ見ていき、EDAを持つかメッセージを返す | ||
*/ | ||
export function showMessageEDA(dps: DirectProductGraph[]): Message { | ||
// 別の経路で同じ文字で移動して自身に戻れたらEDA | ||
if (dps.some((dp) => isEDA(dp))) { | ||
return { status: 'Vulnerable', message: 'Detected EDA.' }; | ||
} else { | ||
return { status: 'Safe', message: "Don't have EDA." }; | ||
} | ||
} | ||
export function showMessageEDA( | ||
pnfa: PrunedNFA, | ||
dfa: DFA, | ||
dps: DirectProductGraph[], | ||
): Message { | ||
const attacker = new Attacker(pnfa, dfa); | ||
|
||
function isEDA(dp: DirectProductGraph): boolean { | ||
const sccs = buildStronglyConnectedComponents(dp); | ||
return sccs.some((scc) => { | ||
// 遷移元と遷移先が同じ遷移が複数存在するかを検出 | ||
for (const source of scc.stateList) { | ||
for (const char of scc.alphabet) { | ||
const destinationArray = scc.transitions | ||
.get(source, char) | ||
.filter((d) => d === source); | ||
|
||
if (destinationArray.length >= 2) { | ||
return true; | ||
} | ||
for (const dp of dps) { | ||
const sccs = buildStronglyConnectedComponents(dp); | ||
for (const scc of sccs) { | ||
const attack = attacker.findExponentialAttack(scc, dp.table); | ||
if (attack !== null) { | ||
return { status: 'Vulnerable', message: 'Detected EDA.', attack }; | ||
} | ||
} | ||
} | ||
|
||
const lrSame = scc.stateList.filter((state) => { | ||
const [lq, rq] = dp.table.get(state)!; | ||
return lq === rq; | ||
}); | ||
// (n, n), (m, k) (m !== k)が存在(すべて同じじゃないが全て異なるわけではない) | ||
return lrSame.length < scc.stateList.length && lrSame.length > 0; | ||
}); | ||
return { status: 'Safe', message: "Don't have EDA." }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,23 @@ | ||
import { buildStronglyConnectedComponents } from './scc'; | ||
import { State } from './state'; | ||
import { TripleDirectProductGraph, Message } from './types'; | ||
import { PrunedNFA, DFA, TripleDirectProductGraph, Message } from './types'; | ||
import { Attacker } from './attack'; | ||
|
||
export function showMessageIDA(tdps: TripleDirectProductGraph[]): Message { | ||
if (tdps.some((tdp) => isIDA(tdp))) { | ||
return { status: 'Vulnerable', message: 'Detected IDA.' }; | ||
} else { | ||
return { status: 'Safe', message: "Don't have IDA." }; | ||
} | ||
} | ||
export function showMessageIDA( | ||
pnfa: PrunedNFA, | ||
dfa: DFA, | ||
tdps: TripleDirectProductGraph[], | ||
): Message { | ||
const attacker = new Attacker(pnfa, dfa); | ||
|
||
function isIDA(tdp: TripleDirectProductGraph): boolean { | ||
const sccs = buildStronglyConnectedComponents(tdp); | ||
return sccs.some((scc) => { | ||
// (lq, lq, rq) と (lq, rq, rq) が存在するか | ||
for (const [lq, cq, rq] of scc.stateList.map((s) => tdp.table.get(s)!)) { | ||
const state = State.fromTriple([lq, rq, rq]); | ||
if (lq === cq && scc.stateList.includes(state)) { | ||
return true; | ||
for (const tdp of tdps) { | ||
const sccs = buildStronglyConnectedComponents(tdp); | ||
for (const scc of sccs) { | ||
const attack = attacker.findPolynomialAttack(scc, tdp.table); | ||
if (attack !== null) { | ||
return { status: 'Vulnerable', message: 'Detected IDA.', attack }; | ||
} | ||
} | ||
return false; | ||
}); | ||
} | ||
|
||
return { status: 'Safe', message: "Don't have IDA." }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters