Obviously nothing prevents you from PInvoking malloc and free directly, or linking to a custom allocator, or even using https://www.nuget.org/packages/TerraFX.Interop.Mimalloc which is a fully managed Mimalloc implementation (which is competitive with the original).
There is also an advanced undocumented API to register a specific segment of memory as nongc-heap where you can allocate otherwise managed objects manually.
But the most important thing to understand that normally in languages with generational GCs the objects are not freed, instead, the surviving objects are copied to an older generation while the rest of the memory is reclaimed and immediately made available for subsequent allocations. As a result, you can't "free" an object since such operation does not exist in the design.
Overall, you're not forced to use objects in low-level code - structs can implement methods, interfaces, used in generics for zero cost abstractions,etc., and if you don't allocate or allocate only a little - the GC will never run.