歡迎您光臨本站 註冊首頁

用 Perl 和 xev 創建連續擊鍵力學監視器

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  了解如何使用 Perl、xev 和自定義演算法,根據特有的鍵入模式監視當前使用鍵盤的人員。
擊鍵力學 是一個相對嶄新的領域,可以通過對鍵入模式進行統計分析來識別使用鍵盤的人員。以前發布在 developerWorks 中的文章已經展示了如何將擊鍵力學的概念集成到應用程序中,以及修改 Gnome Display Manager(GDM)以要求使用正確密碼及 “正確鍵入的” 密碼的實際示例。本文提供的代碼和工具可以使您超越單一的擊鍵力學應用,而是連續監視整個 X Window System 環境以掌握鍵入者的特有模式。

在閱讀本文後,您將可以創建一個連續的擊鍵力學監視器,該監視器可以在沒有檢測到特有鍵入模式時鎖定 X Window System 會話。

所選方法的注意事項

跟蹤每個鍵按下情況的最有效方法也許就是使用內核級鍵盤記錄工具(例如 THC-vlogger 或 ttyrpld)。不幸的是,這些程序是為較舊的內核級別設計的,或者目前很難在現代 Linux® 發行版中使用。鍵盤設備跟蹤程序(例如 uberkey)是另外一種好方法,但是其釋放擊鍵和計時的不精確性使其不適合本文介紹的應用。

雖然不適用於控制台或遠程會話,但是 xev 為檢測在 X Window System 中運行的任意應用程序的鍵盤事件提供了一種健壯且輕量級的方法。

xev 會話中的每個事件都是隨高精度的時間值一同輸出的。在本文中,我們將在具體時窗內使用該時間值記錄 R、S 和 T 鍵的 “停頓” 時間。停頓時間是用戶的手指按住按鍵的時間段。將為 X Window System 桌面中的每個應用程序記錄這個相對簡單的度量。

在開發擊鍵 “簽名” 時,最好使用大量數據。使用模式需要匹配最常見的計算機使用情況。用下面介紹的數據跟蹤選項進行試驗以獲得各種樣例使用數據。然後,開發的簽名將被轉換為密碼散列並存儲到磁碟中,以供稍後在監視階段中進行比較。





硬體和軟體要求

2000 年以後生產的所有 PC 都應當能夠處理本文中提供的代碼。您將需要 X Window System 以及 xev 程序(請參閱 參考資料)。您需要使用 mkpasswd 程序(附帶於大多數 Linux 分發版中)才能生成擊鍵簽名的密碼散列。需要 Perl 模塊 X11::GUITest、threads 和 Thread::Queue。UNIX® 和 Linux 用戶請注意:如果您剛開始安裝 Perl 模塊,使用 Andreas J. Konig 的 CPAN 模塊可以自動安裝其他模塊(請參閱 參考資料)。




continuousKeystrokes.pl 程序

記錄在 X Window System 會話中按下的每個擊鍵的簡單方法是啟動與 xwininfo -root -tree 所列出的每個窗口綁定的 xev 程序。在理論上,這種方法將對於少量窗口有效,但是最終,將達到 X 客戶機的最大數目,並且需要重新編譯 X Window System 才能增加允許的 X 客戶機數目。更合理的解決方案是跟蹤持有焦點的當前窗口並把單個 xev 程序與該窗口綁定起來。然後將記錄當前持有焦點的窗口的每個鍵盤事件。

清單 1 顯示了用於跟蹤當前焦點並創建擊鍵簽名的 continuousKeystrokes.pl 程序的開頭。


清單 1. continuousKeystrokes.pl 變數聲明

#! perl -w
# continuousKeystrokes.pl - monitor dwell time of r,s,t for all X Window System
use strict;
use X11::GUITest qw( :ALL );
use threads;
use Thread::Queue;
die "specify mode, minimum samples" unless _cnnew1@ARGV == 2 ;

my $sleepTime = 5; # seconds between key event processing runs
my %windows = (); # hash of window keystrokes
my @samp = (); # most recent sample averages of keystrokes
my $checkRng = 10; # fuzziness of dwell time matching
my $userMatch = 0; # user or impostor?
my %keys = (); # average of key dwell times

my $mode = $ARGV[0]; # record baseline or monitor matches
my $minSamples = $ARGV[1]; # required base samples to match with
my ( $salt, $hash ) = ""; # read from keystroke.Signatures

if( $mode eq "monitor" ){ loadSignatureFile() }



在模塊 include 語句和初始變數聲明之後,進入主控制循環。在處於監視模式下時,將裝入先前生成的簽名文件。清單 2 顯示了主程序循環的開頭。


清單 2. 主程序循環的開始部分

