Chủ Nhật, 23 tháng 4, 2017

[ASM]Các kiểu mã hóa thường gặp

Thuật ngữ "mã hóa" chắc không còn xa lạ với dân công nghệ, đặc biệt là đối với giới gamer. Thuật ngữ này chỉ phương pháp biểu ký hay thuật toán dùng để che giấu nội dung dữ liệu để người thứ ba không biết được, trong thông tín hay dữ liệu máy tính. Bài này tập trung giới thiệu các phương pháp mã hóa thường gặp trong dữ liệu máy tính (game), và là cơn đau đầu đối với dân hack/dịch game.

 


I. Nén dữ liệu

Nén giữ liệu là phương pháp, thuật toán bảo đảm thông tin nguồn không bị mất đi mà lại giảm được lượng thông tin. Do dữ liệu nguồn và dữ liệu đích khác nhau nên đây cũng được coi là một dạng "mã hóa". Có hai kiểu nén dữ liệu là kiểu có thể truy ngược (lossles) và kiểu không thể truy ngược.
Kiểu nén dữ liệu không thể truy ngược thường được áp dụng để nén dữ liệu video, hình ảnh, âm thanh chạy trên PC và dữ liệu đích có sự mất mát so với dữ liệu nguồn. Kiểu nén dữ liệu có thể truy ngược được dùng nhiều để nén hội thoại, hình ảnh, video hay âm thanh trong game.
Các kiểu nén có thể truy ngược thường gặp trong game là RLE, Huffman và LZZ.


II. Code ký tự

Code ký tự (encoding) là phương pháp dùng byte để biểu hiện từng ký tự trong bản chữ cái của các ngôn ngữ. Chẳng hạn, theo code ký tự ASCII (tiêu chuẩn Mỹ quốc) thì chữ Z (viết hoa) ứng với byte $5A. Khi gặp $5A trong dữ liệu thì CPU sẽ in chữ "Z" ra màn hình, và khi gõ "Z" trên bàn phím thì $5A được ghi vào bộ nhớ.
Vấn đề thực tế nảy sinh là mỗi quốc gia lại có những kiểu văn tự khác nhau, cho nên có nhiều hệ thống code ký tự khác nhau. Chẳng hạn, theo tiêu chuẩn Shift-Jis thì chữ "Z" trong bản Hán tự chiếm 2 byte là $8269, còn theo tiêu chuẩn EUC là $A3DA. Đến đây ta hiểu được vì sao các máy tính cũ có thể hiển thị được ký tự của ngôn ngữ này nhưng lại hiện ô vuông (không hiển thị được) khi hiển thị ký tự của ngôn ngữ nọ. Ngay cả trong hệ thống Unicode, các kiểu encoding khác nhau cũng cho các giá trị byte khác nhau đối với cùng một ký tự. Chẳng hạn, theo lối encoding Unicode UTF-7 thì chữ "á" chiếm 5 byte là $2B414F452D, còn theo lối Unicode UTF-8 thì ký tự này chiếm 2 byte là $C3A1.

[​IMG]
Có thể đọc nội dung của file text này dễ dàng bởi nó dùng chuẩn ASCII phổ biến

Các game console thế hệ mới như PS3, PS4 hay game PC đa phần lối encoding tiêu chuẩn như ASCII, Shift-JIS hay EUC. Còn các game console thế hệ cũ đa phần không theo các chuẩn này, chẳng hạn chữ "Z" có thể là một byte nào đó khác $5A. Đây có thể xem là một kiểu "mã hóa" đơn giản. Và đôi khi, chúng ta cũng gặp những kiểu mã hóa phức tạp hơn bằng cách cộng hay trừ giá trị thật của byte với một giá trị nào đó.
Chẳng hạn, cụm từ "I LOVE YOU" có thể được mã hóa thành "J MPWF ZPV" hay "H KNUD XNT" trước khi được CPU giải mã theo đúng giá trị thật của nó.
Trong ví dụ trên, giá trị thực của dữ liệu được che đậy bằng cách cộng/trừ với 1 đơn vị. Trong thực tế thì người ta thường dùng những cách mã hóa phức tạp hơn như đề cập dưới đây.


