From aa5b2906d0b9e6cfb1691ca75cff010bbfc6d459 Mon Sep 17 00:00:00 2001 From: xavo95 Date: Thu, 27 Mar 2025 00:19:21 +0100 Subject: [PATCH] Added extra tool and update cargo deps --- Cargo.lock | 128 +++++++++---- Cargo.toml | 15 +- README.md | 2 + descriptor-to-proto-rs/Cargo.toml | 9 + descriptor-to-proto-rs/src/extend.rs | 62 ++++++ descriptor-to-proto-rs/src/lib.rs | 272 +++++++++++++++++++++++++++ 6 files changed, 449 insertions(+), 39 deletions(-) create mode 100644 descriptor-to-proto-rs/Cargo.toml create mode 100644 descriptor-to-proto-rs/src/extend.rs create mode 100644 descriptor-to-proto-rs/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 7edd40e..8a748d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aes-key-finder" @@ -10,12 +10,24 @@ dependencies = [ "offset-finder", ] +[[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + [[package]] name = "crc" version = "3.2.1" @@ -60,21 +72,30 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" name = "cursor" version = "0.1.0" dependencies = [ - "thiserror 2.0.9", + "thiserror 2.0.12", "widestring", ] +[[package]] +name = "descriptor-to-proto-rs" +version = "0.1.0" +dependencies = [ + "prost", + "prost-types", + "thiserror 2.0.12", +] + [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "goblin" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53ab3f32d1d77146981dea5d6b1e8fe31eedcb7013e5e00d6ccd1259a4b4d923" +checksum = "daa0a64d21a7eb230583b4c5f4e23b7e4e57974f96620f42a7e75e08ae66d745" dependencies = [ "log", "plain", @@ -82,10 +103,19 @@ dependencies = [ ] [[package]] -name = "log" -version = "0.4.22" +name = "itertools" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "lzma-rs" @@ -106,7 +136,7 @@ dependencies = [ "patternscanner", "pe-utils", "serde", - "thiserror 2.0.9", + "thiserror 2.0.12", ] [[package]] @@ -124,7 +154,7 @@ name = "pe-utils" version = "0.1.0" dependencies = [ "goblin", - "thiserror 2.0.9", + "thiserror 2.0.12", ] [[package]] @@ -135,18 +165,50 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] -name = "quote" -version = "1.0.38" +name = "prost" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -178,7 +240,7 @@ dependencies = [ "goblin", "log", "pe-utils", - "thiserror 2.0.9", + "thiserror 2.0.12", ] [[package]] @@ -203,18 +265,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -223,9 +285,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.94" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -243,11 +305,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.9" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.9", + "thiserror-impl 2.0.12", ] [[package]] @@ -263,9 +325,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.9" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", @@ -277,17 +339,17 @@ name = "tlzma" version = "0.1.0" dependencies = [ "lzma-rs", - "thiserror 2.0.9", + "thiserror 2.0.12", ] [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "widestring" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" diff --git a/Cargo.toml b/Cargo.toml index 586e92f..1d2ddea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "aes-key-finder", "cursor", + "descriptor-to-proto-rs", "offset-finder", "pe-utils", "restorer", @@ -11,16 +12,18 @@ members = [ [workspace.package] version = "0.1.0" -edition = "2021" +edition = "2024" [workspace.dependencies] -goblin = "0.9.2" -log = "0.4.22" +goblin = "0.9.3" +log = "0.4.27" lzma-rs = { version = "0.3.0", features = ["raw_decoder"] } patternscanner = "0.5.0" -serde = { version = "1.0.217", features = ["derive"] } -thiserror = "2.0.9" -widestring = "1.1.0" +prost = "0.13.5" +prost-types = "0.13.5" +serde = { version = "1.0.219", features = ["derive"] } +thiserror = "2.0.12" +widestring = "1.2.0" offset-finder = { path = "offset-finder" } pe-utils = { path = "pe-utils" } diff --git a/README.md b/README.md index 0a61821..76e6aad 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ dump? So I hope this helps people wanting to learn to - Cursor - An implementation for a cursor Read + Write. Rust already has one, but I needed something like string and wide string parsing, so I created my own +- Descriptor to Proto + - Tool to convert protocol descriptors back to protocol buffer files - Offset Finder - This library allows to find patterns in executables - Allows to find either exact or partial matches by leveraging wildcards(??) diff --git a/descriptor-to-proto-rs/Cargo.toml b/descriptor-to-proto-rs/Cargo.toml new file mode 100644 index 0000000..ef3a19d --- /dev/null +++ b/descriptor-to-proto-rs/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "descriptor-to-proto-rs" +version.workspace = true +edition.workspace = true + +[dependencies] +prost.workspace = true +prost-types.workspace = true +thiserror.workspace = true \ No newline at end of file diff --git a/descriptor-to-proto-rs/src/extend.rs b/descriptor-to-proto-rs/src/extend.rs new file mode 100644 index 0000000..aa3152e --- /dev/null +++ b/descriptor-to-proto-rs/src/extend.rs @@ -0,0 +1,62 @@ +use prost_types::field_descriptor_proto::Type; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ExtendError { + #[error("Type group should be deprecated")] + GroupTypeDeprecated, + #[error("Explicit type name required when type is message or enum")] + ExplicitTypeRequired, +} + +pub(crate) trait AsStrType { + fn as_str_type<'a>(&self, explicit_type: Option<&'a String>) -> Result<&'a str, ExtendError>; + #[allow(unused)] + fn as_string_type(&self, package: Option<&String>, explicit_type: Option) -> Result; +} + +impl AsStrType for Type { + fn as_str_type<'a>(&self, explicit_type: Option<&'a String>) -> Result<&'a str, ExtendError> { + match self { + Type::Double => Ok("double"), + Type::Float => Ok("float"), + Type::Int64 => Ok("int64"), + Type::Uint64 => Ok("uint64"), + Type::Int32 => Ok("int32"), + Type::Fixed64 => Ok("fixed64"), + Type::Fixed32 => Ok("fixed32"), + Type::Bool => Ok("bool"), + Type::String => Ok("string"), + Type::Group => Err(ExtendError::GroupTypeDeprecated), + Type::Message | Type::Enum => Ok(explicit_type.unwrap().as_str()), + Type::Bytes => Ok("bytes"), + Type::Uint32 => Ok("uint32"), + Type::Sfixed32 => Ok("sfixed64"), + Type::Sfixed64 => Ok("sfixed64"), + Type::Sint32 => Ok("sint32"), + Type::Sint64 => Ok("sint32"), + } + } + + fn as_string_type(&self, package: Option<&String>, explicit_type: Option) -> Result { + match self { + Type::Group => Err(ExtendError::GroupTypeDeprecated), + Type::Message | Type::Enum => { + match explicit_type { + None => Err(ExtendError::ExplicitTypeRequired), + Some(explicit_type) => { + let new_type = explicit_type.clone(); + let mut new_type_str = new_type.as_str(); + new_type_str = explicit_type.strip_prefix(".").unwrap_or(new_type_str); + if let Some(package) = package { + new_type_str = new_type_str.strip_prefix(package).unwrap_or(new_type_str); + new_type_str = new_type_str.strip_prefix(".").unwrap_or(new_type_str); + } + Ok(new_type_str.to_string()) + } + } + } + _ => Ok(self.as_str_type(None)?.to_string()), + } + } +} \ No newline at end of file diff --git a/descriptor-to-proto-rs/src/lib.rs b/descriptor-to-proto-rs/src/lib.rs new file mode 100644 index 0000000..d67e691 --- /dev/null +++ b/descriptor-to-proto-rs/src/lib.rs @@ -0,0 +1,272 @@ +mod extend; + +use crate::extend::AsStrType; +use prost::Message; +use prost_types::field_descriptor_proto::Label; +use prost_types::{ + DescriptorProto, EnumDescriptorProto, FieldDescriptorProto, FileDescriptorProto, FileOptions, + ServiceDescriptorProto, SourceCodeInfo, +}; +use std::collections::HashMap; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ConverterError { + #[error("Proto decoding error")] + DecodeError(#[from] prost::DecodeError), + #[error("Internal extend error")] + ExtendError(#[from] extend::ExtendError), + #[error("{0}")] + GenericError(String), +} + +enum Syntax { + Proto2, + Proto3, +} + +pub fn process_file_descriptor_set( + file_descriptor_set_data: &[u8], +) -> Result, ConverterError> { + let mut descriptors = HashMap::new(); + let file_descriptor_set = prost_types::FileDescriptorSet::decode(file_descriptor_set_data)?; + for descriptor_proto in &file_descriptor_set.file { + let (file_name, content) = process_file_descriptor(descriptor_proto)?; + descriptors.insert(file_name, content); + } + Ok(descriptors) +} + +fn process_file_descriptor( + descriptor: &FileDescriptorProto, +) -> Result<(String, String), ConverterError> { + let mut buffer = String::with_capacity(1024 * 1024); // 1MB + let syntax = process_version(&mut buffer, descriptor.syntax.as_ref()); + let name = descriptor.name().to_string(); + process_package(&mut buffer, descriptor.package.as_ref()); + + if let Some(options) = &descriptor.options { + process_options(&mut buffer, options)?; + } + + for dependency in &descriptor.dependency { + buffer.push_str(format!("import \"{dependency}\";\n").as_str()); + } + buffer.push('\n'); + + // TODO: Hande public_dependency and weak_dependency + + process_enum(&mut buffer, &descriptor.enum_type)?; + process_message( + &mut buffer, + &syntax, + &descriptor.message_type, + descriptor.package.as_ref(), + )?; + process_service(&mut buffer, &descriptor.service)?; + process_extension(&mut buffer, &descriptor.extension)?; + + if let Some(source_code_info) = &descriptor.source_code_info { + process_source_code_info(&mut buffer, source_code_info)?; + } + + Ok((name, buffer)) +} + +fn process_version(buffer: &mut String, syntax: Option<&String>) -> Syntax { + // According to google, if no edition or syntax is specified, one shall default to proto2 + let syntax = syntax.cloned().unwrap_or("proto2".to_string()); + buffer.push_str(format!("syntax = \"{syntax}\";\n\n").as_str()); + match syntax.as_str() { + "proto3" => Syntax::Proto3, + _ => Syntax::Proto2, + } +} + +fn process_package(buffer: &mut String, package: Option<&String>) { + if let Some(package) = package { + buffer.push_str(format!("package {package};\n\n").as_str()); + } +} + +fn process_message( + buffer: &mut String, + syntax: &Syntax, + messages: &[DescriptorProto], + package: Option<&String>, +) -> Result<(), ConverterError> { + for message in messages { + buffer.push_str(format!("message {} {{\n", message.name()).as_str()); + for field in &message.field { + buffer.push('\t'); + // TODO: use label to know if optional shall be used + match field.label() { + Label::Optional => buffer.push_str("optional "), + Label::Required => match syntax { + Syntax::Proto2 => buffer.push_str("required "), + Syntax::Proto3 => {} + }, + Label::Repeated => buffer.push_str("repeated "), + } + buffer.push_str(field.r#type().as_str_type(field.type_name.as_ref())?); + buffer.push_str(format!(" {} = {}", field.name(), field.number()).as_str()); + + if let Some(extendee) = &field.extendee { + // TODO: Handle extendee + return Err(ConverterError::GenericError(format!( + "Descriptor extendee is not supported {extendee:?}" + ))); + } + + if let Some(default_value) = &field.default_value { + // TODO: Handle default_value + return Err(ConverterError::GenericError(format!( + "Descriptor default_value is not supported {default_value:?}" + ))); + } + + if let Some(oneof_index) = &field.oneof_index { + // TODO: Handle oneof_index + return Err(ConverterError::GenericError(format!( + "Descriptor oneof_index is not supported {oneof_index:?}" + ))); + } + + if let Some(json_name) = &field.json_name { + buffer.push_str(format!(" [json_name=\"{json_name}\"]").as_str()); + } + buffer.push_str(";\n"); + } + process_extension(buffer, &message.extension)?; + // TODO: We might require indentation without external tool + process_message(buffer, syntax, &message.nested_type, package)?; + // TODO: We might require indentation without external tool + process_enum(buffer, &message.enum_type)?; + + if !message.extension_range.is_empty() { + // TODO: Handle extension_range + return Err(ConverterError::GenericError(format!( + "Descriptor extension_range is not supported {:?}", + message.extension_range + ))); + } + + if !message.oneof_decl.is_empty() { + // TODO: Handle oneof_decl + return Err(ConverterError::GenericError(format!( + "Descriptor oneof_decl is not supported {:?}", + message.oneof_decl + ))); + } + + if let Some(options) = &message.options { + // TODO: Handle options + return Err(ConverterError::GenericError(format!( + "Descriptor options is not supported {options:?}" + ))); + } + + if !message.reserved_range.is_empty() { + // TODO: Handle reserved_range + return Err(ConverterError::GenericError(format!( + "Descriptor reserved_range is not supported {:?}", + message.reserved_range + ))); + } + + if !message.reserved_name.is_empty() { + // TODO: Handle reserved_name + return Err(ConverterError::GenericError(format!( + "Descriptor reserved_name is not supported {:?}", + message.reserved_name + ))); + } + + buffer.push_str("}\n\n"); + } + Ok(()) +} + +fn process_enum(buffer: &mut String, enums: &[EnumDescriptorProto]) -> Result<(), ConverterError> { + for enum_descriptor in enums { + buffer.push_str(format!("enum {} {{\n", enum_descriptor.name()).as_str()); + for variant in &enum_descriptor.value { + buffer.push_str(format!("\t{} = {};\n", variant.name(), variant.number()).as_str()); + if let Some(options) = &variant.options { + // TODO: Handle options + return Err(ConverterError::GenericError(format!( + "Descriptor options is not supported {options:?}" + ))); + } + } + + if let Some(options) = &enum_descriptor.options { + // TODO: Handle options + return Err(ConverterError::GenericError(format!( + "Descriptor options is not supported {options:?}" + ))); + } + + if !enum_descriptor.reserved_range.is_empty() { + // TODO: Handle reserved_range + return Err(ConverterError::GenericError(format!( + "Descriptor reserved_range is not supported {:?}", + enum_descriptor.reserved_range + ))); + } + + if !enum_descriptor.reserved_name.is_empty() { + // TODO: Handle reserved_name + return Err(ConverterError::GenericError(format!( + "Descriptor reserved_name is not supported {:?}", + enum_descriptor.reserved_name + ))); + } + + buffer.push_str("}\n\n"); + } + Ok(()) +} + +fn process_service( + _buffer: &mut String, + services: &[ServiceDescriptorProto], +) -> Result<(), ConverterError> { + if !services.is_empty() { + // TODO: Handle service + return Err(ConverterError::GenericError(format!( + "Descriptor service is not supported {services:?}" + ))); + } + Ok(()) +} + +fn process_options(_buffer: &mut String, options: &FileOptions) -> Result<(), ConverterError> { + // TODO: Handle options + Err(ConverterError::GenericError(format!( + "Descriptor options is not supported {options:?}" + ))) +} + +fn process_extension( + _buffer: &mut String, + extension: &[FieldDescriptorProto], +) -> Result<(), ConverterError> { + if !extension.is_empty() { + // TODO: Handle extension + return Err(ConverterError::GenericError(format!( + "Descriptor extension is not supported {extension:?}" + ))); + } + Ok(()) +} + +fn process_source_code_info( + _buffer: &mut String, + source_code_info: &SourceCodeInfo, +) -> Result<(), ConverterError> { + // TODO: Handle source_code_info + Err(ConverterError::GenericError(format!( + "Descriptor source_code_info is not supported {source_code_info:?}" + ))) +}