# ctrl-c to exit the program and drop the threads without error
while(1)
{
my @activeId = GetInputFocus();
my $foundPipe = 0;

for my $key ( keys %windows )
{
if( $key eq "@activeId" && $windows{$key}{pipeDef} == 0 )
{
my $res = "xev -id $key |";
$windows{$key}{ input } = createPipe( $res ) or die "no pipe ";
$windows{$key}{pipeDef} = 1;
$foundPipe = 1;
}#if not a match

}#for each windows key

if( $foundPipe == 0 )
{
# if pipe doesn't already exist, add a new one
my $key = "@activeId";
if( !exists($windows{$key}) || $windows{$key}{pipeDef} == 0 )
{
my $res = "xev -id $key |";
$windows{$key}{ input } = createPipe( $res ) or die "no pipe ";
$windows{$key}{pipeDef} = 1;
}#if pipe doesn't already exist
}#foundpipe check



第一個 for 循環將搜索目前沒有綁定管道的窗口散列中的已有條目。如果找到這樣一個條目,則創建一個管道。維護一個由目前擁有管道的窗口組成的可用工作列表,將允許在一段時間內收集 xev 輸出。xev 輸出是非緩衝的,這會造成較少使用的窗口無法以足夠快的速度填充輸出緩衝。為了讓輸出數據在窗口失去焦點後繼續保留,然後重新獲得這些數據,窗口散列將記錄輸出。清單 3 顯示了主邏輯循環的其餘部分。


清單 3. 主邏輯循環結束

# read any available date from a pipe
for my $xevPipe( keys %windows )
{
next unless( $windows{$xevPipe}{pipeDef} == 1 );

while( $windows{$xevPipe}{input}->pending )
{
my $line = $windows{$xevPipe}{input}->dequeue or next;
$windows{$xevPipe}{keyString} .= $line;

}#while data to be added to the buffer

next unless( exists( $windows{$xevPipe}{keyString} ) );
next unless( length( $windows{$xevPipe}{keyString} ) > 8192 );

compareSignature( getKeyAverages( $windows{$xevPipe}{keyString} ) );

$windows{$xevPipe}{keyString} = "";

}#for windows keys

# kill all xevs except currently monitored
for my $key ( keys %windows )
{
next unless( $key ne "@activeId" && $windows{$key}{pipeDef} == 1 );

$windows{$key}{pipeDef} = 0;
my $cmd = qq{ps -aef | grep $key | grep xev | perl -lane '`kill \$F[1]`'};
system($cmd);

}#for each windows key

sleep( $sleepTime );

}#while main loop



在創建了管道(或者如果已經存在一個管道)后,每個管道的輸出將被讀入到該窗口記錄的事件變數中。如果記錄了足夠的數據,則整個緩衝將被傳遞給 getKeyAverages 子常式,然後再傳遞給 compareSignature 子常式。接下來,如果焦點事件發生更改,則終止舊的 xev 程序。

清單 4 顯示了前兩個子常式:loadSignatureFile 和 createPipe。


清單 4. loadSignatureFile 和 createPipe 子常式

sub loadSignatureFile
{
open(INFILE,"keystroke.signatures") or die "no signature file";
my $line =<INFILE>;
die "empty file " unless defined $line;
chomp($line);
( undef, undef, $salt, $hash ) = split '\$', $line;
close(INFILE);

}#loadSignatureFile

sub createPipe
{
my $cmd = shift;
my $queue = new Thread::Queue;
async{
my $pid = open my $pipe, $cmd or die $!;
$queue->enqueue( $_ ) while <$pipe>;
$queue->enqueue( undef );
}->detach;

# detach causes the threads to be silently terminated on program exit
return $queue;

}#createPipe



loadSignatureFile 只是讀取程序的 “record” 模式中存儲的 salt 和 hash 信息。這些值稍後將用於擊鍵簽名比較。createPipe 子常式是從使用線程的管道中進行無阻塞讀取的簡單方法。清單 5 顯示了下一個子常式: getKeyAverages。


清單 5. getKeyAverages 子常式

