オンライン暗号解析サービスを呼び出して使ってみる
簡易な暗号やハッシュ値の解析は、オンラインのサービスが充実しているところが多い。
独自に解析プログラムを書くときもあるが、もともとオンラインで用意されてるものを使ってしまうこともある。
特にハッシュのデータベースなんかは自分で集めるよりも遥かに楽だと思っている。
ということで、利用時にはブラウザを立ち上げ、お気に入りからサイトに飛び、暗号文字やハッシュ値を入力後に(必要ならCAPTCHAを入力して)ボタンを押す動作をしなければならない。
APIを公開しているサイトならば良いのだけど、そういうサイトは多くないので、今回はいくつかのサイトに対してスクリプトから結果を取りに行くことを試してみた。
換字暗号
これは言わずと知れたquipquipが有名だ。
簡単な換字暗号を解くときにはお世話になっている。
文字種はアルファベットだけだが、指定するmodeによってスペースの位置も検知してくれる。
サイトを解析するとPostで値を送信しているが、getメソッドでも結果を返してくれるように作ってあるため、クエリストリングを構築して呼んでやるだけで答えが返ってくる。親切な設計だ。
ブラウザから行かなくても良いように、アクセスして結果を返してくれるスクリプトを書いてみた。
といっても、アクセスして返却されたhtmlから該当文字列を抜き出すだけ。
#!/usr/bin/env python import sys, urllib2 def decrypt_online(encoded, mode, clues): url = 'http://quipqiup.com/index.php?ciphertext=%22' url = url + urllib2.quote(encoded) + '%22&mode=' + mode + '&clues=' + clues + '&action=Solve' try: response = urllib2.urlopen(url) except urllib2.HTTPError, e: print 'http error:', e.code, e.reason quit() for line in response.read().split('\n'): if line.startswith('<script>solsum(0,'): return line.split('"')[1][1:-1] return '' def main(argv): if(len(argv) != 2): print 'Usage: # python %s "Substitution cipher text"' % argv[0] quit() mode = raw_input('input mode.\nTrust spaces (Cryptogram mode): 1\nFind spaces (Patristocrat mode): 2\nAuto-detect puzzle type: 3\n>> ') if mode not in ['1','2','3']: print 'input 1 ~ 3' quit() clues = urllib2.quote(raw_input('input clues if you already know(ex: "X=T M=Y"): ')) decoded = decrypt_online(argv[1], mode, clues) if '' == decoded: print 'cannot decrypt.' else: print decoded if __name__ == '__main__': main(sys.argv)
少し時間はかかるようだがコマンドラインで作業しているときに呼び出せるのはありがたい。
[root@kali] # ./dec_substitution.py "TLIOU FOIIK VDTDI VYIOI OCOPJ TFSTD IJXNP FJDRO BVNNO PCOYO VCBKI JXNPT D" input mode. Trust spaces (Cryptogram mode): 1 Find spaces (Patristocrat mode): 2 Auto-detect puzzle type: 3 >> 3 input clues if you already know(ex: "X=T M=Y"): IF WE KNEW WHAT IT WAS WE WERE DOING IT WOULD NOT BECALLED RESEARCH WOULD IT
すでにわかっている文字があれば、Solverに与えることもできる。
[root@kali] # ./dec_substitution.py "PG XOYHLM XOYLY PZ GH TPUUYLYGRY EYXBYYG XOYHLM WGT JLWRXPRY. PG JLWRXPRY, XOYLY PZ." input mode. Trust spaces (Cryptogram mode): 1 Find spaces (Patristocrat mode): 2 Auto-detect puzzle type: 3 >> 3 input clues if you already know(ex: "X=T M=Y"): X=T M=Y IN THEORY THERE IS NO DIFFERENCE BETWEEN THEORY AND PRACTICE. IN PRACTICE, THERE IS.
数秒の時間がかかることがネックになることがあれば、個別にSolverを作ったほうが良いかもしれない。
MD5ハッシュ
MD5は以前使っていたサイトが閉鎖されていたり重いサイトが多かったりして、なかなか「ここを使おう」というところがない。
今回はGoogleで検索して出てきたものをいくつか使ってみた。
一般的な英単語は大差はなくても、日本語の名詞などはサイトによって載ってたり載ってなかったりしている(まぁ同じデータベース使っているわけがないのでそうだけど)。
いくつかスクリプトを作って試してみた中だとここがサイトの作りも簡単で安定していた。
#!/usr/bin/env python import urllib, urllib2, sys, re def abstStr(string, splitter, search): sep = string.split(splitter, 1)[1] m = re.search(search, sep) return m.group(0) def main(argv): if(len(argv) != 2): print 'Usage: # python %s md5text' % argv[0] quit() elif(len(argv[1]) != 32): print 'please input md5text(length==32).' quit() url = 'http://md5decryption.com/' # post data formdata = { 'hash':argv[1], 'submit':'Decrypt It!' } formdata = urllib.urlencode(formdata) req = urllib2.Request(url) req.add_data(formdata) try: res = urllib2.urlopen(req) res = res.read() if 'Decrypted Text: </b>' in res: res = res.split('Decrypted Text: </b>', 1)[1] res = res.split('</font>', 1)[0] print argv[1] + ' is:', res elif "Sorry, this MD5 hash wasn't found in our database" in res: print argv[1] + ': Not Found' else: print 'something error' except urllib2.HTTPError, e: print 'http error:', e.code, e.reason if __name__ == '__main__': main(sys.argv)
ここはレスポンスも1sec未満ぐらいだし、簡単なMD5の元値を調べるには便利そうだ。
[root@kali] # ./dec_md5.py 098f6bcd4621d373cade4e832627b4f6 098f6bcd4621d373cade4e832627b4f6 is: test
せっかくなら少しでも多くのデータを持ってるサイトでやりたいけど、それってどこなんだろう…
やっぱりググって上のほうに出てくるサイトなのかな。
CAPTCHAのあるサイト
ということでGoogle検索で上のほうに出てくるサイトを見てくるとHASHKILLERなんかがヒットする。
ためしにSHA1のデータベース総量の説明を見ると、"We have a total of just over 43.745 billion unique decrypted SHA1 hashes"とのこと。すごい。
他にもMD5やNTLMもデータベースを持っており解析に有用だが、下部にCAPTCHAを入力する欄がある。
HTML構造を見ると、ランダム値がhidden項目が複数生成されていたりしてちょっと複雑。
また、アクセス時にUser-Agentヘッダがないと403 Forbiddenになり、利用には工夫が必要だ。
CAPTCHAはぐちゃぐちゃってなっているノイズを消してやれれば良さそう。PILを使って赤色の閾値を取り2値化してみた。
その後python-tesseractを使って読んでみると、たまに文字認識できないときがあるけど、何度か試せばいけそう。
#!/usr/bin/env python import tesseract, cookielib, urllib, urllib2, os, sys, re from PIL import Image def editColorChannel(inputfile): img1 = Image.open(inputfile).convert('RGB') # get pixel size pixelSizeTuple = img1.size img2 = Image.new('RGB', img1.size) # edit color for i in range(pixelSizeTuple[0]): for j in range(pixelSizeTuple[1]): r,g,b = img1.getpixel((i,j)) if (r < 115): img2.putpixel((i,j), (0,0,0)) else: img2.putpixel((i,j), (255,255,255)) img2.save(inputfile) def readchar(inputfile): api = tesseract.TessBaseAPI() api.Init('.', 'eng', tesseract.OEM_DEFAULT) api.SetVariable('tessedit_char_whitelist', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') api.SetPageSegMode(tesseract.PSM_SINGLE_WORD) mBuffer = open(inputfile, 'rb').read() return tesseract.ProcessPagesBuffer(mBuffer,len(mBuffer),api) def abstStr(string, splitter, search): sep = string.split(splitter, 1)[1] m = re.search(search, sep) return m.group(0) def main(argv): if(len(argv) != 2): print 'Usage: # python %s sha1text' % argv[0] quit() elif(len(argv[1]) != 40): print 'please input sha1text(length==40).' quit() trials = 3 # try 3times baseurl = 'http://www.hashkiller.co.uk/' imgname = 'capt.ashx?d=' pageurl = 'sha1-decrypter.aspx' for i in range(trials): # get html cj = cookielib.CookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) # avoid 403 Forbidden opener.addheaders = [('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:36.0) Gecko/20100101 Firefox/36.0')] r = opener.open(baseurl+pageurl) html = r.read() # get picture imgnum = abstStr(html, imgname, '\d*\.\d*') filename = imgnum+'.jpg' f = open(filename, 'wb') r = opener.open(baseurl+imgname+imgnum) f.write(r.read()) f.close() # edit img editColorChannel(filename) # ocr txtCaptcha = readchar(filename).translate(None, ' \n') txtCaptcha = txtCaptcha.replace(' ','') os.system('rm -f ' + filename) #print 'captcha:', txtCaptcha # post data viewstate = abstStr(html, 'id="__VIEWSTATE" value="', '[a-zA-Z0-9+_=/]*') eventval = abstStr(html, 'id="__EVENTVALIDATION" value="', '[a-zA-Z0-9+_=/]*') formdata = { 'ctl00$ScriptMan1':'ctl00$content1$updDecrypt|ctl00$content1$btnSubmit', '__EVENTTARGET':'', '__EVENTARGUMENT':'', '__VIEWSTATE':viewstate, '__EVENTVALIDATION':eventval, 'ctl00$content1$txtInput':argv[1], 'ctl00$content1$txtCaptcha':txtCaptcha, '__ASYNCPOST':'true', 'ctl00$content1$btnSubmit':'Submit' } data_encoded = urllib.urlencode(formdata) response = opener.open(baseurl+pageurl, data_encoded) res = response.read() if '[Not found]' in res: print argv[1] + ': Not Found' break elif('<span class="text-green">' in res): res = res.split('<span class="text-green">', 1)[1] res = res.split('</span>', 1)[0] print argv[1] + ' is:', res break elif(i > 1): print 'failed to solve captcha', print '.', if __name__ == '__main__': main(sys.argv)
これでsha1の元の値までは見れるようになった。
[root@kali] # ./dec_sha1.py 451dcf9913bf3b329c05c2a46ad555eeae267ba8
451dcf9913bf3b329c05c2a46ad555eeae267ba8 is: sushi
たまに突破できないが、簡単なCAPTCHAの解析やワンタイムトークンを生成しているサイトぐらいは割とすぐにスクリプトから呼び出して利用できることがわかった。
オンラインサービス利用の利点・欠点
いくつかのサイトを利用しながら、オンラインサービスを使うメリット・デメリットを考えてみた。
メリット
- 自分でレインボーテーブルを揃えたり暗号解読ロジックを書かなくても、ネットに繋ぐだけで簡単に利用できる
- あるサイトでdecryptできなくても、他のサイトではdecryptできたりして、色んなサイトを試すことでHIT率が簡単に上がる
デメリット
- (当たり前だが)落ちていると使えない。あるサイトはCTFがある日に高確率で落ちていることもあり、何かの陰謀なんじゃないかと思うこともある
- Solverに負荷をかけるような使い方ができない(したくないし、公開側もされたくないだろう)
- あとは、それほど気にする必要はないけどネット環境に繋がらないと使えない、ぐらいか
上述の特性を踏まえ、時間があるときに次のようなことをしておこうかと思った。どれもハッシュの元の値を求めたりするときを想定している。
- オンラインサイトを利用する場合
- あるサイトに繋がらなかったり結果が見つからなくても、他の複数のサイトも見にいくようにすることでヒット率を上げる
- オンラインサイトを利用しない手段
- デメリットで上げたとおり、落ちていたら利用ができないので自前でもこういうものを用意してしまう。さっきのHASHKILLERにもWordlistが転がっていたり、色んなところにパスワードリストがあるので、これらを使って自前でDBを作っておく。もちろん悪用しない前提であり、させないように公開もしないのが望ましい。
ということで、作ったものの公開はできないだろうと思うけど、こういうものを作ってみるのは勉強にもなりそうだと思った。
Wordlistを集めているときに、「あ、こういうパスワード使っちゃうとヤバいんだな」とかの発見もありそうだし、身になることがあればまた書こうと思う。