The libc crate in Rust simplifies the use of most of the functions available in libc.
The main features of this crate are :
errno
) or signal constants. These constants facilitate the use of system APIs.Please note that not all libc functions are described in the crate, so please consult the documentation.
Unfortunately, it is not possible to use the functions available in this crate without using unsafe
blocks.
This crate uses features that can be found in the crate documentation at docs.rs.
The Nix crate exposes wrappers around the main features of libc to make it safer and easier to use.
The crate is divided into features. You can find a list of these features on docs.rs.
In this section, we'll look at how to use the two crates through three simple examples.
To include one of the crates in a project, simply add the crate to the dependencies in the Cargo.toml
file, as in the case of the libc
crate:
[dependencies]
libc = "0.2.150"
For this same crate, it is possible to use it without using the standard library (std). To do so, you need to specify the absence of default features (and possibly activate the other features one by one):
[dependencies]
libc = { version = "0.2.150", default-features = false }
You can retrieve the user and group with which the process is running using the geteuid
function and the getegid
function respectively.
Using the libc
crate:
extern crate libc;
fn main() {
   unsafe {
       let uid = libc::geteuid();
       println!("Current user's UID: {}", uid);
       let gid = libc::getegid();
       println!("GID of current group: {}", gid);
   }
}
For the Nix crate, the code is virtually the same except for the absence of the unsafe block:
extern crate nix;
use nix::unistd::{geteuid, getegid};
fn main() {
   let uid = geteuid();
   println!("Current user's UID: {}", uid);
   let gid = getegid();
   println!("GID of current group: {}", gid);
}
Note that you need to use the user feature to access both functions.
In this example, we'll see how to retrieve the current directory by specifying the buffer where the string will be stored, passing it by reference.
extern crate libc;
fn main() {
   unsafe {
       let mut buffer: [libc::c_char; libc::PATH_MAX as usize] = [0; libc::PATH_MAX as usize];
       if libc::getcwd(buffer.as_mut_ptr(), buffer.len() as libc::size_t).is_null() {
           eprintln!("Erreur lors de l'obtention du répertoire de travail actuel");
       } else {
           let cwd = std::ffi::CStr::from_ptr(buffer.as_ptr());
           println!("Répertoire de travail actuel : {:?}", cwd);
       }
   }
}
You can see that we're using an array with the size libc::PATH_MAX
and initialized to 0, just as you'd find in C.
The std::ffi::CStr::from_ptr
function allows you to convert the string and more securely use the return string as it comes from a function written in C.
The version using the Nix crate is considerably simpler:
extern crate nix;
use nix::unistd::getcwd;
fn main() {
   match getcwd() {
       Ok(cwd) => {
           let current_dir = cwd.to_string_lossy();
           println!("Current working directory: {}", current_dir);
       }
       Err(_) => {
           eprintln!("Could not get current working directory.");
       }
   }
}
The feature required to use the getcwd
function is fs.
Here's how to create a TCP socket using the libc crate to connect to address 127.0.0.1:12345 (works on x86 or x64 systems):
extern crate libc;
use std::net::Ipv4Addr;
fn main() {
   unsafe {
       // Create a TCP socket (SOCK_STREAM)
       let socket_fd = libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0);
       if socket_fd == -1 {
           eprintln!("Error during socket creation");
           return;
       }
       // Fills struct sockaddr_in with server address (here '127.0.0.1:12345')
       let mut server_address: libc::sockaddr_in = std::mem::zeroed();
       server_address.sin_family = libc::AF_INET as libc::sa_family_t;
       server_address.sin_addr.s_addr = u32::from_le_bytes("127.0.0.1".parse::<Ipv4Addr>().unwrap().octets());
       server_address.sin_port = u16::to_be(12345);
       // connect system call
       if libc::connect(
           socket_fd,
           &server_address as *const _ as *const libc::sockaddr,
           std::mem::size_of_val(&server_address) as libc::socklen_t,
       ) == -1
       {
           eprintln!("Could not connect");
           libc::close(socket_fd);
           return;
       }
       println!("Connected to 127.0.0.1:12345");
       // Close connection
       libc::close(socket_fd);
   }
}
You can see that the htons
and inet_addr
functions are not used when creating the sockaddr_in
structure.
This is because they are not available in the crate, so you have to use other language functions to achieve the same result.
Here, we retrieve the IP address required in s_addr
by converting the IP address into an IPv4Addr
, available in the std::net
library, then retrieving the bytes that make it up to form a u32
. You can replace the u32::from_le_bytes
function with u32::from_be_bytes
on big-endian systems like ARM.
To get the port in the right format, convert the port number into a u16
in big-endian.
The Nix version is much simpler:
extern crate nix;
use std::os::fd::AsRawFd;
use nix::sys::socket::{socket, connect, AddressFamily, SockType, SockaddrIn};
fn main() {
   // Create a TCP socket
   let socket_fd = socket(
       AddressFamily::Inet,
       SockType::Stream,
       nix::sys::socket::SockFlag::empty(),
       None,
   );
   match socket_fd {
       Ok(fd) => {
           // Set IP address and port
           let server_addr = SockaddrIn::new(127, 0, 0, 1, 12345);
           // Connect to the server
           match connect(fd.as_raw_fd(), &server_addr) {
               Ok(_) => {
                   println!("Connected to 127.0.0.1:12345");
               }
               Err(e) => {
                   eprintln!("Server connection error : {}", e);
               }
           }
       }
       Err(e) => {
           eprintln!("Could not create socket : {}", e);
       }
   }
}
As you can see, no type conversions are required. The use of file descriptors defined in the os module of the standard library clearly shows how the libc fits into the language thanks to Nix, whereas the crate libc simply exposes the functions. Conclusion
In conclusion, the crate libc in Rust is a simple interface to the standard C library. It is particularly useful for low-level operations and interoperability with C code, which is very useful for malware development.
The Nix crate is a wrapper around the library, facilitating development in Rust as well as program stability.
For detailed information on a libc function, you can consult the man-pages available on any Linux system. They provide detailed documentation of standard library functions.
To consult a man-page, use the command man
followed by the function name. For example, to obtain information on the malloc function, you can run man malloc
in the terminal.
There are also many online resources providing man-pages, which can be useful. You can find online versions on sites such as man7.