sub getKeyAverages
{
my %temp = (); # temporary hash to record key press and release times
my %avg = (); # average for entire buffer read key press and release times

open(my $fh, '<', \$_[0]) or die "Could not open string for reading";

while(my $inLine = <$fh> )
{
next unless( $inLine =~ /KeyPress event/ || $inLine =~ /KeyRelease event/ );
my $state = (split " ", $inLine)[0];

# get type of entry
my $eventType = (split " ", $inLine)[0];

# get the time entry
my $currTime = <$fh>;

# make sure the line exists and has the required data
next unless( defined($currTime) );
next unless( length($currTime) > 43 );
$currTime = substr( $currTime, index($currTime,"time ")+5);
$currTime = substr( $currTime, 0, index($currTime,","));

# get the key name
my $currKey = <$fh>;
next unless( defined($currKey) );
next unless( length($currKey) > 40 );
$currKey = substr( $currKey, index($currKey,"keysym ")+7);
$currKey = substr( $currKey, 0, index($currKey,"),"));
$currKey = substr( $currKey, index($currKey, ", ")+2);

next unless( $currKey eq "r" || $currKey eq "s" || $currKey eq "t" );

# add the key press
if( $state eq "KeyPress" ){ $temp{$currKey} = $currTime }

next unless ( $state eq "KeyRelease" );

if( exists( $temp{ $currKey } ) )
{
$avg{$currKey}{val} += $currTime - $temp{$currKey};
$avg{$currKey}{count} ++;
}#if a press has been recorded

# either the data has been recorded or it was a release on a key never pressed
# in this window
delete $temp{ $currKey };

}#while file handle
close( $fh );

my( $rVal, $sVal, $tVal ); $rVal = $sVal = $tVal = 0;
if( exists( $avg{"r"} ) ){ $rVal = ($avg{"r"}{val} / $avg{"r"}{count}) };
if( exists( $avg{"s"} ) ){ $sVal = ($avg{"s"}{val} / $avg{"s"}{count}) };
if( exists( $avg{"t"} ) ){ $tVal = ($avg{"t"}{val} / $avg{"t"}{count}) };

return( $rVal, $sVal, $tVal );

}#getKeyAverages



xev 程序輸出將在連接的窗口中列出每個 X Window System 事件。清單 6 是該程序輸出的示例。


清單 6. xev 示常式序輸出

KeyPress event, serial 16, synthetic NO, window 0x2000002,
root 0x76, subw 0x2000012, time 248543985, (719,86), root:(964,107),
state 0x0, keycode 27 (keysym 0x72, r), same_screen YES,
XLookupString gives 1 bytes: (72) "r"
XmbLookupString gives 1 bytes: (72) "r"
XFilterEvent returns: False

KeyRelease event, serial 16, synthetic NO, window 0x2000002,
root 0x76, subw 0x2000012, time 248544153, (719,86), root:(964,107),
state 0x0, keycode 27 (keysym 0x72, r), same_screen YES,
XLookupString gives 1 bytes: (72) "r"
XFilterEvent returns: False

KeyPress event, serial 16, synthetic NO, window 0x2000002,
root 0x76, subw 0x2000012, time 248544206, (719,86), root:(964,107),
state 0x0, keycode 39 (keysym 0x73, s), same_screen YES,
XLookupString gives 1 bytes: (73) "s"
XmbLookupString gives 1 bytes: (73) "s"
XFilterEvent returns: False

KeyPress event, serial 16, synthetic NO, window 0x2000002,
root 0x76, subw 0x2000012, time 248544263, (719,86), root:(964,107),
state 0x0, keycode 28 (keysym 0x74, t), same_screen YES,
XLookupString gives 1 bytes: (74) "t"
XmbLookupString gives 1 bytes: (74) "t"
XFilterEvent returns: False

KeyRelease event, serial 16, synthetic NO, window 0x2000002,
root 0x76, subw 0x2000012, time 248544365, (719,86), root:(964,107),
state 0x0, keycode 39 (keysym 0x73, s), same_screen YES,
XLookupString gives 1 bytes: (73) "s"
XFilterEvent returns: False



此處的按鍵數據值包括鍵名、事件類型和時間條目。在正常輸入期間,注意按下和釋放不同按鍵的事件如何重疊。getKeyAverages 子常式中的代碼將把輸入字元串緩衝處理為文件句柄,然後從輸入緩衝中提取相關的時間、事件類型和鍵名。將計算並返回整個緩衝中的每個按鍵停頓時間的平均值。

如主程序循環所示,將把 getKeyAverages 子常式輸出發送給如下所示的 compareSignature 子常式。


清單 7. compareSignature 子常式

sub compareSignature
{
if( $_[0] ne "0" )
{
$keys{ "r" }{ val } += $_[0];
$keys{ "r" }{ count }++;
}#if r is not 0

if( $_[1] ne "0" )
{
$keys{ "s" }{ val } += $_[1];
$keys{ "s" }{ count }++;
}#if s is not 0

if( $_[2] ne "0" )
{
$keys{ "t" }{ val } += $_[2];
$keys{ "t" }{ count }++;
}#if t is not 0

return unless ( exists($keys{"r"}) );
return unless ( exists($keys{"s"}) );
return unless ( exists($keys{"t"}) );
if( $keys{ "r" }{ count } >= $minSamples &&
$keys{ "s" }{ count } >= $minSamples &&
$keys{ "t" }{ count } >= $minSamples )
{

$samp[0] = sprintf( "%0.0f", $keys{r}{val} / $keys{r}{count} );
$samp[1] = sprintf( "%0.0f", $keys{s}{val} / $keys{s}{count} );
$samp[2] = sprintf( "%0.0f", $keys{t}{val} / $keys{t}{count} );

if( $mode eq "record" )
{
#print "[@samp]\n"; # uncomment to see plain keystroke signature
print `echo "@samp" | mkpasswd -H md5 --stdin`;
}else
{
$userMatch = 0;
checkDynamics( "", 0 );
if( $userMatch == 0 )
{
print "\nno match\n";
#system( "xscreensaver-command -lock" );
}else
{
print "user verified\n";
}#if the signatures did not match

}#if in record mode

%keys = ();

}#enough samples

}#compareSignature



