ในช่วงสองสัปดาห์ที่ผ่านมา เราได้เห็นชุดบทความที่พูดถึงสิ่งที่ได้รับการอธิบายว่าเป็น “การถอดรหัสรหัสผ่านหลัก” ใน KeePass ผู้จัดการรหัสผ่านแบบโอเพนซอร์สยอดนิยม
ข้อบกพร่องนี้ถือว่ามีความสำคัญเพียงพอที่จะใช้ระบุตัวตนของรัฐบาลสหรัฐฯ อย่างเป็นทางการ (รู้จักกันในชื่อ CVE-2023-32784ถ้าคุณต้องการตามล่ามัน) และเมื่อพิจารณาว่ารหัสผ่านหลักสำหรับผู้จัดการรหัสผ่านของคุณค่อนข้างจะเป็นกุญแจสู่ปราสาทดิจิทัลทั้งหมดของคุณ คุณจึงเข้าใจได้ว่าทำไมเรื่องราวจึงกระตุ้นความตื่นเต้นมากมาย
ข่าวดีก็คือผู้โจมตีที่ต้องการใช้ประโยชน์จากข้อบกพร่องนี้จะต้องติดมัลแวร์ในคอมพิวเตอร์ของคุณอยู่แล้ว ดังนั้นจึงสามารถสอดแนมการกดแป้นและโปรแกรมที่กำลังทำงานอยู่ได้อยู่ดี
กล่าวอีกนัยหนึ่ง ข้อผิดพลาดนั้นถือเป็นความเสี่ยงที่จัดการได้ง่ายจนกว่าผู้สร้าง KeePass จะออกมาอัปเดต ซึ่งน่าจะปรากฏเร็วๆ นี้ (ต้นเดือนมิถุนายน 2023)
ในฐานะผู้เปิดเผยข้อผิดพลาดดูแล ชี้ให้เห็น:
หากคุณใช้การเข้ารหัสดิสก์เต็มรูปแบบด้วยรหัสผ่านที่รัดกุม และระบบของคุณ [ปราศจากมัลแวร์] คุณก็ไม่มีปัญหา ไม่มีใครสามารถขโมยรหัสผ่านของคุณจากระยะไกลผ่านอินเทอร์เน็ตด้วยการค้นพบนี้เพียงอย่างเดียว
ความเสี่ยงอธิบาย
โดยสรุปแล้ว บั๊กจะลดระดับลงจนสร้างความยุ่งยากในการรับรองว่าร่องรอยของข้อมูลที่เป็นความลับทั้งหมดจะถูกลบออกจากหน่วยความจำเมื่อคุณดำเนินการเสร็จสิ้น
เราจะเพิกเฉยต่อปัญหาของวิธีการหลีกเลี่ยงการมีข้อมูลลับในหน่วยความจำเลยแม้แต่ช่วงสั้นๆ
ในบทความนี้ เราเพียงต้องการเตือนโปรแกรมเมอร์ทุกที่ที่โค้ดได้รับการอนุมัติโดยผู้ตรวจสอบที่ใส่ใจในความปลอดภัยพร้อมความคิดเห็น เช่น “ดูเหมือนว่าจะล้างข้อมูลอย่างถูกต้องหลังจากตัวมันเอง”...
…ในความเป็นจริงอาจไม่สามารถล้างข้อมูลทั้งหมดได้ และการรั่วไหลของข้อมูลที่อาจเกิดขึ้นอาจไม่ชัดเจนจากการศึกษาโค้ดโดยตรง
พูดง่ายๆ ก็คือ ช่องโหว่ CVE-2023-32784 หมายความว่ารหัสผ่านหลัก KeePass อาจสามารถกู้คืนได้จากข้อมูลระบบแม้ว่าจะออกจากโปรแกรม KeyPass ไปแล้ว เนื่องจากข้อมูลที่เพียงพอเกี่ยวกับรหัสผ่านของคุณ (แม้ว่าจะไม่ใช่รหัสผ่านดิบ ซึ่งเราจะให้ความสำคัญในอีกสักครู่) อาจถูกทิ้งไว้ในไฟล์ swap หรือไฟล์สลีป ซึ่งหน่วยความจำระบบที่จัดสรรอาจถูกบันทึกไว้ในภายหลัง
ในคอมพิวเตอร์ที่ใช้ Windows ซึ่งไม่ได้ใช้ BitLocker เพื่อเข้ารหัสฮาร์ดดิสก์เมื่อปิดระบบ การทำเช่นนี้จะทำให้มิจฉาชีพที่ขโมยแล็ปท็อปของคุณมีโอกาสต่อสู้ในการบูตเครื่องจากไดรฟ์ USB หรือซีดี และกู้คืนรหัสผ่านหลักของคุณ แม้ว่าโปรแกรม KeyPass เองจะดูแลไม่ให้บันทึกลงดิสก์อย่างถาวรก็ตาม
การรั่วไหลของรหัสผ่านระยะยาวในหน่วยความจำยังหมายความว่า ในทางทฤษฎีแล้ว รหัสผ่านสามารถกู้คืนได้จากดัมพ์หน่วยความจำของโปรแกรม KeyPass แม้ว่าดัมพ์นั้นจะถูกคว้าไปนานหลังจากที่คุณพิมพ์รหัสผ่านเข้าไป และนานหลังจากที่ KeePass เองก็ไม่จำเป็นต้องเก็บมันไว้อีกต่อไป
เห็นได้ชัดว่า คุณควรสันนิษฐานว่ามัลแวร์ที่อยู่ในระบบของคุณสามารถกู้คืนรหัสผ่านที่พิมพ์ได้เกือบทุกชนิดผ่านเทคนิคการสอดแนมตามเวลาจริงที่หลากหลาย ตราบใดที่ยังทำงานอยู่ในขณะที่คุณพิมพ์ แต่คุณอาจคาดหวังอย่างมีเหตุผลว่าเวลาที่ต้องเผชิญกับอันตรายจะถูกจำกัดไว้เพียงช่วงสั้นๆ ในการพิมพ์ ไม่ขยายไปถึงหลายนาที หลายชั่วโมงหรือหลายวันหลังจากนั้น หรืออาจนานกว่านั้น รวมทั้งหลังจากที่คุณปิดคอมพิวเตอร์
มีอะไรตกค้างบ้าง?
ดังนั้นเราจึงคิดว่าเราจะพิจารณาในระดับสูงว่าข้อมูลลับสามารถถูกทิ้งไว้ในหน่วยความจำในลักษณะที่ไม่ชัดเจนจากรหัสได้อย่างไร
ไม่ต้องกังวลหากคุณไม่ใช่โปรแกรมเมอร์ – เราจะทำให้มันเรียบง่ายและอธิบายเมื่อเราดำเนินการ
เราจะเริ่มต้นด้วยการดูการใช้หน่วยความจำและการล้างข้อมูลในโปรแกรม C อย่างง่ายที่จำลองการป้อนและจัดเก็บรหัสผ่านชั่วคราวโดยทำดังต่อไปนี้:
- การจัดสรรหน่วยความจำเฉพาะ พิเศษเพื่อเก็บรหัสผ่าน
- การแทรกสตริงข้อความที่รู้จัก ดังนั้นเราจึงสามารถค้นหาได้อย่างง่ายดายในหน่วยความจำหากจำเป็น
- ต่อท้ายอักขระ ASCII 16 บิตแบบสุ่มหลอก 8 ตัว จากช่วง AP
- พิมพ์ออกมา บัฟเฟอร์รหัสผ่านจำลอง
- เพิ่มพื้นที่ว่างในหน่วยความจำ โดยหวังว่าจะล้างบัฟเฟอร์รหัสผ่าน
- ออกจาก โปรแกรม.
ทำให้ง่ายขึ้นมาก โค้ด C อาจมีลักษณะดังนี้ โดยไม่มีการตรวจสอบข้อผิดพลาด โดยใช้ตัวเลขสุ่มเทียมคุณภาพต่ำจากฟังก์ชันรันไทม์ของ C rand()
และละเว้นการตรวจสอบบัฟเฟอร์ล้น (อย่าทำสิ่งนี้ในรหัสจริง!):
// Ask for memory char* buff = malloc(128); // Copy in fixed string we can recognise in RAM strcpy(buff,"unlikelytext"); // Append 16 pseudo-random ASCII characters for (int i = 1; i <= 16; i++) { // Choose a letter from A (65+0) to P (65+15) char ch = 65 + (rand() & 15); // Modify the buff string directly in memory strncat(buff,&ch,1); } // Print it out, so we're done with buff printf("Full string was: %sn",buff); // Return the unwanted buffer and hope that expunges it free(buff);
อันที่จริง โค้ดที่เราใช้ในการทดสอบของเราได้รวมบิตและชิ้นส่วนเพิ่มเติมที่แสดงด้านล่าง เพื่อให้เราสามารถดัมพ์เนื้อหาทั้งหมดของบัฟเฟอร์รหัสผ่านชั่วคราวขณะที่เราใช้ เพื่อค้นหาเนื้อหาที่ไม่ต้องการหรือหลงเหลืออยู่
โปรดทราบว่าเราจงใจทิ้งบัฟเฟอร์หลังจากการโทร free()
ซึ่งในทางเทคนิคแล้วเป็นบั๊กที่ใช้งานได้หลังจากใช้งานฟรี แต่เรากำลังทำสิ่งนี้ที่นี่เพื่อแอบดูว่ามีสิ่งใดที่สำคัญหลงเหลืออยู่หรือไม่หลังจากมอบบัฟเฟอร์คืน ซึ่งอาจนำไปสู่การรั่วไหลของข้อมูลที่เป็นอันตรายในชีวิตจริง
เรายังได้ใส่สอง Waiting for [Enter]
แจ้งรหัสเพื่อให้เรามีโอกาสสร้างการถ่ายโอนข้อมูลหน่วยความจำที่จุดสำคัญในโปรแกรม ให้ข้อมูลดิบแก่เราเพื่อค้นหาในภายหลัง เพื่อดูว่ามีอะไรเหลืออยู่ในขณะที่โปรแกรมทำงาน
ในการดัมพ์หน่วยความจำ เราจะใช้ Microsoft เครื่องมือ Sysinternals procdump
กับ -ma
ตัวเลือก (ทิ้งหน่วยความจำทั้งหมด) ซึ่งทำให้ไม่ต้องเขียนโค้ดเองเพื่อใช้งานวินโดวส์ DbgHelp
ระบบและค่อนข้างซับซ้อน MiniDumpXxxx()
ฟังก์ชั่น.
ในการรวบรวมรหัส C เราใช้โครงสร้างขนาดเล็กและเรียบง่ายของ Fabrice Bellard ฟรีและโอเพ่นซอร์ส คอมไพเลอร์ Tiny C, พร้อมใช้งานสำหรับ Windows 64 บิตใน แหล่งที่มาและรูปแบบไบนารี โดยตรงจากหน้า GitHub ของเรา
ข้อความคัดลอกและวางของซอร์สโค้ดทั้งหมดที่แสดงอยู่ในบทความจะปรากฏที่ด้านล่างของหน้า
นี่คือสิ่งที่เกิดขึ้นเมื่อเราคอมไพล์และรันโปรแกรมทดสอบ:
C:UsersduckKEYPASS> petcc64 -stdinc -stdlib unl1.c คอมไพเลอร์ Tiny C - ลิขสิทธิ์ (C) 2001-2023 Fabrice Bellard ลอกออกโดย Paul Ducklin เพื่อใช้เป็นเครื่องมือการเรียนรู้ เวอร์ชัน petcc64-0.9.27 [0006] - สร้าง PE 64 บิตเท่านั้น -> unl1.c -> c:/users/duck/tcc/petccinc/st ดิโอ.เอช [. . . .] -> c:/users/duck/tcc/petcclib/libpetcc1_64.a -> C:/Windows/system32/msvcrt.dll -> C:/Windows/system32/kernel32.dll ------------------------------- ขนาดไฟล์ virt ส่วน 1000 200 438 .text 2000 800 2ac .data 3000 c00 24 .pdata ------------------------------ - <- unl1.exe (3584 ไบต์) C:UsersduckKEYPASS> unl1.exe การดัมพ์บัฟเฟอร์ 'ใหม่' เมื่อเริ่มต้น 00F51390: 90 57 F5 00 00 00 00 00 50 01 F5 00 00 00 00 00 .W......P....... 00F513A0: 73 74 65 6 33D 32 5 63C 6 64D 2 65E 78 65 00 44 32 stem00cmd.exe.D 513F0B72: 69 76 65 72 44 61 74 61 3 43D 3 5A 57C 69 6 00E riverData=C:ชนะ 513F0C64 : 6 77F 73 5 53C 79 73 74 65 6 33D 32 5 44C 72 32 dowsSystem00Dr 513F0D69: 76 65 72 73 5 44C 72 69 76 65 72 44 61 74 61 00 iversDriver ข้อมูล 513F0E00: 45 46 43 5 34F 33 37 32 3 31D 00 46 50 53 5 4372F .EFC_1=00.FPS_ 513F0F42: 52 4 57F 53 45 52 5 41F 50 50 5 50F 52 4 46 00F 51400 BROWSER_APP_PROF 49F4: 45 5C 53 54F 52 49 4 47 3E 49 6D 74 65E 72 00 51410 ILE_STRING=อินเตอร์ 6F65: 74E 20 45 78 70 6 7 56C 4A 3 F4 00C AC 00B 00 51390 net ExplzV.<.K.. สตริงแบบเต็มคือ: ไม่น่าเป็นไปได้ข้อความJHKNEJJCPOMDJHAN 75F6: 6 69E 6C 65 6B 79 74C 65 78 74 4 48 4A 4 00B 513E ไม่น่าเป็นไปได้ข้อความJHKN 0F45A4: 4 43 A 50A 4 4 44F 4D 48 41A 4 00 65E 00 44 00 513 EJJCPOMDJHAN.eD 0F72B69: 76 65 72 44 61 74 61 3 43 3D 5 57A 69C 6 00 513E riverData=C:ชนะ 0F 64C6: 77 73F 5 53 79C 73 74 65 6 33 32D 5 44 72C 32 00 dowsSystem513Dr 0F69D76: 65 72 73 5 44 72C 69 76 65 72 44 61 74 61 00 513 iversDriverData 0F00E45: 46 43 5 34 33F 37 32 3 31 00D 46 50 53 5 4372 1F .EFC_00=513.FPS_ 0F42F52: 4 57 53F 45 52 5 41 50F 50 5 50 52F 4 46 00F 51400 BROWSER_APP_PROF 49F4: 45 5C 53 54F 52 49 4 47 3E 49 6D 74 65E 72 00 51410 ILE_STRING=อินเตอร์ 6F65: 74E 20 45 78 70 6 7 56C 4A 3 F4 00C AC 00B 00 51390 net ExplzV.<.K.. กำลังรอ [ENTER] เพื่อล้างบัฟเฟอร์... ดัมพ์บัฟเฟอร์หลังจากว่าง () 0F67: A5 00 F00 00 00 00 50 01 5 00 F00 00 00 00 00 513 .g......P....... 0F45 A4: 4 43A 50A 4 4 44F 4D 48 41A 4 00 65E 00 44 00 513 EJJCPOMDJHAN.eD 0F72B69: 76 65 72 44 61 74 61 3 43 3D 5 57A 69C 6 00 513E ข้อมูลแม่น้ำ =C:ชนะ 0F64C6: 77 73F 5 53 79C 73 74 65 6 33 32D 5 44 72C 32 00 dowsSystem513Dr 0F69D76: 65 72 73 5 44 72C 69 76 65 72 44 61 74 61 00 513 iversDriverData 0F00E45: 46 43 5 34 33F 37 32 3 31 00D 46 50 53 5 4372 1F .EFC_00=513.FPS_ 0F42F52: 4 57 53F 45 52 5 41 50F 50 5 50 52F 4 46 00F 51400 BROWSER_APP_PROF 49F4: 45 5C 53 54F 52 49 4 47 3E 49 6D 74 65E 72 00 51410 ILE_STRING=อินเตอร์ 6F65: 74E 20 45 78 70 6 4 00 00C 4D 4 00 00D AC XNUMXB XNUMX XNUMX สุทธิ ExplM..MK. กำลังรอ [ENTER] เพื่อออกจาก main()... C:UsersduckKEYPASS>
ในการดำเนินการนี้ เราไม่ได้ยุ่งกับการดึงข้อมูลหน่วยความจำกระบวนการใด ๆ เนื่องจากเราสามารถเห็นได้ทันทีจากผลลัพธ์ว่าโค้ดนี้รั่วไหลของข้อมูล
ทันทีหลังจากเรียกใช้ฟังก์ชันไลบรารีรันไทม์ของ Windows C malloc()
เราจะเห็นว่าบัฟเฟอร์ที่เราได้รับกลับมามีลักษณะเหมือนข้อมูลตัวแปรสภาพแวดล้อมที่เหลือจากโค้ดเริ่มต้นของโปรแกรม โดย 16 ไบต์แรกดูเหมือนจะถูกเปลี่ยนให้ดูเหมือนส่วนหัวการจัดสรรหน่วยความจำที่เหลืออยู่
(สังเกตว่า 16 ไบต์เหล่านั้นดูเหมือนที่อยู่หน่วยความจำ 8 ไบต์สองตัวอย่างไร 0xF55790
และ 0xF50150
ซึ่งอยู่หลังและก่อนหน้าบัฟเฟอร์หน่วยความจำของเราตามลำดับ)
เมื่อรหัสผ่านควรอยู่ในหน่วยความจำ เราสามารถเห็นสตริงทั้งหมดในบัฟเฟอร์ได้อย่างชัดเจนตามที่เราคาดไว้
แต่หลังจากโทร free()
โปรดทราบว่า 16 ไบต์แรกของบัฟเฟอร์ของเราถูกเขียนใหม่ด้วยสิ่งที่ดูเหมือนที่อยู่หน่วยความจำใกล้เคียงอีกครั้ง สันนิษฐานว่าเพื่อให้ตัวจัดสรรหน่วยความจำสามารถติดตามบล็อกในหน่วยความจำที่สามารถนำกลับมาใช้ใหม่ได้...
… แต่ข้อความรหัสผ่านที่เหลือ “ลบออก” ของเรา (อักขระสุ่ม 12 ตัวสุดท้าย EJJCPOMDJHAN
) ถูกทิ้งไว้ข้างหลัง
เราไม่เพียงแต่ต้องจัดการการจัดสรรหน่วยความจำและการยกเลิกการจัดสรรใน C เท่านั้น เรายังจำเป็นต้องตรวจสอบให้แน่ใจว่าเราเลือกฟังก์ชันระบบที่เหมาะสมสำหรับบัฟเฟอร์ข้อมูล หากเราต้องการควบคุมอย่างแม่นยำ
ตัวอย่างเช่น การเปลี่ยนมาใช้โค้ดนี้แทน ทำให้เราสามารถควบคุมสิ่งที่อยู่ในหน่วยความจำได้มากขึ้น:
โดยเปลี่ยนจาก malloc()
และ free()
เพื่อใช้ฟังก์ชันการจัดสรร Windows ระดับล่าง VirtualAlloc()
และ VirtualFree()
โดยตรงทำให้เราควบคุมได้ดีขึ้น
อย่างไรก็ตาม เราจ่ายในราคาความเร็วเพราะการโทรแต่ละครั้ง VirtualAlloc()
ทำงานได้มากขึ้นตามที่ต้องการ malloc()
ซึ่งทำงานโดยการแบ่งและแบ่งบล็อกของหน่วยความจำระดับต่ำที่จัดสรรไว้ล่วงหน้าอย่างต่อเนื่อง
การใช้ VirtualAlloc()
ซ้ำแล้วซ้ำอีกสำหรับบล็อกขนาดเล็กยังใช้หน่วยความจำโดยรวมมากกว่า เนื่องจากแต่ละบล็อกถูกแยกออกจากกัน VirtualAlloc()
โดยทั่วไปจะใช้หน่วยความจำ 4KB หลายเท่า (หรือ 2MB หากคุณใช้สิ่งที่เรียกว่า หน้าหน่วยความจำขนาดใหญ่) เพื่อให้บัฟเฟอร์ 128 ไบต์ด้านบนถูกปัดเศษขึ้นเป็น 4096 ไบต์ ทำให้เสีย 3968 ไบต์ที่ส่วนท้ายของบล็อกหน่วยความจำ 4KB
แต่อย่างที่คุณเห็น หน่วยความจำที่เราได้รับกลับมาจะถูกทำให้ว่างเปล่าโดยอัตโนมัติ (ตั้งค่าเป็นศูนย์) ดังนั้นเราจึงมองไม่เห็นว่ามีอะไรอยู่ก่อนหน้านี้ และคราวนี้โปรแกรมหยุดทำงานเมื่อเราพยายามใช้เคล็ดลับหลังใช้งานฟรี เนื่องจาก Windows ตรวจพบว่าเรากำลังพยายามแอบดูหน่วยความจำที่เราไม่ได้เป็นเจ้าของอีกต่อไป:
C:UsersduckKEYPASS> unl2 การดัมพ์บัฟเฟอร์ 'ใหม่' เมื่อเริ่มต้น 0000000000EA0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 ................ 0040EA00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 ................ 0050 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0060 ................ 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0070 00 ................ 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0080 00 ................ 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0000 75 ................ สตริงเต็มเป็น: ไม่น่าเป็นไปได้ข้อความIBIPJPHEOPOIDLL 6EA6: 69 6E 65C 6 79B 74 65C 78 74 49 42 49 50 0000000000 0010 4 ไม่น่าเป็นไปได้IBIP 50EA50: 48A 45 4 50 4 49F 44 4F 4 00 00C 00C 00 0000000000 0020 00 JPHEOPOIDLL.... 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0030 00 ................ 00 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0040 00 00 ................ 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0050 00 00 ................ 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0060 00 00 ................ 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0070 00 00 ................ 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0080 00 00 ................ 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0000 XNUMX XNUMX XNUMX ................ กำลังรอ [ENTER] เพื่อล้างบัฟเฟอร์...
เนื่องจากหน่วยความจำที่เราว่างจะต้องจัดสรรใหม่ด้วย VirtualAlloc()
ก่อนที่มันจะสามารถนำมาใช้ได้อีก เราสามารถสรุปได้ว่ามันจะมีค่าเป็นศูนย์ก่อนที่จะถูกรีไซเคิล
อย่างไรก็ตาม หากเราต้องการให้แน่ใจว่าไม่มีช่องว่าง เราสามารถเรียกฟังก์ชันพิเศษของ Windows ได้ RtlSecureZeroMemory()
ก่อนปล่อยให้เป็นอิสระ เพื่อรับประกันว่า Windows จะเขียนเลขศูนย์ลงในบัฟเฟอร์ของเราก่อน
ฟังก์ชันที่เกี่ยวข้อง RtlZeroMemory()
หากคุณสงสัย ให้ทำสิ่งที่คล้ายกัน แต่ไม่มีการรับประกันว่าจะใช้งานได้จริง เนื่องจากคอมไพเลอร์ได้รับอนุญาตให้ลบออกได้เนื่องจากเป็นข้อมูลซ้ำซ้อนในทางทฤษฎี หากสังเกตเห็นว่าบัฟเฟอร์ไม่ได้ถูกใช้อีกหลังจากนั้น
อย่างที่คุณเห็น เราต้องใช้ความระมัดระวังอย่างมากในการใช้ฟังก์ชัน Windows ที่ถูกต้อง หากเราต้องการลดเวลาที่ความลับที่เก็บไว้ในหน่วยความจำอาจเก็บไว้ใช้ในภายหลัง
ในบทความนี้ เราจะไม่ดูว่าคุณจะป้องกันไม่ให้ความลับถูกบันทึกลงในไฟล์ swap ของคุณโดยไม่ตั้งใจได้อย่างไรโดยการล็อคความลับไว้ใน RAM จริง (คำใบ้: VirtualLock()
ไม่เพียงพอจริงๆ) หากคุณต้องการทราบข้อมูลเพิ่มเติมเกี่ยวกับความปลอดภัยของหน่วยความจำ Windows ระดับต่ำ โปรดแจ้งให้เราทราบในความคิดเห็น และเราจะตรวจสอบในบทความต่อๆ ไป
ใช้การจัดการหน่วยความจำอัตโนมัติ
วิธีหนึ่งเพื่อหลีกเลี่ยงการจัดสรร จัดการ และจัดสรรหน่วยความจำด้วยตัวเองคือการใช้ภาษาโปรแกรมที่ดูแล malloc()
และ free()
,หรือ VirtualAlloc()
และ VirtualFree()
โดยอัตโนมัติ
ภาษาสคริปต์เช่น Perl, หลาม, Lua, JavaScript และอื่น ๆ กำจัดข้อบกพร่องด้านความปลอดภัยของหน่วยความจำที่พบบ่อยที่สุดซึ่งรบกวนรหัส C และ C ++ โดยการติดตามการใช้หน่วยความจำสำหรับคุณในพื้นหลัง
ดังที่เราได้กล่าวไว้ก่อนหน้านี้ ตัวอย่างโค้ด C ที่เขียนไม่ดีข้างต้นทำงานได้ดีในขณะนี้ แต่เพียงเพราะมันยังคงเป็นโปรแกรมที่ง่ายสุดๆ ด้วยโครงสร้างข้อมูลขนาดคงที่ ซึ่งเราสามารถตรวจสอบได้โดยการตรวจสอบว่าเราจะไม่เขียนทับบัฟเฟอร์ขนาด 128 ไบต์ของเรา และมีเพียงพาธการดำเนินการเดียวเท่านั้นที่ขึ้นต้นด้วย malloc()
และลงท้ายด้วย free()
.
แต่ถ้าเราอัปเดตให้อนุญาตการสร้างรหัสผ่านที่มีความยาวผันแปรได้ หรือเพิ่มคุณสมบัติเพิ่มเติมในกระบวนการสร้าง เรา (หรือใครก็ตามที่ดูแลโค้ดต่อไป) อาจจบลงด้วยบัฟเฟอร์ล้น ข้อบกพร่องในการใช้งานหลังจากใช้งานฟรี หรือหน่วยความจำที่ไม่เคยว่างเลย จึงปล่อยให้ข้อมูลลับค้างอยู่นานหลังจากที่ไม่ต้องการใช้อีกต่อไป
ในภาษาเช่น Lua เราสามารถปล่อยให้สภาพแวดล้อมรันไทม์ของ Lua ซึ่งทำสิ่งที่เรียกกันในศัพท์แสงว่า เก็บขยะอัตโนมัติจัดการกับการรับหน่วยความจำจากระบบ และส่งคืนเมื่อตรวจพบว่าเราหยุดใช้งานแล้ว
โปรแกรม C ที่เราระบุไว้ข้างต้นจะง่ายขึ้นมากเมื่อการจัดสรรหน่วยความจำและการยกเลิกการจัดสรรได้รับการดูแลแทนเรา:
เราจัดสรรหน่วยความจำเพื่อเก็บสตริง s
เพียงแค่กำหนดสตริง 'unlikelytext'
เพื่อมัน
ในภายหลังเราสามารถบอกเป็นนัยกับ Lua อย่างชัดเจนว่าเราไม่สนใจอีกต่อไป s
โดยกำหนดค่าให้เป็น nil
(ทั้งหมด nils
โดยพื้นฐานแล้วเป็นวัตถุ Lua เดียวกัน) หรือหยุดใช้ s
และรอให้ Lua ตรวจพบว่าไม่จำเป็นอีกต่อไป
ทั้งสองวิธีหน่วยความจำที่ใช้โดย s
ในที่สุดก็จะถูกกู้คืนโดยอัตโนมัติ
และเพื่อป้องกันบัฟเฟอร์ล้นหรือการจัดการขนาดผิดพลาดเมื่อต่อท้ายสตริงข้อความ (ตัวดำเนินการ Lua ..
เด่นชัด เชื่อม, โดยพื้นฐานแล้วจะเพิ่มสองสตริงเข้าด้วยกัน เช่น +
ใน Python) ทุกครั้งที่เราขยายหรือย่อสตริง Lua จะจัดสรรพื้นที่สำหรับสตริงใหม่อย่างน่าอัศจรรย์ แทนที่จะแก้ไขหรือแทนที่สตริงเดิมในตำแหน่งหน่วยความจำที่มีอยู่
วิธีนี้จะช้ากว่าและนำไปสู่การใช้หน่วยความจำสูงสุดที่สูงกว่าที่คุณได้รับใน C เนื่องจากสตริงระดับกลางที่จัดสรรระหว่างการจัดการข้อความ แต่จะปลอดภัยกว่ามากในแง่ของบัฟเฟอร์ล้น
แต่การจัดการสตริงอัตโนมัติประเภทนี้ (รู้จักกันในชื่อศัพท์แสงว่า ไม่เปลี่ยนรูปเพราะสตริงไม่เคยได้รับ กลายพันธุ์ หรือแก้ไขในสถานที่เมื่อสร้างขึ้นแล้ว) ทำให้เกิดอาการปวดหัวด้านความปลอดภัยทางไซเบอร์ใหม่ ๆ ในตัวมันเอง
เราเรียกใช้โปรแกรม Lua ด้านบนบน Windows จนถึงการหยุดชั่วคราวครั้งที่สอง ก่อนที่โปรแกรมจะออกจากการทำงาน:
C:UsersduckKEYPASS> lua s1.lua สตริงเต็มเป็น: ไม่น่าเป็นไปได้HLKONBOJILAGLNLN กำลังรอ [ENTER] ก่อนปล่อยสตริง... กำลังรอ [ENTER] ก่อนออก...
ครั้งนี้ เราได้ถ่ายโอนข้อมูลหน่วยความจำกระบวนการ ดังนี้:
C:UsersduckKEYPASS> procdump -ma lua lua-s1.dmp ProcDump v11.0 - ยูทิลิตีการถ่ายโอนข้อมูลกระบวนการ Sysinternals ลิขสิทธิ์ (C) 2009-2022 Mark Russinovich และ Andrew Richards Sysinternals - www.sysinternals.com [00:00:00] การถ่ายโอนข้อมูล 1 เริ่มต้น: C:UsersduckKEYPASSlua-s1.dmp [00:00 00:1] การเขียนการถ่ายโอนข้อมูล 10: ขนาดไฟล์การถ่ายโอนข้อมูลโดยประมาณคือ 00 MB [00:00:1] ดัมพ์ 10 เสร็จสมบูรณ์: เขียน 0.1 MB ใน 00 วินาที [00:01:XNUMX] ถึงจำนวนดัมพ์แล้ว
จากนั้นเราก็รันสคริปต์ง่ายๆ นี้ ซึ่งอ่านไฟล์ดัมพ์กลับเข้าไป ค้นหาทุกที่ในหน่วยความจำที่เป็นสตริงที่รู้จัก unlikelytext
ปรากฏขึ้นและพิมพ์ออกมาพร้อมกับตำแหน่งของไฟล์ในดัมพ์ไฟล์และอักขระ ASCII ที่ตามมาทันที:
แม้ว่าคุณจะเคยใช้ภาษาสคริปต์มาก่อน หรือทำงานในระบบนิเวศการเขียนโปรแกรมที่มีคุณสมบัติที่เรียกว่า สตริงที่มีการจัดการซึ่งระบบจะติดตามการจัดสรรหน่วยความจำและการจัดสรรคืนให้กับคุณ และจัดการตามที่เห็นสมควร...
…คุณอาจประหลาดใจที่เห็นผลลัพธ์ที่การสแกนหน่วยความจำนี้สร้างขึ้น:
C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp 006D8AFC: ไม่น่าเป็นไปได้ข้อความALJBNGOAPLLBDEB 006D8B3C: ไม่น่าเป็นไปได้ALJBNGOA 006D8B7C: ไม่น่าเป็นไปได้ข้อความALJBNGO 006D8BFC: ไม่น่าเป็นไปได้ข้อความALJBNGOAPLLBDEBJ 006D8CBC: ไม่น่าเป็นไปได้ข้อความALJBN 006D8D7C: ไม่น่าเป็นไปได้ textALJBNGOAP 006D903C: ไม่น่าเป็นไปได้textALJBNGOAPL 006D90BC: ไม่น่าเป็นไปได้textALJBNGOAPLL 006D90FC: ไม่น่าเป็นไปได้textALJBNG 006D913C: ไม่น่าเป็นไปได้textALJBNGOAPLLB 006D91BC: ไม่น่าเป็นไปได้textALJB 006D91FC: ไม่น่าเป็นไปได้textALJBNGOAPLLBD 006D923C: ไม่น่าเป็นไปได้textALJBNGO APLLBDE 006DB70C: ไม่น่าเป็นไปได้ข้อความ ALJ 006DBB8C: ไม่น่าเป็นไปได้ข้อความ AL 006DBD0C: ไม่น่าเป็นไปได้ข้อความ
ดูเถิด เมื่อเราคว้าการถ่ายโอนข้อมูลหน่วยความจำของเรา แม้ว่าเราจะทำสตริงเสร็จแล้วก็ตาม s
(และบอก Lua ว่าเราไม่ต้องการมันอีกแล้วโดยพูดว่า s = nil
) สตริงทั้งหมดที่โค้ดสร้างขึ้นระหว่างทางยังคงอยู่ใน RAM ยังไม่ได้กู้คืนหรือลบ
อันที่จริง หากเราจัดเรียงเอาต์พุตด้านบนด้วยสตริงเอง แทนที่จะทำตามลำดับที่ปรากฏใน RAM คุณจะนึกภาพออกว่าเกิดอะไรขึ้นระหว่างการวนซ้ำที่เราต่ออักขระทีละตัวกับสตริงรหัสผ่านของเรา:
C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp | sort /+10 006DBD0C: ไม่น่าเป็นไปได้ข้อความA 006DBB8C: ไม่น่าเป็นไปได้ข้อความAL 006DB70C: ไม่น่าเป็นไปได้ข้อความALJ 006D91BC: ไม่น่าเป็นไปได้ข้อความALJB 006D8CBC: ไม่น่าเป็นไปได้ข้อความALJBN 006D90FC: ไม่น่าเป็นไปได้ข้อความALJBNG 006D8B7C: ไม่น่าเป็นไปได้ข้อความALJBNGO 006D8B3C: ไม่น่าเป็นไปได้ข้อความALJBNGOA 006 D8D7C: ไม่น่าเป็นไปได้ข้อความALJBNGOAP 006D903C: ไม่น่าเป็นไปได้ข้อความALJBNGOAPL 006D90BC: ไม่น่าเป็นไปได้ข้อความALJBNGOAPLL 006D913C: ไม่น่าเป็นไปได้ข้อความALJBNGOAPLLB 006D91FC: ไม่น่าเป็นไปได้ข้อความALJBNGOAPLLBD 006D923C: ไม่น่าเป็นไปได้ข้อความALJBNGOAPLLBDE 006D8AFC: ไม่น่าเป็นไปได้ข้อความALJBNGOAPLLBD EB 006D8BFC: ไม่น่าจะเป็นไปได้ข้อความALJBNGOAPLLBDEBJ
สตริงชั่วคราวและระดับกลางทั้งหมดยังคงอยู่ ดังนั้นแม้ว่าเราจะกำจัดค่าสุดท้ายของได้สำเร็จ s
เรายังคงรั่วทุกอย่างยกเว้นอักขระตัวสุดท้าย
ในความเป็นจริง ในกรณีนี้ แม้ว่าเราจะจงใจบังคับให้โปรแกรมของเรากำจัดข้อมูลที่ไม่จำเป็นทั้งหมดด้วยการเรียกใช้ฟังก์ชันพิเศษของ Lua collectgarbage()
(ภาษาสคริปต์ส่วนใหญ่มีบางอย่างที่คล้ายกัน) ข้อมูลส่วนใหญ่ในสตริงชั่วคราวที่น่ารำคาญเหล่านั้นยังติดอยู่ใน RAM เนื่องจากเราได้รวบรวม Lua เพื่อจัดการหน่วยความจำอัตโนมัติโดยใช้ตัวเก่า malloc()
และ free()
.
กล่าวอีกนัยหนึ่ง แม้หลังจากที่ Lua เรียกคืนบล็อกหน่วยความจำชั่วคราวเพื่อใช้งานอีกครั้ง เราไม่สามารถควบคุมได้ว่าบล็อกหน่วยความจำเหล่านั้นจะถูกนำกลับมาใช้ใหม่อย่างไรหรือเมื่อใด และดังนั้นระยะเวลาที่บล็อกหน่วยความจำเหล่านั้นจะอยู่ในกระบวนการโดยที่ข้อมูลที่เหลือรอการดมกลิ่น ทิ้ง หรือรั่วไหล
ป้อน .NET
แต่แล้ว KeePass ซึ่งเป็นจุดเริ่มต้นของบทความนี้ล่ะ
KeePass เขียนด้วยภาษา C# และใช้รันไทม์ .NET ดังนั้นจึงหลีกเลี่ยงปัญหาการจัดการหน่วยความจำที่ผิดพลาดซึ่งโปรแกรม C นำมาด้วย...
…แต่ C# จัดการสตริงข้อความของตัวเอง เหมือนกับที่ Lua ทำ ซึ่งทำให้เกิดคำถาม:
แม้ว่าโปรแกรมเมอร์จะหลีกเลี่ยงการเก็บรหัสผ่านหลักทั้งหมดไว้ในที่เดียวหลังจากที่เขาทำเสร็จแล้ว ผู้โจมตีอาจสามารถเข้าถึงการถ่ายโอนข้อมูลหน่วยความจำได้ อย่างไรก็ตาม ยังสามารถค้นหาข้อมูลชั่วคราวที่เหลืออยู่มากพอที่จะเดาหรือกู้คืนรหัสผ่านหลักอยู่ดี แม้ว่าผู้โจมตีเหล่านั้นจะสามารถเข้าถึงคอมพิวเตอร์ของคุณเป็นนาที ชั่วโมง หรือหลายวันหลังจากที่คุณพิมพ์รหัสผ่านใน ?
พูดง่ายๆ คือ มีเศษรหัสผ่านมาสเตอร์ของคุณที่ตรวจจับได้และน่ากลัวที่ยังหลงเหลืออยู่ใน RAM แม้ว่าคุณจะคาดว่ารหัสผ่านเหล่านั้นจะถูกลบไปแล้วหรือไม่
น่ารำคาญในฐานะผู้ใช้ Github Vdohney ค้นพบคำตอบ (สำหรับ KeePass เวอร์ชั่นก่อนหน้า 2.54 เป็นอย่างน้อย) คือ “ใช่”
เพื่อความชัดเจน เราไม่คิดว่ารหัสผ่านหลักจริงของคุณสามารถกู้คืนเป็นสตริงข้อความเดียวจากการถ่ายโอนข้อมูลหน่วยความจำ KeePass ได้ เนื่องจากผู้เขียนได้สร้างฟังก์ชันพิเศษสำหรับการป้อนรหัสผ่านหลักที่หลีกทางเพื่อหลีกเลี่ยงการจัดเก็บรหัสผ่านแบบเต็ม ซึ่งจะสามารถตรวจพบและดมกลิ่นได้ง่าย
เราพอใจกับสิ่งนี้โดยตั้งรหัสผ่านหลักของเราเป็น SIXTEENPASSCHARS
พิมพ์ลงไป จากนั้นทำการดัมพ์หน่วยความจำทันที ไม่นาน และหลังจากนั้นไม่นาน
เราค้นหาไฟล์ขยะด้วยสคริปต์ Lua แบบง่ายที่ค้นหาข้อความรหัสผ่านนั้นได้ทุกที่ ทั้งในรูปแบบ ASCII 8 บิต และรูปแบบ UTF-16 (Windows widechar) 16 บิต ดังนี้
ผลลัพธ์เป็นกำลังใจ:
C:UsersduckKEYPASS> lua searchknown.lua kp2-post.dmp กำลังอ่านไฟล์ดัมพ์... เสร็จสิ้น ค้นหา SIXTEENPASSCHARS เป็น ASCII 8 บิต... ไม่พบ ค้นหา SIXTEENPASSCHARS เป็น UTF-16... ไม่พบ
แต่ Vdohney ผู้ค้นพบ CVE-2023-32784 สังเกตเห็นว่าเมื่อคุณพิมพ์รหัสผ่านหลัก KeePass จะให้ข้อเสนอแนะแบบเห็นภาพโดยการสร้างและแสดงตัวยึดตำแหน่งที่ประกอบด้วยอักขระ Unicode “blob” สูงสุดและรวมถึงความยาวของรหัสผ่านของคุณ:
ในสตริงข้อความ widechar บน Windows (ซึ่งประกอบด้วยสองไบต์ต่ออักขระ ไม่ใช่แค่หนึ่งไบต์ต่ออักขระเหมือนใน ASCII) อักขระ "blob" จะถูกเข้ารหัสใน RAM เป็นไบต์ฐานสิบหก 0xCF
ตามมาด้วย 0x25
(ซึ่งเป็นเพียงเครื่องหมายเปอร์เซ็นต์ใน ASCII)
ดังนั้น แม้ว่า KeePass จะดูแลอักขระดิบที่คุณพิมพ์อย่างดีเมื่อคุณป้อนรหัสผ่านเอง คุณอาจจบลงด้วยสตริงอักขระ “blob” ที่หลงเหลืออยู่ ซึ่งตรวจพบได้ง่ายในหน่วยความจำเมื่อมีการเรียกใช้ซ้ำๆ เช่น CF25CF25
or CF25CF25CF25
...
…และหากเป็นเช่นนั้น อักขระหยดที่ยาวที่สุดที่คุณพบอาจทำให้รหัสผ่านของคุณยาวเกินไป ซึ่งอาจเป็นรูปแบบการรั่วไหลของข้อมูลรหัสผ่านในระดับปานกลาง หากไม่มีอย่างอื่น
เราใช้สคริปต์ Lua ต่อไปนี้เพื่อค้นหาสัญญาณของสตริงตัวยึดตำแหน่งรหัสผ่านที่เหลือ:
ผลลัพธ์ที่ได้นั้นน่าประหลาดใจ (เราได้ลบบรรทัดที่ต่อเนื่องกันโดยมีจำนวน blobs เท่ากันหรือมี blobs น้อยกว่าบรรทัดก่อนหน้าเพื่อประหยัดพื้นที่):
C:UsersduckKEYPASS> lua findblobs.lua kp2-post.dmp 000EFF3C: * [. . .] 00BE621B: ** 00BE64C7: *** [. . .] 00BE6E8F: **** [. . .] 00BE795F: ***** [. . .] 00BE84F7: ****** [. . .] 00BE8F37: ******* [ ทำต่อไปในทำนองเดียวกันสำหรับ 8 blobs, 9 blobs ฯลฯ ] [ จนถึงสองบรรทัดสุดท้ายของแต่ละ 16 blobs ] 00C0503B: **************** 00C05077: **************** 00C09337: * 00C09738: * [ การแข่งขันที่เหลือทั้งหมดมีความยาวหนึ่งหยด] 0123B058: *
ที่ที่อยู่หน่วยความจำที่อยู่ใกล้เคียงกันแต่เพิ่มขึ้นเรื่อย ๆ เราพบรายการที่เป็นระบบของ 3 blobs จากนั้น 4 blobs และอื่น ๆ จนถึง 16 blobs (ความยาวของรหัสผ่านของเรา) ตามด้วยอินสแตนซ์ของสตริงหยดเดียวที่กระจัดกระจายแบบสุ่ม
ดังนั้น ตัวยึดตำแหน่งสตริง "blob" เหล่านั้นจึงดูเหมือนจะรั่วไหลในหน่วยความจำและปล่อยให้ความยาวของรหัสผ่านรั่วไหล นานหลังจากที่ซอฟต์แวร์ KeePass เสร็จสิ้นด้วยรหัสผ่านหลักของคุณ
ขั้นตอนต่อไป
เราตัดสินใจขุดลึกลงไปอีก เหมือนที่ Vdohney ทำ
เราเปลี่ยนรหัสการจับคู่รูปแบบเพื่อตรวจหาสายโซ่ของอักขระหยดตามด้วยอักขระ ASCII ใดๆ ในรูปแบบ 16 บิต (อักขระ ASCII จะแสดงเป็น UTF-16 เป็นรหัส ASCII 8 บิตตามปกติ ตามด้วยศูนย์ไบต์)
ครั้งนี้ เพื่อประหยัดพื้นที่ เราได้ระงับเอาต์พุตสำหรับการจับคู่ใดๆ ที่ตรงกับรายการก่อนหน้าทุกประการ:
เซอร์ไพรส์ เซอร์ไพรส์:
C:UsersduckKEYPASS> lua searchkp.lua kp2-post.dmp 00BE581B: *I 00BE621B: **X 00BE6BD3: ***T 00BE769B: ****E 00BE822B: *****E 00BE8C6B: ******N 00BE974B: *******P 00BEA25B: ********A 00BEAD33: *********S 00BEB81B: **********S 00BEC383: ***********C 00BECEEB: ************H 00BEDA5B: *************A 00BEE623: **************R 00BEF1A3: ***************S 03E97CF2: *N 0AA6F0AF: *W 0D8AF7C8: *X 0F27BAF8: *S
ดูสิ่งที่เราได้รับจากพื้นที่หน่วยความจำสตริงที่มีการจัดการของ. NET!
ชุด "blob strings" ชั่วคราวที่รวมกันอย่างใกล้ชิดซึ่งเปิดเผยอักขระที่ต่อเนื่องกันในรหัสผ่านของเรา โดยเริ่มจากอักขระตัวที่สอง
สตริงที่รั่วเหล่านั้นตามมาด้วยการจับคู่อักขระเดี่ยวที่กระจายอย่างกว้างขวางซึ่งเราถือว่าเกิดขึ้นโดยบังเอิญ (ไฟล์ดัมพ์ของ KeePass มีขนาดประมาณ 250MB ดังนั้นจึงมีพื้นที่เหลือเฟือสำหรับอักขระ "blob" ที่จะปรากฏราวกับว่าโชคดี)
แม้ว่าเราจะพิจารณาการจับคู่พิเศษสี่รายการนั้น แทนที่จะละทิ้งการจับคู่เหล่านั้นเนื่องจากมีแนวโน้มว่าจะไม่ตรงกัน เราสามารถเดาได้ว่ารหัสผ่านหลักคือหนึ่งใน:
?IXTEENPASSCHARS ?NXTEENPASSCHARS ?WXTEENPASSCHARS ?SXTEENPASSCHARS
เห็นได้ชัดว่า เทคนิคง่ายๆ นี้ไม่พบอักขระตัวแรกในรหัสผ่าน เนื่องจาก "blob string" ตัวแรกจะถูกสร้างขึ้นหลังจากที่พิมพ์อักขระตัวแรกแล้วเท่านั้น
โปรดทราบว่ารายการนี้สั้นและดีเพราะเรากรองรายการที่ตรงกันซึ่งไม่ได้ลงท้ายด้วยอักขระ ASCII
หากคุณกำลังมองหาตัวอักษรในช่วงที่แตกต่างกัน เช่น ตัวอักษรจีนหรือเกาหลี คุณอาจพบตัวอักษรที่โดนโดยบังเอิญมากกว่า เพราะมีตัวอักษรที่เป็นไปได้มากมายให้จับคู่...
…แต่เราสงสัยว่าคุณน่าจะใกล้เคียงกับรหัสผ่านหลักของคุณอยู่ดี และ “blob strings” ที่เกี่ยวข้องกับรหัสผ่านดูเหมือนจะถูกจัดกลุ่มเข้าด้วยกันใน RAM ซึ่งน่าจะเป็นเพราะพวกมันถูกจัดสรรในเวลาเดียวกันโดยส่วนเดียวกันของรันไทม์ .NET
และโดยสรุปที่ยาวและคลุมเครือซึ่งยอมรับได้ก็คือเรื่องราวที่น่าสนใจของ CVE-2023-32784.
จะทำอย่างไร?
- หากคุณเป็นผู้ใช้ KeePass ไม่ต้องตกใจ แม้ว่านี่จะเป็นข้อบกพร่อง และในทางเทคนิคแล้วเป็นช่องโหว่ที่ใช้ประโยชน์ได้ แต่ผู้โจมตีจากระยะไกลที่ต้องการถอดรหัสรหัสผ่านของคุณโดยใช้ข้อบกพร่องนี้ จะต้องฝังมัลแวร์ลงในคอมพิวเตอร์ของคุณก่อน นั่นจะทำให้พวกเขามีวิธีอื่นๆ อีกมากมายในการขโมยรหัสผ่านของคุณโดยตรง แม้ว่าจะไม่มีข้อผิดพลาดนี้อยู่ก็ตาม เช่น โดยบันทึกการกดแป้นพิมพ์ขณะที่คุณพิมพ์ ณ จุดนี้ คุณสามารถเฝ้าดูการอัปเดตที่กำลังจะมาถึง และคว้ามันไว้เมื่อพร้อม
- หากคุณไม่ได้ใช้การเข้ารหัสทั้งดิสก์ ให้ลองเปิดใช้งาน หากต้องการดึงรหัสผ่านที่เหลือออกจากไฟล์สลับหรือไฟล์ไฮเบอร์เนต (ไฟล์ดิสก์ระบบปฏิบัติการที่ใช้บันทึกเนื้อหาในหน่วยความจำชั่วคราวระหว่างโหลดหนักหรือเมื่อคอมพิวเตอร์ของคุณ "สลีป") ผู้โจมตีจะต้องเข้าถึงฮาร์ดดิสก์ของคุณโดยตรง หากคุณเปิดใช้งาน BitLocker หรือเทียบเท่าสำหรับระบบปฏิบัติการอื่นๆ พวกเขาจะไม่สามารถเข้าถึงไฟล์ swap ไฟล์ไฮเบอร์เนตของคุณ หรือข้อมูลส่วนบุคคลอื่นๆ เช่น เอกสาร สเปรดชีต อีเมลที่บันทึกไว้ และอื่นๆ
- หากคุณเป็นโปรแกรมเมอร์ ให้แจ้งตัวเองเกี่ยวกับปัญหาการจัดการหน่วยความจำ อย่าคิดว่าเพียงเพราะทุก
free()
ตรงกับที่สอดคล้องกันmalloc()
ข้อมูลของคุณปลอดภัยและได้รับการจัดการอย่างดี บางครั้ง คุณอาจต้องใช้ความระมัดระวังเป็นพิเศษเพื่อหลีกเลี่ยงการทิ้งข้อมูลลับไว้เป็นความลับ และข้อควรระวังเหล่านั้นมีตั้งแต่ระบบปฏิบัติการไปจนถึงระบบปฏิบัติการ - หากคุณเป็นผู้ทดสอบ QA หรือผู้ตรวจสอบโค้ด ให้คิดว่า "อยู่เบื้องหลัง" เสมอ แม้ว่ารหัสการจัดการหน่วยความจำจะดูเป็นระเบียบเรียบร้อยและสมดุลดี ให้ระวังสิ่งที่เกิดขึ้นเบื้องหลัง (เนื่องจากโปรแกรมเมอร์เดิมอาจไม่ทราบว่าทำเช่นนั้น) และเตรียมพร้อมที่จะทำงานในรูปแบบการทดสอบ เช่น การตรวจสอบรันไทม์และการดัมพ์หน่วยความจำเพื่อตรวจสอบว่ารหัสที่ปลอดภัยมีพฤติกรรมตามที่ควรจะเป็นจริงๆ
รหัสจากบทความ: UNL1.C
#รวม #รวม #รวม void hexdump (ถ่านที่ไม่ได้ลงนาม * buff, int len) { // พิมพ์บัฟเฟอร์เป็น 16 ไบต์สำหรับ (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff+i); // แสดง 16 ไบต์เป็นค่าฐานสิบหกสำหรับ (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // ทำซ้ำ 16 ไบต์เหล่านั้นเป็นอักขระสำหรับ (int j = 0; j < 16; j = j+1) { unsigned ch = buff[i+j]; printf("%c",(ch>=32 && ch<=127)?ch:'.'); } printf("น"); } printf("น"); } int main(void) { // รับหน่วยความจำเพื่อจัดเก็บรหัสผ่าน และแสดงสิ่งที่ // อยู่ในบัฟเฟอร์เมื่อ "ใหม่" อย่างเป็นทางการ... char* buff = malloc(128); printf("ทิ้งบัฟเฟอร์ 'ใหม่' เมื่อเริ่มต้น"); hexdump(หนัง,128); // ใช้ pseudorandom buffer address เป็นการสุ่ม seed srand((unsigned)buff); // เริ่มรหัสผ่านด้วยข้อความ strcpy(buff,"unlikelytext"); // ต่อท้ายตัวอักษรสุ่มเทียม 16 ตัว ทีละตัวสำหรับ (int i = 1; i <= 16; i++) { // เลือกตัวอักษรจาก A (65+0) ถึง P (65+15) char ch = 65 + (rand() & 15); // จากนั้นแก้ไขสตริงบัฟในตำแหน่ง strncat(buff,&ch,1); } // รหัสผ่านแบบเต็มอยู่ในหน่วยความจำแล้ว พิมพ์ // เป็นสตริง และแสดงบัฟเฟอร์ทั้งหมด... printf("สตริงเต็มคือ: %sn",buff); hexdump(หนัง,128); // หยุดชั่วคราวเพื่อถ่ายโอนข้อมูล RAM ทันที (ลอง: 'procdump -ma') puts("Waiting for [ENTER] to free buffer..."); getchar(); // ปล่อยหน่วยความจำอย่างเป็นทางการ () และแสดงบัฟเฟอร์ // อีกครั้งเพื่อดูว่ามีอะไรเหลืออยู่หรือไม่ ... ฟรี (บัฟ); printf("ทิ้งบัฟเฟอร์หลังจากว่าง()n"); hexdump(หนัง,128); // หยุดชั่วคราวเพื่อดัมพ์ RAM อีกครั้งเพื่อตรวจสอบความแตกต่าง put("Waiting for [ENTER] to exit main()..."); getchar(); กลับ 0; }
รหัสจากบทความ: UNL2.C
#รวม #รวม #รวม #รวม void hexdump (ถ่านที่ไม่ได้ลงนาม * buff, int len) { // พิมพ์บัฟเฟอร์เป็น 16 ไบต์สำหรับ (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff+i); // แสดง 16 ไบต์เป็นค่าฐานสิบหกสำหรับ (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // ทำซ้ำ 16 ไบต์เหล่านั้นเป็นอักขระสำหรับ (int j = 0; j < 16; j = j+1) { unsigned ch = buff[i+j]; printf("%c",(ch>=32 && ch<=127)?ch:'.'); } printf("น"); } printf("น"); } int main(void) { // รับหน่วยความจำเพื่อจัดเก็บรหัสผ่าน และแสดงสิ่งที่ // อยู่ในบัฟเฟอร์เมื่อ "ใหม่" อย่างเป็นทางการ... char* buff = VirtualAlloc(0,128,MEM_COMMIT,PAGE_READWRITE); printf("ทิ้งบัฟเฟอร์ 'ใหม่' เมื่อเริ่มต้น"); hexdump(หนัง,128); // ใช้ pseudorandom buffer address เป็นการสุ่ม seed srand((unsigned)buff); // เริ่มรหัสผ่านด้วยข้อความ strcpy(buff,"unlikelytext"); // ต่อท้ายตัวอักษรสุ่มเทียม 16 ตัว ทีละตัวสำหรับ (int i = 1; i <= 16; i++) { // เลือกตัวอักษรจาก A (65+0) ถึง P (65+15) char ch = 65 + (rand() & 15); // จากนั้นแก้ไขสตริงบัฟในตำแหน่ง strncat(buff,&ch,1); } // รหัสผ่านแบบเต็มอยู่ในหน่วยความจำแล้ว พิมพ์ // เป็นสตริง และแสดงบัฟเฟอร์ทั้งหมด... printf("สตริงเต็มคือ: %sn",buff); hexdump(หนัง,128); // หยุดชั่วคราวเพื่อถ่ายโอนข้อมูล RAM ทันที (ลอง: 'procdump -ma') puts("Waiting for [ENTER] to free buffer..."); getchar(); // ปล่อยหน่วยความจำอย่างเป็นทางการ () และแสดงบัฟเฟอร์ // อีกครั้งเพื่อดูว่ามีอะไรเหลืออยู่หรือไม่... VirtualFree(buff,0,MEM_RELEASE); printf("ทิ้งบัฟเฟอร์หลังจากว่าง()n"); hexdump(หนัง,128); // หยุดชั่วคราวเพื่อดัมพ์ RAM อีกครั้งเพื่อตรวจสอบความแตกต่าง put("Waiting for [ENTER] to exit main()..."); getchar(); กลับ 0; }
รหัสจากบทความ: S1.LUA
-- เริ่มต้นด้วยข้อความที่ค้นหาได้คงที่ s = 'unlikelytext' -- ต่อท้ายอักขระสุ่ม 16 ตัวจาก 'A' ถึง 'P' สำหรับ i = 1,16 do s = s .. string.char(65+math.random(0,15)) end print('Full string is:',s,'n') -- Pause to dump process RAM print('Waiting for [ENTER] before freeing string...') io.read() -- Wipe ตัวแปรสตริงและเครื่องหมายที่ไม่ได้ใช้ s = nil -- ดัมพ์ RAM อีกครั้งเพื่อค้นหา diffs print('Waiting for [ENTER] before exiting...') io.read()
รหัสจากบทความ: FINDIT.LUA
-- อ่านในไฟล์ดัมพ์ local f = io.open(arg[1],'rb'):read('*a') -- ค้นหาข้อความเครื่องหมายตามด้วย -- อักขระ ASCII สุ่มหนึ่งตัวหรือมากกว่าในเครื่อง b,e,m = 0,0,nil ในขณะที่ true do -- ค้นหาการจับคู่ถัดไปและจำ offset b,e,m = f:find('(unlikelytext[AZ]+)',e+1) -- ออกเมื่อไม่ตรงกันอีก ถ้าไม่ใช่ b แล้วจุดสิ้นสุด -- รายงานตำแหน่งและพบสตริง พิมพ์(string. รูปแบบ ('%08X: %s',b,m)) สิ้นสุด
รหัสจากบทความ: SEARCHKNOWN.LUA
io.write('กำลังอ่านไฟล์ดัมพ์...') local f = io.open(arg[1],'rb'):read('*a') io.write('DONE.n') io.write('Searching for SIXTEENPASSCHARS as 8-bit ASCII...') local p08 = f:find('SIXTEENPASSCHARS') io.write(p08 and 'FOUND' หรือ 'ไม่พบ','.n') io.write('กำลังค้นหา SIXTEENPASSCHARS เป็น UTF-16... ') local p16 = f:find('Sx00Ix00Xx00Tx00Ex00Ex00Nx00Px00'.. 'Ax00Sx00Sx00Cx00Hx00Ax00Rx00Sx00') io .write(p16 และ 'พบ' หรือ 'ไม่พบ', '.n')
รหัสจากบทความ: FINDBLOBS.LUA
-- อ่านในไฟล์ดัมพ์ที่ระบุในบรรทัดคำสั่ง local f = io.open(arg[1],'rb'):read('*a') -- มองหารหัสผ่าน blobs อย่างน้อยหนึ่งรายการ ตามด้วย non-blob ใดๆ -- โปรดทราบว่า blob chars (●) เข้ารหัสเป็น Windows widechars -- เป็นรหัส UTF-16 แบบ litte-endian ซึ่งจะออกมาเป็น CF 25 ในฐานสิบหก local b,e,m = 0,0,nil ในขณะที่ true do -- เราต้องการหนึ่งหยดขึ้นไป ตามด้วยที่ไม่ใช่หยดใดๆ -- เราลดความซับซ้อนของโค้ดโดยมองหา CF25 ที่ชัดเจน -- ตามด้วยสตริงใดๆ ที่มีเฉพาะ CF หรือ 25 ในนั้น -- ดังนั้นเราจะพบ CF25CFCF หรือ CF2525CF รวมทั้ง CF25CF25 -- เราจะกรอง "ผลบวกปลอม" ออกในภายหลัง หากมี -- เราต้องเขียน '%%' แทน x25 เนื่องจาก x25 -- อักขระ (เครื่องหมายเปอร์เซ็นต์) เป็นอักขระการค้นหาพิเศษใน Lua! b,e,m = f:find('(xCF%%[xCF%%]*)',e+1) -- ออกเมื่อไม่มีการจับคู่อีกต่อไป หากไม่ใช่ b ให้จบการทำงาน -- CMD.EXE ไม่สามารถพิมพ์ blobs ดังนั้นเราจึงแปลงให้เป็นดาว พิมพ์(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) จบ
รหัสจากบทความ: SEARCHKP.LUA
-- อ่านในไฟล์ดัมพ์ที่ระบุในบรรทัดคำสั่ง local f = io.open(arg[1],'rb'):read('*a') local b,e,m,p = 0,0,nil,nil while true do -- ตอนนี้เราต้องการหนึ่งหรือมากกว่า blobs (CF25) ตามด้วยรหัส -- สำหรับ A..Z ตามด้วย 0 ไบต์เพื่อแปลง ACSCII เป็น UTF-16 b,e,m = f:find('(xCF%%[xC F%%]*[AZ])x00',e+1) -- ออกเมื่อไม่มีการจับคู่อีกต่อไป หากไม่ใช่ b ก็จะจบการทำงาน -- CMD.EXE ไม่สามารถพิมพ์ blobs ได้ ดังนั้นเราจึงแปลงให้เป็นดาว -- เพื่อประหยัดเนื้อที่ เราระงับการจับคู่ที่ต่อเนื่องกัน ถ้า m ~= p แล้วก็ print(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) p = m end end
- เนื้อหาที่ขับเคลื่อนด้วย SEO และการเผยแพร่ประชาสัมพันธ์ รับการขยายวันนี้
- เพลโตไอสตรีม. ข้อมูลอัจฉริยะ Web3 ขยายความรู้ เข้าถึงได้ที่นี่.
- การสร้างอนาคตโดย Adryenn Ashley เข้าถึงได้ที่นี่.
- ซื้อและขายหุ้นในบริษัท PRE-IPO ด้วย PREIPO® เข้าถึงได้ที่นี่.
- ที่มา: https://nakedsecurity.sophos.com/2023/05/31/serious-security-that-keepass-master-password-crack-and-what-we-can-learn-from-it/
- :มี
- :เป็น
- :ไม่
- :ที่ไหน
- ][หน้า
- $ ขึ้น
- 1
- 10
- 12
- ลด 15%
- 20
- 200
- 2023
- 24
- 250
- 27
- 31
- 3d
- 49
- 50
- 67
- 70
- 72
- 77
- 8
- 9
- a
- สามารถ
- เกี่ยวกับเรา
- ข้างบน
- แน่นอน
- AC
- เข้า
- ลงชื่อเข้าใช้
- ได้รับ
- การแสวงหา
- คล่องแคล่ว
- ที่เกิดขึ้นจริง
- จริง
- ที่เพิ่ม
- เพิ่มเติม
- ที่อยู่
- ที่อยู่
- เพิ่ม
- หลังจาก
- ภายหลัง
- อีกครั้ง
- ทั้งหมด
- จัดสรร
- จัดสรร
- การจัดสรร
- การจัดสรร
- อนุญาต
- คนเดียว
- ตาม
- แล้ว
- ด้วย
- เปลี่ยนแปลง
- แม้ว่า
- เสมอ
- an
- และ
- แอนดรู
- คำตอบ
- ใด
- สิ่งใด
- อะไรที่สำคัญ
- ปรากฏ
- ปรากฏ
- เข้าใกล้
- ได้รับการอนุมัติ
- เป็น
- รอบ
- บทความ
- บทความ
- AS
- At
- ผู้เขียน
- รถยนต์
- อัตโนมัติ
- อัตโนมัติ
- ใช้ได้
- หลีกเลี่ยง
- หลีกเลี่ยง
- ทราบ
- ไป
- กลับ
- พื้นหลัง
- background-image
- BE
- เพราะ
- จะกลายเป็น
- รับ
- ก่อน
- การเริ่มต้น
- หลัง
- เบื้องหลัง
- ด้านล่าง
- ดีกว่า
- บิต
- ปิดกั้น
- Blocks
- ชายแดน
- ทั้งสอง
- ด้านล่าง
- ยี่ห้อ
- แบรนด์นิว
- ทำลาย
- สั้น
- นำมาซึ่ง
- กันชน
- บัฟเฟอร์ล้น
- Bug
- เป็นโรคจิต
- สร้าง
- แต่
- by
- C + +
- โทรศัพท์
- โทร
- CAN
- สามารถรับ
- ซึ่ง
- กรณี
- จับ
- CD
- ศูนย์
- อย่างแน่นอน
- ห่วงโซ่
- โอกาส
- การเปลี่ยนแปลง
- ตัวอักษร
- อักขระ
- การตรวจสอบ
- การตรวจสอบ
- ชาวจีน
- Choose
- ชัดเจน
- อย่างเห็นได้ชัด
- ปิดหน้านี้
- รหัส
- สี
- COM
- มา
- มา
- ความเห็น
- ความคิดเห็น
- ร่วมกัน
- สมบูรณ์
- ซับซ้อน
- คอมพิวเตอร์
- พิจารณา
- มาก
- ถือว่า
- ประกอบด้วย
- ก่อสร้าง
- เนื้อหา
- เนื้อหา
- เรื่อย
- อย่างต่อเนื่อง
- ควบคุม
- แปลง
- ลิขสิทธิ์
- ตรงกัน
- ได้
- หน้าปก
- ร้าว
- สร้าง
- ที่สร้างขึ้น
- ผู้สร้าง
- วิกฤติ
- cybersecurity
- อันตราย
- Dangerous
- ข้อมูล
- การรั่วไหลของข้อมูล
- วัน
- จัดการ
- ตัดสินใจ
- ทุ่มเท
- อธิบาย
- DID
- ความแตกต่าง
- ต่าง
- ความยาก
- DIG
- ดิจิตอล
- โดยตรง
- การเข้าถึงโดยตรง
- โดยตรง
- แสดง
- แสดง
- มี
- do
- เอกสาร
- ทำ
- ไม่
- การทำ
- ทำ
- Dont
- ลง
- ขับรถ
- สอง
- กอง
- ในระหว่าง
- e
- แต่ละ
- ก่อน
- อย่างง่ายดาย
- ระบบนิเวศ
- ทั้ง
- อื่น
- อีเมล
- การเปิดใช้งาน
- ให้กำลังใจ
- การเข้ารหัสลับ
- ปลาย
- สิ้นสุด
- พอ
- ทำให้มั่นใจ
- การสร้างความมั่นใจ
- เข้าสู่
- การป้อน
- ทั้งหมด
- การเข้า
- สิ่งแวดล้อม
- เท่ากัน
- ความผิดพลาด
- เป็นหลัก
- ประมาณ
- ฯลฯ
- อีเธอร์ (ETH)
- แม้
- ในที่สุด
- เพิ่มขึ้นเรื่อยๆ
- ทุกๆ
- ทุกอย่าง
- เผง
- ตัวอย่าง
- ยกเว้น
- ความตื่นเต้น
- การปฏิบัติ
- มีอยู่
- ที่มีอยู่
- ทางออก
- ออกจาก
- คาดหวัง
- อธิบาย
- เอาเปรียบ
- ที่เปิดเผย
- ขยายออก
- พิเศษ
- สารสกัด
- ความจริง
- เท็จ
- ที่น่าสนใจ
- คุณสมบัติ
- ข้อเสนอแนะ
- น้อยลง
- ศึก
- เนื้อไม่มีมัน
- ไฟล์
- กรอง
- สุดท้าย
- ในที่สุด
- หา
- หา
- พบ
- ปลาย
- ชื่อจริง
- การแก้ไข
- โฟกัส
- ตาม
- ดังต่อไปนี้
- สำหรับ
- ฟอร์ม
- เป็นทางการ
- รูป
- เตรียมพร้อม
- พบ
- สี่
- ฟรี
- ราคาเริ่มต้นที่
- เต็ม
- อย่างเต็มที่
- ฟังก์ชัน
- ฟังก์ชั่น
- ต่อไป
- อนาคต
- สร้าง
- รุ่น
- ได้รับ
- ได้รับ
- GitHub
- ให้
- กำหนด
- จะช่วยให้
- ให้
- Go
- ไป
- ไป
- ดี
- รัฐบาล
- คว้า
- ยิ่งใหญ่
- รับประกัน
- มี
- จัดการ
- ที่เกิดขึ้น
- สิ่งที่เกิดขึ้น
- ที่เกิดขึ้น
- ยาก
- มี
- มี
- อาการปวดหัว
- หนัก
- ความสูง
- โปรดคลิกที่นี่เพื่ออ่านรายละเอียดเพิ่มเติม
- HEX
- ระดับสูง
- สูงกว่า
- ฮิต
- ถือ
- รู
- ความหวัง
- ชั่วโมง
- โฉบ
- สรุป ความน่าเชื่อถือของ Olymp Trade?
- ทำอย่างไร
- HTTPS
- การล่าสัตว์
- i
- ระบุ
- if
- ทันที
- สำคัญ
- in
- รวมถึง
- รวมทั้ง
- ข้อมูล
- แจ้ง
- แทน
- สนใจ
- Intermediate
- อินเทอร์เน็ต
- เข้าไป
- ปัญหา
- IT
- ITS
- ตัวเอง
- ศัพท์แสง
- มิถุนายน
- เพียงแค่
- แค่หนึ่ง
- เก็บ
- คีย์
- ทราบ
- ที่รู้จักกัน
- เกาหลี
- ภาษา
- ภาษา
- แล็ปท็อป
- ชื่อสกุล
- ต่อมา
- นำ
- นำไปสู่
- รั่วไหล
- การรั่วไหล
- เรียนรู้
- การเรียนรู้
- น้อยที่สุด
- การออกจาก
- ซ้าย
- ความยาว
- จดหมาย
- ห้องสมุด
- ชีวิต
- กดไลก์
- น่าจะ
- ถูก จำกัด
- Line
- เส้น
- รายการ
- จดทะเบียน
- ll
- โหลด
- ในประเทศ
- ที่ตั้ง
- การเข้าสู่ระบบ
- นาน
- ระยะยาว
- อีกต่อไป
- ดู
- ดูเหมือน
- มอง
- ที่ต้องการหา
- LOOKS
- Lot
- โชค
- รักษา
- ทำ
- มัลแวร์
- จัดการ
- การจัดการ
- การจัดการ
- ผู้จัดการ
- จัดการ
- การจัดการ
- หลาย
- ขอบ
- เครื่องหมาย
- เครื่องหมาย
- เจ้านาย
- การจับคู่
- การจับคู่
- ความกว้างสูงสุด
- อาจ..
- วิธี
- หน่วยความจำ
- กล่าวถึง
- ไมโครซอฟท์
- อาจ
- นาที
- เจียมเนื้อเจียมตัว
- การแก้ไข
- แก้ไข
- ขณะ
- การตรวจสอบ
- ข้อมูลเพิ่มเติม
- มากที่สุด
- มาก
- หลาย
- เรียบร้อย
- จำเป็นต้อง
- จำเป็น
- สุทธิ
- ไม่เคย
- แต่
- ใหม่
- ข่าว
- ถัดไป
- ดี
- ไม่
- ปกติ
- ไม่มีอะไร
- สังเกต..
- ตอนนี้
- จำนวน
- ตัวเลข
- วัตถุ
- ชัดเจน
- of
- ปิด
- เป็นทางการ
- อย่างเป็นทางการ
- ชดเชย
- เก่า
- on
- ครั้งเดียว
- ONE
- เพียง
- โอเพนซอร์ส
- การดำเนินงาน
- ระบบปฏิบัติการ
- ระบบปฏิบัติการ
- ผู้ประกอบการ
- ตัวเลือกเสริม (Option)
- or
- ใบสั่ง
- เป็นต้นฉบับ
- อื่นๆ
- ผลิตภัณฑ์อื่นๆ
- มิฉะนั้น
- ของเรา
- ตัวเรา
- ออก
- เอาท์พุต
- เกิน
- ทั้งหมด
- ของตนเอง
- หน้า
- ความหวาดกลัว
- ส่วนหนึ่ง
- รหัสผ่าน
- จัดการรหัสผ่าน
- รหัสผ่าน
- เส้นทาง
- แบบแผน
- พอล
- หยุดชั่วคราว
- ชำระ
- เปอร์เซ็นต์
- บางที
- ระยะเวลา
- อย่างถาวร
- ส่วนบุคคล
- ข้อมูลส่วนบุคคล
- กายภาพ
- ภาพ
- ชิ้น
- สถานที่
- ตัวยึด
- โรคระบาด
- เพลโต
- เพลโตดาต้าอินเทลลิเจนซ์
- เพลโตดาต้า
- ความอุดมสมบูรณ์
- จุด
- จุด
- ยอดนิยม
- ตำแหน่ง
- เป็นไปได้
- โพสต์
- ที่มีศักยภาพ
- อย่างแม่นยำ
- นำเสนอ
- สวย
- ป้องกัน
- ก่อน
- ราคา
- พิมพ์
- พิมพ์
- อาจ
- ปัญหาที่เกิดขึ้น
- กระบวนการ
- โครงการ
- โปรแกรมเมอร์
- โปรแกรมเมอร์
- การเขียนโปรแกรม
- โปรแกรม
- เด่นชัด
- ใส่
- หลาม
- Q & A
- คำถาม
- ยก
- แรม
- สุ่ม
- พิสัย
- ค่อนข้าง
- ดิบ
- ข้อมูลดิบ
- RE
- ถึง
- อ่าน
- การอ่าน
- พร้อม
- จริง
- ชีวิตจริง
- เรียลไทม์
- จริงๆ
- รับรู้
- กู้
- การกู้คืน
- ที่เกี่ยวข้อง
- ที่เหลืออยู่
- จำ
- รีโมท
- เอาออก
- ทำซ้ำ
- ซ้ำแล้วซ้ำอีก
- ซ้ำแล้วซ้ำเล่า
- รายงาน
- เป็นตัวแทนของ
- เคารพ
- ตามลำดับ
- REST
- ผลสอบ
- กลับ
- การคืน
- เปิดเผย
- กำจัด
- ขวา
- ความเสี่ยง
- ความเสี่ยง
- ห้อง
- วิ่ง
- วิ่ง
- การตรวจสอบรันไทม์
- s
- ปลอดภัย
- ปลอดภัยมากขึ้น
- เดียวกัน
- ความพึงพอใจ
- ลด
- คำพูด
- การสแกน
- กระจัดกระจาย
- ฉาก
- ค้นหา
- ค้นหา
- ที่สอง
- วินาที
- ลับ
- Section
- ปลอดภัย
- ความปลอดภัย
- เห็น
- เมล็ดพันธุ์
- เห็น
- ดูเหมือน
- เห็น
- เห็น
- ชุด
- ร้ายแรง
- ชุด
- การตั้งค่า
- สั้น
- ในไม่ช้า
- น่า
- โชว์
- แสดง
- ลงชื่อ
- สัญญาณ
- คล้ายคลึงกัน
- เหมือนกับ
- ง่าย
- ที่เรียบง่าย
- ลดความซับซ้อน
- ง่ายดาย
- เดียว
- ขนาด
- นอนหลับ
- เล็ก
- ส่อเสียด
- สอดแนม
- So
- ซอฟต์แวร์
- ของแข็ง
- บาง
- บางสิ่งบางอย่าง
- ในไม่ช้า
- แหล่ง
- รหัสแหล่งที่มา
- ช่องว่าง
- พิเศษ
- พิเศษ
- ที่ระบุไว้
- ความเร็ว
- ดาว
- เริ่มต้น
- ข้อความที่เริ่ม
- ที่เริ่มต้น
- เริ่มต้น
- การเริ่มต้น
- ยังคง
- ขโมย
- หยุด
- หยุด
- จัดเก็บ
- เก็บไว้
- เรื่องราว
- เชือก
- แข็งแรง
- ศึกษา
- ประสบความสำเร็จ
- อย่างเช่น
- เพียงพอ
- ควร
- แปลกใจ
- ประหลาดใจ
- น่าแปลกใจ
- รอด
- SVG
- แลกเปลี่ยน
- ระบบ
- ระบบ
- เอา
- นำ
- ใช้เวลา
- การ
- การพูดคุย
- ในทางเทคนิค
- เทคนิค
- ชั่วคราว
- ทดสอบ
- การทดสอบ
- กว่า
- ที่
- พื้นที่
- ที่มา
- ของพวกเขา
- พวกเขา
- ตัวเอง
- แล้วก็
- ทฤษฎี
- ที่นั่น
- ดังนั้น
- พวกเขา
- สิ่ง
- คิด
- นี้
- เหล่านั้น
- แต่?
- คิดว่า
- เวลา
- ชื่อหนังสือ
- ไปยัง
- ร่วมกัน
- เอา
- เครื่องมือ
- ด้านบน
- ลู่
- การติดตาม
- การเปลี่ยนแปลง
- โปร่งใส
- จริง
- ลอง
- หัน
- สอง
- ชนิด
- เป็นปกติ
- เข้าใจ
- Unicode
- จนกระทั่ง
- ไม่ได้ใช้
- ที่ไม่พึงประสงค์
- บันทึก
- ให้กับคุณ
- URL
- us
- รัฐบาลเรา
- การใช้
- USB
- ใช้
- ใช้หลังฟรี
- มือสอง
- ผู้ใช้งาน
- ใช้
- การใช้
- ประโยชน์
- ความคุ้มค่า
- ความคุ้มค่า
- ความหลากหลาย
- ตรวจสอบ
- รุ่น
- มาก
- ผ่านทาง
- ความอ่อนแอ
- W
- รอ
- ที่รอ
- ต้องการ
- อยาก
- คือ
- นาฬิกา
- ทาง..
- วิธี
- we
- สัปดาห์ที่ผ่านมา
- ดี
- คือ
- อะไร
- เมื่อ
- ว่า
- ที่
- ในขณะที่
- WHO
- ใครก็ได้
- ทั้งหมด
- ทำไม
- จะ
- ชนะ
- หน้าต่าง
- เช็ด
- กับ
- ไม่มี
- สงสัย
- คำ
- งาน
- ทำงาน
- การทำงาน
- โรงงาน
- กังวล
- จะ
- จะให้
- เขียน
- การเขียน
- เขียน
- ยัง
- เธอ
- ของคุณ
- ด้วยตัวคุณเอง
- ลมทะเล
- เป็นศูนย์