In this fourth module we will take a look at the fundamentals of computing.
These external graded assignments are for ASU students only. Non-ASU students do not need to complete them.
Computers run computer programs to achieve different goals. One program might be your favorite video game, another is the web browser you're using to access this website, and so on.
A program is made of computer code, and this code is made of a huge amount of individual instructions that cause the computer to carry out computation and take certain actions based on the results. Each individual instruction is typically very simple, and only in aggregate do they enable awesome things like letting you look at memes on the internet.
This computation is done by the Central Processing Unit (CPU), in tandem with other pieces of hardware inside your computer. Instructions are specified to the CPU in something called Assembly Language, and each CPU architecture uses a different flavor of this language. Any program, no matter what language it is originally written in (e.g., C, C++, Java, Python, etc.), is eventually converted to or interpreted by Assembly instructions.
Most of pwn.college's material uses the x86 CPU architecture, which is Zardus' favorite architecture. x86 was created by Intel in the dawn of the PC age, and has continued to evolve over the years. Together, x86 and ARM (a different, less cool architecture) make up the majority of PC CPUs out there.
In this module, we will start out with the simplest x86 program that we can imagine, which we will write in x86 assembly, and build up from there! Let's dig in, and write your first program!
Wow, you are a budding x86 assembly programmer! You've set registers, triggered system calls, and wrote your first program that cleanly exits. Now, we have one more big concept for you: memory.
You, as (presumably) a human being, have Short Term Memory and Long Term Memory. When performing specific computation, your brain loads information you've previously learned into your short term memory, then acts on that information, then eventually puts new resulting information into your long-term memory. Societally, we also invented other, longer-term forms of storage: oral histories, journals, books, and wikipedia. If there's not enough space in your long-term memory for some information, or the information is not important to commit to long-term memory, you can always go and look it up on wikipedia, have your brain stick it into long-term memory, and pull it into your short-term memory when you need it later.
This multi-level hierarchy of information access from "small but accessible" (your short term memory, which is right there when you need it but only stores 5 to 9 pieces of information to "large but slow" (remembering stuff from your massive long-term memory) to "massive but absolutely glacial" (looking stuff up on wikipedia) is actually the foundation of the Memory Hierarchy of modern computing. We've already learned about the "small but accessible" part of this in the previous module: those are registers, limited but FAST.
More spacious than even all the registers put together, but much much MUCH slower to access, is computer memory, and this is what we'll dig into with this module, giving you a glimpse into another level of the memory hierarchy.
So far, you've been reading from memory addresses that we set up for you.
But your program already has a region of memory ready to go: the stack.
The stack is pointed to by the rsp register, and it contains useful data about how your program was launched and accumulates other data as the program executes.
Let's explore it!
A byte is eight bits; half of one (four bits) is a nibble. This module nibbles at numbers from the bit up: how a pile of bits comes to mean a positive or a negative number in binary, hexadecimal, and decimal. Understanding this is critical to truly knowing how the CPU processes bits into something more meaningful!
As you write larger and larger programs, you (yes, even you!) might make mistakes when implementing certain functionality, introducing bugs into your programs. When this happens, you'll need to have a reliable toolbox of resources to understand what is going wrong and fix it. Of course, the exact same techniques can be used to understand what is wrong with code that you didn't write, and fix or exploit it as you might desire!
This module will introduce you to several ways to introspect, debug, and understand software. You'll carry this critical knowledge with you and use it throughout pwn.college, so harken well!
Until now, your program's single interaction with the wider world was changing its exit code when exiting.
Of course, more interaction is possible!
In this module, we will learn about the write system call, which is used to write output to the command-line terminal!
This is going to be an exciting journey: the logic of this program is going to be both as close as you can possibly get to the hardware itself (e.g., you are writing raw x86 assembly that the CPU directly understands!) and as close as you can possibly get to the Linux operating system (e.g., you are triggering system calls directly!).
x86 stores every multi-byte value little-endian (low byte first), and that fact turns up everywhere you look at raw memory: hex dumps, debuggers, exploits. To work properly in these contexts, you need to understand byte order. This module makes it second nature.
You'll crack a series of programs that hide a password in their own compiled code, reading it back out of the disassembly at every size, whether it's a "qword", a lone byte, or a structure. By the end, byte order won't trip you up in a hex dump, a debugger, or an exploit ever again (or we'll add more challenges to make it so!).
You have a working knowledge of assembly from your journey thus far. Let's broaden it!
This module will explore the effects of a number of different assembly instructions, teaching you to recognize them and not panic in their presence. In most of these challenges, you'll reverse-engineer a binary to understand an instruction's effect on data and feed it input it will accept. In a few others, you'll put an instruction to work yourself, writing a small function around it. Either way, if you understand what the instruction does, you will get flags!
Back when you first met the stack, it was just some data the kernel had set up for you: argc, argv, a few pointers. Now that you've written your own functions, made your own calls, and seen rsp move in response to push and pop, it's time to revisit the stack with sharper eyes. How does it actually get laid out? Why does it "grow downwards," and what does that mean for the data you can reach? And how does the way your program was launched shape the addresses of everything on it?
You've learned to read and write registers, walk through memory, and package your code as a function inside a shared library.
Now let's put those skills together and build some real algorithms.
When you need to debug one of these .so submissions, refer back to Writing from a Shared Library for the shared-library debugging pattern.
Through this module, we'll gradually build on our solutions until we create code that addresses a very common program need: turning text into numbers. Along the way, we'll learn how to write reusable assembly code, how text and numbers relate to each other, and how to reason about algorithms (and their failings!). You'll grow in knowledge, and in flags!
You can turn text into a number with your atoi, and a number back into text with your itoa.
Now you'll use both at once to build a small command-line calculator.
It reads an expression from argv --- a left operand, an operator, and a right operand --- as in prog 6 + 7.
The operands are strings you already know how to handle: atoi each one.
The operator is the new piece: a single character you branch on to decide what to compute, quitting on any operator you don't support.
Then itoa the result and write it to standard output, exactly as your summing program did.
We'll add one operator group at a time --- addition, then subtraction, then multiplication, then the bitwise operators, and finally the unary operators --- each reusing the atoi and itoa you've already written.
Programs rarely print just one fixed string.
They print messages assembled from fixed words and changing values: answer: 13, hello, hacker, opened 3 files, and so on.
You could write each message by hand, but then every new message shape needs its own copy loops, number conversions, and write calls.
A format string is a compact recipe for building those messages.
Ordinary characters in the recipe are printed as-is, while special markers say where the next value should go.
The code that follows such a recipe is called a formatter.
printf is the traditional name for a formatter that prints its result.
This mini-module will have you build printf.
Your version will be special: it's yours!
Many other versions of printf exist as well.
For example, the "standard C library", which includes useful functions to use when writing applications in the C programming language, includes an implementation, and your commandline has it too:
hacker@dojo:~$ printf Hello
Hello
hacker@dojo:~$
We'll build up that idea in small steps: literal text, the ASCII newline byte, escaped syntax characters, decimal numbers, repeated values, strings, and finally arbitrary bytes.
A critical part of working with computing is understanding what goes wrong when something inevitably does. This module will build on your prior exposure to GDB with some more debugging of programs: digging in, poking around, and gaining knowledge. This is one of the most critical skills that you will learn in your computing journey, and this module will hopefully help water the seed that we planted before.
As you know, GDB is a very powerful dynamic analysis tool which you can use in order to understand the state of a program throughout its execution. You will become more familiar with some of its capabilities in this module.
Now that you know how to write and debug assembly, it is time to do something real! In this module, you will develop the skills needed to build a web server from scratch, starting with a simple program and progressing to handling multiple HTTP GET and POST requests. Good luck!
As you proceed in your journey, remember your system call table.