I am currently experimenting with a workflow to transform Rust programs into C code.
The approach I am using is:
Rust source
↓ rustc
LLVM IR
↓ LLVM-CBE
C source
↓ C compiler
Executable
My goal is to obtain C source code so that I can apply existing C source obfuscation tools. There seem to be far fewer source-level obfuscation tools available for Rust than for C.
LLVM-CBE is able to generate C code from the LLVM IR produced by rustc, but when I try to compile the generated C code (after including all the Rust and C libraries), I frequently encounter linker errors such as:
/usr/bin/ld: /tmp/ccv2dnQc.o: in function `core::fmt::rt::Argument::new_display`:
s179264673.cbe.c:(.text+0x365): undefined reference to `_ZN4core3fmt3num3imp52__EC_LT_EC_impl_EC_u20_EC_core_OC__OC_fmt_OC__OC_Display_EC_u20_EC_for_EC_u20_EC_i32_EC_GT_EC_3fmt17hf9cd6342de4889e1E`
collect2: error: ld returned 1 exit status
From what I understand, these symbols correspond to functions generated by the Rust compiler during compilation.
My questions are:
Is there a known way to make this LLVM IR → LLVM-CBE → C workflow work reliably with Rust programs?
Are there specific Rust compilation options that reduce dependencies on Rust runtime symbols?
Does anyone know of alternative approaches for translating Rust into C source code?
Any advice or experience with similar workflows would be greatly appreciated.
Eyeballing it, that looks like a standard library function that's missing (specifically I think it's <Display for i32>::fmt if I'm unmangling it correctly) - which is shipped already compiled as a static library and linked into your rust build.
You could try using something like cargo -Z build-std but I expect this is only going to kick the can down the road? It should get you closer to what you're after, at least.
I tried rebuilding with cargo -Z build-std, and it did indeed get me further. The generated C compiles more successfully than before, but I still end up with linker errors for Rust runtime and standard library symbols, even after linking against the Rust libraries I could identify.
/usr/bin/ld: /tmp/ccJ0AQ8P.o: in function `std::rt::lang_start::<()>`:
testproj-66687eadd765e3b6.cbe.c:(.text+0x55): undefined reference to `std::rt::lang_start_internal`
/usr/bin/ld: /tmp/ccJ0AQ8P.o: in function `testproj::main`:
testproj-66687eadd765e3b6.cbe.c:(.text+0x127): undefined reference to `<i32 as core::fmt::Display>::fmt`
/usr/bin/ld: testproj-66687eadd765e3b6.cbe.c:(.text+0x140): undefined reference to `std::io::stdio::_print`
/usr/bin/ld: /tmp/ccJ0AQ8P.o: in function `main`:
testproj-66687eadd765e3b6.cbe.c:(.text+0x1b8): undefined reference to `std::rt::lang_start_internal`
collect2: error: ld returned 1 exit status
So it seems that LLVM-CBE correctly translates the LLVM IR it receives into C, but it does not by itself reproduce the complete linking environment and runtime support that Rust normally relies on to build a final executable.
Yes. I tried linking against the Rust standard library from the Rust sysroot. I'm passing rustlib/x86_64-unknown-linux-gnu/lib/libstd-*.so to gcc, together with libc and libgcc_s.
You need the standard library crates compiled by LLVM-CBE. The one compiled using the regular LLVM backend is not ABI compatible. LLVM-CBE changes the symbol names and the calling convention probably also doesn't match exactly.
You probably have to use -Zbuild-std and wire up everything in your codegen backend such that all produced C files from all dependencies get linked together with the current crate.
It seems that translating the entire Rust ecosystem to C using LLVM-CBE is likely a dead end, since LLVM-CBE produces approximate translations and is far from preserving a consistent ABI or runtime behavior.
I will need to reconsider my approach for translating Rust into C.
The translation tool itself hasn't been published yet, but it looks very promising. I'll definitely keep an eye on the project and I'd be happy to test it once it becomes available.