Zalo PC mã hóa bản backup để bảo vệ ai?
Một bản xuất dữ liệu, một lớp AES-CBC và chiếc chìa khóa vẫn nằm trên chính chiếc máy đang chạy Zalo.
Mình từng nghĩ chức năng xuất dữ liệu có một nhiệm vụ khá thẳng thắn. Người dùng chứng minh họ là chủ tài khoản, dịch vụ đóng gói dữ liệu rồi trả lại một định dạng có thể đọc được. Zalo PC chọn một con đường vòng hơn. File mang đuôi .zl.zip nhưng không phải ZIP thông thường, bên trong là một TAR đã được mã hóa.
Mã hóa dữ liệu không phải chuyện đáng chê. Điều khiến mình tò mò là người dùng đang xuất chính dữ liệu của mình, trên chiếc máy đang đăng nhập, nhưng muốn đọc lại thì phải đi tìm chiếc chìa khóa mà ứng dụng vẫn đang cầm. Có cảm giác chiếc két sắt được đặt trong nhà, chìa khóa treo cạnh cửa, còn người bị làm khó nhất lại là chủ nhà muốn dọn đồ.
Mọi quan sát trong bài đều được thực hiện với dữ liệu của chính mình và phiên Zalo mình có quyền sử dụng. Đây là bài phân tích một thiết kế kỹ thuật, không phải hướng dẫn truy cập tài khoản hay dữ liệu của người khác.
Một bản xuất dữ liệu nhưng chưa thật sự được trao cho người dùng
Meta làm việc này theo cách dễ hiểu hơn. Tài liệu chính thức của Facebook cho phép người dùng chọn HTML hoặc JSON. Gói HTML được tải về dưới dạng ZIP, giải nén rồi mở file index; JSON dành cho xử lý bằng máy hoặc chuyển sang dịch vụ khác. Mật khẩu và bước xác minh bổ sung được đặt trước lúc tải xuống, tức quyền truy cập được kiểm tra trước khi dữ liệu rời dịch vụ.
Điểm mình muốn so sánh không phải Meta tốt còn Zalo xấu. Hai hệ thống có threat model và lịch sử khác nhau. Nhưng với một tính năng mang tên xuất dữ liệu, định dạng HTML, JSON hoặc một ZIP bình thường nói rõ rằng dữ liệu sau khi xác minh là của người dùng. Một ciphertext riêng của ứng dụng lại phát ra thông điệp hơi lạ. Bạn được phép mang dữ liệu về, chỉ là chưa chắc được đọc nó bằng công cụ mình chọn.
Cái đuôi ZIP chỉ là tấm bảng treo ngoài cửa
Quan sát cấu trúc file cho thấy .zl.zip không có header ZIP quen thuộc. Sau khi giải mã đúng, plaintext xuất hiện dưới dạng TAR. Luồng dữ liệu có thể tóm lại như sau.
.zl.zip sau khi bóc lớp tên gọi bên ngoài.
Tải ảnh sơ đồ
Cách tạo key và IV mình quan sát được khá ngắn.
key = SHA256(UTF8(uin))
iv = UTF8("zie" + uin[0:13])
plaintext = AES_256_CBC_DECRYPT(ciphertext, key, iv)
SHA-256 tạo ra 32 byte, vừa kích thước khóa AES-256. Chuỗi zie ghép với 13 ký tự đầu của uin tạo thành IV 16 byte. Công thức không phải bí mật bền vững vì ứng dụng cần chứa hoặc thực hiện nó. Thứ hệ thống đang trông cậy vào là uin.
Đây cũng là chỗ lớp mã hóa bắt đầu có nét hài khô. uin không phải mật khẩu do người dùng đặt, không đi qua KDF có salt như PBKDF2, scrypt hay Argon2, và ứng dụng đang đăng nhập vẫn cần biết nó. Một khóa dẫn xuất từ định danh có thể làm file bớt đọc được khi nằm riêng lẻ trên đĩa, nhưng không nên được nhầm với một bí mật có entropy cao.
Không cần đánh bại AES khi dữ liệu dẫn đến khóa vẫn ở trong RAM
Phần thú vị không nằm ở việc phá AES. AES vẫn làm đúng việc của AES. Vấn đề nằm ở vòng đời của khóa trên một ứng dụng desktop. Khi Zalo đang chạy, tiến trình hợp lệ cần giữ đủ trạng thái để làm việc với tài khoản hiện tại. Một tiến trình khác trong cùng ngữ cảnh người dùng có thể dùng các API Windows chỉ đọc như VirtualQueryEx và ReadProcessMemory để quan sát những vùng nhớ phù hợp.
Các ứng viên uin có thể được tìm dưới dạng chuỗi hex ở ASCII và UTF-16LE. Với mỗi ứng viên, chỉ cần dẫn xuất key, IV rồi giải mã block đầu của file. TAR có header 512 byte và trường checksum ở vị trí cố định, nên nó trở thành một phép kiểm tra known-plaintext rất tiện. Ứng viên sai gần như không thể đồng thời tạo ra checksum hợp lệ và tên entry đầu tiên có thể đọc được.
for region in readable_memory_regions:
candidates = find_uin_like_strings(region)
for uin in candidates:
key = SHA256(UTF8(uin))
iv = UTF8("zie" + uin[:13])
header = decrypt_first_block(backup, key, iv)
if looks_like_tar_header(header):
accept(uin)
Không ai bẻ khóa AES ở đây. Chiếc chìa khóa được tìm ở nơi ứng dụng buộc phải sử dụng nó, còn TAR xác nhận đã nhặt đúng chìa.
Điều này không chứng minh mã hóa vô dụng trong mọi tình huống. Nếu ai đó chỉ lấy được file backup nhưng không có máy, phiên đăng nhập hoặc dữ liệu liên quan, ciphertext vẫn tạo thêm một rào cản. Nhưng nếu kẻ tấn công đã chạy được mã dưới cùng tài khoản Windows và đọc được bộ nhớ tiến trình, câu chuyện đã sang một cấp độ khác. Lúc đó lớp mã hóa backup không cứu được endpoint đã bị kiểm soát.
Nếu mục tiêu là chống hacker thì ranh giới đang được đặt hơi muộn
Giả thuyết dễ nghĩ nhất là Zalo mã hóa để bảo vệ bản backup nếu file bị đánh cắp. Trong threat model rất hẹp đó, lớp mã hóa có tác dụng. Người chỉ cầm một file sẽ không mở nó như ZIP thông thường.
Nhưng từ “hacker” thường được dùng rộng hơn nhiều. Nếu máy đã nhiễm malware, kẻ tấn công có thể đọc file, theo dõi phiên đăng nhập, lấy token, chụp màn hình, móc vào tiến trình hoặc đơn giản chờ dữ liệu được ứng dụng giải mã. Mã hóa một gói export bằng vật liệu khóa có thể tìm thấy trong phiên đang chạy không biến chiếc máy đã mất quyền kiểm soát thành an toàn.
Nói ngắn gọn, lớp này có thể bảo vệ trước người nhặt được một file. Nó không bảo vệ trước người đã vào được căn phòng nơi file và chìa khóa đang cùng tồn tại.
Nếu mục tiêu là chống sửa dữ liệu thì CBC đang bị giao nhầm nghề
AES-256-CBC cung cấp tính bí mật, nhưng bản thân CBC không cung cấp tính toàn vẹn hay xác thực. Trong định dạng mình quan sát, ciphertext không đi kèm MAC hoặc authentication tag. Không có HMAC và cũng không có cơ chế AEAD như AES-GCM để chứng minh dữ liệu chưa bị thay đổi.
- Key được tạo bằng một lần SHA-256 trực tiếp, không có salt và không có cơ chế kéo giãn khóa.
- IV được tạo cố định từ
uin, nên nhiều backup cùng tài khoản có thể tái sử dụng cả key lẫn IV. - CBC có tính malleable. Ciphertext có thể bị chỉnh để tác động có chủ đích lên plaintext, dù cấu trúc TAR làm việc khai thác khó hơn.
- Checksum TAR phát hiện lỗi cấu trúc và thay đổi ngẫu nhiên. Nó không phải MAC, không xác nhận nguồn tạo file và không chống giả mạo có chủ đích.
Nếu nhu cầu thật sự là kiểm tra file có nguyên vẹn trước khi import, cách diễn đạt kỹ thuật rõ hơn sẽ là manifest có chữ ký, MAC trên toàn bộ archive, hash cho từng thành phần, phiên bản schema và quy tắc xác minh chặt chẽ. Mã hóa có thể đi cùng những lớp đó, nhưng không thể đóng thế cho chúng.
Nếu mục tiêu là ngăn người dùng sửa rồi import ngược thì vẫn chưa thuyết phục
Một định dạng khó đọc có thể làm giảm số người tự sửa dữ liệu. Nó không tạo ra ranh giới tin cậy. Khi ứng dụng phía người dùng chứa đủ logic để đọc file, logic ấy có thể được quan sát và dựng lại. Sau đó dữ liệu có thể được giải mã, thay đổi, đóng gói và mã hóa lại.
Muốn chống import dữ liệu đã bị sửa, ứng dụng cần xác minh thứ mà người dùng không thể tự tạo lại. Chữ ký số bằng private key chỉ nằm ở phía dịch vụ là một ví dụ. Kiểm tra schema, ID, quan hệ giữa record, timestamp và trạng thái máy chủ cũng có thể loại bỏ dữ liệu giả. Chỉ bọc archive bằng một key dẫn xuất từ dữ liệu có sẵn trên máy giống việc dán tem “không mở” rồi để cuộn băng keo ngay bên cạnh.
Vậy Zalo thật sự muốn đạt được điều gì?
Mình không có tài liệu thiết kế nội bộ của Zalo, nên không thể khẳng định động cơ. Những gì có thể nói chắc chỉ là hành vi quan sát được. Bản export không mở như ZIP, dữ liệu sau giải mã là TAR, khóa được dẫn xuất từ uin và phiên Zalo đang hoạt động giữ đủ dấu vết để tìm lại giá trị đó.
Từ đó có vài khả năng hợp lý.
- Zalo muốn bảo vệ file khi nó bị tách khỏi chiếc máy đã tạo ra nó.
- Zalo muốn giảm việc người dùng chỉnh sửa dữ liệu bằng công cụ phổ thông.
- Zalo muốn giữ định dạng như một chi tiết triển khai riêng để có thể thay đổi mà không phải hỗ trợ hệ sinh thái công cụ bên ngoài.
- Lớp mã hóa có thể là di sản của một quyết định cũ, tiếp tục tồn tại vì hệ thống hiện tại đã phụ thuộc vào nó.
Cả bốn đều là suy đoán, không phải kết luận về ý định của Zalo. Điều mình có thể kết luận là thiết kế hiện tại làm người dùng hợp lệ khó đọc dữ liệu của họ hơn, trong khi giá trị bảo vệ trước một endpoint đã bị xâm nhập vẫn rất hạn chế. Đây là một cái giá khá rõ cho một lợi ích chưa được giải thích rõ.
Sau lớp mã hóa vẫn còn một archive cần được đối xử như dữ liệu không tin cậy
Giải mã thành công không đồng nghĩa có thể giải nén vô điều kiện. TAR có đường dẫn, symbolic link, hard link và nhiều kiểu entry khác nhau. Chỉ kiểm tra tên không bắt đầu bằng /, không có ổ đĩa Windows và không chứa .. vẫn chưa đủ nếu link có thể trỏ ra ngoài thư mục đích.
Một quy trình an toàn cần hiểu type của entry, kiểm tra link target, resolve đường dẫn rồi xác nhận containment trước khi ghi. Nếu thực hiện một lượt kiểm tra và một lượt giải nén riêng biệt, file nguồn cũng cần được giữ ổn định hoặc đối chiếu hash để tránh khoảng TOCTOU. Sau cùng vẫn phải có quota cho số file, tổng dung lượng, độ sâu thư mục và thời gian xử lý để tránh archive bomb hoặc resource exhaustion.
Đây là phần thường bị che khuất bởi chữ “AES-256”. Tên thuật toán nghe rất an toàn, nhưng archive sau giải mã vẫn là input do người dùng chọn và parser vẫn là nơi dữ liệu chạm vào hệ thống file. Mật mã không sửa hộ lỗi path traversal, symlink hay giới hạn tài nguyên.
Threat model gọn hơn những lời quảng cáo về mã hóa
| Tình huống | Lớp mã hóa giúp được gì | Phần vẫn còn hở |
|---|---|---|
| Chỉ bị lấy file backup | File không mở trực tiếp như ZIP và cần đúng dữ liệu dẫn xuất khóa | Độ an toàn phụ thuộc vào tính bí mật cùng entropy thực tế của uin |
| Máy đang đăng nhập Zalo bị kiểm soát | Gần như không giải quyết được endpoint đã mất quyền kiểm soát | Tiến trình cùng user có thể đọc RAM, file và trạng thái phiên |
| Backup bị chỉnh sửa | CBC che nội dung nhưng không chứng minh tính toàn vẹn | Không có MAC hoặc authentication tag |
| Backup độc hại được giải nén | Mã hóa không giúp parser TAR an toàn hơn | Path traversal, link, TOCTOU và resource exhaustion |
Điều còn lại sau lần mổ xẻ này
Phần dịch ngược thực ra không cần phép màu. Đuôi file nói ZIP nhưng header không khớp. Plaintext hợp lệ có cấu trúc TAR. Key và IV đi từ uin. Dữ liệu dẫn đến uin xuất hiện trong renderer đang đăng nhập. Header TAR trở thành phép thử để xác nhận. Mỗi bước là một suy luận nhỏ, ghép lại thành câu trả lời hoàn chỉnh.
Điều khó hiểu hơn vẫn là quyết định ban đầu. Người dùng đã xác minh tài khoản và chủ động bấm xuất dữ liệu, nhưng file nhận về lại cần ứng dụng giữ vai trò người gác cổng thêm một lần nữa. Nếu mục tiêu là bảo vệ bản sao nằm riêng lẻ, thiết kế có chút tác dụng. Nếu mục tiêu là chống một kẻ đã kiểm soát máy, chống giả mạo hay chống import ngược, nó đang dùng sai công cụ hoặc thiếu những lớp quan trọng hơn.
Có lẽ đây là kiểu bảo mật khiến mọi người đều bận rộn. Ứng dụng bận mã hóa, người dùng bận tìm cách đọc dữ liệu của mình, còn chiếc máy đã bị xâm nhập thì vẫn bình thản đứng ngoài cuộc tranh luận.
Tài liệu tham khảo
- Facebook Help Centre về xuất dữ liệu dưới dạng HTML hoặc JSON
- Tài liệu ReadProcessMemory của Microsoft
- Tài liệu VirtualQueryEx của Microsoft
- NIST SP 800-38A về các mode của block cipher
- Python tarfile và extraction filters
- NhanAZ - 30.06.2026