在記錄值(如果這些值不為零)后,如果有足夠的樣例數據,則計算 R、S 和 T 的平均停頓時間。當處於 “record” 模式時,這些停頓時間將被擴展為字元串並用於生成簽名的密碼散列。在 “monitor” 模式下,將調用 checkDyanmics 子常式以確定目前的停頓時間是否與 keystroke.signatures 文件中記錄的停頓時間(在允許的範圍內)匹配。如果找到匹配,則不採取任何操作。如果未找到匹配,則鎖定屏幕保護程序,從而可以將那些碰運氣的攻擊者有效地鎖定在系統外部。清單 8 詳細說明了 checkDynamics 子常式。


清單 8. checkDynamics 子常式

sub checkDynamics
{
my $inString = $_[0];
my $level = $_[1];

my $start = $samp[$level] - $checkRng;
my $stop = $samp[$level] + $checkRng;
my $curr = $start;
#
while( $curr <= $stop && $userMatch != 1 )
{
if( $level == 2 ) # deepest level for only three letters
{
my $res = `echo "$inString $curr" | mkpasswd -S $salt -H md5 --stdin`;
chomp($res);
if( $res eq qq/\$1\$${salt}\$${hash}/ ){ $userMatch = 1 }

}else
{
# append to the current 'signature', go to next level
my $tempStr = ""; # temporary signature string

if( length($inString) != 0 ){ $tempStr = "$inString $curr" }
else { $tempStr = $curr }

checkDynamics( $tempStr, $level+1 );

}#if at maximum level

$curr++;

}#while current less than stop

return("");

}#//checkDynamics



checkDynamics 子常式在構建包含 checkRng 參數所定義的各種可能性的簽名時將遞歸調用自身。傳遞給 mkpasswd 的每個字元串都是從單個按鍵停頓時間一直到用戶名中每個記錄字母的停頓時間逐級構建的。例如,如果平均停頓時間為 “130 130 130”(分別代表 R、S、T),checkDynamics 子常式將完成必要的置換以檢查 “125 125 125”、“135 135 135” 及兩者之間的內容。鬆散匹配(帶有較高的 checkRng 值)將需要更多的時間來檢查所有可能性。


用法

將以上代碼另存為 continuousKeystrokes.pl 並在 record 模式下運行程序以生成擊鍵簽名:perl continuousKeystrokes.pl record 10 2>/dev/null。這條命令將監視持有焦點的 X 窗口的擊鍵,並在所有窗口中記錄了 10 個平均數后輸出擊鍵停頓時間的密碼散列。出於測試目的,將清單 7 中的樣例輸出行取消註釋以在加密之前顯示擊鍵簽名。雖然上面使用的 10 個平均數對於測試來說十分有用,但是最好使用更大量的數據來精確地創建簽名。先嘗試在普通使用場景下鍵入數千個單詞,然後再輸出密碼散列。在收集到令您滿意的數據后,獲取已輸出的散列並將其放入 keystroke.signatures 文件。

要監視當前用戶的鍵入模式並在檢測到模式中出現偏差時鎖定屏幕,請用 perl continuousKeystrokes.pl monitor 10 2>>dev/null 運行程序。(stderr 重定向為 null 是由於 threads.pm 中的標量(scalar)釋放問題)。如述,此程序將監視當前的鍵入模式並在簽名不同於 keystroke.signatures 文件中記錄的內容時鎖定屏幕。

注意,您需要用 checkRng 參數和 minSamples 參數進行試驗,找到可以在您的環境及特定鍵入模式下正常工作的設置。


結束語

使用本文介紹的工具和代碼,您可以創建使用擊鍵力學連續進行用戶驗證的自定義框架。雖然是圍繞三個按鍵的停頓時間構建的,但是 xev 程序和本文介紹的代碼將允許監視 X Window System 的鍵盤(和滑鼠)交互的所有方面。找出在後退按鍵之前常用的字元,或者監視哪些 vi 或 emacs 組合鍵最常用。找到最容易拼錯的單詞並測量其他鍵入模式,例如給定應用程序的應用程序快捷鍵和常用按鍵。 (責任編輯:A6)


[火星人 ] 用 Perl 和 xev 創建連續擊鍵力學監視器已經有677次圍觀

http://coctec.com/docs/linux/show-post-69038.html