Skip to content

Commit

Permalink
Implement the obfuscated MTProto transport
Browse files Browse the repository at this point in the history
  • Loading branch information
er-azh committed Dec 20, 2024
1 parent bb46ef0 commit 8f16edb
Show file tree
Hide file tree
Showing 10 changed files with 317 additions and 47 deletions.
1 change: 1 addition & 0 deletions lib/grammers-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pbkdf2 = "0.12.2"
sha1 = "0.10.6"
sha2 = "0.10.8"
num-traits = "0.2.19"
ctr = "0.9.2"

[dev-dependencies]
bencher = "0.1.5"
Expand Down
4 changes: 4 additions & 0 deletions lib/grammers-crypto/DEPS.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,7 @@ Used for methods relied on by the 2-factor offered by Telegram.
## toml

Used to test that this file lists all dependencies from `Cargo.toml`.

## ctr

Used for the AES-CTR mode needed for the obfuscated MTProto transport.
1 change: 1 addition & 0 deletions lib/grammers-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod auth_key;
pub mod deque_buffer;
pub mod factorize;
pub mod hex;
pub mod obfuscated;
pub mod rsa;
pub mod sha;
pub mod two_factor_auth;
Expand Down
72 changes: 72 additions & 0 deletions lib/grammers-crypto/src/obfuscated.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2020 - developers of the `grammers` project.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use aes::cipher::{generic_array::GenericArray, KeyIvInit, StreamCipher, StreamCipherSeek};

/// This implements the AES-256-CTR cipher used by Telegram to encrypt data
/// when using the obfuscated transport.
///
/// You're not supposed to use this directly, You're probably looking for the
/// actual implementation in `grammers-mtproto`.
pub struct ObfuscatedCipher {
rx: ctr::Ctr128BE<aes::Aes256>,
tx: ctr::Ctr128BE<aes::Aes256>,
}

impl ObfuscatedCipher {
pub fn new(init: &[u8; 64]) -> Self {
let init_rev = init.iter().copied().rev().collect::<Vec<_>>();
Self {
rx: ctr::Ctr128BE::<aes::Aes256>::new(
GenericArray::from_slice(&init_rev[8..40]),
GenericArray::from_slice(&init_rev[40..56]),
),
tx: ctr::Ctr128BE::<aes::Aes256>::new(
GenericArray::from_slice(&init[8..40]),
GenericArray::from_slice(&init[40..56]),
),
}
}

pub fn encrypt(&mut self, buffer: &mut [u8]) {
self.tx.apply_keystream(buffer);
}

pub fn decrypt(&mut self, buffer: &mut [u8]) {
self.rx.apply_keystream(buffer);
}

/// Undo the decryption of the last `decrypt` call.
/// The buffer must be a slice to the end from the same buffer that
/// was last passed to `decrypt`.
pub fn undo_decrypt(&mut self, buffer: &mut [u8]) {
let pos = self
.rx
.current_pos::<u128>()
.wrapping_sub(buffer.len() as u128);
self.rx.seek(pos);
self.rx.apply_keystream(buffer);
self.rx.seek(pos);
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_revert() {
let mut cipher = ObfuscatedCipher::new(&[0x69; 64]);
let orignial = [0x42; 128];
let mut buffer = orignial.clone();

cipher.decrypt(&mut buffer);
cipher.undo_decrypt(&mut buffer);
assert_eq!(&buffer, &orignial);
}
}
45 changes: 32 additions & 13 deletions lib/grammers-mtproto/src/transport/abridged.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use super::{Error, Transport, UnpackedOffset};
use super::{Error, Tagged, Transport, UnpackedOffset};
use grammers_crypto::DequeBuffer;

/// The lightest MTProto transport protocol available. This is an
Expand Down Expand Up @@ -63,9 +63,9 @@ impl Transport for Abridged {
}
}

