Viết Code JavaScript chỉ dùng Toàn các kí hiệu ?

8216

9c663ad9a43b87b9696cbb9688c5bfe7dd8772c5

Bài viết được dịch từ tài liệu: http://pferrie2.tripod.com/papers/jjencode.pdf của tác giả Peter Ferrie (Microsoft). Bài viết này tuy khá cũ rồi (2011) nhưng kiến thức của nó vẫn không hề cũ một tẹo nào và mình thích cách tác giả nhẫn nại nghiên cứu và đọc code. Đó là một đức tính vô cùng quý báu, cần có của một coder. Bạn cần có một chút kiến thức về Javascript, theo dõi từng bước một, và nếu có thể thì hãy mở cửa sổ Console của trình duyệt để làm theo luôn nhé. Hãy cố gắng đọc đến cùng nhé.

DISCLAIMER: Nội dung của bài viết này nguy hiểm. Tác giả không chịu trách nhiệm nếu bạn đọc bài viết này.

Phân tích

Giới thiệu

Đã bao giờ bạn tưởng tượng có một phương pháp mã hoá Javascript mà tạo được một file chỉ gồm các kí hiệu ví dụ như $, _, + hay chưa. Thật khó tưởng tượng phải không, nhưng thật không may, có một cách mã hoá như vậy. Nó được gọi là JJEncoder. Bản demo của phương pháp này được công khai trên web của tác giả và đã được sử dụng trong các mã độc. Bài viết này sẽ miêu tả chi tiết cách thức hoạt động của phương pháp này.

Part 1

Chúng ta bắt đầu với cái này:

Cần chú ý là phần ENCODED ở trên không chứa trong file đã được mã hoá, mà sẽ ở nơi mà phần code mã hoá được chứa. Nghĩa là nếu bạn đem nó vào file js, nó sẽ không chạy bởi vì nó yêu cầu một tính năng của HTML 4.0. Do đó, nó phải được ghi trong file HTML và file này cũng cần phải khai báo như sau:

Phần HTML 4.0 có thể được thay thế bằng các phiên bản mới hơn ví dụ như HTML 4.1 hoặcXHTML 1.0, vân vân

Giờ thì xem code thôi

Part 2

Biểu thức [] trả về cho ta tham chiếu đến một mảng rỗng. Toán tử ~ truy cập giá trị của tham chiếu đó, trong trường hợp này là 0 và đảo ngược giá trị đó, dẫn đến ta thu được giá trị là -1 lưu vào biến $.

Dòng này có thể được viết như sau:

Phần {} thể hiện rằng đây là khởi tạo một đối tượng (object) và mỗi dòng bên trong ngoặc sẽ là các thuộc tính (property) và sẽ được gán giá trị khi khởi tạo object. Chúng ta sẽ xem xét từng dòng một. Đầu tiên là:

Biểu thức ++$ tăng giá trị của biến $ lên 1, nghĩa là thuộc tính ___ sẽ được gán giá trị 0.

Biểu thức [] trả về cho ta tham chiếu đến một mảng rỗng. Toán tử ! kiểm tra xem tham chiếu đó có phải là 0 hay không, và ở đây là ko phải nên sẽ trả về false. Khi sử dụng + ta sẽ chuyển giá trị false này thành xâu "false". Biểu thức [$] sẽ chuyển xâu thành mảng và lấy ra một kí tự. Bạn có nhớ $ ko ?. Giờ nó có giá trị 0 nên kí tự "f" của "false" sẽ được trả về và gán cho thuộc tính $$$$.

Tương tự, biến $ được tăng thêm 1, giờ nó có giá trị 1 và gán cho thuộc tính ‘__$

