Everything about rust
I started experimenting with asynchronous Rust code back when
was all we had - before
async/await. I was a Rust baby then (I'm at least
a toddler now), so I quickly drowned in a sea of
I've been banging the same drum for years: APIs must be carefully designed.
This statement doesn't resonate the same way with everyone. In order to really understand what I mean by "careful API design", one has to have experienced both ends of the spectrum.
Welcome back and thanks for joining us for the reads notes... the thirteenth installment of our series on ELF files, what they are, what they can do, what does the dynamic linker do to them, and how can we do it ourselves.
Welcome back to the "Making our own executable packer" series, where digressions are our bread and butter.
Last time, we implemented indirect functions in a no-libc C program. Of course, we got lost on the way and accidentally implemented a couple of useful elk-powered GDB functions - with only the minimal required amount of Python code.
In the last article, we cleaned up our dynamic linker a little. We even implemented
But it's still pretty far away from running real-world applications.
Let's pick up where we left off: we had just taught
elk to load
not only an executable, but also its dependencies, and then their
dependencies as well.
In order to increase fluency in a programming language, one has to read a lot of it. But how can you read a lot of it if you don't know what it means?
Up until now, we've been loading a single ELF file, and there wasn't much
structure to how we did it: everyhing just kinda happened in
main, in no
In our last article, we managed to load and execute a PIE (position-independent executable) compiled from the following code:
; in `elk/samples/hello.asm` global _start section .text _start: mov rdi, 1 ; stdout fd mov rsi, msg mov rdx, 9 ; 8 chars + newline mov rax, 1 ; write syscall syscall xor rdi, rdi ; return code 0 mov rax, 60 ; exit syscall syscall msg: db "hi there", 10
In the last article, we found where code was hiding in our
executable, by disassembling the whole file and then looking for syscalls.
In part 1, we've looked at three executables:
sample, an assembly program that prints "hi there" using the
entry_point, a C program that prints the address of
/bin/trueexecutable, probably also a C program (because it's part of GNU coreutils), and which just exits with code 0.
Executables have been fascinating to me ever since I discovered, as a kid,
that they were just files. If you renamed a
.exe to something else, you
could open it in notepad! And if you renamed something else to a
you'd get a neat error dialog.
So. Serializing IPv4 packets. Easy? Well, not exactly.
IPv4 was annoying to parse, because we had 3-bit integers, and 13-bit integers, and who knows what else. Serializing it is going to be exactly the same.
Alright. ALRIGHT. I know, we're all excited, but let's think about what we're doing again.
So we've managed to look at real network traffic and parse it completely. We've also taken some ICMP packets, parsed them, and then serialized them right back and we got the exact same result.
In the last part, we've finally parsed some IPv4 packets. We even found a way to filter only IPv4 packets that contain ICMP packets.
Hello and welcome to Part 11 of this series, wherein we finally use some of the code I prototyped way back when I was planning this series.
Where are we standing?
Before we move on to parsing more of our raw packets, I want to take some time to improve our error handling strategy.
ersatz codebase contains a mix of
Result<T, E>, and some
methods that panic, like
Blog posts that praise Rust are many but funding is generally in short supply.
If even a small percentage of the money Rust saves companies was put back into the ecosystem it would help secure the future of the platform tremendously.
Multiple sources of funding
Now that we've found the best way to find the "default network interface"... what can we do with that interface?
Okay, I lied.
I'm deciding - right this instant - that using wmic is cheating too. Oh, it was fair game when we were learning about Windows, but we're past that now.
Let's set aside our
sup project for a while.
Don't get me wrong - it's a perfectly fine project, and, were we simply rewriting "ping" for Windows in Rust, we could (almost) stop there.
Our ping API is simple, but it's also very limited:
pub fn ping(dest: ipv4::Addr) -> Result<(), String> // called as: ping(ipv4::Addr([8, 8, 8, 8])).unwrap();
We've just spent a lot of time abstracting over LoadLibrary, but we still have all the gory details of the Win32 ICMP API straight in our main.rs file! That won't do.
It's time to make
sup, our own take on
ping, use the Win32 APIs to send
an ICMP echo. Earlier we discovered that Windows's
IcmpSendEcho2Ex. But for our purposes, the simpler
IcmpSendEcho will do
So, how does
ping.exe actually send a ping? It seems unrealistic that
ping.exe itself implements all the protocols involved in sending a ping.
So it must be calling some sort of library. Also, since it ends up
talking to the outside world via a NIC (network interface controller),
the kernel is probably involved at some point.
It feels like an eternity since I've started using Rust, and yet I remember vividly what it felt like to bang my head against the borrow checker for the first few times.
So far, we've seen many ways to read a file from different programming languages, we've learned about syscalls, how to make those from assembly, then we've learned about memory mapping, virtual address spaces, and generally some of the mechanisms in which userland and the kernel interact.
Everybody knows how to use files. You just open up File Explorer, the Finder, or a File Manager, and bam - it's chock-full of files. There's folders and files as far as the eye can see. It's a genuine filapalooza. I have never once heard someone complain there were not enough files on their computer.