III. Phép thao túng bit

Ta đều biết, đơn vị thông tin nhỏ nhất là bit, và 1 byte có 8 bit. Phép thao túng bit là các phương pháp luận lý làm thay đổi các bit trong byte, từ đó làm thay đổi giá trị thật của byte, khiến dữ liệu bị biến dạng so với nguyên thủy. Các phép luận lý này bao gồm AND, OR, XOR, NOT và các phép dịch chuyển bit. Ở đây đề ra ví dụ về phép luận lý AND.
Phép AND cho ra kết quả 1 khi cả 2 toán hạng đều chứa 1 ở cùng vị trí.
3D AND 12 = 10
Thử phân tích ví dụ trên ở cấp độ bit

3D = 00111101
AND
12 = 00010010
Được kết quả
10 = 00010000

Rõ ràng, trong dữ liệu nguồn, byte gốc có giá trị $3D sau khi được AND với $12 (đeo lớp mặt nạ) thì $3D biến thành $10 trong dữ liệu đích.

Còn trong các phép dịch chuyển bit, từng đơn vị bit được di chuyển sang trái hoặc phải theo số lần được định trước.
Chẳng hạn 10 dời sang trái 1 lần: $10 Lsh (Left shift) 1 = $20
Phân tích ví dụ trên ở cấp độ bit

10 = 00010000
Dời sang trái 1 lần, được
20 = 00100000

Để ý, số 1 trong 10 nằm ở vị trí thứ 4 từ trái sang, và khi dời sang trái thì nó nằm ở vị trí thứ 3. Việc này khiến giá trị của byte tăng gấp đôi.
Tương tự, với phép dịch chuyển bit sang phải thì giá trị của byte giảm còn một nửa khi số 1 từ vị trí thứ 4 chuyển sang vị trí thứ 5.
00010000--> 00001000 = 8.
Chú ý: 10 (thập lục phân) = 16 (thập phân) nên một nửa của nó là 8.

Các phép luận lý này được áp dụng để che đậy giá trị thuật của chuỗi dữ liệu. Chẳng hạn, chuỗi "I LOVE YOU" trong game dùng mã ASCII có giá trị

49 20 4C 4F 56 45 20 59 4F 55
(Không tính giá trị khoảng trắng)

Nhưng khi XOR chuỗi trên với giá trị $01 thì được kết quả $20000001.
Như vậy, khi thực hiện phép luận lý trên thì giá trị và độ dài của dữ liệu gốc bị thay đổi hoàn toàn, khiến cho việc tìm kiếm khó khăn hơn. Việc này nhằm chống lại việc hack/dịch dữ liệu trong game. Vandal Hearts trên PSX là một trong số những game áp dụng phương pháp này.
Chẳng hạn, các giá trị $4F trong dữ liệu hội thoại được luận lý, trở thành giá $5A.
Còn trong Valkyrie Profile, mỗi một đoại thoại lại được luận lý với những chuỗi giá trị khác nhau, chẳng hạn $4F trong đoạn 1 được AND với $FE và trở thành $4E, trong khi $4F trong đoạn 2 được OR với $12 và trở thành $5F, $4F trong đoạn 3 được XOR với $AB và trở thành $E4,.... Việc này khiến dữ liệu được che giấu kỹ hơn, khó bị dò ra hơn.

Trên đây là những phương pháp mã hóa thường gặp nhất trong game.
Cách mã hóa hữu hiệu nhất khi thực hiện luận lý chuỗi giá trị gốc với một hay một chuỗi giá trị biến thiên trong RAM. Vì giá trị trong RAM thay đổi mỗi lần mỗi khác, nên khi thực hiện luận lý với nó cho giá trị gốc thì giá trị gốc sẽ thay đổi mỗi lần mỗi khác nhau.

Không có nhận xét nào:

Đăng nhận xét