+1

General knowledge in Cryptography - kiến thức tổng quan trong mật mã học (phần 2)

II. Một số kỹ thuật mã hóa cơ bản (tiếp)

3. Mã hóa Base64

Base64 là một dạng chương trình mã hóa thực hiện mã hóa đối tượng bằng cách sử dụng 6464 ký tự trong bảng mã ASCII: bao gồm các ký tự từ A-Z, a-z, 0-9, ký tự /+, ngoài ra có thêm ký tự đệm =. So sánh với mã ASCII, một ký tự biểu diễn theo mã ASCII sẽ cần 88 bit, trong khi với Base64 chỉ cần 66 bit, giúp giảm đi một phần không gian lưu trữ tài nguyên.

image.png

Để mã hóa một đối tượng sang dạng Base64, chúng ta thực hiện các bước sau:

  • Bước 11: Chuyển đối tượng sang dạng bit.
  • Bước 22: Chia chuỗi bit thành từng nhóm, mỗi nhóm 66 ký tự.
  • Bước 33: Chuyển từng nhóm tương ứng với một ký tự dựa vào bảng tra như trên.
  • Bước 44: Thêm các ký tự đệm = nếu cần.

Chúng ta cùng xem xét ví dụ sau để hiểu rõ hơn các bước chuyển đổi: Mã hóa từ Viblo sang dạng Base64. Đầu tiên, biểu diễn Viblo sang dạng bit:

Viblo \rightarrow 01010110 01101001 01100010 01101100 01101111

Chia thành từng nhóm gồm 66 bit mỗi nhóm:

010101 100110 100101 100010 011011 000110 1111

Dễ thấy nhóm cuối cùng chỉ có 44 bit, như vậy sẽ cần bổ sung thêm hai bit 0 vào cuối, chúng ta có 77 nhóm:

010101 100110 100101 100010 011011 000110 111100

Chuyển từng nhóm sang các ký tự tương ứng của Base64:

V m l i b G 8

Chuỗi VmlibG877 ký tự, dựa vào tỉ lệ 88 bit mã hóa thành 66 bit - 33 byte được biểu diễn bằng 44 ký tự, chúng ta sẽ bổ sung lần lượt các ký tự đệm = tới khi thu được output có số ký tự là bội của 44. Trong trường hợp này chỉ cần bổ sung 11 ký tự đệm. Như vậy kết quả mã hóa từ VibloVmlibG8=. Một mẹo bổ sung ký tự đệm nhanh hơn là cứ mượn thêm 22 bit 0 sẽ cần trả lại một ký tự đệm =.

Quá trình giải mã có thể thực hiện dễ dàng bằng cách làm ngược lại các bước trên (Chú ý mỗi ký tự đệm tương đương hai bit 0).

Base64 thường được ứng dụng trong việc truyền tải các tệp hình ảnh, âm thanh, video, ... Tuy nhiên, do số lượng ký tự cần sử dụng cho việc mã hóa âm thanh và video khá lớn, nên ngày nay Base64 được dùng chủ yếu trong việc truyền tải hình ảnh.

image.png (Ví dụ mã nguồn hiển thị hình ảnh bằng cách mã hóa dưới dạng Base64)

Ngôn ngữ Python chứa module base64 hỗ trợ làm việc với Base64, trong đó có thể mã hóa đối tượng bằng hàm base64.b64encode(), giải mã ngược lại bằng hàm base64.b64decode(). Đối tượng truyền vào và kết quả trả về đều ở dạng bytes. Ví dụ:

import base64

str = 'Base64_is_amazing'

str_en_b64 = base64.b64encode(str.encode())
print('Base64 encode: ', str_en_b64)

str_de_b64 = base64.b64decode(str_en_b64)
print('Base64 decode: ', str_de_b64)

image.png

Challenge CTF: Hex64.

image.png

Từ tên đề bài và mô tả, có thể dự đoán công việc chúng ta cần thực hiện là giải mã một file về dạng plain text (thường là flag), khả năng lớn liên quan đến mã hóa Hex và Base64. Tệp hex64 chứa một chuỗi ký tự lớn (bao gồm 5684603856846038 ký tự), quan sát nhận thấy chuỗi chỉ bao gồm 1616 ký tự khác nhau từ 0-9, a-f \rightarrow khả năng lớn là mã hóa Hex.

image.png

image.png

Thử giải mã từ Hex, kiểm tra nhận thấy số lượng ký tự đã giảm đi và chuỗi ký tự trở thành dạng mã hóa Base64:

image.png

image.png

Thử decode một lần nữa từ Base64 chúng ta lại thu được chuỗi ở dạng mã hóa của Hex.

image.png

Như vậy, ý tưởng giải quyết bài CTF này đã rõ, chúng ta sẽ liên tục decode xen kẽ giữa Hex và Base64 cho tới plain text cuối cùng chính là flag cần tìm. Lưu ý dạng dữ liệu truyền vào của các hàm decode. Lời giải tham khảo:

import base64

f = open('hex64', 'r')
data = f.read()
f.close()

data = data.encode()

for i in range(15):
    data = bytes.fromhex(data.decode())
    data = base64.b64decode(data)

print(data.decode())

image.png

III. Phép toán XOR

XOR (Exclusive or) là một phép toán trên thao tác bit (bitwise operation): phép toán thực hiện tại cấp độ của từng bit riêng biệt, thường được ký hiệu là \oplus. Đặc điểm chung của các phép toán thao tác bit là tốc độ thực hiện nhanh, thứ tự tính toán ưu tiên và được hỗ trợ tốt trên đa nền tảng.

image.png (Ký hiệu một số logic gate symbols)

Kết quả của phép toán XOR giữa hai bit riêng biệt được thể hiện qua bảng chân lý (truth table) như sau:

A B A \oplus B
0 0 0
0 1 1
1 0 1
1 1 0

Một mẹo dễ nhớ dành cho kết quả phép XOR là: Hai bit giống nhau trả về 0, hai bit khác nhau trả về 1. Ví dụ phép tính: 0110 \oplus 1010 = 1100

Từ truth table trên, chúng ta có thể suy ra một số tính chất cơ bản của phép toán XOR áp dụng với chuỗi bit:

Tính chất Biểu diễn
XOR với 0 A \oplus 0 = A
Chuỗi XOR với chính nó A \oplus A = 0
Giao hoán A \oplus B = B \oplus A
Kết hợp (A \oplus B) \oplus C = A \oplus (B \oplus C)

Trong Python, toán tử XOR được biểu diễn bằng ký tự ^. Chúng ta có thể thực hiện phép toán trực tiếp với số nguyên mà không cần chuyển sang dạng bit. Kết quả trả về cũng là số nguyên.

image.png

Challenge luyện tập: XOR easy

image.png

Cipher text đưa ra có đầy đủ dấu hiệu của một chuỗi hex, kết hợp với tên challenge "XOR easy" chúng ta có ý tưởng giải quyết như sau: Cần thực hiện phép toán XOR chuỗi cipher với một chuỗi khác (tạm gọi là chuỗi key) để thu được flag. Tuy nhiên, chúng ta đang chưa được cung cấp chuỗi key.

Tiếp tục để ý Hint #1 "one letter" có thể dự đoán chuỗi key có một ký tự, như vậy phép toán XOR cần thực hiện là XOR từng ký tự của cipher text với key. Tới đây, có hai hướng giải quyết:

Hướng 1: Brute force key

Thực hiện tấn công vét cạn key bằng cách vét cạn giá trị ASCII của ký tự, bắt đầu từ 0. Với từng trường hợp đều thực hiện XOR key với tất cả ký tự của cipher text để thu được flag. Lời giải tham khảo:

import time

cipher = '321815130f0c44062b1d072b07441911001c451a4d09'
cipher = bytes.fromhex(cipher).decode()

key = 0
while True:
    flag = ''
    for i in cipher:
        flag += chr(key ^ ord(i))
    print('key: %s: flag = %s' % (key, flag))
    key += 1
    time.sleep(2)

image.png

Ngoài ra, thay vì sử dụng hàm time.sleep() rồi chờ quan sát output như trên, chúng ta có thể dựa vào dấu hiệu flag bắt đầu bằng Flag{... để in ra flag nếu thỏa mãn dấu hiệu này.

cipher = '321815130f0c44062b1d072b07441911001c451a4d09'
cipher = bytes.fromhex(cipher).decode()

key = 0
while True:
    flag = ''
    for i in cipher:
        flag += chr(key ^ ord(i))
    if flag.startswith('Flag{'):
        print(flag)
        break
    key += 1

image.png

Hướng 2: Dựa vào dấu hiệu flag tìm ra key

Như trên chúng ta đã nhắc tới flag có dạng Flag{..., chú ý hình thức thực hiện phép toán XOR là XOR từng ký tự của cipher text với key:

flag[0] ^ key = cipher[0]
flag[1] ^ key = cipher[1]
flag[2] ^ key = cipher[2]
flag[3] ^ key = cipher[3]
...

Dựa vào các tính chất của phép toán XOR, ta có:

cipher[0] ^ flag[0] = flag[0] ^ key ^ flag[0] = (flag[0] ^ flag[0]) ^ key = 0 ^ key = key

flag[0] = F nên có thể dễ dàng tìm ra chuỗi key rồi tìm ra flag đầy đủ. Lời giải tham khảo:

cipher = '321815130f0c44062b1d072b07441911001c451a4d09'
cipher = bytes.fromhex(cipher).decode()

key = chr(ord('F') ^ ord(cipher[0]))

flag = ''
for i in cipher:
    flag += chr(ord(key) ^ ord(i))

print(flag)

image.png

Tài liệu tham khảo


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.