obfstr_impl/
lib.rs

1#![allow(bare_trait_objects, ellipsis_inclusive_range_patterns)]
2
3extern crate proc_macro;
4use proc_macro::*;
5
6//----------------------------------------------------------------
7
8/// Strips any outer `Delimiter::None` groups from the input,
9/// returning a `TokenStream` consisting of the innermost
10/// non-empty-group `TokenTree`.
11/// This is used to handle a proc macro being invoked
12/// by a `macro_rules!` expansion.
13/// See https://github.com/rust-lang/rust/issues/72545 for background
14fn ignore_groups(mut input: TokenStream) -> TokenStream {
15    let mut tokens = input.clone().into_iter();
16    loop {
17        if let Some(TokenTree::Group(group)) = tokens.next() {
18            if group.delimiter() == Delimiter::None {
19                input = group.stream();
20                continue;
21            }
22        }
23        return input;
24    }
25}
26
27#[cfg(feature = "rand")]
28#[proc_macro_attribute]
29pub fn obfstr_attribute(args: TokenStream, input: TokenStream) -> TokenStream {
30	drop(args);
31	replace_macro(replace_macro(input, "_obfstr_", obfstr_impl), "_strlen_", strlen_impl)
32}
33
34#[cfg(feature = "rand")]
35fn strlen_impl(mut input: TokenStream) -> TokenStream {
36    input = ignore_groups(input);
37	if let Some(TokenTree::Literal(literal)) = input.into_iter().next() {
38		let s = string_parse(literal);
39		TokenStream::from(TokenTree::Literal(Literal::usize_suffixed(s.len())))
40	}
41	else {
42		panic!("expected a string literal")
43	}
44}
45#[cfg(feature = "rand")]
46fn obfstr_impl(mut input: TokenStream) -> TokenStream {
47    input = ignore_groups(input);
48	let mut tt = input.into_iter();
49	let mut token = tt.next();
50
51	// Optional L ident prefix to indicate wide strings
52	let mut wide = false;
53	if let Some(TokenTree::Ident(ident)) = &token {
54		if ident.to_string() == "L" {
55			wide = true;
56			token = tt.next();
57		}
58	}
59
60	// Followed by a string literal
61	let string = match token {
62		Some(TokenTree::Literal(lit)) => string_parse(lit),
63		Some(tt) => panic!("expected a string literal: `{}`", tt),
64		None => panic!("expected a string literal"),
65	};
66
67	// End of macro arguments
68	token = tt.next();
69	if let Some(tt) = token {
70		panic!("unexpected token: `{}`", tt);
71	}
72
73	// Generate a random key
74	let key = rand::random::<u32>();
75	// Obfuscate the string itself
76	let array = if wide {
77		let mut words = {string}.encode_utf16().collect::<Vec<u16>>();
78		wencrypt(&mut words, key)
79	}
80	else {
81		let mut bytes = string.into_bytes();
82		encrypt(&mut bytes, key)
83	}.into_iter().collect();
84
85	// Generate `key, [array]` to be passed to ObfString constructor
86	vec![
87		TokenTree::Literal(Literal::u32_suffixed(key)),
88		TokenTree::Punct(Punct::new(',', Spacing::Alone)),
89		TokenTree::Group(Group::new(Delimiter::Bracket, array)),
90	].into_iter().collect()
91}
92
93#[cfg(feature = "rand")]
94fn next_round(mut x: u32) -> u32 {
95	x ^= x << 13;
96	x ^= x >> 17;
97	x ^= x << 5;
98	x
99}
100
101#[cfg(feature = "rand")]
102fn encrypt(bytes: &mut [u8], mut key: u32) -> Vec<TokenTree> {
103	for byte in bytes.iter_mut() {
104		key = next_round(key);
105		*byte = (*byte).wrapping_sub(key as u8);
106	}
107	let mut array = Vec::new();
108	for &byte in bytes.iter() {
109		array.push(TokenTree::Literal(Literal::u8_suffixed(byte)));
110		array.push(TokenTree::Punct(Punct::new(',', Spacing::Alone)));
111	}
112	array
113}
114
115#[cfg(feature = "rand")]
116fn wencrypt(words: &mut [u16], mut key: u32) -> Vec<TokenTree> {
117	for word in words.iter_mut() {
118		key = next_round(key);
119		*word = (*word).wrapping_sub(key as u16);
120	}
121	let mut array = Vec::new();
122	for &word in words.iter() {
123		array.push(TokenTree::Literal(Literal::u16_suffixed(word)));
124		array.push(TokenTree::Punct(Punct::new(',', Spacing::Alone)));
125	}
126	array
127}
128
129//----------------------------------------------------------------
130
131fn string_parse(input: Literal) -> String {
132	let string = input.to_string();
133	let mut bytes = string.as_bytes();
134
135	// Trim the string from its outer quotes
136	if bytes.len() < 2 || bytes[0] != b'"' || bytes[bytes.len() - 1] != b'"' {
137		panic!("expected a string literal: `{}`", input);
138	}
139	bytes = &bytes[1..bytes.len() - 1];
140	let string: &str = unsafe { &*(bytes as *const _ as *const str) };
141
142	// Parse escape sequences
143	let mut unescaped = String::new();
144	let mut chars = string.chars();
145	while let Some(mut chr) = chars.next() {
146		if chr == '\\' {
147			chr = match chars.next() {
148				Some('t') => '\t',
149				Some('n') => '\n',
150				Some('r') => '\r',
151				Some('0') => '\0',
152				Some('\\') => '\\',
153				Some('\'') => '\'',
154				Some('\"') => '\"',
155				Some('u') => {
156					match chars.next() {
157						Some('{') => (),
158						Some(chr) => panic!("invalid unicode escape character: `{}`", chr),
159						None => panic!("invalid unicode escape at end of string"),
160					}
161					let mut u = 0;
162					loop {
163						match chars.next() {
164							Some(chr @ '0'...'9') => {
165								u = u * 16 + (chr as u32 - '0' as u32);
166							},
167							Some(chr @ 'a'...'f') => {
168								u = u * 16 + (chr as u32 - 'a' as u32) + 10;
169							},
170							Some(chr @ 'A'...'F') => {
171								u = u * 16 + (chr as u32 - 'A' as u32) + 10;
172							},
173							Some('}') => break,
174							Some(chr) => panic!("invalid unicode escape character: `{}`", chr),
175							None => panic!("invalid unicode escape at end of string"),
176						};
177					}
178					match std::char::from_u32(u) {
179						Some(chr) => chr,
180						None => panic!("invalid unicode escape character: `\\u{{{}}}`", u),
181					}
182				},
183				Some(chr) => panic!("invalid escape character: `{}`", chr),
184				None => panic!("invalid escape at end of string"),
185			}
186		}
187		unescaped.push(chr);
188	}
189	unescaped
190}
191
192//----------------------------------------------------------------
193
194#[proc_macro_attribute]
195pub fn wide_attribute(args: TokenStream, input: TokenStream) -> TokenStream {
196	drop(args);
197	replace_macro(input, "_wide_", wide_impl)
198}
199
200fn wide_impl(mut input: TokenStream) -> TokenStream {
201    input = ignore_groups(input);
202	// Parse the input as a single string literal
203	let mut iter = input.into_iter();
204	let string = match iter.next() {
205		Some(TokenTree::Literal(lit)) => string_parse(lit),
206		Some(tt) => panic!("expected a string literal: `{}`", tt),
207		None => panic!("expected a string literal"),
208	};
209	if let Some(tt) = iter.next() {
210		panic!("unexpected token: `{}`", tt);
211	}
212	// Encode the string literal as an array of words
213	let mut array = Vec::new();
214	for word in string.encode_utf16() {
215		array.push(TokenTree::Literal(Literal::u16_suffixed(word)));
216		array.push(TokenTree::Punct(Punct::new(',', Spacing::Alone)));
217	}
218	let elements = array.into_iter().collect();
219	// Wrap the array of words in a reference
220	vec![
221		TokenTree::Punct(Punct::new('&', Spacing::Alone)),
222		TokenTree::Group(Group::new(Delimiter::Bracket, elements)),
223	].into_iter().collect()
224}
225
226//----------------------------------------------------------------
227
228#[cfg(feature = "rand")]
229#[proc_macro_attribute]
230pub fn random_attribute(args: TokenStream, input: TokenStream) -> TokenStream {
231	drop(args);
232	replace_macro(input, "_random_", random_impl)
233}
234
235#[cfg(feature = "rand")]
236fn random_impl(mut input: TokenStream) -> TokenStream {
237    input = ignore_groups(input);
238	let mut tt = input.into_iter();
239	match tt.next() {
240		Some(TokenTree::Ident(ident)) => {
241			if let Some(tt) = tt.next() {
242				panic!("unexpected token: `{}`", tt);
243			}
244			random_parse(ident).into()
245		},
246		Some(tt) => panic!("expected a primitive name: `{}`", tt),
247		None => panic!("expected a primitive name"),
248	}
249}
250
251#[cfg(feature = "rand")]
252fn random_parse(input: Ident) -> TokenTree {
253	match &*input.to_string() {
254		"u8" => Literal::u8_suffixed(rand::random::<u8>()),
255		"u16" => Literal::u16_suffixed(rand::random::<u16>()),
256		"u32" => Literal::u32_suffixed(rand::random::<u32>()),
257		"u64" => Literal::u64_suffixed(rand::random::<u64>()),
258		"usize" => Literal::usize_suffixed(rand::random::<usize>()),
259		"i8" => Literal::i8_suffixed(rand::random::<i8>()),
260		"i16" => Literal::i16_suffixed(rand::random::<i16>()),
261		"i32" => Literal::i32_suffixed(rand::random::<i32>()),
262		"i64" => Literal::i64_suffixed(rand::random::<i64>()),
263		"isize" => Literal::isize_suffixed(rand::random::<isize>()),
264		"f32" => Literal::f32_suffixed(rand::random::<f32>()),
265		"f64" => Literal::f64_suffixed(rand::random::<f64>()),
266		s => panic!("unsupported type: `{}`", s),
267	}.into()
268}
269
270//----------------------------------------------------------------
271// Implements a tt muncher for proc-macros
272
273fn replace<F>(input: TokenStream, mut f: F) -> TokenStream
274	where F: FnMut(&[TokenTree]) -> Option<(usize, TokenStream)>
275{
276	let input: Vec<TokenTree> = input.into_iter().collect();
277	let mut output = Vec::new();
278	replace_rec(input, &mut output, &mut f);
279	output.into_iter().collect()
280}
281fn replace_rec(input: Vec<TokenTree>, output: &mut Vec<TokenTree>, f: &mut FnMut(&[TokenTree]) -> Option<(usize, TokenStream)>) {
282	let mut into_iter = input.into_iter();
283	loop {
284		// If tokens are matched, insert the replacement and skip some tokens
285		if let Some((mut skip, replace)) = f(into_iter.as_slice()) {
286			output.extend(replace);
287			while skip > 0 {
288				let _ = into_iter.next();
289				skip -= 1;
290			}
291		}
292		match into_iter.next() {
293			// Recursively process into groups
294			Some(TokenTree::Group(group)) => {
295				let group_input = group.stream().into_iter().collect();
296				let mut group_output = Vec::new();
297				replace_rec(group_input, &mut group_output, f);
298				let group_stream = group_output.into_iter().collect();
299				output.push(TokenTree::Group(Group::new(group.delimiter(), group_stream)));
300			},
301			Some(tt) => output.push(tt),
302			None => break,
303		}
304	}
305}
306// Replaces invocations of `$name!($tokens)` with the output of the callable given the `$tokens`.
307fn replace_macro(input: TokenStream, name: &str, f: fn(TokenStream) -> TokenStream) -> TokenStream {
308	replace(input, |tokens| {
309		if tokens.len() >= 3 {
310			if let (
311				TokenTree::Ident(ident),
312				TokenTree::Punct(punct),
313				TokenTree::Group(group),
314			) = (
315				&tokens[0],
316				&tokens[1],
317				&tokens[2],
318			) {
319				if punct.as_char() == '!' && ident.to_string() == name {
320					return Some((3, f(group.stream())));
321				}
322			}
323		}
324		None
325	})
326}