سيناريو الاستخدام: QQNT على Windows بالإصدار 9.9.26-44343، تحتاج إلى استخراج مفتاح قاعدة بيانات SQLCipher (لاستخدامه لاحقًا في فك تشفير قواعد بيانات مثل nt_msg.db وغيرها).
0. توضيح المخاطر
- توجد درجة معينة من مخاطر رقابة/مكافحة إساءة الاستخدام على الحساب، يُنصح أولًا بعمل نسخة احتياطية من سجل الدردشة.
- للاستخدام في تصدير البيانات الشخصية والتعلّم فقط، يُرجى الالتزام بالقوانين واللوائح واتفاقية المنصة.
1. تجهيز البيئة
- PowerShell بصلاحيات المدير
- تثبيت QQNT:
9.9.26-44343 - Python 3.10+ (تم الاختبار ووجد أن 3.13 يعمل)
تثبيت الاعتماديات:
py -m pip install frida psutil
2. حفظ السكربت (خاص بهذا الإصدار)
أنشئ ملفًا جديدًا: qqnt_get_key_9926.py، والمحتوى كما يلي:
import time
import frida
import psutil
# متوافق فقط مع QQNT 9.9.26-44343 (الإزاحة داخل wrapper.node)
TARGET_RVA = 0x021A5BF0
QQ_EXE = r"C:\\Program Files\\Tencent\\QQNT\\QQ.exe"
def try_attachable_main_pid():
for p in psutil.process_iter(['pid', 'name', 'cmdline']):
if (p.info['name'] or '').lower() != 'qq.exe':
continue
cmd = p.info['cmdline'] or []
if len(cmd) != 1:
continue
pid = p.info['pid']
try:
s = frida.attach(pid)
s.detach()
return pid
except Exception:
pass
return None
def build_script():
return f"""
function bytesToHex(ab) {{
if (!ab) return '';
const u8 = new Uint8Array(ab);
let s = '';
for (let i = 0; i < u8.length; i++) s += u8[i].toString(16).padStart(2, '0');
return s;
}}
(function waitAndHook() {{
const moduleName = 'wrapper.node';
const targetRva = ptr('0x{TARGET_RVA:x}');
let hooked = false;
function doHook() {{
if (hooked) return;
let m = null;
try {{
m = Process.getModuleByName(moduleName);
}} catch (e) {{
return;
}}
if (!m) return;
hooked = true;
const target = m.base.add(targetRva);
console.log('[hook] base=' + m.base + ' target=' + target);
Interceptor.attach(target, {{
onEnter(args) {{
try {{
// x64 على Windows: rcx, rdx, r8, r9
const dbNamePtr = args[1];
const keyPtr = args[2];
const nKey = args[3].toInt32();
const dbName = dbNamePtr.isNull() ? '' : (dbNamePtr.readUtf8String() || '');
const keyHex = (keyPtr.isNull() || nKey <= 0) ? '' : bytesToHex(keyPtr.readByteArray(nKey));
console.log('[dbName] ' + dbName);
console.log('[nKey] ' + nKey);
console.log('[key] ' + keyHex);
}} catch (e) {{
console.log('[hook-error] ' + e);
}}
}}
}});
}}
doHook();
const timer = setInterval(() => {{
if (hooked) {{
clearInterval(timer);
return;
}}
doHook();
}}, 200);
}})();
"""
def run_attach_mode(pid):
sess = frida.attach(pid)
script = sess.create_script(build_script())
def on_message(msg, data):
if msg.get('type') == 'send':
print(msg.get('payload'))
elif msg.get('type') == 'error':
print(msg)
script.on('message', on_message)
script.load()
print(f'[info] attached pid={pid}, waiting...')
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
pass
sess.detach()
def run_spawn_mode():
dev = frida.get_local_device()
pid = dev.spawn([QQ_EXE])
sess = dev.attach(pid)
script = sess.create_script(build_script())
def on_message(msg, data):
if msg.get('type') == 'send':
print(msg.get('payload'))
elif msg.get('type') == 'error':
print(msg)
script.on('message', on_message)
script.load()
print(f'[info] spawned pid={pid}, resuming...')
dev.resume(pid)
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
pass
sess.detach()
if __name__ == '__main__':
pid = try_attachable_main_pid()
if pid:
run_attach_mode(pid)
else:
run_spawn_mode()
3. التشغيل
تأكد من أن QQ قد تم تسجيل الدخول إليه (أو اترك السكربت ليقوم بتشغيل QQ تلقائيًا):
py .\\qqnt_get_key_9926.py
ظهور مخرجات مشابهة يعني نجاح العملية:
[dbName] main
[nKey] 16
[key] 5a6b457d79777b2e6150376e582d6572
أو:
[dbName] main
[nKey] 20
[key] 4244313536443637313044353444383738324634
4. كيفية استخدام المفتاح (key)
- قد يظهر طولان مختلفان؛ خذ المجموعة المطابقة لقاعدة البيانات المستهدفة لديك.
- قيمة
keyهي سلسلة ست عشرية (Hex)؛ وأحيانًا تكون في الأصل نص ASCII ممثلًا بصيغة Hex.
مثال PowerShell لتحويله إلى ASCII:
[Text.Encoding]::ASCII.GetString([Convert]::FromHexString('4244313536443637313044353444383738324634'))
5. مسارات قواعد البيانات الشائعة
C:\\Users\\你的用户名\\Documents\\Tencent Files\\你的QQ号\\nt_qq\\nt_db\\nt_msg.db
إذا كنت ترى فقط dbName=main، فهذا اسم مستعار لاتصال SQLite، ولا يعني أن اسم ملف قاعدة البيانات خاطئ.
6. توضيح التوافق مع الإصدارات
القيمة TARGET_RVA = 0x021A5BF0 في هذا الدليل تخص فقط QQNT 9.9.26-44343. إذا توقفت عن العمل في الإصدارات اللاحقة، فستحتاج إلى إعادة تحديد الإزاحة.
