Trang chủ Blog Uncategorized Zalo đã mã hóa Web API của họ như thế nào?

Zalo đã mã hóa Web API của họ như thế nào?

Mã hóa API response

Mặc dù HTTPS là giao thức HTTP đi kèm mã hóa đầu cuối để phòng chống MITM. Tuy nhiên các nhà phát triển Zalo thấy đây vẫn là chưa đủ, và họ đã đi thêm 1 bước nữa trên con đường phòng chống những kẻ tò mò. Hãy mở firefox, bật F12 và bắt đầu xem thử API của Zalo có gì hay nào?

Một chuỗi data loằng toằng ngoằng. Nhìn không hiểu cái mô tê gì hết trơn luôn. Tuy nhiên sau khi thực hiện một vài thao tác đơn giản nhưng hơi lag máy để inspect file code js nặng 7.6MB của zalo web (đã minify), mình tìm được đoạn code sau được thực thi để giải mã mỗi khi có request:

function decodeAES(input) {
var count = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : 0;
try {
var a = void 0;
input = decodeURIComponent(input);
var secretKey = i.default.enc.Base64.parse(this.getSecretKey());
return a = i.default.AES.decrypt({
ciphertext: i.default.enc.Base64.parse(input),
salt: ""
}, secretKey, {
iv: i.default.enc.Hex.parse("00000000000000000000000000000000"),
mode: i.default.mode.CBC,
padding: i.default.pad.Pkcs7
}).toString(i.default.enc.Utf8), w && (w = 0), a
} catch (err) {
return count < 3 ? this.decodeAES(input, count + 1) : (console.error("Error decodeAES " + err), f.default.doCheck(w, function(key) {
t.logCoreInfo("[w]Change zkey"), t.setDecryptKey(key), w++
}), null)
}
}



Hóa ra là zalo đã sử dụng mã hóa AES để mã hóa toàn bộ response trả về. Trong đoạn code trên, các bạn có thể dễ dàng nhận thấy cái chúng ta cần để decrypt được response từ API là secretKey khi mà các thành phần khác đã fix cứng.

Đầu tiên là ý tưởng secretKey được giấu trong code đến với mình. Mặc dù đây là cách tù tội nhất để lưu secretKey. Bởi khi ấy secretKey sẽ tồn tại quá lâu, và mỗi khi thay đổi sẽ khiến file js phải cập nhật lại. Tuy nhiên ngay sau đó mình nhìn thấy phần catch error xuất hiện log: Change zkey. Vậy là có thể loại trừ việc secretKey được giấu trong code mà sẽ là được lấy từ API.

Trong mấy cái đó, thì API getLoginInfo này có vẻ có triển vọng chứa secretKey nhất. Mình tiếp tục nhận thấy secretKey được giải mã Base64 trước khi dùng: var secretKey = i.default.enc.Base64.parse(this.getSecretKey());.

Vậy tức là secretKey sẽ có dạng Base64. Và sự chú ý của ta đã va phải vào cái key của nàng, mình thử với zpk_enk: U0hHcmksxATNSMjEJ/RGiQ== trong response trả về từ API này và… VIOLA, mình đã bắt trúng bài:

Chung quy lại là phương pháp này bao gồm các bước:

  • Tạo 1 Secret Key trên server.
  • Chia sẻ Secret Key giữa client và server (Qua fix cứng trong code, file config, API).
  • Dùng Secret Key với mã hóa AES (hoặc bất kỳ thuật toán mã hóa tin cậy nào đó) mã hóa response trả về từ server.
  • Dùng Secret Key để giải mã trở lại lấy thông tin từ Response.

Về cơ bản thì nó không khác gì concept của HTTPS, tức là mã hóa và giải mã đầu cuối để người ở giữa không đọc được. Cái khác ở đây chính là HTTPS được trình duyệt thực hiện tự động, còn ở đây ta phải tự thực hiện trong code client.

Như vậy, khi ai đó inspect hay bắt các request tới Zalo thì cũng sẽ chỉ thấy các đoạn dữ liệu đã được mã hóa và không hiểu gì. Điều này giúp hạn chế việc các ứng dụng hay bên thứ 3, web crawler không phải client web Zalo sử dụng trái phép API của Zalo. Nhưng các bạn thấy đó, chỉ sau khoảng chục phút mình đã có thể mô phỏng lại quá trình giải mã dưới client. Do vậy phương pháp này được dùng để chống người ngay thôi nhé =))

Tổng kết

Qua câu chuyện hoàn toàn hư cấu phía trên, mình rút ra 3 điều:

  • Mọi thứ trên client đều không thể tin tưởng.
  • Bạn không thể ngăn chặn kẻ thứ 3 sử dụng API của bạn. Nhưng bạn có thể hạn chế nó. Làm nó mất thời gian mày mò code của bạn như phía trên là 1 ví dụ.
  • Hết rồi 😄
← Bài trước Hướng dẫn lên tích xanh – Ngâm tích xanh chính chủ 5-10 ngày. Bài sau → Kỹ thuật đảo ngược cho người mới bắt đầu – Mã hóa XOR – Windows x64