fn unpack(&mut self, buffer: &[u8]) -> Result<UnpackedOffset, Error> {
fn unpack(&mut self, buffer: &mut [u8]) -> Result<Vec<UnpackedOffset>, Error> {
if buffer.is_empty() {
return Err(Error::MissingBytes);
return Ok(vec![]);
}

let header_len;
Expand All @@ -75,7 +75,7 @@ impl Transport for Abridged {
len as i32
} else {
if buffer.len() < 4 {
return Err(Error::MissingBytes);
return Ok(vec![]);
}

header_len = 4;
Expand All @@ -84,7 +84,7 @@ impl Transport for Abridged {

let len = len * 4;
if (buffer.len() as i32) < header_len + len {
return Err(Error::MissingBytes);
return Ok(vec![]);
}

if header_len == 1 && len >= 4 {
Expand All @@ -99,11 +99,11 @@ impl Transport for Abridged {
let header_len = header_len as usize;
let len = len as usize;

Ok(UnpackedOffset {
Ok(vec![UnpackedOffset {
data_start: header_len,
data_end: header_len + len,
next_offset: header_len + len,
})
}])
}

fn reset(&mut self) {
Expand All @@ -112,6 +112,13 @@ impl Transport for Abridged {
}
}

impl Tagged for Abridged {
fn obfuscated_tag(&mut self) -> [u8; 4] {
self.init = true;
[0xef, 0xef, 0xef, 0xef]
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -160,7 +167,7 @@ mod tests {
let mut transport = Abridged::new();
let mut buffer = DequeBuffer::with_capacity(1, 0);
buffer.extend([1]);
assert_eq!(transport.unpack(&buffer[..]), Err(Error::MissingBytes));
assert_eq!(transport.unpack(&mut buffer[..]), Ok(vec![]));
}

#[test]
Expand All @@ -169,7 +176,11 @@ mod tests {
let orig = buffer.clone();
transport.pack(&mut buffer);
let n = 1; // init byte
let offset = transport.unpack(&buffer[n..][..]).unwrap();
let offset = transport
.unpack(&mut buffer[n..][..])
.unwrap()
.pop()
.unwrap();
assert_eq!(&buffer[n..][offset.data_start..offset.data_end], &orig[..]);
}

Expand All @@ -187,12 +198,20 @@ mod tests {
transport.pack(&mut buffer);
two_buffer.extend(&buffer[..]);

let offset = transport.unpack(&two_buffer[..]).unwrap();
let offset = transport
.unpack(&mut two_buffer[..])
.unwrap()
.pop()
.unwrap();
assert_eq!(&buffer[offset.data_start..offset.data_end], &orig[..]);
assert_eq!(offset.next_offset, single_size);

let n = offset.next_offset;
let offset = transport.unpack(&two_buffer[n..]).unwrap();
let offset = transport
.unpack(&mut two_buffer[n..])
.unwrap()
.pop()
.unwrap();
assert_eq!(&buffer[offset.data_start..offset.data_end], &orig[..]);
}

Expand All @@ -202,7 +221,7 @@ mod tests {
let orig = buffer.clone();
transport.pack(&mut buffer);
let n = 1; // init byte
let offset = transport.unpack(&buffer[n..]).unwrap();
let offset = transport.unpack(&mut buffer[n..]).unwrap().pop().unwrap();
assert_eq!(&buffer[n..][offset.data_start..offset.data_end], &orig[..]);
}

Expand All @@ -214,7 +233,7 @@ mod tests {
buffer.extend(&(-404_i32).to_le_bytes());

assert_eq!(
transport.unpack(&buffer[..]),
transport.unpack(&mut buffer[..]),
Err(Error::BadStatus { status: 404 })
);
}
Expand Down
32 changes: 20 additions & 12 deletions lib/grammers-mtproto/src/transport/full.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ impl Transport for Full {
self.send_seq += 1;
}

fn unpack(&mut self, buffer: &[u8]) -> Result<UnpackedOffset, Error> {
fn unpack(&mut self, buffer: &mut [u8]) -> Result<Vec<UnpackedOffset>, Error> {
// Need 4 bytes for the initial length
if buffer.len() < 4 {
return Err(Error::MissingBytes);
return Ok(vec![]);
}

let total_len = buffer.len() as i32;
Expand All @@ -82,7 +82,7 @@ impl Transport for Full {
}

if total_len < len {
return Err(Error::MissingBytes);
return Ok(vec![]);
}

// receive counter
Expand Down Expand Up @@ -112,11 +112,11 @@ impl Transport for Full {
}

self.recv_seq += 1;
Ok(UnpackedOffset {
Ok(vec![UnpackedOffset {
data_start: 8,
data_end: len - 4,
next_offset: len,
})
}])
}

fn reset(&mut self) {
Expand Down Expand Up @@ -188,15 +188,15 @@ mod tests {
let mut transport = Full::new();
let mut buffer = DequeBuffer::with_capacity(3, 0);
buffer.extend([0, 1, 3]);
assert_eq!(transport.unpack(&buffer[..]), Err(Error::MissingBytes));
assert_eq!(transport.unpack(&mut buffer[..]), Ok(vec![]));
}

#[test]
fn unpack_normal() {
let (mut transport, mut buffer) = setup_pack(128);
let orig = buffer.clone();
transport.pack(&mut buffer);
let offset = transport.unpack(&buffer[..]).unwrap();
let offset = transport.unpack(&mut buffer[..]).unwrap().pop().unwrap();
assert_eq!(&buffer[offset.data_start..offset.data_end], &orig[..]);
}

Expand All @@ -214,12 +214,20 @@ mod tests {
transport.pack(&mut buffer);
two_buffer.extend(&buffer[..]);

let offset = transport.unpack(&two_buffer[..]).unwrap();
let offset = transport
.unpack(&mut two_buffer[..])
.unwrap()
.pop()
.unwrap();
assert_eq!(&buffer[offset.data_start..offset.data_end], &orig[..]);
assert_eq!(offset.next_offset, single_size);

let n = offset.next_offset;
let offset = transport.unpack(&two_buffer[n..]).unwrap();
let offset = transport
.unpack(&mut two_buffer[n..])
.unwrap()
.pop()
.unwrap();
assert_eq!(&buffer[offset.data_start..offset.data_end], &orig[..]);
}

Expand All @@ -230,7 +238,7 @@ mod tests {
buffer[4] = 1;

assert_eq!(
transport.unpack(&buffer[..]),
transport.unpack(&mut buffer[..]),
Err(Error::BadSeq {
expected: 0,
got: 1,
Expand All @@ -246,7 +254,7 @@ mod tests {
buffer[len - 1] ^= 0xff;

assert_eq!(
transport.unpack(&buffer[..]),
transport.unpack(&mut buffer[..]),
Err(Error::BadCrc {
expected: 932541318,
got: 3365237638,
Expand All @@ -261,7 +269,7 @@ mod tests {
buffer.extend(&(-404_i32).to_le_bytes());

assert_eq!(
transport.unpack(&buffer[..]),
transport.unpack(&mut buffer[..]),
Err(Error::BadStatus { status: 404 })
);
}
Expand Down
Loading

0 comments on commit 8f16edb

Please sign in to comment.