Tương tự như trên, kí tự thứ hai ("a")của xâu `"false" được trả về và gán cho thuộc tính$_$_. Có vẻ tác giả thích sử dụng biểu thức ![] trong khi có thể dùng !$ thay thế, khi đó ta có thể tiết kiệm được 1 byte.

Biểu thức ++$ có giá trị là 2, được gán cho thuộc tính _$_.

{} trả về tham chiếu của một object rỗng. Như trên, tham chiếu này được chuyển thành một xâu. Kết quả sẽ là xâu "[object Object]". Kí tự thứ 3 xâu này (“b”) được gán vào thuộc tính$_$$.

Biểu thức $[$] sẽ truy cập vào phần tử thứ 3 của mảng được chỉ định bởi biến $ nếu mảng đó tồn tại. Tuy nhiên. vì biến $ không phải là mảng nên giá trị undefined được trả về, chuyển thành xâu, lấy phần tử thứ 3 là kí tự ("d") gán vào thuộc tính $$_$.

Biểu thức ++$ giờ có giá trị là 3, $ giờ cũng mang giá trị là 3, được gán cho thuộc tính __$.

Biểu thức "" trả về một xâu rỗng. ! kiểm tra xem xâu đó có phải là rỗng ko, ở đây giá trị Boolean true được trả về, chuyển thành xâu "true", và kí tự thứ 4 ("e") được trả về.

Biểu thức ++$ giờ có giá trị là 4, $ giờ cũng mang giá trị là 4, được gán cho thuộc tính $__.

Biểu thức ++$ giờ có giá trị là 5, $ giờ cũng mang giá trị là 5, được gán cho thuộc tính $_$.

Kí tự thứ 6 của xâu "‘[object Object]" là ("c") được gán cho thuộc tính $$__. Biểu thức({}+"") được lặp lại bởi vì không thể tham chiếu đến một thuộc tính vừa được khởi tạo trong quá trình khởi tạo đối tượng. Nếu gán biến, ta có thể rút gắn code của đoạn này

Biểu thức ++$ giờ có giá trị là 6, $ giờ cũng mang giá trị là 6, được gán cho thuộc tính $$_.

Biểu thức ++$ giờ có giá trị là 7, $ giờ cũng mang giá trị là 7, được gán cho thuộc tính $$$.

Biểu thức ++$ giờ có giá trị là 8, $ giờ cũng mang giá trị là 8, được gán cho thuộc tính$___.

Biểu thức ++$ giờ có giá trị là 9, $ giờ cũng mang giá trị là 9, được gán cho thuộc tính$__$.

Tại thời điểm này, ta đã có các thuộc tính ___, $$$$, __$, $_$_, _$_, $_$$, $$_$, _$$,$$$_, $__, $_$, $$__, $$_, $$$, $___, và $__$ lần lượt có các giá trị là 0, f, 1, a, 2,b, d, 3, e, 4, 5, c, 6, 7, 8, và 9.

Dòng này chứa rất nhiều các phép gán thuộc tính và nối kí tự. Ta có thể viết lại như sau:

Tham chiếu đến $ giờ là tham chiếu đến đối tượng, không còn tham chiếu đến giá trị 9 nữa. Phần $. ở trước mỗi thuộc tính được dùng để truy cập một thuộc tính đã có.

"[object Object]" được gán cho thuộc tính $_. Kí tự thứ 6 ($_$ là 5) là "c" được trả về. Tuy nhiên $$__ cũng đã có giá trị này. Tác giả bị lẫn chăng ? (cười)

Kí tự thứ 2 (__$ là 1) của xâu [object Object]"o" được gán cho thuộc tính _$ và trả về.

Thuộc tính $ không tồn tại bị truy cập, do đó, giá trị undefined được trả về. Giá trị này được chuyển thành xâu như trên. Kí tự thứ 2 (__$ là 1) của undefined"n" được gán vào $$ và cũng được trả về.

Biểu thức !$ kiểm tra xem tham chiếu đến đối tượng $ có phải là 0 hay ko, trường hợp này thì ko phải nên false được trả về. Giá trị này cũng được chuyển thành xâu và kí tự thứ 4 (_$$ = 3) tức là "s" được trả về.

Kí tự thứ 7 ($$_ = 6) của xâu “[object Object]” là "t" được gán vào thuộc tính __ và trả về.

Xâu “true” được xây dựng, giống như trên. Kí tự thứ 2 (__$ = 1) của xâu “true” là "r" được gán vào thuộc tính $ và trả về.

Xâu “true” được xây dựng, giống như trên. Kí tự thứ 3 (_$_ = 2) của xâu “true” là "u" được gán vào thuộc tính _ và trả về.

Kí tự thứ 6 ($_$ = 5) của xâu "[object Object]""c" được trả về.

Giá trị của thuộc tính __"t" được trả về.

Giá trị của thuộc tính _$"o" được trả về.

Giá trị của thuộc tính $"r" được trả về.

Kết quả cuối cùng là xâu "constructor" dược gán vào thuộc tính $_.

Part 4

Dòng này ta có thêm các đoạn nối kí tự. Ta viết lại như sau:

Một lần nữa, ta xem lần lượt các dòng.

Giá trị của thuộc tính $"r" được trả về.

Xâu “true” được xây dựng, giống như trên. Kí tự thứ 4 (_$$ = 4) của xâu “true” là "e" được trả vể.

Giá trị của thuộc tính __"t" được trả về.

Giá trị của thuộc tính _"u" được trả về.

Giá trị của thuộc tính $"r" được trả về.

Giá trị của thuộc tính $$"n" được trả về.

Kết quả cuối cùng là chuỗi return được gán vào thuộc tính $$

Part 5

Phần này sẽ được chuyển thành biểu thức (0)[“constructor”][“constructor”] và được gán vào thuộc tính $. Biêu thức này tương đương với 0.constructor.constructor nhưng ta cần có các dấu ngoặc để phần tách giữa các xâu. Nếu không thì biểu thức giống với việc truy cập vào một thuộc tính qua nhiều level $.$_.$.$_.

Biểu thức <number>.constructor tham chiếu đến constructor của một đối tượng kiểu số (numberic) trong khi <object>.constructor.constructor là tham chiếu đến constructorcủa một đối tượng chung.

Dòng này giải mã và thực thi phần code bẳng cách sử dụng hai lần gọi constructor. Lần gọiconstructor đầu tiên giải mã phần code đã được mã hoá và lệnh gọi constructor thứ hai thực thi nó theo cách sau:

Giá trị của thuộc tính $$ (‘return’) được dùng ở lần gọi contructor đầu tiên để trả về phần code đã được giải mã gắn kèm với " và được thể hiện ở đây là ENCODED

Dòng này được chuyển thành biểu thức 0.constructor.constructor(return"ENCODED")()anonymous function trả về phần code đã được mã hoá như một xâu. Việc này tương đương với việc thực hiện eval()

Dòng này được chuyển thành .constructor.constructor(DECODED)(), một anonymous function sẽ thực thi phần code đã được giải mã. Thật đáng ngạc nhiên phải không 😀

Part 6

Mỗi kí tự cùa phần code được mã hoá theo một trong các cách sau:

  • Nếu kí tự là " hoặc \ thì các kí tự này được gắn vào trước đó kí tự \, nghĩa là " sẽ trở thành \"\ sẽ trở thành \\
  • Nếu các kí tự đó là các kí hiệu sau:

thì nó được dùng đúng như vậy.

  • Nếu các kí tự là số hoặc là một trong các chữ cái a đến f, o đến t hoặc u (kiểm tra case-sensitive) thì thuộc tính tương ứng sẽ được sử dụng.
  • Nếu kí tự là chữ l thì ![]+"")[_$_]’ sẽ được sử dụng.

Nếu giá trị của kí tự nhỏ hơn 128 thì biểu thức \<val> với <val> là giá trị của kí tự đó.

Nếu không thì biểu thức \u<val> được sử dụng với <val> là giá trị hexaldecimal của kí tự đó.

Kết luận

Vậy là chúng ta đã phân tích được cách thức mà JJEncode hoạt động. Tuy nhiên, cách mã hoá này chứa rất nhiều các hằng số và dễ nhận biết. Cho dù tác giả có thực hiện một số phép đa hình polymorphism trên code thì ta vẫn có thể dễ dàng nhận ra. Việc khó khăn có lẽ là làm thế nào để hiểu được phần code sẽ được thực thi nếu chỉ nhìn qua. Tuy nhiên, khi đã phân tích được rồi thì điều đó cũng không còn quá khó nữa. Bạn có thể lên trang chủ tại địa chỉhttp://utf-8.jp/public/jjencode.html để dùng thử. Ngoài ra tác giả cũng phát triển thêm một kiểu encode khác sử dụng các emoticon của Nhật Bản ví dụ ゚ω゚ノ= /`m´)ノ ~┻━┻ //*´∇`*/ ['_']; o=(゚ー゚) =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); 😀

Techtalk via viblo

người viết Nguyễn Anh Tiến

CHIA SẺ