├── .gitattributes ├── .gitignore ├── DESCRIPTION.md ├── LICENSE ├── README.md ├── assembly-crash-course ├── DESCRIPTION.md ├── level-1 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-10-a │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-10-b │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-10 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-11-a │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-11 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-12 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-13 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-14 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-15 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-16 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-17-a │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-17-b │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-17 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-18 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-19 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-2-a │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-2 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-20 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-21 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-22 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-23 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-3 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-4 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-5 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-6-a │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-6 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-7 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-8 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-9 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── module.yml └── run ├── building-a-web-server ├── DESCRIPTION.md ├── level-1 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-10 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-11 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-2 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-3 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-4 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-5 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-6 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-7 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-8 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── level-9 │ ├── .config │ ├── DESCRIPTION.md │ └── run ├── module.yml └── run ├── check ├── debugging-refresher ├── DESCRIPTION.md ├── level-1 │ ├── DESCRIPTION.md │ └── embryogdb_level1 ├── level-2 │ ├── DESCRIPTION.md │ └── embryogdb_level2 ├── level-3 │ ├── DESCRIPTION.md │ └── embryogdb_level3 ├── level-4 │ ├── DESCRIPTION.md │ └── embryogdb_level4 ├── level-5 │ ├── DESCRIPTION.md │ └── embryogdb_level5 ├── level-6 │ ├── DESCRIPTION.md │ └── embryogdb_level6 ├── level-7 │ ├── DESCRIPTION.md │ └── embryogdb_level7 ├── level-8 │ ├── DESCRIPTION.md │ └── embryogdb_level8 └── module.yml ├── dojo.yml ├── hello-hackers ├── DESCRIPTION.md ├── hello-hackers │ ├── .py │ │ ├── chal.py │ │ └── chalconf.py │ ├── DESCRIPTION.md │ └── check ├── module.yml ├── read │ ├── .py │ │ ├── chal.py │ │ └── chalconf.py │ ├── DESCRIPTION.md │ └── check ├── write-exit │ ├── .py │ │ ├── chal.py │ │ └── chalconf.py │ ├── DESCRIPTION.md │ └── check └── write │ ├── .py │ ├── chal.py │ └── chalconf.py │ ├── DESCRIPTION.md │ └── check ├── introspecting ├── DESCRIPTION.md ├── gdb-launch │ ├── .gdb │ ├── .init │ ├── DESCRIPTION.md │ ├── bin │ │ └── gdb │ └── submit-number ├── gdb-starti │ ├── .gdb │ ├── .init │ ├── DESCRIPTION.md │ ├── bin │ │ └── gdb │ └── submit-number ├── module.yml └── strace │ ├── .init │ ├── DESCRIPTION.md │ └── submit-number ├── memory ├── DESCRIPTION.md ├── mem-dereference-self │ ├── .py │ │ ├── chal.py │ │ └── chalconf.py │ ├── DESCRIPTION.md │ └── check ├── mem-dereference │ ├── .py │ │ ├── chal.py │ │ └── chalconf.py │ ├── DESCRIPTION.md │ └── check ├── mem-double-deref │ ├── .py │ │ ├── chal.py │ │ └── chalconf.py │ ├── DESCRIPTION.md │ └── check ├── mem-load-2 │ ├── .py │ │ ├── chal.py │ │ └── chalconf.py │ ├── DESCRIPTION.md │ └── check ├── mem-load │ ├── .py │ │ ├── chal.py │ │ └── chalconf.py │ ├── DESCRIPTION.md │ └── check ├── mem-offsets │ ├── .py │ │ ├── chal.py │ │ └── chalconf.py │ ├── DESCRIPTION.md │ └── check ├── mem-stored-addr │ ├── .py │ │ ├── chal.py │ │ └── chalconf.py │ ├── DESCRIPTION.md │ └── check ├── mem-triple-deref │ ├── .py │ │ ├── chal.py │ │ └── chalconf.py │ ├── DESCRIPTION.md │ └── check └── module.yml ├── secret-value-checker.py ├── tests ├── all.sh └── solves │ ├── hello-hackers~hello-hackers.sh │ ├── hello-hackers~write-exit.sh │ ├── hello-hackers~write.sh │ ├── memory~mem-dereference-self.sh │ ├── memory~mem-dereference.sh │ ├── memory~mem-double-deref.sh │ ├── memory~mem-load-2.sh │ ├── memory~mem-load.sh │ ├── memory~mem-offsets.sh │ ├── memory~mem-stored-addr.sh │ ├── memory~mem-triple-deref.sh │ ├── yfp~building.sh │ ├── yfp~exit-code.sh │ ├── yfp~exit.sh │ ├── yfp~movreg.sh │ └── yfp~rax.sh └── your-first-program ├── DESCRIPTION.md ├── building ├── .py │ └── chal.py ├── DESCRIPTION.md └── check ├── exit-code ├── .py │ └── chal.py ├── DESCRIPTION.md └── check ├── exit ├── .py │ └── chal.py ├── DESCRIPTION.md └── check ├── module.yml ├── movreg ├── .py │ ├── chal.py │ └── chalconf.py ├── DESCRIPTION.md └── check └── rax ├── .py └── chal.py ├── DESCRIPTION.md └── check /.gitattributes: -------------------------------------------------------------------------------- 1 | solves/** filter=git-crypt diff=git-crypt 2 | tests/solves/** filter=git-crypt diff=git-crypt 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ 2 | **/.gdb_history 3 | -------------------------------------------------------------------------------- /DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Computers run an incredible managerie of programs that make modern life possible. 2 | But how do they work, deep down inside? 3 | In this dojo, we will dive into the depths of computation, reach (something close to) its very underpinnings, and strive to understand it all. 4 | 5 | This dojo will start with teaching you the underlying machine code that computers process directly. 6 | From there, we will explore additional concepts, gradually solidifying your understanding and preparing you for the rest of pwn.college. 7 | 8 | Join us for this journey, and let's learn computing together. 9 | 10 | **NOTE:** 11 | This dojo is a work in progress *and* a community effort! 12 | If you are interested in contributing, please make your way over to [github](https://github.com/pwncollege/computing-101)! 13 | If you have questions, comments, feedback, and so on, join us on [the Discord channel](https://discord.com/channels/750635557666816031/1269933644319817748). 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2022, pwn.college 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Computing 101 2 | 3 | This is a [pwn.college](https://pwn.college) dojo built around teaching low-level computing. 4 | -------------------------------------------------------------------------------- /assembly-crash-course/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Now that you have the hang of very basic assembly, let's dive in and explore a few different instructions and some additional concepts! 2 | The Assembly Crash Course is a romp through a lot of different things you can do in assembly, and will prepare you for the adventures to come! 3 | 4 | To interact with any level you can either run the challenges with an ELF as an argument (e.g., `/challenge/run /path/to/your/elf`) or send raw bytes over stdin to this program. 5 | -------------------------------------------------------------------------------- /assembly-crash-course/level-1/.config: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-1/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In this level, you will be working with registers. You will be asked to modify or read from registers. 2 | 3 | In this level, you will work with registers! Please set the following: 4 | 5 | `rdi = 0x1337` -------------------------------------------------------------------------------- /assembly-crash-course/level-1/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-10-a/.config: -------------------------------------------------------------------------------- 1 | 12 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-10-a/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | We will now set some values in memory dynamically before each run. On each run, the values will change. This means you will need to do some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 2 | 3 | In this level, you will be working with memory. This will require you to read or write to things stored linearly in memory. If you are confused, go look at the linear addressing module in 'ike. You may also be asked to dereference things, possibly multiple times, to things we dynamically put in memory for your use. 4 | 5 | Up until now, you have worked with registers as the only way for storing things, essentially variables such as 'x' in math. 6 | 7 | However, we can also store bytes into memory! 8 | 9 | Recall that memory can be addressed, and each address contains something at that location. Note that this is similar to addresses in real life! 10 | 11 | As an example: the real address '699 S Mill Ave, Tempe, AZ 85281' maps to the 'ASU Brickyard'. We would also say it points to 'ASU Brickyard'. We can represent this like: 12 | 13 | ``` 14 | ['699 S Mill Ave, Tempe, AZ 85281'] = 'ASU Brickyard' 15 | ``` 16 | 17 | The address is special because it is unique. But that also does not mean other addresses can't point to the same thing (as someone can have multiple houses). 18 | 19 | Memory is exactly the same! 20 | 21 | For instance, the address in memory where your code is stored (when we take it from you) is `0x400000`. 22 | 23 | In x86, we can access the thing at a memory location, called dereferencing, like so: 24 | 25 | ``` 26 | mov rax, [some_address] <=> Moves the thing at 'some_address' into rax 27 | ``` 28 | 29 | This also works with things in registers: 30 | 31 | ``` 32 | mov rax, [rdi] <=> Moves the thing stored at the address of what rdi holds to rax 33 | ``` 34 | 35 | This works the same for writing to memory: 36 | 37 | ``` 38 | mov [rax], rdi <=> Moves rdi to the address of what rax holds. 39 | ``` 40 | 41 | So if `rax` was `0xdeadbeef`, then `rdi` would get stored at the address `0xdeadbeef`: 42 | 43 | ``` 44 | [0xdeadbeef] = rdi 45 | ``` 46 | 47 | Note: Memory is linear, and in x86_64, it goes from `0` to `0xffffffffffffffff` (yes, huge). 48 | 49 | Please perform the following: Place the value stored at `0x404000` into `rax`. Make sure the value in `rax` is the original value stored at `0x404000`. 50 | -------------------------------------------------------------------------------- /assembly-crash-course/level-10-a/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-10-b/.config: -------------------------------------------------------------------------------- 1 | 13 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-10-b/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | We will now set some values in memory dynamically before each run. On each run, the values will change. This means you will need to do some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 2 | 3 | In this level, you will be working with memory. This will require you to read or write to things stored linearly in memory. If you are confused, go look at the linear addressing module in 'ike. You may also be asked to dereference things, possibly multiple times, to things we dynamically put in memory for your use. 4 | 5 | Please perform the following: 6 | Place the value stored in `rax` to `0x404000`. 7 | -------------------------------------------------------------------------------- /assembly-crash-course/level-10-b/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-10/.config: -------------------------------------------------------------------------------- 1 | 14 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-10/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | We will now set some values in memory dynamically before each run. On each run, the values will change. This means you will need to do some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 2 | 3 | In this level, you will be working with memory. This will require you to read or write to things stored linearly in memory. If you are confused, go look at the linear addressing module in 'ike. You may also be asked to dereference things, possibly multiple times, to things we dynamically put in memory for your use. 4 | 5 | Please perform the following: 6 | 7 | - Place the value stored at `0x404000` into `rax`. 8 | - Increment the value stored at the address `0x404000` by `0x1337`. 9 | 10 | Make sure the value in `rax` is the original value stored at `0x404000` and make sure that `[0x404000]` now has the incremented value. 11 | -------------------------------------------------------------------------------- /assembly-crash-course/level-10/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-11-a/.config: -------------------------------------------------------------------------------- 1 | 15 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-11-a/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | We will now set some values in memory dynamically before each run. On each run, the values will change. This means you will need to do some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 2 | 3 | In this level, you will be working with memory. This will require you to read or write to things stored linearly in memory. If you are confused, go look at the linear addressing module in 'ike. You may also be asked to dereference things, possibly multiple times, to things we dynamically put in memory for your use. 4 | 5 | Recall that registers in x86_64 are 64 bits wide, meaning they can store 64 bits. Similarly, each memory location can be treated as a 64-bit value. We refer to something that is 64 bits (8 bytes) as a quad word. 6 | 7 | Here is the breakdown of the names of memory sizes: 8 | 9 | - Quad Word = 8 Bytes = 64 bits 10 | - Double Word = 4 bytes = 32 bits 11 | - Word = 2 bytes = 16 bits 12 | - Byte = 1 byte = 8 bits 13 | 14 | In x86_64, you can access each of these sizes when dereferencing an address, just like using bigger or smaller register accesses: 15 | 16 | - `mov al, [address]` <=> moves the least significant byte from address to `rax` 17 | - `mov ax, [address]` <=> moves the least significant word from address to `rax` 18 | - `mov eax, [address]` <=> moves the least significant double word from address to `rax` 19 | - `mov rax, [address]` <=> moves the full quad word from address to `rax` 20 | 21 | Remember that moving into `al` does not fully clear the upper bytes. 22 | 23 | Please perform the following: 24 | Set `rax` to the byte at `0x404000`. 25 | -------------------------------------------------------------------------------- /assembly-crash-course/level-11-a/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-11/.config: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-11/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | We will now set some values in memory dynamically before each run. On each run, the values will change. This means you will need to perform some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 2 | 3 | In this level, you will be working with memory. This will require you to read or write to things stored linearly in memory. If you are confused, refer to the linear addressing module in 'ike. You may also be asked to dereference things, possibly multiple times, to things we dynamically put in memory for your use. 4 | 5 | Recall the following: 6 | 7 | - The breakdown of the names of memory sizes: 8 | - Quad Word = 8 Bytes = 64 bits 9 | - Double Word = 4 bytes = 32 bits 10 | - Word = 2 bytes = 16 bits 11 | - Byte = 1 byte = 8 bits 12 | 13 | In x86_64, you can access each of these sizes when dereferencing an address, just like using bigger or smaller register accesses: 14 | 15 | - `mov al, [address]` <=> moves the least significant byte from address to `rax` 16 | - `mov ax, [address]` <=> moves the least significant word from address to `rax` 17 | - `mov eax, [address]` <=> moves the least significant double word from address to `rax` 18 | - `mov rax, [address]` <=> moves the full quad word from address to `rax` 19 | 20 | Please perform the following: 21 | 22 | - Set `rax` to the byte at `0x404000` 23 | - Set `rbx` to the word at `0x404000` 24 | - Set `rcx` to the double word at `0x404000` 25 | - Set `rdx` to the quad word at `0x404000` 26 | -------------------------------------------------------------------------------- /assembly-crash-course/level-11/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-12/.config: -------------------------------------------------------------------------------- 1 | 17 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-12/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | We will now set some values in memory dynamically before each run. On each run, the values will change. This means you will need to do some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 2 | 3 | In this level, you will be working with memory. This will require you to read or write to things stored linearly in memory. If you are confused, go look at the linear addressing module in 'ike. You may also be asked to dereference things, possibly multiple times, to things we dynamically put in memory for your use. 4 | 5 | It is worth noting, as you may have noticed, that values are stored in reverse order of how we represent them. 6 | 7 | As an example, say: 8 | ``` 9 | [0x1330] = 0x00000000deadc0de 10 | ``` 11 | 12 | If you examined how it actually looked in memory, you would see: 13 | ``` 14 | [0x1330] = 0xde 15 | [0x1331] = 0xc0 16 | [0x1332] = 0xad 17 | [0x1333] = 0xde 18 | [0x1334] = 0x00 19 | [0x1335] = 0x00 20 | [0x1336] = 0x00 21 | [0x1337] = 0x00 22 | ``` 23 | 24 | This format of storing things in 'reverse' is intentional in x86, and it's called "Little Endian". 25 | 26 | For this challenge, we will give you two addresses created dynamically each run. 27 | 28 | The first address will be placed in `rdi`. 29 | The second will be placed in `rsi`. 30 | 31 | Using the earlier mentioned info, perform the following: 32 | - Set `[rdi] = 0xdeadbeef00001337` 33 | - Set `[rsi] = 0xc0ffee0000` 34 | 35 | Hint: it may require some tricks to assign a big constant to a dereferenced register. Try setting a register to the constant value, then assigning that register to the dereferenced register. 36 | -------------------------------------------------------------------------------- /assembly-crash-course/level-12/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-13/.config: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-13/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | We will now set some values in memory dynamically before each run. On each run, the values will change. This means you will need to perform some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it’s `rax`. 2 | 3 | In this level, you will be working with memory. This will require you to read or write to things stored linearly in memory. If you are confused, go look at the linear addressing module in 'ike. You may also be asked to dereference things, possibly multiple times, to things we dynamically put in memory for your use. 4 | 5 | Recall that memory is stored linearly. 6 | 7 | What does that mean? 8 | 9 | Say we access the quad word at `0x1337`: 10 | ``` 11 | [0x1337] = 0x00000000deadbeef 12 | ``` 13 | 14 | The real way memory is laid out is byte by byte, little endian: 15 | ``` 16 | [0x1337] = 0xef 17 | [0x1337 + 1] = 0xbe 18 | [0x1337 + 2] = 0xad 19 | ... 20 | [0x1337 + 7] = 0x00 21 | ``` 22 | 23 | What does this do for us? 24 | 25 | Well, it means that we can access things next to each other using offsets, similar to what was shown above. 26 | 27 | Say you want the 5th *byte* from an address, you can access it like: 28 | ``` 29 | mov al, [address+4] 30 | ``` 31 | 32 | Remember, offsets start at 0. 33 | 34 | Perform the following: 35 | - Load two consecutive quad words from the address stored in `rdi`. 36 | - Calculate the sum of the previous steps' quad words. 37 | - Store the sum at the address in `rsi`. 38 | -------------------------------------------------------------------------------- /assembly-crash-course/level-13/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-14/.config: -------------------------------------------------------------------------------- 1 | 19 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-14/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | We will now set some values in memory dynamically before each run. On each run, the values will change. This means you will need to do some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 2 | 3 | In this level, you will be working with the stack, the memory region that dynamically expands and shrinks. You will be required to read and write to the stack, which may require you to use the `pop` and `push` instructions. You may also need to use the stack pointer register (`rsp`) to know where the stack is pointing. 4 | 5 | In these levels, we are going to introduce the stack. 6 | 7 | The stack is a region of memory that can store values for later. 8 | 9 | To store a value on the stack, we use the `push` instruction, and to retrieve a value, we use `pop`. 10 | 11 | The stack is a last in, first out (LIFO) memory structure, and this means the last value pushed is the first value popped. 12 | 13 | Imagine unloading plates from the dishwasher. Let's say there are 1 red, 1 green, and 1 blue. First, we place the red one in the cabinet, then the green on top of the red, then the blue. 14 | 15 | Our stack of plates would look like: 16 | 17 | ``` 18 | Top ----> Blue 19 | Green 20 | Bottom -> Red 21 | ``` 22 | 23 | Now, if we wanted a plate to make a sandwich, we would retrieve the top plate from the stack, which would be the blue one that was last into the cabinet, ergo the first one out. 24 | 25 | On x86, the `pop` instruction will take the value from the top of the stack and put it into a register. 26 | 27 | Similarly, the `push` instruction will take the value in a register and push it onto the top of the stack. 28 | 29 | Using these instructions, take the top value of the stack, subtract `rdi` from it, then put it back. 30 | -------------------------------------------------------------------------------- /assembly-crash-course/level-14/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-15/.config: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-15/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | We will now set some values in memory dynamically before each run. On each run the values will change. This means you will need to do some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 2 | 3 | In this level, you will be working with the stack, the memory region that dynamically expands and shrinks. You will be required to read and write to the stack, which may require you to use the `pop` and `push` instructions. You may also need to use the stack pointer register (`rsp`) to know where the stack is pointing. 4 | 5 | In this level, we are going to explore the last in first out (LIFO) property of the stack. 6 | 7 | Using only the following instructions: 8 | - `push` 9 | - `pop` 10 | 11 | Swap values in `rdi` and `rsi`. 12 | 13 | Example: 14 | - If to start `rdi = 2` and `rsi = 5` 15 | - Then to end `rdi = 5` and `rsi = 2` 16 | -------------------------------------------------------------------------------- /assembly-crash-course/level-15/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-16/.config: -------------------------------------------------------------------------------- 1 | 21 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-16/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | We will now set some values in memory dynamically before each run. On each run, the values will change. This means you will need to do some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 2 | 3 | In this level, you will be working with the stack, the memory region that dynamically expands and shrinks. You will be required to read and write to the stack, which may require you to use the `pop` and `push` instructions. You may also need to use the stack pointer register (`rsp`) to know where the stack is pointing. 4 | 5 | In the previous levels, you used `push` and `pop` to store and load data from the stack. However, you can also access the stack directly using the stack pointer. 6 | 7 | On x86, the stack pointer is stored in the special register, `rsp`. `rsp` always stores the memory address of the top of the stack, i.e., the memory address of the last value pushed. 8 | 9 | Similar to the memory levels, we can use `[rsp]` to access the value at the memory address in `rsp`. 10 | 11 | Without using `pop`, please calculate the average of 4 consecutive quad words stored on the stack. Push the average on the stack. 12 | 13 | Hint: 14 | - `RSP+0x??` Quad Word A 15 | - `RSP+0x??` Quad Word B 16 | - `RSP+0x??` Quad Word C 17 | - `RSP` Quad Word D 18 | -------------------------------------------------------------------------------- /assembly-crash-course/level-16/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-17-a/.config: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-17-a/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | We will now set some values in memory dynamically before each run. On each run, the values will change. This means you will need to do some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 2 | 3 | In this level, you will be working with control flow manipulation. This involves using instructions to both indirectly and directly control the special register `rip`, the instruction pointer. You will use instructions such as `jmp`, `call`, `cmp`, and their alternatives to implement the requested behavior. 4 | 5 | Earlier, you learned how to manipulate data in a pseudo-control way, but x86 gives us actual instructions to manipulate control flow directly. 6 | 7 | There are two major ways to manipulate control flow: 8 | - Through a jump 9 | - Through a call 10 | 11 | In this level, you will work with jumps. 12 | 13 | There are two types of jumps: 14 | - Unconditional jumps 15 | - Conditional jumps 16 | 17 | Unconditional jumps always trigger and are not based on the results of earlier instructions. 18 | 19 | As you know, memory locations can store data and instructions. Your code will be stored at `0x400042` (this will change each run). 20 | 21 | For all jumps, there are three types: 22 | - Relative jumps: jump + or - the next instruction. 23 | - Absolute jumps: jump to a specific address. 24 | - Indirect jumps: jump to the memory address specified in a register. 25 | 26 | In x86, absolute jumps (jump to a specific address) are accomplished by first putting the target address in a register `reg`, then doing `jmp reg`. 27 | 28 | In this level, we will ask you to do an absolute jump. Perform the following: Jump to the absolute address `0x403000`. 29 | -------------------------------------------------------------------------------- /assembly-crash-course/level-17-a/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-17-b/.config: -------------------------------------------------------------------------------- 1 | 23 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-17-b/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | We will now set some values in memory dynamically before each run. On each run, the values will change. This means you will need to perform some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 2 | 3 | In this level, you will be working with control flow manipulation. This involves using instructions to both indirectly and directly control the special register `rip`, the instruction pointer. You will use instructions such as `jmp`, `call`, `cmp`, and their alternatives to implement the requested behavior. 4 | 5 | Recall that for all jumps, there are three types: 6 | 7 | - Relative jumps 8 | - Absolute jumps 9 | - Indirect jumps 10 | 11 | In this level, we will ask you to do a relative jump. You will need to fill space in your code with something to make this relative jump possible. We suggest using the `nop` instruction. It's 1 byte long and very predictable. 12 | 13 | In fact, the assembler that we're using has a handy `.rept` directive that you can use to repeat assembly instructions some number of times: [GNU Assembler Manual](https://ftp.gnu.org/old-gnu/Manuals/gas-2.9.1/html_chapter/as_7.html) 14 | 15 | Useful instructions for this level: 16 | 17 | - `jmp (reg1 | addr | offset)` 18 | - `nop` 19 | 20 | Hint: For the relative jump, look up how to use `labels` in x86. 21 | 22 | Using the above knowledge, perform the following: 23 | 24 | - Make the first instruction in your code a `jmp`. 25 | - Make that `jmp` a relative jump to 0x51 bytes from the current position. 26 | - At the code location where the relative jump will redirect control flow, set `rax` to 0x1. 27 | -------------------------------------------------------------------------------- /assembly-crash-course/level-17-b/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-17/.config: -------------------------------------------------------------------------------- 1 | 24 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-17/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | We will now set some values in memory dynamically before each run. On each run, the values will change. This means you will need to do some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 2 | 3 | In this level, you will be working with control flow manipulation. This involves using instructions to both indirectly and directly control the special register `rip`, the instruction pointer. You will use instructions such as `jmp`, `call`, `cmp`, and their alternatives to implement the requested behavior. 4 | 5 | Now, we will combine the two prior levels and perform the following: 6 | 7 | - Create a two jump trampoline: 8 | - Make the first instruction in your code a `jmp`. 9 | - Make that `jmp` a relative jump to 0x51 bytes from its current position. 10 | - At 0x51, write the following code: 11 | - Place the top value on the stack into register `rdi`. 12 | - `jmp` to the absolute address 0x403000. 13 | -------------------------------------------------------------------------------- /assembly-crash-course/level-17/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-18/.config: -------------------------------------------------------------------------------- 1 | 25 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-18/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In this level, you will be working with control flow manipulation. This involves using instructions to both indirectly and directly control the special register `rip`, the instruction pointer. You will use instructions such as `jmp`, `call`, `cmp`, and their alternatives to implement the requested behavior. 2 | 3 | We will be testing your code multiple times in this level with dynamic values! This means we will be running your code in a variety of random ways to verify that the logic is robust enough to survive normal use. 4 | 5 | We will now introduce you to conditional jumps--one of the most valuable instructions in x86. In higher-level programming languages, an if-else structure exists to do things like: 6 | 7 | ```plaintext 8 | if x is even: 9 | is_even = 1 10 | else: 11 | is_even = 0 12 | ``` 13 | 14 | This should look familiar since it is implementable in only bit-logic, which you've done in a prior level. In these structures, we can control the program's control flow based on dynamic values provided to the program. 15 | 16 | Implementing the above logic with jmps can be done like so: 17 | 18 | ```assembly 19 | ; assume rdi = x, rax is output 20 | ; rdx = rdi mod 2 21 | mov rax, rdi 22 | mov rsi, 2 23 | div rsi 24 | ; remainder is 0 if even 25 | cmp rdx, 0 26 | ; jump to not_even code if it's not 0 27 | jne not_even 28 | ; fall through to even code 29 | mov rbx, 1 30 | jmp done 31 | ; jump to this only when not_even 32 | not_even: 33 | mov rbx, 0 34 | done: 35 | mov rax, rbx 36 | ; more instructions here 37 | ``` 38 | 39 | Often though, you want more than just a single 'if-else'. Sometimes you want two if checks, followed by an else. To do this, you need to make sure that you have control flow that 'falls-through' to the next `if` after it fails. All must jump to the same `done` after execution to avoid the else. 40 | 41 | There are many jump types in x86, it will help to learn how they can be used. Nearly all of them rely on something called the ZF, the Zero Flag. The ZF is set to 1 when a `cmp` is equal, 0 otherwise. 42 | 43 | Using the above knowledge, implement the following: 44 | 45 | ```plaintext 46 | if [x] is 0x7f454c46: 47 | y = [x+4] + [x+8] + [x+12] 48 | else if [x] is 0x00005A4D: 49 | y = [x+4] - [x+8] - [x+12] 50 | else: 51 | y = [x+4] * [x+8] * [x+12] 52 | ``` 53 | 54 | Where: 55 | - `x = rdi`, `y = rax`. 56 | 57 | Assume each dereferenced value is a signed dword. This means the values can start as a negative value at each memory position. 58 | 59 | A valid solution will use the following at least once: 60 | - `jmp` (any variant), `cmp` 61 | -------------------------------------------------------------------------------- /assembly-crash-course/level-18/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-19/.config: -------------------------------------------------------------------------------- 1 | 26 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-19/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In this level, you will work with control flow manipulation. This involves using instructions to indirectly and directly control the special register `rip`, the instruction pointer. You will use instructions such as `jmp`, `call`, `cmp`, and their alternatives to implement the requested behavior. 2 | 3 | We will be testing your code multiple times in this level with dynamic values! This means we will run your code in various random ways to verify that the logic is robust enough to survive normal use. 4 | 5 | The last jump type is the indirect jump, often used for switch statements in the real world. Switch statements are a special case of if-statements that use only numbers to determine where the control flow will go. 6 | 7 | Here is an example: 8 | 9 | ``` 10 | switch(number): 11 | 0: jmp do_thing_0 12 | 1: jmp do_thing_1 13 | 2: jmp do_thing_2 14 | default: jmp do_default_thing 15 | ``` 16 | 17 | The switch in this example works on `number`, which can either be 0, 1, or 2. If `number` is not one of those numbers, the default triggers. You can consider this a reduced else-if type structure. In x86, you are already used to using numbers, so it should be no surprise that you can make if statements based on something being an exact number. Additionally, if you know the range of the numbers, a switch statement works very well. 18 | 19 | Take, for instance, the existence of a jump table. A jump table is a contiguous section of memory that holds addresses of places to jump. 20 | 21 | In the above example, the jump table could look like: 22 | 23 | ``` 24 | [0x1337] = address of do_thing_0 25 | [0x1337+0x8] = address of do_thing_1 26 | [0x1337+0x10] = address of do_thing_2 27 | [0x1337+0x18] = address of do_default_thing 28 | ``` 29 | 30 | Using the jump table, we can greatly reduce the amount of `cmps` we use. Now all we need to check is if `number` is greater than 2. If it is, always do: 31 | 32 | ``` 33 | jmp [0x1337+0x18] 34 | ``` 35 | 36 | Otherwise: 37 | 38 | ``` 39 | jmp [jump_table_address + number * 8] 40 | ``` 41 | 42 | Using the above knowledge, implement the following logic: 43 | ```plaintext 44 | if rdi is 0: 45 | jmp 0x40301e 46 | else if rdi is 1: 47 | jmp 0x4030da 48 | else if rdi is 2: 49 | jmp 0x4031d5 50 | else if rdi is 3: 51 | jmp 0x403268 52 | else: 53 | jmp 0x40332c 54 | ``` 55 | 56 | Please do the above with the following constraints: 57 | 58 | - Assume `rdi` will NOT be negative. 59 | - Use no more than 1 `cmp` instruction. 60 | - Use no more than 3 jumps (of any variant). 61 | - We will provide you with the number to 'switch' on in `rdi`. 62 | - We will provide you with a jump table base address in `rsi`. 63 | 64 | Here is an example table: 65 | 66 | ``` 67 | [0x40427c] = 0x40301e (addrs will change) 68 | [0x404284] = 0x4030da 69 | [0x40428c] = 0x4031d5 70 | [0x404294] = 0x403268 71 | [0x40429c] = 0x40332c 72 | ``` -------------------------------------------------------------------------------- /assembly-crash-course/level-19/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-2-a/.config: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-2-a/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In this level, you will be working with registers. You will be asked to modify or read from registers. 2 | 3 | In this level, you will work with multiple registers. Please set the following: 4 | 5 | - `rax = 0x1337` 6 | - `r12 = 0xCAFED00D1337BEEF` 7 | - `rsp = 0x31337` -------------------------------------------------------------------------------- /assembly-crash-course/level-2-a/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-2/.config: -------------------------------------------------------------------------------- 1 | 3 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-2/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In this level, you will be working with registers. You will be asked to modify or read from registers. 2 | 3 | We will set some values in memory dynamically before each run. On each run, the values will change. This means you will need to perform some formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 4 | 5 | Many instructions exist in x86 that allow you to perform all the normal math operations on registers and memory. 6 | 7 | For shorthand, when we say `A += B`, it really means `A = A + B`. 8 | 9 | Here are some useful instructions: 10 | - `add reg1, reg2` <=> `reg1 += reg2` 11 | - `sub reg1, reg2` <=> `reg1 -= reg2` 12 | - `imul reg1, reg2` <=> `reg1 *= reg2` 13 | 14 | `div` is more complicated, and we will discuss it later. Note: all `regX` can be replaced by a constant or memory location. 15 | 16 | Do the following: 17 | - Add `0x331337` to `rdi` 18 | -------------------------------------------------------------------------------- /assembly-crash-course/level-2/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-20/.config: -------------------------------------------------------------------------------- 1 | 27 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-20/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | We will now set some values in memory dynamically before each run. On each run, the values will change. This means you will need to perform some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 2 | 3 | In this level, you will be working with control flow manipulation. This involves using instructions to both indirectly and directly control the special register `rip`, the instruction pointer. You will use instructions such as `jmp`, `call`, `cmp`, and their alternatives to implement the requested behavior. 4 | 5 | In a previous level, you computed the average of 4 integer quad words, which was a fixed amount of things to compute. But how do you work with sizes you get when the program is running? 6 | 7 | In most programming languages, a structure exists called the for-loop, which allows you to execute a set of instructions for a bounded amount of times. The bounded amount can be either known before or during the program's run, with "during" meaning the value is given to you dynamically. 8 | 9 | As an example, a for-loop can be used to compute the sum of the numbers 1 to n: 10 | 11 | ```plaintext 12 | sum = 0 13 | i = 1 14 | while i <= n: 15 | sum += i 16 | i += 1 17 | ``` 18 | 19 | Please compute the average of `n` consecutive quad words, where: 20 | - `rdi` = memory address of the 1st quad word 21 | - `rsi` = `n` (amount to loop for) 22 | - `rax` = average computed 23 | -------------------------------------------------------------------------------- /assembly-crash-course/level-20/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-21/.config: -------------------------------------------------------------------------------- 1 | 28 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-21/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In this level, you will be working with control flow manipulation. This involves using instructions to both indirectly and directly control the special register `rip`, the instruction pointer. You will use instructions such as `jmp`, `call`, `cmp`, and their alternatives to implement the requested behavior. 2 | 3 | We will be testing your code multiple times in this level with dynamic values! This means we will be running your code in a variety of random ways to verify that the logic is robust enough to survive normal use. 4 | 5 | In previous levels, you discovered the for-loop to iterate for a *number* of times, both dynamically and statically known, but what happens when you want to iterate until you meet a condition? 6 | 7 | A second loop structure exists called the while-loop to fill this demand. In the while-loop, you iterate until a condition is met. 8 | 9 | As an example, say we had a location in memory with adjacent numbers and we wanted to get the average of all the numbers until we find one bigger or equal to `0xff`: 10 | 11 | ```plaintext 12 | average = 0 13 | i = 0 14 | while x[i] < 0xff: 15 | average += x[i] 16 | i += 1 17 | average /= i 18 | ``` 19 | 20 | Using the above knowledge, please perform the following: 21 | 22 | Count the consecutive non-zero bytes in a contiguous region of memory, where: 23 | - `rdi` = memory address of the 1st byte 24 | - `rax` = number of consecutive non-zero bytes 25 | 26 | Additionally, if `rdi = 0`, then set `rax = 0` (we will check)! 27 | 28 | An example test-case, let: 29 | - `rdi = 0x1000` 30 | - `[0x1000] = 0x41` 31 | - `[0x1001] = 0x42` 32 | - `[0x1002] = 0x43` 33 | - `[0x1003] = 0x00` 34 | 35 | Then: `rax = 3` should be set. 36 | -------------------------------------------------------------------------------- /assembly-crash-course/level-21/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-22/.config: -------------------------------------------------------------------------------- 1 | 29 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-22/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | We will be testing your code multiple times in this level with dynamic values! This means we will be running your code in a variety of random ways to verify that the logic is robust enough to survive normal use. 2 | 3 | In this level, you will be working with functions! This will involve manipulating the instruction pointer (rip), as well as doing harder tasks than normal. You may be asked to use the stack to store values or call functions that we provide you. 4 | 5 | In previous levels, you implemented a while loop to count the number of consecutive non-zero bytes in a contiguous region of memory. 6 | 7 | In this level, you will be provided with a contiguous region of memory again and will loop over each performing a conditional operation till a zero byte is reached. All of which will be contained in a function! 8 | 9 | A function is a callable segment of code that does not destroy control flow. 10 | 11 | Functions use the instructions "call" and "ret". 12 | 13 | The "call" instruction pushes the memory address of the next instruction onto the stack and then jumps to the value stored in the first argument. 14 | 15 | Let's use the following instructions as an example: 16 | ``` 17 | 0x1021 mov rax, 0x400000 18 | 0x1028 call rax 19 | 0x102a mov [rsi], rax 20 | ``` 21 | 22 | 1. `call` pushes `0x102a`, the address of the next instruction, onto the stack. 23 | 2. `call` jumps to `0x400000`, the value stored in `rax`. 24 | 25 | The "ret" instruction is the opposite of "call". 26 | 27 | `ret` pops the top value off of the stack and jumps to it. 28 | 29 | Let's use the following instructions and stack as an example: 30 | 31 | ``` 32 | Stack ADDR VALUE 33 | 0x103f mov rax, rdx RSP + 0x8 0xdeadbeef 34 | 0x1042 ret RSP + 0x0 0x0000102a 35 | ``` 36 | 37 | Here, `ret` will jump to `0x102a`. 38 | 39 | Please implement the following logic: 40 | ```plaintext 41 | str_lower(src_addr): 42 | i = 0 43 | if src_addr != 0: 44 | while [src_addr] != 0x00: 45 | if [src_addr] <= 0x5a: 46 | [src_addr] = foo([src_addr]) 47 | i += 1 48 | src_addr += 1 49 | return i 50 | ``` 51 | 52 | `foo` is provided at `0x403000`. `foo` takes a single argument as a value and returns a value. 53 | 54 | All functions (`foo` and `str_lower`) must follow the Linux amd64 calling convention (also known as System V AMD64 ABI): 55 | [System V AMD64 ABI](https://en.wikipedia.org/wiki/X86_calling_conventions#System_V_AMD64_ABI) 56 | 57 | Therefore, your function `str_lower` should look for `src_addr` in `rdi` and place the function return in `rax`. 58 | 59 | An important note is that `src_addr` is an address in memory (where the string is located) and `[src_addr]` refers to the byte that exists at `src_addr`. 60 | 61 | Therefore, the function `foo` accepts a byte as its first argument and returns a byte. 62 | -------------------------------------------------------------------------------- /assembly-crash-course/level-22/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-23/.config: -------------------------------------------------------------------------------- 1 | 30 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-23/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | We will be testing your code multiple times in this level with dynamic values! This means we will be running your code in a variety of random ways to verify that the logic is robust enough to survive normal use. 2 | 3 | In this level, you will be working with functions! This will involve manipulating the instruction pointer (`rip`), as well as doing harder tasks than normal. You may be asked to use the stack to store values or call functions that we provide you. 4 | 5 | In the previous level, you learned how to make your first function and how to call other functions. Now we will work with functions that have a function stack frame. 6 | 7 | A function stack frame is a set of pointers and values pushed onto the stack to save things for later use and allocate space on the stack for function variables. 8 | 9 | First, let's talk about the special register `rbp`, the Stack Base Pointer. 10 | 11 | The `rbp` register is used to tell where our stack frame first started. As an example, say we want to construct some list (a contiguous space of memory) that is only used in our function. The list is 5 elements long, and each element is a `dword`. A list of 5 elements would already take 5 registers, so instead, we can make space on the stack! 12 | 13 | The assembly would look like: 14 | 15 | ```assembly 16 | ; setup the base of the stack as the current top 17 | mov rbp, rsp 18 | ; move the stack 0x14 bytes (5 * 4) down 19 | ; acts as an allocation 20 | sub rsp, 0x14 21 | ; assign list[2] = 1337 22 | mov eax, 1337 23 | mov [rbp-0xc], eax 24 | ; do more operations on the list ... 25 | ; restore the allocated space 26 | mov rsp, rbp 27 | ret 28 | ``` 29 | 30 | Notice how `rbp` is always used to restore the stack to where it originally was. If we don't restore the stack after use, we will eventually run out. In addition, notice how we subtracted from `rsp`, because the stack grows down. To make the stack have more space, we subtract the space we need. The `ret` and `call` still work the same. 31 | 32 | Consider the fact that to assign a value to `list[2]` we subtract 12 bytes (3 dwords). That is because stack grows down and when we moved `rsp` our stack contains addresses <`rsp`, `rbp`). 33 | 34 | Once again, please make function(s) that implement the following: 35 | 36 | ```plaintext 37 | most_common_byte(src_addr, size): 38 | i = 0 39 | while i <= size-1: 40 | curr_byte = [src_addr + i] 41 | [stack_base - curr_byte * 2] += 1 42 | i += 1 43 | 44 | b = 1 45 | max_freq = 0 46 | max_freq_byte = 0 47 | while b <= 0x100: 48 | if [stack_base - b * 2] > max_freq: 49 | max_freq = [stack_base - b * 2] 50 | max_freq_byte = b 51 | b += 1 52 | 53 | return max_freq_byte 54 | ``` 55 | 56 | **Assumptions:** 57 | 58 | - There will never be more than `0xffff` of any byte 59 | - The size will never be longer than `0xffff` 60 | - The list will have at least one element 61 | 62 | **Constraints:** 63 | 64 | - You must put the "counting list" on the stack 65 | - You must restore the stack like in a normal function 66 | - You cannot modify the data at `src_addr` 67 | -------------------------------------------------------------------------------- /assembly-crash-course/level-23/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-3/.config: -------------------------------------------------------------------------------- 1 | 4 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-3/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In this level, you will be working with registers. You will be asked to modify or read from registers. 2 | 3 | We will now set some values in memory dynamically before each run. On each run, the values will change. This means you will need to do some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 4 | 5 | Using your new knowledge, please compute the following: 6 | - `f(x) = mx + b`, where: 7 | - `m = rdi` 8 | - `x = rsi` 9 | - `b = rdx` 10 | 11 | Place the result into `rax`. 12 | 13 | Note: There is an important difference between `mul` (unsigned multiply) and `imul` (signed multiply) in terms of which registers are used. Look at the documentation on these instructions to see the difference. 14 | 15 | In this case, you will want to use `imul`. 16 | -------------------------------------------------------------------------------- /assembly-crash-course/level-3/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-4/.config: -------------------------------------------------------------------------------- 1 | 5 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-4/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In this level, you will be working with registers. You will be asked to modify or read from registers. 2 | 3 | We will set some values in memory dynamically before each run. On each run, the values will change. This means you will need to perform some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result, which is usually `rax`. 4 | 5 | Division in x86 is more special than in normal math. Math here is called integer math, meaning every value is a whole number. 6 | 7 | As an example: `10 / 3 = 3` in integer math. 8 | 9 | Why? 10 | 11 | Because `3.33` is rounded down to an integer. 12 | 13 | The relevant instructions for this level are: 14 | - `mov rax, reg1` 15 | - `div reg2` 16 | 17 | Note: `div` is a special instruction that can divide a 128-bit dividend by a 64-bit divisor while storing both the quotient and the remainder, using only one register as an operand. 18 | 19 | How does this complex `div` instruction work and operate on a 128-bit dividend (which is twice as large as a register)? 20 | 21 | For the instruction `div reg`, the following happens: 22 | - `rax = rdx:rax / reg` 23 | - `rdx = remainder` 24 | 25 | `rdx:rax` means that `rdx` will be the upper 64-bits of the 128-bit dividend and `rax` will be the lower 64-bits of the 128-bit dividend. 26 | 27 | You must be careful about what is in `rdx` and `rax` before you call `div`. 28 | 29 | Please compute the following: 30 | - `speed = distance / time`, where: 31 | - `distance = rdi` 32 | - `time = rsi` 33 | - `speed = rax` 34 | 35 | Note that distance will be at most a 64-bit value, so `rdx` should be 0 when dividing. 36 | -------------------------------------------------------------------------------- /assembly-crash-course/level-4/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-5/.config: -------------------------------------------------------------------------------- 1 | 6 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-5/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In this level, you will be working with registers. You will be asked to modify or read from registers. 2 | 3 | We will set some values in memory dynamically before each run. On each run, the values will change. This means you will need to perform a formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 4 | 5 | Modulo in assembly is another interesting concept! 6 | 7 | x86 allows you to get the remainder after a `div` operation. 8 | 9 | For instance: `10 / 3` results in a remainder of `1`. 10 | 11 | The remainder is the same as modulo, which is also called the "mod" operator. 12 | 13 | In most programming languages, we refer to mod with the symbol `%`. 14 | 15 | Please compute the following: `rdi % rsi` 16 | 17 | Place the value in `rax`. 18 | -------------------------------------------------------------------------------- /assembly-crash-course/level-5/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-6-a/.config: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-6-a/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In this level, you will be working with registers. You will be asked to modify or read from registers. 2 | 3 | We will set some values in memory dynamically before each run. On each run, the values will change. This means you will need to do some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result, which is typically in `rax`. 4 | 5 | Another cool concept in x86 is the ability to independently access the lower register bytes. 6 | 7 | Each register in x86_64 is 64 bits in size, and in the previous levels, we have accessed the full register using `rax`, `rdi`, or `rsi`. 8 | 9 | We can also access the lower bytes of each register using different register names. 10 | 11 | For example, the lower 32 bits of `rax` can be accessed using `eax`, the lower 16 bits using `ax`, and the lower 8 bits using `al`. 12 | 13 | ``` 14 | MSB LSB 15 | +----------------------------------------+ 16 | | rax | 17 | +--------------------+-------------------+ 18 | | eax | 19 | +---------+---------+ 20 | | ax | 21 | +----+----+ 22 | | ah | al | 23 | +----+----+ 24 | ``` 25 | 26 | Lower register bytes access is applicable to almost all registers. 27 | 28 | Using only one move instruction, please set the upper 8 bits of the `ax` register to `0x42`. 29 | -------------------------------------------------------------------------------- /assembly-crash-course/level-6-a/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-6/.config: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-6/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In this level, you will be working with registers. You will be asked to modify or read from registers. 2 | 3 | We will set some values in memory dynamically before each run. On each run, the values will change. This means you will need to perform some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 4 | 5 | It turns out that using the `div` operator to compute the modulo operation is slow! 6 | 7 | We can use a math trick to optimize the modulo operator (`%`). Compilers use this trick a lot. 8 | 9 | If we have `x % y`, and `y` is a power of 2, such as `2^n`, the result will be the lower `n` bits of `x`. 10 | 11 | Therefore, we can use the lower register byte access to efficiently implement modulo! 12 | 13 | Using only the following instruction(s): 14 | - `mov` 15 | 16 | Please compute the following: 17 | - `rax = rdi % 256` 18 | - `rbx = rsi % 65536` 19 | -------------------------------------------------------------------------------- /assembly-crash-course/level-6/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-7/.config: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-7/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In this level, you will be working with registers. You will be asked to modify or read from registers. 2 | 3 | We will set some values in memory dynamically before each run. On each run, the values will change. This means you will need to perform some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 4 | 5 | In this level, you will be working with bit logic and operations. This will involve heavy use of directly interacting with bits stored in a register or memory location. You will also likely need to make use of the logic instructions in x86: `and`, `or`, `not`, `xor`. 6 | 7 | Shifting bits around in assembly is another interesting concept! 8 | 9 | x86 allows you to 'shift' bits around in a register. 10 | 11 | Take, for instance, `al`, the lowest 8 bits of `rax`. 12 | 13 | The value in `al` (in bits) is: 14 | ``` 15 | rax = 10001010 16 | ``` 17 | 18 | If we shift once to the left using the `shl` instruction: 19 | ``` 20 | shl al, 1 21 | ``` 22 | 23 | The new value is: 24 | ``` 25 | al = 00010100 26 | ``` 27 | 28 | Everything shifted to the left, and the highest bit fell off while a new 0 was added to the right side. 29 | 30 | You can use this to do special things to the bits you care about. 31 | 32 | Shifting has the nice side effect of doing quick multiplication (by 2) or division (by 2), and can also be used to compute modulo. 33 | 34 | Here are the important instructions: 35 | - `shl reg1, reg2` <=> Shift `reg1` left by the amount in `reg2` 36 | - `shr reg1, reg2` <=> Shift `reg1` right by the amount in `reg2` 37 | 38 | Note: 'reg2' can be replaced by a constant or memory location. 39 | 40 | Using only the following instructions: 41 | - `mov`, `shr`, `shl` 42 | 43 | Please perform the following: 44 | Set `rax` to the 5th least significant byte of `rdi`. 45 | 46 | For example: 47 | ``` 48 | rdi = | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 | 49 | Set rax to the value of B4 50 | ``` 51 | -------------------------------------------------------------------------------- /assembly-crash-course/level-7/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-8/.config: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-8/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In this level, you will be working with registers. You will be asked to modify or read from registers. 2 | 3 | We will set some values in memory dynamically before each run. On each run, the values will change. This means you will need to perform some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it's `rax`. 4 | 5 | In this level, you will be working with bit logic and operations. This will involve heavy use of directly interacting with bits stored in a register or memory location. You will also likely need to make use of the logic instructions in x86: `and`, `or`, `not`, `xor`. 6 | 7 | Bitwise logic in assembly is yet another interesting concept! x86 allows you to perform logic operations bit by bit on registers. 8 | 9 | For the sake of this example, say registers only store 8 bits. 10 | 11 | The values in `rax` and `rbx` are: 12 | - `rax = 10101010` 13 | - `rbx = 00110011` 14 | 15 | If we were to perform a bitwise AND of `rax` and `rbx` using the `and rax, rbx` instruction, the result would be calculated by ANDing each bit pair one by one, hence why it's called bitwise logic. 16 | 17 | So from left to right: 18 | - 1 AND 0 = 0 19 | - 0 AND 0 = 0 20 | - 1 AND 1 = 1 21 | - 0 AND 1 = 0 22 | - ... 23 | 24 | Finally, we combine the results together to get: 25 | - `rax = 00100010` 26 | 27 | Here are some truth tables for reference: 28 | 29 | - **AND** 30 | ``` 31 | A | B | X 32 | ---+---+--- 33 | 0 | 0 | 0 34 | 0 | 1 | 0 35 | 1 | 0 | 0 36 | 1 | 1 | 1 37 | ``` 38 | 39 | - **OR** 40 | ``` 41 | A | B | X 42 | ---+---+--- 43 | 0 | 0 | 0 44 | 0 | 1 | 1 45 | 1 | 0 | 1 46 | 1 | 1 | 1 47 | ``` 48 | 49 | - **XOR** 50 | ``` 51 | A | B | X 52 | ---+---+--- 53 | 0 | 0 | 0 54 | 0 | 1 | 1 55 | 1 | 0 | 1 56 | 1 | 1 | 0 57 | ``` 58 | 59 | Without using the following instructions: `mov`, `xchg`, please perform the following: 60 | 61 | Set `rax` to the value of `(rdi AND rsi)` 62 | -------------------------------------------------------------------------------- /assembly-crash-course/level-8/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/level-9/.config: -------------------------------------------------------------------------------- 1 | 11 2 | -------------------------------------------------------------------------------- /assembly-crash-course/level-9/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In this level, you will be working with registers. You will be asked to modify or read from registers. 2 | 3 | We will set some values in memory dynamically before each run. On each run, the values will change. This means you will need to perform some type of formulaic operation with registers. We will tell you which registers are set beforehand and where you should put the result. In most cases, it is `rax`. 4 | 5 | In this level, you will be working with bit logic and operations. This will involve heavy use of directly interacting with bits stored in a register or memory location. You will also likely need to make use of the logic instructions in x86: `and`, `or`, `not`, `xor`. 6 | 7 | Using only the following instructions: 8 | - `and` 9 | - `or` 10 | - `xor` 11 | 12 | Implement the following logic: 13 | 14 | ```plaintext 15 | if x is even then 16 | y = 1 17 | else 18 | y = 0 19 | ``` 20 | 21 | Where: 22 | - `x = rdi` 23 | - `y = rax` 24 | -------------------------------------------------------------------------------- /assembly-crash-course/level-9/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /assembly-crash-course/module.yml: -------------------------------------------------------------------------------- 1 | name: Assembly Crash Course 2 | 3 | resources: 4 | - name: "(review) Computer Architecture" 5 | type: lecture 6 | video: o_kSgUPJk4c 7 | playlist: PL-ymxv0nOtqox6nF4HXtXHnTQFGkRQU_2 8 | slides: 1sVyPL92gbzg_it9aIeC-CjXtF2tpvAmZTKjWc-SlU0c 9 | - name: "(review) Assembly" 10 | type: lecture 11 | video: TdTOH1GfrWg 12 | playlist: PL-ymxv0nOtqox6nF4HXtXHnTQFGkRQU_2 13 | slides: 1sBB34ZUVjT3MDjYvGiZU3z6CTLGvO1hHEdHliPUJ0iw 14 | - name: "(review) Data" 15 | type: lecture 16 | video: 21MvbpgssU8 17 | playlist: PL-ymxv0nOtqox6nF4HXtXHnTQFGkRQU_2 18 | slides: 1SeyZbM_qCDz4t03KDZ8Q0qi1GzsSSSJAkO-KeVlAL7w 19 | - name: "(review) Registers" 20 | type: lecture 21 | video: IWbIZerH930 22 | playlist: PL-ymxv0nOtqox6nF4HXtXHnTQFGkRQU_2 23 | slides: 1U8Rsz7FNTmy-3wGtPB-YCyanIIbox1J90adze9-1cfo 24 | - name: "(review) Memory" 25 | type: lecture 26 | video: HahXfnOsSUU 27 | playlist: PL-ymxv0nOtqox6nF4HXtXHnTQFGkRQU_2 28 | slides: 1lbPbd-jLj7VS7M-ntGhpv4_8WqSfqzLBNWG68_BIRL0 29 | - name: "Control Flow" 30 | type: lecture 31 | video: 0a8NlF7z7Ro 32 | playlist: PL-ymxv0nOtqox6nF4HXtXHnTQFGkRQU_2 33 | slides: 1wX5TH6DQjXw4GJo_T6I9GF7bhC689T52HSHaMUCmj1E 34 | - name: "(review) System Calls" 35 | type: lecture 36 | video: vspq0u2tvhU 37 | playlist: PL-ymxv0nOtqox6nF4HXtXHnTQFGkRQU_2 38 | slides: 1vEuZ1PW8Wvm88INmWbjoKY8srEKSkv2g4o-V42BsN9Y 39 | - name: "Building Programs" 40 | type: lecture 41 | video: IITocH-WGH4 42 | playlist: PL-ymxv0nOtqox6nF4HXtXHnTQFGkRQU_2 43 | slides: 1Y35BuIvvG8k3txjcsMXjygzsP7rDaNpbDHz3TMO91Fo 44 | - name: Tip - Debugging Your Assembly 45 | type: markdown 46 | content: | 47 | These challenges are written in Python and run your assembly code in an emulator. 48 | This means you cannot use the normal debugging tools such as `gdb` to debug the challenge. However, we have added a special debug functionality to these challenges. 49 | If an `int3` instruction is executed by the challenge in your assembly code, the emulator will print out the current state of the registers and memory. 50 | This can be extremely useful to reason about your code! 51 | - name: Further Reading 52 | type: markdown 53 | content: | 54 | - An awesome intro series that covers some of the fundamentals from [LiveOverflow](https://www.youtube.com/watch?v=iyAyN3GFM7A&list=PLhixgUqwRTjxglIswKp9mpkfPNfHkzyeN&index=1). 55 | - [`Ike: The Systems Hacking Handbook](https://ike.mahaloz.re/1_introduction/introduction.html), an excellent guide to Computer Organization. 56 | - A [comprehensive assembly tutorial](https://github.com/mytechnotalent/Reverse-Engineering-Tutorial) for several architectures (amd64 is the relevant one here). 57 | - The course ["Architecture 1001: x86-64 Assembly"](https://ost2.fyi/Arch1001) from OpenSecurityTraining2. 58 | - A whole [x86_64 assembly book](https://open.umn.edu/opentextbooks/textbooks/733) to help you out! 59 | - A [game](https://squallygame.com/) to teach you x86 assembly and one to [stress test your knowledge](https://oooverflow.io/zero-is-you/)! 60 | - A [flowchart](https://soc.me/interfaces/x86-prefixes-and-escape-opcodes-flowchart) of x86 prefix and escape opcodes. 61 | - An unofficial, but extremely detailed and useful [x86 reference](https://www.felixcloutier.com/x86/). 62 | 63 | challenges: 64 | - id: level-1 65 | name: set-register 66 | - id: level-2-a 67 | name: set-multiple-registers 68 | - id: level-2 69 | name: add-to-register 70 | - id: level-3 71 | name: linear-equation-registers 72 | - id: level-4 73 | name: integer-division 74 | - id: level-5 75 | name: modulo-operation 76 | - id: level-6-a 77 | name: set-upper-byte 78 | - id: level-6 79 | name: efficient-modulo 80 | - id: level-7 81 | name: byte-extraction 82 | - id: level-8 83 | name: bitwise-and 84 | - id: level-9 85 | name: check-even 86 | - id: level-10-a 87 | name: memory-read 88 | - id: level-10-b 89 | name: memory-write 90 | - id: level-10 91 | name: memory-increment 92 | - id: level-11-a 93 | name: byte-access 94 | - id: level-11 95 | name: memory-size-access 96 | - id: level-12 97 | name: little-endian-write 98 | - id: level-13 99 | name: memory-sum 100 | - id: level-14 101 | name: stack-subtraction 102 | - id: level-15 103 | name: swap-stack-values 104 | - id: level-16 105 | name: average-stack-values 106 | - id: level-17-a 107 | name: absolute-jump 108 | - id: level-17-b 109 | name: relative-jump 110 | - id: level-17 111 | name: jump-trampoline 112 | - id: level-18 113 | name: conditional-jump 114 | - id: level-19 115 | name: indirect-jump 116 | - id: level-20 117 | name: average-loop 118 | - id: level-21 119 | name: count-non-zero 120 | - id: level-22 121 | name: string-lower 122 | - id: level-23 123 | name: most-common-byte 124 | -------------------------------------------------------------------------------- /building-a-web-server/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Now that you know how to write and debug assembly, it is time to do something real! 2 | 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. 3 | Good luck! 4 | 5 | ---- 6 | 7 | As you proceed in your journey, remember your [system call table](https://x64.syscall.sh/). 8 | -------------------------------------------------------------------------------- /building-a-web-server/level-1/.config: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /building-a-web-server/level-1/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Your first task is to create the simplest possible program—one that immediately terminates when run. 2 | In this challenge, you will use the [exit](https://man7.org/linux/man-pages/man2/_exit.2.html) syscall, which is responsible for ending a process and returning an exit status to the operating system. 3 | This syscall takes a single argument: the exit status (with `0` typically indicating success). 4 | Understanding how to cleanly exit a program is crucial because it ensures your process communicates its completion state properly. 5 | -------------------------------------------------------------------------------- /building-a-web-server/level-1/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /building-a-web-server/level-10/.config: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /building-a-web-server/level-10/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Expanding your server’s capabilities further, this challenge focuses on handling HTTP POST requests concurrently. 2 | POST requests are more complex because they include both headers and a message body. 3 | You will once again use [fork](https://man7.org/linux/man-pages/man2/fork.2.html) to manage multiple connections, while using [read](https://man7.org/linux/man-pages/man2/read.2.html) to capture the entire request. 4 | Again, you will parse the URL path to determine the specified file, but this time instead of reading from that file, you will instead write to it with the incoming POST data. 5 | In order to do so, you must determine the length of the incoming POST data. 6 | The *obvious* way to do this is to parse the `Content-Length` header, which specifies exactly that. 7 | Alternatively, consider using the return value of [read](https://man7.org/linux/man-pages/man2/read.2.html) to determine the total length of the request, parsing the request to find the total length of the headers (which end with `\r\n\r\n`), and using that difference to determine the length of the body--this seemingly more complicated algorithm may actually be easier to implement. 8 | Finally, return just a `200 OK` response to the client to indicate that the POST request was successful. 9 | -------------------------------------------------------------------------------- /building-a-web-server/level-10/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /building-a-web-server/level-11/.config: -------------------------------------------------------------------------------- 1 | 11 2 | -------------------------------------------------------------------------------- /building-a-web-server/level-11/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In the final challenge, your server must seamlessly support both GET and POST requests within a single program. 2 | After reading the incoming request using [read](https://man7.org/linux/man-pages/man2/read.2.html), your server will inspect the first few characters to determine whether it is dealing with a GET or a POST. 3 | Depending on the request type, it will process the data accordingly and then send back an appropriate response using [write](https://man7.org/linux/man-pages/man2/write.2.html). 4 | Throughout this process, [fork](https://man7.org/linux/man-pages/man2/fork.2.html) is employed to handle each connection concurrently, ensuring that your server can manage multiple requests at the same time. 5 | After completing this, you will have built a simple, but fully functional, web server capable of handling different types of HTTP requests. 6 | -------------------------------------------------------------------------------- /building-a-web-server/level-11/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /building-a-web-server/level-2/.config: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /building-a-web-server/level-2/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In this challenge, you’ll begin your journey into networking by creating a socket using the [socket](https://man7.org/linux/man-pages/man2/socket.2.html) syscall. 2 | A socket is the basic building block for network communication; it serves as an endpoint for sending and receiving data. 3 | When you invoke [socket](https://man7.org/linux/man-pages/man2/socket.2.html), you provide three key arguments: the domain (for example, `AF_INET` for IPv4), the type (such as `SOCK_STREAM` for TCP), and the protocol (usually set to `0` to choose the default). 4 | Mastering this syscall is important because it lays the foundation for all subsequent network interactions. 5 | -------------------------------------------------------------------------------- /building-a-web-server/level-2/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /building-a-web-server/level-3/.config: -------------------------------------------------------------------------------- 1 | 3 2 | -------------------------------------------------------------------------------- /building-a-web-server/level-3/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | After creating a socket, the next step is to assign it a network identity. 2 | In this challenge, you will use the [bind](https://man7.org/linux/man-pages/man2/bind.2.html) syscall to connect your socket to a specific IP address and port number. 3 | The call requires you to provide the socket file descriptor, a pointer to a `struct sockaddr` (specifically a `struct sockaddr_in` for IPv4 that holds fields like the address family, port, and IP address), and the size of that structure. 4 | Binding is essential because it ensures your server listens on a known address, making it reachable by clients. 5 | -------------------------------------------------------------------------------- /building-a-web-server/level-3/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /building-a-web-server/level-4/.config: -------------------------------------------------------------------------------- 1 | 4 2 | -------------------------------------------------------------------------------- /building-a-web-server/level-4/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | With your socket bound to an address, you now need to prepare it to accept incoming connections. 2 | The [listen](https://man7.org/linux/man-pages/man2/listen.2.html) syscall transforms your socket into a passive one that awaits client connection requests. 3 | It requires the socket’s file descriptor and a backlog parameter, which sets the maximum number of queued connections. 4 | This step is vital because without marking the socket as listening, your server wouldn’t be able to receive any connection attempts. 5 | -------------------------------------------------------------------------------- /building-a-web-server/level-4/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /building-a-web-server/level-5/.config: -------------------------------------------------------------------------------- 1 | 5 2 | -------------------------------------------------------------------------------- /building-a-web-server/level-5/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Once your socket is listening, it’s time to actively accept incoming connections. 2 | In this challenge, you will use the [accept](https://man7.org/linux/man-pages/man2/accept.2.html) syscall, which waits for a client to connect. 3 | When a connection is established, it returns a new socket file descriptor dedicated to communication with that client and fills in a provided address structure (such as a `struct sockaddr_in`) with the client’s details. 4 | This process is a critical step in transforming your server from a passive listener into an active communicator. 5 | -------------------------------------------------------------------------------- /building-a-web-server/level-5/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /building-a-web-server/level-6/.config: -------------------------------------------------------------------------------- 1 | 6 2 | -------------------------------------------------------------------------------- /building-a-web-server/level-6/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Now that your server can establish connections, it’s time to learn how to send data. 2 | In this challenge, your goal is to send a fixed HTTP response (`HTTP/1.0 200 OK\r\n\r\n`) to any client that connects. 3 | You will use the [write](https://man7.org/linux/man-pages/man2/write.2.html) syscall, which requires a file descriptor, a pointer to a data buffer, and the number of bytes to write. 4 | This exercise is important because it teaches you how to format and deliver data over the network. 5 | -------------------------------------------------------------------------------- /building-a-web-server/level-6/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /building-a-web-server/level-7/.config: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /building-a-web-server/level-7/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In this challenge, your server evolves to handle dynamic content based on HTTP GET requests. 2 | You will first use the [read](https://man7.org/linux/man-pages/man2/read.2.html) syscall to receive the incoming HTTP request from the client socket. 3 | By examining the request line--particularly, in this case, the URL path--you can determine what the client is asking for. 4 | Next, use the [open](https://man7.org/linux/man-pages/man2/open.2.html) syscall to open the requested file and [read](https://man7.org/linux/man-pages/man2/read.2.html) to read its contents. 5 | Send the file contents back to the client using the [write](https://man7.org/linux/man-pages/man2/write.2.html) syscall. 6 | This marks a significant step toward interactivity, as your server begins tailoring its output rather than simply echoing a static message. 7 | -------------------------------------------------------------------------------- /building-a-web-server/level-7/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /building-a-web-server/level-8/.config: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /building-a-web-server/level-8/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Previously, your server served just one GET request before terminating. 2 | Now, you will modify it so that it can handle multiple GET requests sequentially. 3 | This involves wrapping the accept-read-write-close sequence in a loop. 4 | Each time a client connects, your server will accept the connection, process the GET request, and then cleanly close the client session while remaining active for the next request. 5 | This iterative approach is essential for building a persistent server. 6 | -------------------------------------------------------------------------------- /building-a-web-server/level-8/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /building-a-web-server/level-9/.config: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /building-a-web-server/level-9/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | To enable your server to handle several clients at once, you will introduce concurrency using the [fork](https://man7.org/linux/man-pages/man2/fork.2.html) syscall. 2 | When a client connects, [fork](https://man7.org/linux/man-pages/man2/fork.2.html) creates a child process dedicated to handling that connection. 3 | Meanwhile, the parent process immediately returns to accept additional connections. 4 | With this design, the child uses [read](https://man7.org/linux/man-pages/man2/read.2.html) and [write](https://man7.org/linux/man-pages/man2/write.2.html) to interact with the client, while the parent continues to listen. 5 | This concurrent model is a key concept in building scalable, real-world servers. 6 | -------------------------------------------------------------------------------- /building-a-web-server/level-9/run: -------------------------------------------------------------------------------- 1 | ../run -------------------------------------------------------------------------------- /building-a-web-server/module.yml: -------------------------------------------------------------------------------- 1 | name: Building a Web Server 2 | challenges: 3 | - id: level-1 4 | name: Exit 5 | - id: level-2 6 | name: Socket 7 | - id: level-3 8 | name: Bind 9 | - id: level-4 10 | name: Listen 11 | - id: level-5 12 | name: Accept 13 | - id: level-6 14 | name: Static Response 15 | - id: level-7 16 | name: Dynamic Response 17 | - id: level-8 18 | name: Iterative GET Server 19 | - id: level-9 20 | name: Concurrent GET Server 21 | - id: level-10 22 | name: Concurrent POST Server 23 | - id: level-11 24 | name: Web Server 25 | resources: 26 | - name: "Building a Web Server: Introduction" 27 | type: lecture 28 | video: 4S78xEhxh7k 29 | playlist: PL-ymxv0nOtqqYlFBiJ_gMff9zHfHMNCtG 30 | slides: 1U33Vcbv2nVoufatHhmIK4XKRzyXZCujR45paGABblnk 31 | - name: "Building a Web Server: Linux Processes" 32 | type: lecture 33 | video: nEJiM1ryJQw 34 | playlist: PL-ymxv0nOtqqYlFBiJ_gMff9zHfHMNCtG 35 | slides: 1l0VHGTYtAk2FzWY4c-m5e8yTS1hCnGit2ve7y7BAlyg 36 | - name: "Building a Web Server: Network System Calls" 37 | type: lecture 38 | video: Q1SvbIBCx1E 39 | playlist: PL-ymxv0nOtqqYlFBiJ_gMff9zHfHMNCtG 40 | slides: 1MxT1qcwZZHSkaaeYIKKp7Qg0IaespmlrqhZDnWkwxFo 41 | - name: "Building a Web Server: HTTP" 42 | type: lecture 43 | video: 0G0aL8KTeB4 44 | playlist: PL-ymxv0nOtqqYlFBiJ_gMff9zHfHMNCtG 45 | slides: 1_-CxrxPUf9D_6tsueggaaOt27pQ-I7hVe4YIMz1PWA8 46 | - name: "Building a Web Server: Multiprocessing" 47 | type: lecture 48 | video: 9jH2Le-MWuI 49 | playlist: PL-ymxv0nOtqqYlFBiJ_gMff9zHfHMNCtG 50 | slides: 1tycdciuY8aMmBGj98qUMq0-ubClkqeT10QNIT0hE9kU 51 | 52 | -------------------------------------------------------------------------------- /building-a-web-server/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/exec-suid -- /bin/python3 -I 2 | 3 | import os 4 | import sys 5 | import re 6 | import random 7 | import string 8 | import socket 9 | import subprocess 10 | import multiprocessing 11 | import struct 12 | import signal 13 | import fcntl 14 | import ctypes 15 | import tempfile 16 | import pathlib 17 | import contextlib 18 | import shutil 19 | import atexit 20 | 21 | import requests 22 | 23 | 24 | config = (pathlib.Path(__file__).parent / ".config").read_text() 25 | level = int(config) 26 | 27 | strace_expected_parent = r""" 28 | 1..10 execve() = 0 29 | 2..10 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3 30 | 3..10 bind(3, {sa_family=AF_INET, sin_port=htons(), sin_addr=inet_addr("")}, 16) = 0 31 | 4..10 listen(3, 0) = 0 32 | 5..10 accept(3, NULL, NULL) = 4 33 | 6..8 read(4, , ) = 34 | 7..8 open("", O_RDONLY) = 5 35 | 7..8 read(5, , ) = 36 | 7..8 close(5) = 0 37 | 6..8 write(4, "HTTP/1.0 200 OK\r\n\r\n", 19) = 19 38 | 7..8 write(4, , ) = 39 | 9..10 fork() = 40 | 6..10 close(4) = 0 41 | 8..10 accept(3, NULL, NULL) = ? 42 | 1..7 exit(0) = ? 43 | """ 44 | 45 | strace_expected_child = r""" 46 | 9..10 close(3) = 0 47 | 9..10 read(4, , ) = 48 | 9..9 open("", O_RDONLY) = 3 49 | 9..9 read(3, , ) = 50 | 10..10 open("", O_WRONLY|O_CREAT, 0777) = 3 51 | 10..10 write(3, , ) = 52 | 9..10 close(3) = 0 53 | 9..10 write(4, "HTTP/1.0 200 OK\r\n\r\n", 19) = 19 54 | 9..9 write(4, , ) = 55 | 9..10 exit(0) = ? 56 | """ 57 | 58 | 59 | def run_sandbox(target, *, privileged=True): 60 | CLONE_NEWNS = 0x00020000 # New mount namespace group 61 | CLONE_NEWCGROUP = 0x02000000 # New cgroup namespace 62 | CLONE_NEWUTS = 0x04000000 # New utsname namespace 63 | CLONE_NEWIPC = 0x08000000 # New ipc namespace 64 | CLONE_NEWUSER = 0x10000000 # New user namespace 65 | CLONE_NEWPID = 0x20000000 # New pid namespace 66 | CLONE_NEWNET = 0x40000000 # New network namespace 67 | 68 | PR_SET_PDEATHSIG = 1 69 | PR_SET_DUMPABLE = 4 70 | 71 | result = multiprocessing.Value("B", False) 72 | 73 | pid = os.fork() 74 | if pid: 75 | os.wait() 76 | return result.value 77 | 78 | libc = ctypes.CDLL("libc.so.6") 79 | 80 | suid = os.geteuid() != os.getuid() 81 | os.setpgrp() 82 | os.seteuid(os.getuid()) 83 | # TODO: 84 | # libc.prctl(PR_SET_DUMPABLE, not suid) 85 | libc.prctl(PR_SET_DUMPABLE, True) 86 | libc.prctl(PR_SET_PDEATHSIG, signal.SIGKILL) 87 | 88 | sandbox_euid = os.geteuid() 89 | sandbox_egid = os.getegid() 90 | 91 | unshare_result = libc.unshare( 92 | CLONE_NEWUSER | 93 | CLONE_NEWNS | 94 | CLONE_NEWCGROUP | 95 | CLONE_NEWUTS | 96 | CLONE_NEWIPC | 97 | CLONE_NEWPID | 98 | CLONE_NEWNET 99 | ) 100 | assert unshare_result == 0 101 | 102 | pid = os.fork() 103 | if pid: 104 | os.wait() 105 | os._exit(0) 106 | 107 | proc_values = { 108 | f"/proc/self/setgroups": "deny", 109 | f"/proc/self/uid_map": f"0 {sandbox_euid} 1", 110 | f"/proc/self/gid_map": f"0 {sandbox_egid} 1", 111 | } 112 | for path, value in proc_values.items(): 113 | with open(path, "w") as f: 114 | f.write(value) 115 | 116 | libc.prctl(PR_SET_PDEATHSIG, signal.SIGKILL) 117 | 118 | socket.sethostname("sandbox") 119 | subprocess.run(["/sbin/ip", "link", "set", "dev", "lo", "up"]) 120 | 121 | # if not privileged: 122 | # unshare_result = libc.unshare( 123 | # CLONE_NEWUSER 124 | # ) 125 | # assert unshare_result == 0 126 | 127 | # proc_values = { 128 | # "/proc/self/setgroups": "deny", 129 | # "/proc/self/uid_map": f"{sandbox_euid} 0 1", 130 | # "/proc/self/gid_map": f"{sandbox_egid} 0 1", 131 | # } 132 | # for path, value in proc_values.items(): 133 | # with open(path, "w") as f: 134 | # f.write(value) 135 | 136 | result.value = target() 137 | os._exit(0) 138 | 139 | 140 | @contextlib.contextmanager 141 | def strace(argv, *args, results_dir, timeout=None): 142 | sem_start_strace = multiprocessing.Semaphore(0) 143 | sem_start_target = multiprocessing.Semaphore(0) 144 | 145 | pid = os.fork() 146 | if not pid: 147 | PR_SET_DUMPABLE = 4 148 | libc = ctypes.CDLL("libc.so.6") 149 | libc.prctl(PR_SET_DUMPABLE, True) 150 | dev_null_fd = os.open("/dev/null", os.O_RDWR) 151 | for std_fd in [0, 1, 2]: 152 | os.dup2(dev_null_fd, std_fd) 153 | if timeout: 154 | signal.alarm(timeout) 155 | sem_start_strace.release() 156 | sem_start_target.acquire() 157 | os.execve(argv[0], argv, {}) 158 | 159 | args = [ 160 | "/usr/bin/strace", 161 | "-o", f"{results_dir}/strace", 162 | "-ff", 163 | "-s", "512", 164 | "-A", 165 | "-e", "trace=!futex", # Ignore `sem_start_target.acquire()` 166 | *args, 167 | "-p", 168 | str(pid), 169 | ] 170 | 171 | sem_start_strace.acquire() 172 | strace_proc = subprocess.Popen(args, 173 | stdin=subprocess.DEVNULL, 174 | stdout=subprocess.PIPE, 175 | stderr=subprocess.PIPE) 176 | strace_proc.stderr.readline() # TODO: risk of stderr filling up 177 | sem_start_target.release() 178 | 179 | results = {} 180 | try: 181 | yield results 182 | finally: 183 | os.waitpid(pid, 0) 184 | # TODO: capture stdout/stderr (pipes) 185 | stdout = b"" 186 | stderr = b"" 187 | results["stdout"] = stdout 188 | results["stderr"] = stderr 189 | log = {} 190 | for path in results_dir.iterdir(): 191 | pid = int(path.suffix[1:]) 192 | log[pid] = path.read_text() 193 | results["log"] = log 194 | 195 | 196 | def immutable_file_copy(path): 197 | memfd = os.memfd_create(path, os.MFD_CLOEXEC|os.MFD_ALLOW_SEALING) 198 | os.write(memfd, path.read_bytes()) 199 | seals = ( 200 | fcntl.F_SEAL_WRITE | 201 | fcntl.F_SEAL_GROW | 202 | fcntl.F_SEAL_SHRINK | 203 | fcntl.F_SEAL_SEAL 204 | ) 205 | fcntl.fcntl(memfd, fcntl.F_ADD_SEALS, seals) 206 | return memfd 207 | 208 | 209 | def create_secret_dir(): 210 | pid = os.getpid() 211 | 212 | prev_umask = os.umask(0) 213 | tmp = pathlib.Path("/tmp") 214 | tmp_dir = tmp / pathlib.Path(os.urandom(16).hex()) 215 | tmp_dir.mkdir(0o711) 216 | secret_dir = tmp_dir / os.urandom(16).hex() 217 | secret_dir.mkdir(0o777) 218 | os.umask(prev_umask) 219 | 220 | def cleanup(): 221 | if os.getpid() == pid: 222 | shutil.rmtree(tmp_dir) 223 | atexit.register(cleanup) 224 | 225 | return secret_dir 226 | 227 | 228 | def validate_strace(level, results, requirements): 229 | stdout = results["stdout"].decode("latin") 230 | stderr = results["stderr"].decode("latin") 231 | 232 | def strace_validate(validate_name, expected_strace, result_strace, requirements): 233 | expected = [] 234 | for line in expected_strace.strip().splitlines(): 235 | levels, expect = line.split(None, 1) 236 | start, stop = levels.split("..") 237 | if ((not start or level >= int(start)) and 238 | (not stop or level <= int(stop))): 239 | expected.append(expect) 240 | 241 | result = result_strace.strip().splitlines() 242 | 243 | captured = {} 244 | 245 | requirements = { 246 | "bind_port": (lambda port: port == "80", "Bind to port 80"), 247 | "bind_address": (lambda addr: addr == "0.0.0.0", "Bind to address 0.0.0.0"), 248 | **requirements 249 | } 250 | 251 | print(f"===== Expected: {validate_name} =====") 252 | for expect in expected: 253 | print(f"[ ] {expect}") 254 | for name, (requirement, message) in requirements.items(): 255 | if f"<{name}>" in expect: 256 | print(f" - {message}") 257 | print() 258 | 259 | print(f"===== Trace: {validate_name} =====") 260 | # print(f"stdout: {stdout!r}") 261 | # print(f"stderr: {stderr!r}") 262 | errors = [] 263 | while result: 264 | current = result.pop(0) 265 | match = None 266 | if expected: 267 | re_expect = ("^" + 268 | re.sub(r"<(\w+)>", r"(?P<\1>.*)", 269 | re.escape(expected[0]).replace(r"\ =\ ", r"\s*=\s*")) + 270 | "$") 271 | match = re.match(re_expect, current) 272 | if match: 273 | match_captured = match.groupdict() 274 | match = True 275 | for name, capture in match_captured.items(): 276 | if name in requirements: 277 | requirement, message = requirements[name] 278 | if not requirement(capture): 279 | errors.append(f"{validate_name}: {message}") 280 | match = False 281 | captured.update(match_captured) 282 | expected.pop(0) 283 | if match: 284 | print("[✓]", current) 285 | elif match is False: 286 | print("[x]", current) 287 | success = False 288 | else: 289 | print("[?]", current) 290 | print() 291 | 292 | for expect in expected: 293 | errors.append(f"{validate_name}: Missing `{expect}`") 294 | 295 | return captured, errors 296 | 297 | errors = [] 298 | 299 | parent_pid, parent_result = min(results["log"].items()) 300 | parent_captured, parent_errors = strace_validate("Parent Process", strace_expected_parent, parent_result, requirements) 301 | errors.extend(parent_errors) 302 | 303 | child_pid = int(parent_captured.get("fork_result", 0)) 304 | if child_pid: 305 | child_result = results["log"][child_pid] 306 | child_captured, child_errors = strace_validate("Child Process", strace_expected_child, child_result, requirements) 307 | errors.extend(child_errors) 308 | 309 | return errors 310 | 311 | 312 | def retry_session(): 313 | session = requests.Session() 314 | retries = requests.adapters.Retry(total=4, backoff_factor=0.1) 315 | session.mount("http://", requests.adapters.HTTPAdapter(max_retries=retries)) 316 | return session 317 | 318 | 319 | def random_data(): 320 | return ''.join(random.choices(string.ascii_letters + string.digits, 321 | k=random.randrange(32, 256))).encode() 322 | 323 | 324 | def connect(): 325 | session = retry_session() 326 | try: 327 | session.get("http://localhost", timeout=1) 328 | except requests.exceptions.ConnectionError: 329 | pass 330 | 331 | 332 | def validate_connect(): 333 | session = retry_session() 334 | try: 335 | session.get("http://localhost", timeout=1) 336 | except requests.exceptions.ConnectionError: 337 | return "Connect: Failed to connect" 338 | 339 | 340 | def validate_get(data=None): 341 | if data is None: 342 | data = random_data() 343 | session = retry_session() 344 | with tempfile.NamedTemporaryFile() as f: 345 | f.write(data) 346 | f.flush() 347 | try: 348 | response = session.get("http://localhost" + f.name, timeout=1) 349 | except requests.exceptions.ConnectionError: 350 | return "GET: Failed to connect" 351 | if response.text.encode() != data: 352 | return "GET: File contents not correct" 353 | 354 | 355 | def validate_post(data=None): 356 | if data is None: 357 | data = random_data() 358 | session = retry_session() 359 | with tempfile.NamedTemporaryFile() as f: 360 | pass 361 | try: 362 | session.post("http://localhost" + f.name, data=data, timeout=1) 363 | except requests.exceptions.ConnectionError: 364 | return "POST: Failed to connect" 365 | try: 366 | if open(f.name, "rb").read() != data: 367 | return "POST: File contents not correct" 368 | except FileNotFoundError: 369 | return "POST: File does not exist" 370 | 371 | 372 | def challenge(): 373 | description = { 374 | 1: "exit a program", 375 | 2: "create a socket", 376 | 3: "bind an address to a socket", 377 | 4: "listen on a socket", 378 | 5: "accept a connection", 379 | 6: "respond to an http request", 380 | 7: "respond to a GET request for the contents of a specified file", 381 | 8: "accept multiple requests", 382 | 9: "concurrently accept multiple requests", 383 | 10: "respond to a POST request with a specified file and update its contents", 384 | 11: "respond to multiple concurrent GET and POST requests", 385 | }.get(level, "") 386 | description = f"In this challenge you will {description}." 387 | 388 | print("===== Welcome to Building a Web Server! =====") 389 | print("In this series of challenges, you will be writing assembly to interact with your environment, and ultimately build a web server") 390 | print(description) 391 | print() 392 | 393 | if len(sys.argv) != 2: 394 | print(f"Usage: `{sys.argv[0]} `", file=sys.stderr) 395 | print(f""" 396 | $ cat server.s 397 | .intel_syntax noprefix 398 | .globl _start 399 | 400 | .section .text 401 | 402 | _start: 403 | mov rdi, 0 404 | mov rax, 60 # SYS_exit 405 | syscall 406 | 407 | .section .data 408 | 409 | $ as -o server.o server.s && ld -o server server.o 410 | 411 | $ strace ./server 412 | execve("./server", ["./server"], 0x7ffccb8c6480 /* 17 vars */) = 0 413 | exit(0) = ? 414 | +++ exited with 0 +++ 415 | 416 | $ {sys.argv[0]} ./server 417 | """, file=sys.stderr) 418 | exit(1) 419 | 420 | target_path = pathlib.Path(sys.argv[1]) 421 | target_memfd = immutable_file_copy(target_path) 422 | results_dir = create_secret_dir() 423 | 424 | def target(): 425 | target_memfd_path = pathlib.Path(f"/proc/self/fd/{target_memfd}") 426 | 427 | operation_names = { 428 | connect: "connect", 429 | validate_connect: "validated connect", 430 | validate_get: "HTTP GET request", 431 | validate_post: "HTTP POST request", 432 | } 433 | 434 | operations = { 435 | 5: [connect], 436 | 6: [validate_connect], 437 | 7: [validate_get], 438 | 8: [validate_get, connect], 439 | 9: [validate_get, connect], 440 | 10: [validate_post, connect], 441 | 11: [*random.choices([validate_get, validate_post], k=32), connect], 442 | }.get(level, []) 443 | 444 | requirements = {} 445 | 446 | timeout = 10 447 | 448 | if level == 11: 449 | accept_kill = 100 # TODO: SIGCHLD 450 | elif level >= 5: 451 | accept_kill = 2 452 | else: 453 | accept_kill = 1 454 | 455 | errors = [] 456 | 457 | with strace([target_memfd_path], 458 | "-e", "signal=SIGKILL,SIGALRM", 459 | "-e", f"inject=accept:signal=SIGKILL:when={accept_kill}", 460 | "-e", "inject=brk:signal=SIGKILL", 461 | results_dir=results_dir, 462 | timeout=timeout) as results: 463 | for operation in operations: 464 | print(f"Performing operation: {operation_names[operation]}") 465 | try: 466 | error = operation() 467 | if error: 468 | errors.append(error) 469 | except Exception as e: 470 | errors.append(f"Exception: {str(e)}") 471 | print() 472 | 473 | strace_errors = validate_strace(level, results, requirements) 474 | errors.extend(strace_errors) 475 | 476 | print("===== Result =====") 477 | if not errors: 478 | print("[✓] Success") 479 | else: 480 | print("[x] Fail") 481 | for error in errors: 482 | print(f"[x] {error}") 483 | 484 | return not errors 485 | 486 | success = run_sandbox(target) 487 | if success: 488 | print() 489 | print(open("/flag", "r").read()) 490 | 491 | 492 | challenge() 493 | -------------------------------------------------------------------------------- /check: -------------------------------------------------------------------------------- 1 | #!/usr/bin/exec-suid --real -- /bin/python3 -I 2 | 3 | import os 4 | os.environ["PATH"] = "/challenge/bin:/bin:/usr/bin:/usr/local/bin" 5 | os.environ["TERM"] = "xterm-256color" 6 | 7 | import pwnlib.context 8 | import pwnlib.tubes.process 9 | import pwnlib.asm 10 | import pwnlib.elf 11 | import contextlib 12 | import subprocess 13 | import capstone 14 | import tempfile 15 | import socket 16 | import magic 17 | import time 18 | import sys 19 | 20 | pwnlib.context.context.arch = "amd64" 21 | cs = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64) 22 | 23 | sys.path.append(os.path.dirname(__file__)+"/.py") 24 | import chal #pylint:disable=import-error,wrong-import-position 25 | 26 | if os.geteuid() == 0: 27 | os.seteuid(65534) 28 | 29 | class ChallengeFailed(Exception): 30 | pass 31 | class ChallengeFailedPrint(ChallengeFailed): 32 | pass 33 | 34 | def print_prompt(): 35 | print(f"""hacker@{socket.gethostname()}:{ 36 | os.getcwd().replace(os.path.expanduser('~'), '~', 1) 37 | }$ """, end="", flush=True) 38 | 39 | def slow_pause(): 40 | if "FAST" not in os.environ: 41 | time.sleep(0.05) 42 | 43 | def slow_print(what): 44 | for c in what: 45 | print(c, end="", flush=True) 46 | slow_pause() 47 | print("") 48 | 49 | def dramatic_command(command, stdin=None, actual_command=None): 50 | print_prompt() 51 | slow_print(command) 52 | if not stdin: 53 | exit_code = os.WEXITSTATUS( 54 | os.system(command if actual_command is None else actual_command) 55 | ) 56 | else: 57 | p = pwnlib.tubes.process.process( 58 | command if actual_command is None else actual_command, 59 | shell=True 60 | ) 61 | slow_pause() 62 | slow_print(stdin.decode()) 63 | time.sleep(0.05) 64 | p.write(stdin) 65 | p.shutdown("send") 66 | p.wait() 67 | exit_code = p.poll() 68 | 69 | slow_pause() 70 | return exit_code 71 | 72 | @contextlib.contextmanager 73 | def disable_fd(fd): 74 | bkup = os.dup(fd) 75 | os.close(fd) 76 | yield 77 | os.dup2(bkup, fd) 78 | os.close(bkup) 79 | 80 | def _assemble(asm): 81 | with disable_fd(2): 82 | return pwnlib.asm.asm(asm) 83 | 84 | def assemble(asm): 85 | if not getattr(chal, "allow_asm", False): 86 | raise ChallengeFailedPrint( 87 | "This challenge requires you to assemble the code yourself. " 88 | "Please do that." 89 | ) 90 | 91 | try: 92 | x86 = _assemble(asm) 93 | except pwnlib.exception.PwnlibException as e: 94 | errors = e.message.split("\n") 95 | if ( 96 | errors[0].startswith("There was an error running") and 97 | "Assembler messages" in errors[3] 98 | ): 99 | msg = "\n".join( 100 | "- "+line.split(" ", 1)[-1] 101 | for line in errors[4:] 102 | if line 103 | ) 104 | raise ChallengeFailedPrint( 105 | f"Your assembly did not assemble cleanly. The errors:\n{msg}" 106 | ) from e 107 | 108 | raise ChallengeFailedPrint( 109 | "Your assembly resulted in unexpected assembly errors:\n" + 110 | e.message 111 | ) from e 112 | 113 | return x86 114 | 115 | def get_raw_binary(content): 116 | m = magic.from_buffer(content) 117 | if m == "ELF 64-bit relocatable": 118 | raise ChallengeFailedPrint( 119 | "You provided an ELF object file, rather than an actual\n" 120 | "ELF executable. The object file is an intermediate result\n" 121 | "of the compilation/assembly process. Please 'link' it into an\n" 122 | "executable by using:\n" 123 | "\n" 124 | "hacker@dojo:~$ ld final.elf your-object-file.o" 125 | "\n" 126 | "This will create a 'final.elf' file that you can pass to this program!" 127 | ) 128 | if ( 129 | "ELF 64-bit LSB shared object" in m or 130 | "ELF 64-bit LSB pie executable" in m or 131 | "ELF 64-bit LSB executable" in m 132 | ): 133 | if not getattr(chal, "allow_elf", True): 134 | raise ChallengeFailedPrint( 135 | "This challenge requires you to provide raw binary code, " 136 | "but you provided an ELF. Please extract your .text segment " 137 | "using 'objcopy'!" 138 | ) 139 | tmpelf = tempfile.mktemp() 140 | with open(tmpelf, "wb") as o: 141 | o.write(content) 142 | text = pwnlib.elf.ELF(tmpelf).get_section_by_name(".text") 143 | if not text: 144 | raise ChallengeFailedPrint( 145 | "The ELF you provided is missing the .text section!" 146 | ) 147 | rawbin = text.data() 148 | if not rawbin: 149 | raise ChallengeFailedPrint( 150 | "The .text section of the ELF you provided is empty!" 151 | ) 152 | return rawbin 153 | elif "text" in m: 154 | return assemble(content.decode('latin1')+"\n") 155 | else: #assuming this is binary code 156 | return content 157 | 158 | @contextlib.contextmanager 159 | def output_color(color, stream=sys.stdout): 160 | CODES = { 161 | 'HEADER': '\033[95m', 162 | 'OKBLUE': '\033[94m', 163 | 'OKCYAN': '\033[96m', 164 | 'OKGREEN': '\033[92m', 165 | 'WARNING': '\033[93m', 166 | 'FAIL': '\033[91m', 167 | 'ENDC': '\033[0m', 168 | 'BOLD': '\033[1m', 169 | 'UNDERLINE': '\033[4m', 170 | } 171 | print(CODES[color.upper()], end="", flush=True, file=stream) 172 | try: 173 | yield 174 | finally: 175 | print(CODES['ENDC'], end="", flush=True, file=stream) 176 | 177 | def do_check(stage_id, stage_description, *args, **kwargs): 178 | try: 179 | if not hasattr(chal, stage_id): 180 | return None 181 | 182 | print("") 183 | with output_color("header"): 184 | print(getattr(chal, stage_id+"_prologue", f"{stage_description}...").format(**chal.__dict__)) 185 | r = getattr(chal, stage_id)(*args, **kwargs) 186 | with output_color("okgreen"): 187 | print(getattr(chal, stage_id+"_success", "... YES! Great job!").format(**chal.__dict__)) 188 | return r 189 | except (ChallengeFailed, AssertionError) as _e: 190 | with output_color("fail"): 191 | print(getattr(chal, stage_id+"_failure", "... oops, we found an issue! Details below:\n").format(**chal.__dict__)) 192 | print(_e) 193 | raise 194 | 195 | def main(): 196 | if len(sys.argv) == 2: 197 | filename = sys.argv[1] 198 | try: 199 | #pylint:disable=consider-using-with,unspecified-encoding 200 | content = open(filename, "rb").read() 201 | except FileNotFoundError: 202 | print(f"File {filename} not found.") 203 | return 204 | except PermissionError: 205 | print(f"Permission denied when opening {filename}.") 206 | return 207 | elif not os.isatty(0): 208 | content = sys.stdin.buffer.read() 209 | else: 210 | if chal.allow_asm: 211 | print("Please input your assembly. Press Ctrl+D when done!") 212 | else: 213 | print(f"Please run this program as `{sys.argv[0]} your-program.elf`!") 214 | sys.exit(1) 215 | content = b"" 216 | while line := sys.stdin.buffer.readline(): 217 | content += line 218 | 219 | raw_binary = get_raw_binary(content) 220 | 221 | disas = list(cs.disasm(raw_binary, 0)) 222 | if not disas: 223 | print("Your binary failed to disassemble.") 224 | return 225 | 226 | num_instructions = getattr(chal, "num_instructions", None) 227 | if num_instructions is not None and len(disas) != num_instructions: 228 | print( 229 | f"This challenge expects {num_instructions} " 230 | f"instruction{'s' if num_instructions != 1 else ''}, " 231 | f"but you provided {len(disas)}." 232 | ) 233 | return 234 | 235 | if hasattr(chal, "assembly_prefix"): 236 | binary_prefix = _assemble(chal.assembly_prefix) 237 | raw_binary = binary_prefix + raw_binary 238 | if hasattr(chal, "assembly_suffix"): 239 | binary_suffix = _assemble(chal.assembly_suffix) 240 | raw_binary = raw_binary + binary_suffix 241 | 242 | do_check("check_raw_binary", "Checking the binary code", raw_binary) 243 | do_check("check_disassembly", "Checking the assembly code", disas) 244 | 245 | elf_filename = getattr(chal, 'final_filename', "/tmp/your-program") 246 | os.rename( 247 | pwnlib.asm.make_elf(raw_binary, extract=False), 248 | elf_filename 249 | ) 250 | 251 | if os.geteuid() == 65534: 252 | os.seteuid(0) 253 | 254 | do_check("check_runtime", "Checking runtime behavior", elf_filename) 255 | 256 | if getattr(chal, "give_flag", False): 257 | print("") 258 | print("Here is your flag!") 259 | #pylint:disable=consider-using-with,unspecified-encoding 260 | print(open("/flag").read()) 261 | 262 | 263 | if __name__ == '__main__': 264 | try: 265 | main() 266 | except (ChallengeFailedPrint) as _e: 267 | print("Check failed:") 268 | print() 269 | with output_color("FAIL"): 270 | print(_e) 271 | except (ChallengeFailed, AssertionError): 272 | # we assume we already printed 273 | pass 274 | except Exception as _e: #pylint:disable=broad-exception-caught 275 | print("Unexpected error during challenge evaluation! The error:") 276 | print(_e) 277 | raise 278 | -------------------------------------------------------------------------------- /debugging-refresher/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | A critical part of working with computing is understanding what goes wrong when something inevitably does. 2 | This module will build on your prior exposure to GDB with some more _debugging_ of programs: digging in, poking around, and gaining knowledge. 3 | 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. 4 | 5 | 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. 6 | You will become more familiar with some of its capabilities in this module. 7 | -------------------------------------------------------------------------------- /debugging-refresher/level-1/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | This level gets you re-familiarized with gdb. 2 | To get started with this level, and all the other levels of this module, run `/challenge/embryogdb_levelXYZ`, where XYZ is the level number. 3 | That program will launch `gdb`. 4 | Run the actual level logic with `r`, and follow the prompts to get that flag! 5 | 6 | ---- 7 | **RELEVANT DOCUMENTATION:** 8 | - gdb's [run](https://sourceware.org/gdb/current/onlinedocs/gdb#Starting) command 9 | - gdb's [continue](https://sourceware.org/gdb/current/onlinedocs/gdb#Continuing-and-Stepping) command 10 | -------------------------------------------------------------------------------- /debugging-refresher/level-1/embryogdb_level1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwncollege/computing-101/e43e48e1bbb31e8fcb820a0dd268528e3ddd3894/debugging-refresher/level-1/embryogdb_level1 -------------------------------------------------------------------------------- /debugging-refresher/level-2/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Next, we'll learn about how to print out the values of registers. 2 | 3 | You can see the values for all your registers with `info registers`. Alternatively, you can also just print a particular 4 | register's value with the `print` command, or `p` for short. For example, `p $rdi` will print the value of $rdi in 5 | decimal. You can also print it's value in hex with `p/x $rdi`. 6 | 7 | In order to solve this level, you must figure out the current random value of register r12 in hex. 8 | 9 | As before, start the challenge, invoke the `run` gdb command, then follow the instructions. 10 | When you've printed out what you need, remember to `continue` to move on to the next step of the challenge! 11 | 12 | ---- 13 | **RELEVANT DOCUMENTATION:** 14 | - gdb's [run](https://sourceware.org/gdb/current/onlinedocs/gdb#Starting) command 15 | - gdb's [continue](https://sourceware.org/gdb/current/onlinedocs/gdb#Continuing-and-Stepping) command 16 | - gdb's [info](https://sourceware.org/gdb/current/onlinedocs/gdb#Registers) command 17 | - gdb's [print](https://sourceware.org/gdb/current/onlinedocs/gdb#Data) command 18 | -------------------------------------------------------------------------------- /debugging-refresher/level-2/embryogdb_level2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwncollege/computing-101/e43e48e1bbb31e8fcb820a0dd268528e3ddd3894/debugging-refresher/level-2/embryogdb_level2 -------------------------------------------------------------------------------- /debugging-refresher/level-3/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Next, we'll learn to use gdb to peek into process memory! 2 | 3 | You can e**x**amine the contents of memory using the `x/
` parameterized command. In this format `` is 4 | the unit size to display, `` is the format to display it in, and `` is the number of elements to display. Valid 5 | unit sizes are `b` (1 byte), `h` (2 bytes), `w` (4 bytes), and `g` (8 bytes). Valid formats are `d` (decimal), `x` 6 | (hexadecimal), `s` (string) and `i` (instruction). The address can be specified using a register name, symbol name, or 7 | absolute address. Additionally, you can supply mathematical expressions when specifying the address. 8 | 9 | For example, `x/8i $rip` will print the next 8 instructions from the current instruction pointer. `x/16i main` will 10 | print the first 16 instructions of main. You can also use `disassemble main`, or `disas main` for short, to print all of 11 | the instructions of main. Alternatively, `x/16gx $rsp` will print the first 16 values on the stack. `x/gx $rbp-0x32` 12 | will print the local variable stored there on the stack. 13 | 14 | You will probably want to view your instructions using the CORRECT assembly syntax. You can do that with the command 15 | `set disassembly-flavor intel`. 16 | 17 | In order to solve this level, you must figure out the random value on the stack (the value read in from `/dev/urandom`). 18 | Think about what the arguments to the read system call are. 19 | 20 | ---- 21 | **RELEVANT DOCUMENTATION:** 22 | - gdb's [run](https://sourceware.org/gdb/current/onlinedocs/gdb#Starting) command 23 | - gdb's [continue](https://sourceware.org/gdb/current/onlinedocs/gdb#Continuing-and-Stepping) command 24 | - gdb's [info](https://sourceware.org/gdb/current/onlinedocs/gdb#Registers) command 25 | - gdb's [print](https://sourceware.org/gdb/current/onlinedocs/gdb#Data) command 26 | - gdb's [examine](https://sourceware.org/gdb/current/onlinedocs/gdb#Memory) command 27 | -------------------------------------------------------------------------------- /debugging-refresher/level-3/embryogdb_level3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwncollege/computing-101/e43e48e1bbb31e8fcb820a0dd268528e3ddd3894/debugging-refresher/level-3/embryogdb_level3 -------------------------------------------------------------------------------- /debugging-refresher/level-4/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | A critical part of dynamic analysis is getting your program to the state you are interested in analyzing. 2 | So far, these challenges have automatically set breakpoints for you to pause execution at states you may be interested in analyzing. 3 | It is important to be able to do this yourself. 4 | 5 | There are a number of ways to move forward in the program's execution. 6 | You can use the `stepi ` command, or `si ` for short, in order to step forward one instruction. 7 | You can use the `nexti ` command, or `ni ` for short, in order to step forward one instruction, while stepping over any function calls. 8 | The `` parameter is optional, but allows you to perform multiple steps at once. 9 | You can use the `finish` command in order to finish the currently executing function. 10 | You can use the `break *
` parameterized command in order to set a breakpoint at the specified-address. 11 | You have already used the `continue` command, which will continue execution until the program hits a breakpoint. 12 | 13 | While stepping through a program, you may find it useful to have some values displayed to you at all times. 14 | There are multiple ways to do this. 15 | The simplest way is to use the `display/` parameterized command, which follows exactly the same format as the `x/` parameterized command. 16 | For example, `display/8i $rip` will always show you the next 8 instructions. 17 | On the other hand, `display/4gx $rsp` will always show you the first 4 values on the stack. 18 | Another option is to use the `layout regs` command. 19 | This will put gdb into its TUI mode and show you the contents of all of the registers, as well as nearby instructions. 20 | 21 | In order to solve this level, you must figure out a series of random values which will be placed on the stack. 22 | As before, `run` will start you out, but it will interrupt the program and you must, carefully, continue its execution. 23 | 24 | You are highly encouraged to try using combinations of `stepi`, `nexti`, `break`, `continue`, and `finish` to make sure you have a good internal understanding of these commands. 25 | The commands are all absolutely critical to navigating a program's execution. 26 | 27 | ---- 28 | **RELEVANT DOCUMENTATION:** 29 | - gdb's [run](https://sourceware.org/gdb/current/onlinedocs/gdb#Starting) command 30 | - gdb's [continue](https://sourceware.org/gdb/current/onlinedocs/gdb#Continuing-and-Stepping) command 31 | - gdb's [info](https://sourceware.org/gdb/current/onlinedocs/gdb#Registers) command 32 | - gdb's [print](https://sourceware.org/gdb/current/onlinedocs/gdb#Data) command 33 | - gdb's [examine](https://sourceware.org/gdb/current/onlinedocs/gdb#Memory) command 34 | - gdb's [break](https://sourceware.org/gdb/current/onlinedocs/gdb#Set-Breaks) command 35 | - gdb's [display](https://sourceware.org/gdb/current/onlinedocs/gdb#Auto-Display) command 36 | - gdb's [various stepping commands](https://sourceware.org/gdb/current/onlinedocs/gdb#Continuing-and-Stepping) command (that whole section) 37 | 38 | **NOTE:** 39 | This challenge will require you to _read_ and _understand_ assembly! 40 | Don't worry, this skill will come in quite handy later in pwn.college. 41 | -------------------------------------------------------------------------------- /debugging-refresher/level-4/embryogdb_level4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwncollege/computing-101/e43e48e1bbb31e8fcb820a0dd268528e3ddd3894/debugging-refresher/level-4/embryogdb_level4 -------------------------------------------------------------------------------- /debugging-refresher/level-5/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | We write code in order to express an idea which can be reproduced and refined. 2 | We can think of our analysis as a program which injests the target to be analyzed as data. 3 | As the saying goes, code is data and data is code. 4 | 5 | While using gdb interactively as we've done with the past levels is incredibly powerful, another powerful tool is gdb scripting. 6 | By scripting gdb, you can very quickly create a custom-tailored program analysis tool. 7 | If you know how to interact with gdb, you already know how to write a gdb script--the syntax is exactly the same. 8 | You can write your commands to some file, for example `x.gdb`, and then launch gdb using the flag `-x `. 9 | This file will execute all of the gdb commands after gdb launches. 10 | Alternatively, you can execute individual commands with `-ex ''`. 11 | You can pass multiple commands with multiple `-ex` arguments. 12 | Finally, you can have some commands be always executed for any gdb session by putting them in `~/.gdbinit`. 13 | You probably want to put `set disassembly-flavor intel` in there. 14 | 15 | Within gdb scripting, a very powerful construct is breakpoint commands. Consider the following gdb script: 16 | 17 | ```gdb 18 | start 19 | break *main+42 20 | commands 21 | x/gx $rbp-0x32 22 | continue 23 | end 24 | continue 25 | ``` 26 | 27 | In this case, whenever we hit the instruction at `main+42`, we will output a particular local variable and then continue execution. 28 | 29 | Now consider a similar, but slightly more advanced script using some commands you haven't yet seen: 30 | 31 | ```gdb 32 | start 33 | break *main+42 34 | commands 35 | silent 36 | set $local_variable = *(unsigned long long*)($rbp-0x32) 37 | printf "Current value: %llx\n", $local_variable 38 | continue 39 | end 40 | continue 41 | ``` 42 | 43 | In this case, the `silent` indicates that we want gdb to not report that we have hit a breakpoint, to make the output a bit cleaner. 44 | Then we use the `set` command to define a variable within our gdb session, whose value is our local variable. 45 | Finally, we output the current value using a formatted string. 46 | 47 | Use gdb scripting to help you collect the random values in this level. 48 | This may feel difficult, but will serve you well in your journey ahead. 49 | 50 | ---- 51 | **RELEVANT DOCUMENTATION:** 52 | - gdb's [run](https://sourceware.org/gdb/current/onlinedocs/gdb#Starting) command 53 | - gdb's [continue](https://sourceware.org/gdb/current/onlinedocs/gdb#Continuing-and-Stepping) command 54 | - gdb's [info](https://sourceware.org/gdb/current/onlinedocs/gdb#Registers) command 55 | - gdb's [print](https://sourceware.org/gdb/current/onlinedocs/gdb#Data) command 56 | - gdb's [examine](https://sourceware.org/gdb/current/onlinedocs/gdb#Memory) command 57 | - gdb's [break](https://sourceware.org/gdb/current/onlinedocs/gdb#Set-Breaks) command 58 | - gdb's [display](https://sourceware.org/gdb/current/onlinedocs/gdb#Auto-Display) command 59 | - gdb's [various stepping commands](https://sourceware.org/gdb/current/onlinedocs/gdb#Continuing-and-Stepping) command (that whole section) 60 | - gdb's [breakpoint scripting](https://sourceware.org/gdb/current/onlinedocs/gdb#Break-Commands) 61 | -------------------------------------------------------------------------------- /debugging-refresher/level-5/embryogdb_level5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwncollege/computing-101/e43e48e1bbb31e8fcb820a0dd268528e3ddd3894/debugging-refresher/level-5/embryogdb_level5 -------------------------------------------------------------------------------- /debugging-refresher/level-6/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | As it turns out, gdb has FULL control over the target process. 2 | Not only can you analyze the program's state, but you can also modify it. 3 | While gdb probably isn't the best tool for doing long term maintenance on a program, sometimes it can be useful to quickly modify the behavior of your target process in order to more easily analyze it. 4 | 5 | You can modify the state of your target program with the `set` command. 6 | For example, you can use `set $rdi = 0` to zero out $rdi. 7 | You can use `set *((uint64_t *) $rsp) = 0x1234` to set the first value on the stack to 0x1234. 8 | You can use `set *((uint16_t *) 0x31337000) = 0x1337` to set 2 bytes at 0x31337000 to 0x1337. 9 | 10 | Suppose your target is some networked application which reads from some socket on fd 42. 11 | Maybe it would be easier for the purposes of your analysis if the target instead read from stdin. 12 | You could achieve something like that with the following gdb script: 13 | 14 | ```gdb 15 | start 16 | catch syscall read 17 | commands 18 | silent 19 | if ($rdi == 42) 20 | set $rdi = 0 21 | end 22 | continue 23 | end 24 | continue 25 | ``` 26 | 27 | This example gdb script demonstrates how you can automatically break on system calls, and how you can use conditions within your commands to conditionally perform gdb commands. 28 | 29 | In the previous level, your gdb scripting solution likely still required you to copy and paste your solutions. 30 | This time, try to write a script that doesn't require you to ever talk to the program, and instead automatically solves each challenge by correctly modifying registers / memory. 31 | 32 | ---- 33 | **RELEVANT DOCUMENTATION:** 34 | - gdb's [run](https://sourceware.org/gdb/current/onlinedocs/gdb#Starting) command 35 | - gdb's [continue](https://sourceware.org/gdb/current/onlinedocs/gdb#Continuing-and-Stepping) command 36 | - gdb's [info](https://sourceware.org/gdb/current/onlinedocs/gdb#Registers) command 37 | - gdb's [print](https://sourceware.org/gdb/current/onlinedocs/gdb#Data) command 38 | - gdb's [examine](https://sourceware.org/gdb/current/onlinedocs/gdb#Memory) command 39 | - gdb's [break](https://sourceware.org/gdb/current/onlinedocs/gdb#Set-Breaks) command 40 | - gdb's [display](https://sourceware.org/gdb/current/onlinedocs/gdb#Auto-Display) command 41 | - gdb's [various stepping commands](https://sourceware.org/gdb/current/onlinedocs/gdb#Continuing-and-Stepping) command (that whole section) 42 | - gdb's [breakpoint scripting](https://sourceware.org/gdb/current/onlinedocs/gdb#Break-Commands) 43 | - gdb's [set](https://sourceware.org/gdb/current/onlinedocs/gdb#Assignment) command. Keep in mind that, in this challenge, you'll specifically use this command to set registers (e.g., `$rdi` as above) and memory (as described at the bottom of the linked section). 44 | -------------------------------------------------------------------------------- /debugging-refresher/level-6/embryogdb_level6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwncollege/computing-101/e43e48e1bbb31e8fcb820a0dd268528e3ddd3894/debugging-refresher/level-6/embryogdb_level6 -------------------------------------------------------------------------------- /debugging-refresher/level-7/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | This level will expose you to some of the true power of gdb. 2 | 3 | ---- 4 | **RELEVANT DOCUMENTATION:** 5 | - gdb's [run](https://sourceware.org/gdb/current/onlinedocs/gdb#Starting) command 6 | - gdb's [continue](https://sourceware.org/gdb/current/onlinedocs/gdb#Continuing-and-Stepping) command 7 | - gdb's [info](https://sourceware.org/gdb/current/onlinedocs/gdb#Registers) command 8 | - gdb's [print](https://sourceware.org/gdb/current/onlinedocs/gdb#Data) command 9 | - gdb's [examine](https://sourceware.org/gdb/current/onlinedocs/gdb#Memory) command 10 | - gdb's [break](https://sourceware.org/gdb/current/onlinedocs/gdb#Set-Breaks) command 11 | - gdb's [display](https://sourceware.org/gdb/current/onlinedocs/gdb#Auto-Display) command 12 | - gdb's [various stepping commands](https://sourceware.org/gdb/current/onlinedocs/gdb#Continuing-and-Stepping) command (that whole section) 13 | - gdb's [breakpoint scripting](https://sourceware.org/gdb/current/onlinedocs/gdb#Break-Commands) 14 | - gdb's [set](https://sourceware.org/gdb/current/onlinedocs/gdb#Assignment) command 15 | - gdb's [call](https://sourceware.org/gdb/current/onlinedocs/gdb#Calling) command. Keep in mind that the syntax for the expression is similar to C's syntax. 16 | -------------------------------------------------------------------------------- /debugging-refresher/level-7/embryogdb_level7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwncollege/computing-101/e43e48e1bbb31e8fcb820a0dd268528e3ddd3894/debugging-refresher/level-7/embryogdb_level7 -------------------------------------------------------------------------------- /debugging-refresher/level-8/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | The previous level showed you raw, but unrefined power. 2 | This level will force you to refine it, as the `win` function will no longer work. 3 | `break` at it, look around, and understand what is wrong. 4 | 5 | ---- 6 | **RELEVANT DOCUMENTATION:** 7 | - gdb's [run](https://sourceware.org/gdb/current/onlinedocs/gdb#Starting) command 8 | - gdb's [continue](https://sourceware.org/gdb/current/onlinedocs/gdb#Continuing-and-Stepping) command 9 | - gdb's [info](https://sourceware.org/gdb/current/onlinedocs/gdb#Registers) command 10 | - gdb's [print](https://sourceware.org/gdb/current/onlinedocs/gdb#Data) command 11 | - gdb's [examine](https://sourceware.org/gdb/current/onlinedocs/gdb#Memory) command 12 | - gdb's [break](https://sourceware.org/gdb/current/onlinedocs/gdb#Set-Breaks) command 13 | - gdb's [display](https://sourceware.org/gdb/current/onlinedocs/gdb#Auto-Display) command 14 | - gdb's [various stepping commands](https://sourceware.org/gdb/current/onlinedocs/gdb#Continuing-and-Stepping) command (that whole section) 15 | - gdb's [breakpoint scripting](https://sourceware.org/gdb/current/onlinedocs/gdb#Break-Commands) 16 | - gdb's [set](https://sourceware.org/gdb/current/onlinedocs/gdb#Assignment) command 17 | - gdb's [call](https://sourceware.org/gdb/current/onlinedocs/gdb#Calling) command 18 | - gdb's [jump](https://sourceware.org/gdb/current/onlinedocs/gdb#Jumping) command 19 | -------------------------------------------------------------------------------- /debugging-refresher/level-8/embryogdb_level8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwncollege/computing-101/e43e48e1bbb31e8fcb820a0dd268528e3ddd3894/debugging-refresher/level-8/embryogdb_level8 -------------------------------------------------------------------------------- /debugging-refresher/module.yml: -------------------------------------------------------------------------------- 1 | name: Debugging Refresher 2 | challenges: 3 | - id: level-1 4 | name: level1 5 | - id: level-2 6 | name: level2 7 | - id: level-3 8 | name: level3 9 | - id: level-4 10 | name: level4 11 | - id: level-5 12 | name: level5 13 | - id: level-6 14 | name: level6 15 | - id: level-7 16 | name: level7 17 | - id: level-8 18 | name: level8 19 | resources: 20 | - name: Robert's GDB Walkthrough 21 | type: lecture 22 | video: r185fCzdw8Y 23 | playlist: PL-ymxv0nOtqqQzEncNuE6jetlJAiBUda- 24 | - name: GDB Help 25 | type: markdown 26 | content: | 27 | There are a number of good gdb crash courses / reference manuals: 28 | 29 | - [GDB's documentation](https://sourceware.org/gdb/onlinedocs/gdb/index.html) 30 | - [Tudor's gdb crash course](https://web.archive.org/web/20250101052732/https://users.umiacs.umd.edu/~tdumitra/courses/ENEE757/Fall15/misc/gdb_tutorial.html) 31 | - [gdb debugging full example](https://www.brendangregg.com/blog/2016-08-09/gdb-example-ncurses.html) 32 | - [pwndbg: a gdb extension (feature list)](https://github.com/pwndbg/pwndbg/blob/dev/FEATURES.md) 33 | - [gef: another gdb extension (feature list)](https://hugsy.github.io/gef/commands/aliases/) 34 | - The course [Debuggers 1012: Introductory GDB](https://ost2.fyi/Dbg1012) from OpenSecurityTraining2. 35 | -------------------------------------------------------------------------------- /dojo.yml: -------------------------------------------------------------------------------- 1 | name: Computing 101 2 | type: welcome 3 | id: computing-101 4 | image: pwncollege/challenge-legacy:latest 5 | award: 6 | emoji: 💻 7 | modules: 8 | - id: your-first-program 9 | - id: introspecting 10 | - id: memory 11 | - id: hello-hackers 12 | - id: assembly-crash-course 13 | - id: debugging-refresher 14 | - id: building-a-web-server 15 | -------------------------------------------------------------------------------- /hello-hackers/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Until now, your program's single interaction with the wider world was changing its exit code when `exit`ing. 2 | Of course, more interaction is possible! 3 | 4 | In this module, we will learn about the `write` system call, which is used to `write` output to the command-line terminal! 5 | 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!). 6 | -------------------------------------------------------------------------------- /hello-hackers/hello-hackers/.py/chal.py: -------------------------------------------------------------------------------- 1 | ../../../secret-value-checker.py -------------------------------------------------------------------------------- /hello-hackers/hello-hackers/.py/chalconf.py: -------------------------------------------------------------------------------- 1 | addr_chain = [ 1337000 ] 2 | secret_checks = [ 'stdout' ] 3 | secret_value = b"Hello Hackers!" 4 | exit_code = 42 5 | secret_value_desc = """the string "Hello Hackers!" """ 6 | num_instructions = 8 7 | must_set_regs = [ "rax", "rdi", "rsi", "rdx" ] 8 | clean_exit = True 9 | skip_deref_checks = True 10 | success_message = """ 11 | YES! You wrote a "Hello Hackers" and cleanly exited! Great job! 12 | """.strip() 13 | final_reg_vals = { 14 | "rax": (60, "syscall number of exit"), 15 | "rdi": (42, "the exit code we want"), 16 | } 17 | -------------------------------------------------------------------------------- /hello-hackers/hello-hackers/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Okay, we have one thing left for this run of challenges. 2 | You've written out a single byte, and now we'll practice writing out multiple bytes. 3 | I've stored a 14-character secret string at memory location `1337000`. 4 | Can you write it out? 5 | 6 | ---- 7 | **Hint:** 8 | The *only* thing you should have to change compared to your previous solution is the value in `rdx`! 9 | -------------------------------------------------------------------------------- /hello-hackers/hello-hackers/check: -------------------------------------------------------------------------------- 1 | ../../check -------------------------------------------------------------------------------- /hello-hackers/module.yml: -------------------------------------------------------------------------------- 1 | name: Hello Hackers 2 | resources: 3 | - name: "System Calls" 4 | type: lecture 5 | video: vspq0u2tvhU 6 | playlist: PL-ymxv0nOtqox6nF4HXtXHnTQFGkRQU_2 7 | slides: 1vEuZ1PW8Wvm88INmWbjoKY8srEKSkv2g4o-V42BsN9Y 8 | challenges: 9 | - id: write 10 | name: "Writing Output" 11 | - id: write-exit 12 | name: "Chaining Syscalls" 13 | - id: hello-hackers 14 | name: "Writing Strings" 15 | - id: read 16 | name: "Reading Data" 17 | -------------------------------------------------------------------------------- /hello-hackers/read/.py/chal.py: -------------------------------------------------------------------------------- 1 | ../../../secret-value-checker.py -------------------------------------------------------------------------------- /hello-hackers/read/.py/chalconf.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | 4 | addr_chain = [ 1337000 ] 5 | secret_checks = [ 'cat' ] 6 | stdin = "".join(random.sample(string.ascii_letters, 8)).encode() 7 | num_instructions = 13 8 | read_size = 8 9 | exit_code = 42 10 | secret_value_reg = None 11 | must_set_regs = [ "rax", "rdi", "rsi", "rdx" ] 12 | clean_exit = True 13 | skip_deref_checks = True 14 | success_message = """ 15 | YES! You wrote the data and cleanly exited! Great job! 16 | """.strip() 17 | final_reg_vals = { 18 | "rax": (60, "syscall number of exit"), 19 | "rdi": (42, "the exit code we want"), 20 | } 21 | -------------------------------------------------------------------------------- /hello-hackers/read/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | You now know how to output data to stdout using `write`. 2 | But how does your program receive input data? 3 | It `read`s it from stdin! 4 | 5 | Like `write`, `read` is a system call that shunts data around between file descriptors and memory. 6 | In `read`'s case, it reads some amount of bytes from the provided file descriptor and stores them in memory. 7 | The C-style syntax is the same as `write`: 8 | 9 | ```c 10 | read(0, 1337000, 5); 11 | ``` 12 | 13 | This will read `5` bytes from file descriptor `0` (stdin) into memory starting from `1337000`. 14 | So, if you type in (or pipe in) `HELLO HACKERS` into stdin, the above `read` call would result in the following memory configuration: 15 | 16 | ```text 17 | Address │ Contents 18 | +────────────────────+ 19 | │ 1337000 │ 48 │ 20 | │ 1337001 │ 45 │ 21 | │ 1337002 │ 4c │ 22 | │ 1337003 │ 4c │ 23 | │ 1337004 │ 4f │ 24 | +────────────────────+ 25 | ``` 26 | 27 | What are those numbers?? 28 | They are _hexadecimal_ representations of _ASCII_-encoded letters. 29 | If those words don't make sense, please run through the first half or so of the [Dealing with Data](/fundamentals/data-dealings) module and then come back here! 30 | 31 | In this level, we will combine `read` with our previous `write` abilities. 32 | Your program should: 33 | 34 | 1. first `read` 8 bytes from stdin to address `1337000` 35 | 2. then `write` those 8 bytes from address `1337000` to stdout 36 | 3. finally, exit with the exit code `42`. 37 | 38 | Remember: you've already written steps 2 and 3. All you have to do is add step 1! 39 | 40 | ---- 41 | **NOTE:** 42 | Keep in mind that, in this challenge, you'll be writing 8 characters, whereas in the previous challenge, you wrote 14. 43 | Don't forget to update your `write()` size (in `rdx`)! 44 | 45 | **DEBUGGING:** 46 | Having trouble? 47 | Build your program and run it with `strace` to see what's happening at the system call level! 48 | -------------------------------------------------------------------------------- /hello-hackers/read/check: -------------------------------------------------------------------------------- 1 | ../../check -------------------------------------------------------------------------------- /hello-hackers/write-exit/.py/chal.py: -------------------------------------------------------------------------------- 1 | ../../../secret-value-checker.py -------------------------------------------------------------------------------- /hello-hackers/write-exit/.py/chalconf.py: -------------------------------------------------------------------------------- 1 | addr_chain = [ 1337000 ] 2 | secret_checks = [ 'stdout' ] 3 | secret_value = 0x48 4 | exit_code = 42 5 | secret_value_desc = "the letter H" 6 | num_instructions = 8 7 | must_set_regs = [ "rax", "rdi", "rsi", "rdx" ] 8 | clean_exit = True 9 | skip_deref_checks = True 10 | success_message = """ 11 | YES! You wrote an 'H' and cleanly exited! Great job! 12 | """.strip() 13 | final_reg_vals = { 14 | "rax": (60, "syscall number of exit"), 15 | "rdi": (42, "the exit code we want"), 16 | } 17 | -------------------------------------------------------------------------------- /hello-hackers/write-exit/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Okay, our previous solution wrote output but then crashed. 2 | In this level, you will write output, and then _not_ crash! 3 | 4 | We'll do this by invoking the `write` system call, and then invoking the `exit` system call to cleanly exit the program. 5 | How do we invoke two system calls? 6 | Just like you invoke two instructions! 7 | First, you set up the necessary registers and invoke `write`, then you set up the necessary registers and invoke `exit! 8 | 9 | Your previous solution had 5 instructions (setting `rdi`, setting `rsi`, setting `rdx`, setting `rax`, and `syscall`). 10 | This one should have those 5, plus three more for `exit` (setting `rdi` to the exit code, setting `rax` to syscall index `60`, and `syscall`). 11 | For this level, let's exit with exit code `42`! 12 | -------------------------------------------------------------------------------- /hello-hackers/write-exit/check: -------------------------------------------------------------------------------- 1 | ../../check -------------------------------------------------------------------------------- /hello-hackers/write/.py/chal.py: -------------------------------------------------------------------------------- 1 | ../../../secret-value-checker.py -------------------------------------------------------------------------------- /hello-hackers/write/.py/chalconf.py: -------------------------------------------------------------------------------- 1 | addr_chain = [ 1337000 ] 2 | secret_checks = [ 'stdout' ] 3 | secret_value = 0x48 4 | secret_value_desc = "the letter H" 5 | num_instructions = 5 6 | must_set_regs = [ "rax", "rdi", "rsi", "rdx" ] 7 | final_reg_vals = { 8 | "rax": (1, "syscall number of write"), 9 | "rdi": (1, "fd of standard output"), 10 | "rsi": (addr_chain[0], "the address where the secret value is stored"), 11 | "rdx": (1, "just write a single byte") 12 | } 13 | clean_exit = False 14 | skip_deref_checks = True 15 | success_message = """ 16 | Wow, you wrote an "H"!!!!!!! But why did your program crash? Well, you didn't 17 | exit, and as before, the CPU kept executing and eventually crashed. In the next 18 | level, we will learn how to chain two system calls togeter: write and exit! 19 | """ 20 | -------------------------------------------------------------------------------- /hello-hackers/write/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Let's learn to write text! 2 | 3 | Unsurprisingly, your program writes text to the screen by invoking a system call. 4 | Specifically, this is the `write` system call, and its syscall number is `1`. 5 | However, the write system call also needs to specify, via its parameters, _what_ data to write and _where_ to write it to. 6 | 7 | You may remember, from the [Practicing Piping](/linux-luminarium/piping) module of the [Linux Luminarium](/linux-luminarium) dojo, the concept of _File Descriptors_ (FDs). 8 | As a reminder, each process starts out with three FDs: 9 | 10 | - **FD 0:** Standard *Input* is the channel through which the process takes input. For example, your shell uses Standard Input to read the commands that you input. 11 | - **FD 1:** Standard *Output* is the channel through which processes output normal data, such as the flag when it is printed to you in previous challenges or the output of utilities such as `ls`. 12 | - **FD 2:** Standard *Error* is the channel through which processes output error details. For example, if you mistype a command, the shell will output, over standard error, that this command does not exist. 13 | 14 | It turns out that, in your `write` system call, this is how you specify _where_ to write the data to! 15 | The first (and only) parameter to your `exit` system call was your exit code (`mov rdi, 42`), and the first (but, in this case, not only!) parameter to `write` is the file descriptor. 16 | If you want to write to standard output, you would set `rdi` to 1. 17 | If you want to write to standard error, you would set `rdi` to 2. 18 | Super simple! 19 | 20 | This leaves us with _what_ to write. 21 | Now, you could imagine a world where we specify what to write through yet another register parameter to the `write` system call. 22 | But these registers don't fit a ton of data, and to write out a long story like this challenge description, you'd need to invoke the `write` system call multiple times. 23 | Relatively speaking, this has a lot of performance cost --- the CPU needs to switch from executing the instructions of your program to executing the instructions of Linux itself, do a bunch of housekeeping computation, interact with your hardware to get the actual pixels to show up on your screen, and then switch back. 24 | This is slow, and so we try to minimize the number of times we invoke system calls. 25 | 26 | Of course, the solution to this is to write multiple characters at the same time. 27 | The `write` system call does this by taking _two_ parameters for the "what": a _where_ (in memory) to start writing from and a _how many_ characters to write. 28 | These parameters are passed as the second and third parameter to `write`. 29 | In the kinda-C syntax that we learned from `strace`, this would be: 30 | 31 | ```c 32 | write(file_descriptor, memory_address, number_of_characters_to_write) 33 | ``` 34 | 35 | For a more concrete example, if you wanted to write 10 characters from memory address `1337000` to standard output (file descriptor 1), this would be: 36 | 37 | ```c 38 | write(1, 1337000, 10); 39 | ``` 40 | 41 | Wow, that's simple! 42 | Now, how do we actually specify these parameters? 43 | 44 | 1. We'll pass the first parameter of a system call, as we reviewed above, in the `rdi` register. 45 | 2. We'll pass the second parameter via the `rsi` register. 46 | The agreed-upon convention in Linux is that `rsi` is used as the second parameter to system calls. 47 | 3. We'll pass the third parameter via the `rdx` register. 48 | This is the most confusing part of this entire module: `rdi` (the register holding the first parameter) has such a similar name to `rdx` that it's really easy to mix up and, unfortunately, the naming is this way for historic reasons and is here to stay. 49 | Oh well... 50 | It's just something we have to be careful about. 51 | Maybe a mnemonic like "`rdi` is the **i**nitial parameter while `rdx` is the **x**tra parameter"? 52 | Or just think of it as having to keep track of different friends with similar names, and you'll be fine. 53 | 54 | And, of course, the `write` syscall index into `rax` itself: `1`. 55 | Other than the `rdi` vs `rdx` confusion, this is really easy! 56 | 57 | Now, you know how to point a register at a memory address (from the [Memory](../memory) module!), and you know how to set the system call number, and how to set the rest of the registers. 58 | So, this should be cake! 59 | 60 | Similar to before, we wrote a single secret character value into memory at address `1337000`. 61 | Call `write` to that single character (for now! We'll do multiple-character writes later) value onto standard out, and we'll give you the flag! 62 | -------------------------------------------------------------------------------- /hello-hackers/write/check: -------------------------------------------------------------------------------- 1 | ../../check -------------------------------------------------------------------------------- /introspecting/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | As you write larger and larger programs, you (yes, even you!) might make mistakes when implementing certain functionality, introducing _bugs_ into your programs. 2 | When this happens, you'll need to have a reliable toolbox of resources to understand what is going wrong and fix it. 3 | 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! 4 | 5 | This module will introduce you to several ways to introspect, debug, and understand software. 6 | You'll cary this critical knowledge with you and use it throughout pwn.college, so harken well! 7 | -------------------------------------------------------------------------------- /introspecting/gdb-launch/.gdb: -------------------------------------------------------------------------------- 1 | set logging redirect on 2 | set logging file /dev/null 3 | set logging on 4 | set height 0 5 | starti 6 | stepi 7 | set logging off 8 | printf "\n\n" 9 | printf "You successfully started GDB!\n" 10 | printf "Here is the secret number: %d\n", $rsi 11 | printf "Submit that with /challenge/submit-number. Goodbye!\n" 12 | set logging on 13 | stop 14 | quit 15 | -------------------------------------------------------------------------------- /introspecting/gdb-launch/.init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NUM=$RANDOM 4 | echo $NUM > /challenge/.correct 5 | chmod 600 /challenge/.correct 6 | 7 | ASM=$(mktemp).o 8 | as -o $ASM <<< ".intel_syntax noprefix; mov rsi, $NUM; mov rdi, 0; mov rax, 60; syscall" 9 | ld -o /challenge/debug-me $ASM 2>/tmp/.init.log 10 | rm $ASM 11 | -------------------------------------------------------------------------------- /introspecting/gdb-launch/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Next, lets move on to GDB. 2 | GDB stands for the **G**NU **D**e**b**ugger, and it is typically used to hunt down and understand bugs. 3 | More specifically, a debugger is a tool that enables the close monitoring and introspection of another process. 4 | There are many famous debuggers, and in the Linux space, gdb is by far the most common. 5 | 6 | We'll learn gdb step by step through a series of challenges. 7 | In this one, we'll focus on simply launching it. 8 | That's done as so: 9 | 10 | ```console 11 | hacker@dojo:~$ gdb /path/to/binary/file 12 | ``` 13 | 14 | In this challenge, the binary that holds the secret is `/challenge/debug-me`. 15 | Once you load it in gdb, the rest will happen magically: we'll handle the analysis and give you the secret number. 16 | In later levels, you'll learn how to get that number on your own! 17 | 18 | Again, once you have the number, exchange it for the flag with `/challenge/submit-number`. 19 | -------------------------------------------------------------------------------- /introspecting/gdb-launch/bin/gdb: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | if [ "$#" -eq 0 ] 4 | then 5 | fold -s <<<"You called gdb without any arguments. Please provide it the filename!" 6 | exit 1 7 | fi 8 | 9 | if [ "$#" -ne 1 ] 10 | then 11 | fold -s <<<"You are trying to pass multiple options to gdb. Let's keep it simple for this level: just debug the /challenge/debug-me file!" 12 | exit 1 13 | fi 14 | 15 | if [ "$(realpath "$1")" != "/challenge/debug-me" ] 16 | then 17 | fold -s <<<"It looks like you are trying to debug a file other than /challenge/debug-me. Make sure to debug the right file!" 18 | exit 1 19 | fi 20 | 21 | exec /usr/bin/gdb -x /challenge/.gdb "$@" 22 | -------------------------------------------------------------------------------- /introspecting/gdb-launch/submit-number: -------------------------------------------------------------------------------- 1 | #!/usr/bin/exec-suid --real -- /bin/bash -p 2 | 3 | if [ "$UID" -ne 0 ] 4 | then 5 | fold -s <<< "WARNING: I appear to be running as a non-root user. This might be due to you invoking me using strace! Trace /challenge/trace-me, not $0! Please run me directly, without strace or gdb." 6 | fi 7 | 8 | read NUM /challenge/.correct 5 | chmod 600 /challenge/.correct 6 | 7 | ASM=$(mktemp).o 8 | as -o $ASM <<< ".intel_syntax noprefix; mov rsi, $NUM; mov rdi, 0; mov rax, 60; syscall" 9 | ld -o /challenge/debug-me $ASM 2>/tmp/.init.log 10 | rm $ASM 11 | -------------------------------------------------------------------------------- /introspecting/gdb-starti/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Debuggers, including gdb, observe the debugged program _as it runs_ to expose information about its runtime behavior. 2 | In the previous level, we automatically launched the program for you. 3 | Here, we will tone down the magic somewhat: you must start the execution of the program, and we'll do the rest (e.g., recover the secret value from it). 4 | 5 | When you launch gdb now, it will eventually bring up a command prompt, that looks like this: 6 | 7 | ```gdb 8 | (gdb) 9 | ``` 10 | 11 | You start a program with the `starti` command: 12 | 13 | ```gdb 14 | (gdb) starti 15 | ``` 16 | 17 | `starti` "**start**s the program at the very first **i**nstruction. 18 | Give it a try now, and we'll configure gdb to magically extract the secret value once the program is running. 19 | -------------------------------------------------------------------------------- /introspecting/gdb-starti/bin/gdb: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | if [ "$#" -eq 0 ] 4 | then 5 | fold -s <<<"You called gdb without any arguments. Please provide it the filename!" 6 | exit 1 7 | fi 8 | 9 | if [ "$#" -ne 1 ] 10 | then 11 | fold -s <<<"You are trying to pass multiple options to gdb. Let's keep it simple for this level: just debug the /challenge/debug-me file!" 12 | exit 1 13 | fi 14 | 15 | if [ "$(realpath "$1")" != "/challenge/debug-me" ] 16 | then 17 | fold -s <<<"It looks like you are trying to debug a file other than /challenge/debug-me. Make sure to debug the right file!" 18 | exit 1 19 | fi 20 | 21 | exec /usr/bin/gdb -x /challenge/.gdb "$@" 22 | -------------------------------------------------------------------------------- /introspecting/gdb-starti/submit-number: -------------------------------------------------------------------------------- 1 | #!/usr/bin/exec-suid --real -- /bin/bash -p 2 | 3 | if [ "$UID" -ne 0 ] 4 | then 5 | fold -s <<< "WARNING: I appear to be running as a non-root user. This might be due to you invoking me using strace! Trace /challenge/trace-me, not $0! Please run me directly, without strace or gdb." 6 | fi 7 | 8 | read NUM /tmp/.init.log 6 | rm $ASM 7 | -------------------------------------------------------------------------------- /introspecting/strace/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | The first one is pretty simple: the **s**yscall **trace**r, `strace`. 2 | 3 | Given a program to run, `strace` will use functionality of the Linux operating system to introspect and record every system call that the program invokes, and its result. 4 | For example, let's look at our program from the previous challenge: 5 | 6 | ```console 7 | hacker@dojo:~$ strace /tmp/your-program 8 | execve("/tmp/your-program", ["/tmp/your-program"], 0x7ffd48ae28b0 /* 53 vars */) = 0 9 | exit(42) = ? 10 | +++ exited with 42 +++ 11 | hacker@dojo:~$ 12 | ``` 13 | 14 | As you can see, `strace` reports what system calls are triggered, what parameters were passed to them, and what data they returned. 15 | The syntax used here for output is `system_call(parameter, parameter, parameter, ...)`. 16 | This syntax is borrowed from a programming language called C, but we don't have to worry about that yet. 17 | Just keep in mind how to read this specific syntax. 18 | 19 | In this example, `strace` reports two system calls: the second is the `exit` system call that your program uses to request its own termination, and you can see the parameter you passed to it (42). 20 | The first is an `execve` system call. 21 | We'll learn about this system call later, but it's somewhat of a yin to `exit`'s yang: it starts a new program (in this case, `your-program`). 22 | It's not actually invoked by `your-program` in this case: its detection by `strace` is a weird artifact of how `strace` works, that we'll investigate later. 23 | 24 | In the final line, you can see the result of `exit(42)`, which is that the program exits with an exit code of `42`! 25 | 26 | Now, the `exit` syscall is easy to introspect without using `strace` --- after all, part of the point of `exit` is to give you an exit code that you can access. 27 | But other system calls are less visible. 28 | For example, the `alarm` system call (syscall number 37!) will set a timer in the operating system, and when that many seconds pass, Linux will terminate the program. 29 | The point of `alarm` is to, e.g., kill the program when it's frozen, but in this case, we'll use `alarm` to practice our `strace` snooping! 30 | 31 | In this challenge, you must `strace` the `/challenge/trace-me` program to figure out what value it passes as a parameter to the `alarm` system call, then call `/challenge/submit-number` with the number you've retrieved as the argument. 32 | Good luck! 33 | -------------------------------------------------------------------------------- /introspecting/strace/submit-number: -------------------------------------------------------------------------------- 1 | #!/usr/bin/exec-suid --real -- /bin/bash -p 2 | 3 | if [ "$UID" -ne 0 ] 4 | then 5 | fold -s <<< "WARNING: I appear to be running as a non-root user. This might be due to you invoking me using strace! Trace /challenge/trace-me, not $0! Please run me directly, without strace." 6 | fi 7 | 8 | IFS="()" read -a TRACE < <(strace /challenge/trace-me 2>&1 | grep alarm) 9 | NUM=${TRACE[1]} 10 | 11 | if [ -z "$1" ] 12 | then 13 | fold -s <<< "You must run $0 with the alarm value you retrieved from stracing /challenge/trace-me as the first argument:" 14 | echo "" 15 | echo "Usage: $0 [ALARM_VALUE]" 16 | exit 1 17 | fi 18 | 19 | if [ "$NUM" != "$1" ] 20 | then 21 | fold -s <<< "Incorrect... Make sure to retrieve the value passed as a parameter to 'alarm' (e.g., 'alarm(VALUE)')!" 22 | exit 1 23 | fi 24 | 25 | fold -s <<< "CORRECT! Here is your flag:" 26 | cat /flag || fold -s <<< "... I failed to read the flag. This may be because you are running me via 'strace'." 27 | -------------------------------------------------------------------------------- /memory/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Wow, you are a budding x86 assembly programmer! 2 | You've set registers, triggered system calls, and wrote your first program that cleanly exits. 3 | Now, we have one more big concept for you: _memory_. 4 | 5 | You, as (presumably) a human being, have [Short Term Memory](https://en.wikipedia.org/wiki/Short-term_memory) and [Long Term Memory](https://en.wikipedia.org/wiki/Long-term_memory). 6 | 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. 7 | Societally, we also invented other, longer-term forms of storage: oral histories, journals, books, and wikipedia. 8 | 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. 9 | 10 | This multi-level heirarchy 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](https://www.simplypsychology.org/short-term-memory.html) 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](https://en.wikipedia.org/wiki/Memory_hierarchy) of modern computing. 11 | We've already learned about the "small but accessible" part of this in the previous module: those are registers, limited but FAST. 12 | 13 | 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. 14 | -------------------------------------------------------------------------------- /memory/mem-dereference-self/.py/chal.py: -------------------------------------------------------------------------------- 1 | ../../../secret-value-checker.py -------------------------------------------------------------------------------- /memory/mem-dereference-self/.py/chalconf.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | addr_chain = [ random.randrange(0x1337000, 0x1338000) ] 4 | must_set_regs = [ "rax", "rdi" ] 5 | final_reg_vals = { "rax": 60 } 6 | secret_addr_reg = 'rdi' 7 | -------------------------------------------------------------------------------- /memory/mem-dereference-self/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In the previous level, you dereferenced `rax` to read data into `rdi`. 2 | The interesting thing here is that our choice of `rax` was pretty arbitrary. 3 | We could have used any other pointer, even `rdi` itself! 4 | Nothing stops you from dereferencing a register to overwrite its own content with the dereferenced value! 5 | 6 | For example, here is us doing this exact thing with `rax`. 7 | I've annotated each line with comments: 8 | 9 | ```assembly 10 | mov [133700], 42 11 | mov rax, 133700 # after this, rax will be 133700 12 | mov rax, [rax] # after this, rax will be 42 13 | ``` 14 | 15 | Throughout this snippet, `rax` goes from being used as a pointer to being used to hold the data that's been read from memory. 16 | The CPU makes this all work! 17 | 18 | In this challenge, you'll explore this concept. 19 | Rather than initializing `rax`, as before, we've made `rdi` the pointer to the secret value! 20 | You'll need to dereference it to load that value into `rdi`, then `exit` with that value as the exit code. 21 | Good luck! 22 | -------------------------------------------------------------------------------- /memory/mem-dereference-self/check: -------------------------------------------------------------------------------- 1 | ../../check -------------------------------------------------------------------------------- /memory/mem-dereference/.py/chal.py: -------------------------------------------------------------------------------- 1 | ../../../secret-value-checker.py -------------------------------------------------------------------------------- /memory/mem-dereference/.py/chalconf.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | addr_chain = [ random.randrange(0x1337000, 0x1338000) ] 4 | must_set_regs = [ "rax", "rdi" ] 5 | final_reg_vals = { "rax": 60 } 6 | secret_addr_reg = 'rax' 7 | -------------------------------------------------------------------------------- /memory/mem-dereference/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Did you prefer to access memory at `133700` or at `123400`? 2 | Your answer might say something about your personality, but it's not super relevant from a technical perspective. 3 | In fact, in most cases, you don't deal with actual memory addresses when writing programs at all! 4 | 5 | How is this possible? 6 | Well, typically, memory addresses are stored in registers, and we use the values in the registers to point to data in memory! 7 | Let's start with this memory configuration: 8 | 9 | ```text 10 | Address │ Contents 11 | +────────────────────+ 12 | │ 133700 │ 42 │ 13 | +────────────────────+ 14 | ``` 15 | 16 | And consider this assembly snippet: 17 | 18 | ```assembly 19 | mov rax, 133700 20 | ``` 21 | 22 | Now, what you have is the following situation: 23 | 24 | ```text 25 | Address │ Contents 26 | +────────────────────+ 27 | ┌▸│ 133700 │ 42 │ 28 | │ +────────────────────+ 29 | │ 30 | └────────────────────────┐ 31 | │ 32 | Register │ Contents │ 33 | +────────────────────+ │ 34 | │ rax │ 133700 │─┘ 35 | +────────────────────+ 36 | ``` 37 | 38 | `rax` now holds a value that corresponds with the address of the data that want to load! 39 | Let's load it: 40 | 41 | ```assembly 42 | mov rdi, [rax] 43 | ``` 44 | 45 | Here, we are accessing memory, but instead of specifying a fixed address like `133700` for the memory read, we're using the value stored in `rax` as the memory address. 46 | By containing the memory address, `rax` is a _pointer_ that _points to_ the data we want to access! 47 | When we use `rax` in lieu of directly specifying the address that it stores to access the memory address that it references, we call this _dereferencing_ the pointer. 48 | In the above example, we _dereference_ `rax` to load the data it points to (the value `42` at address `133700`) into `rdi`. 49 | Neat! 50 | 51 | This also drives home another point: these registers are _general purpose_! 52 | Just because we've been using `rax` as the syscall index in our challenges so far doesn't mean that it can't have other uses as well. 53 | Here, it's used as a pointer to our secret data in memory. 54 | 55 | Similarly, the _data_ in the registers doesn't have an implicit purpose. 56 | If `rax` contains the value `133700` and we write `mov rdi, [rax]`, the CPU uses the value as a memory address to dereference. 57 | But if we write `mov rdi, rax` in the same conditions, the CPU just happily puts `133700` into `rdi`. 58 | To the CPU, data is data; it only becomes differentiated when it's used in different ways. 59 | 60 | In this challenge, we've initialized `rax` to contain the address of the secret data we've stored in memory. 61 | Dereference `rax` to the secret data into `rdi` and use it as the exit code of the program to get the flag! 62 | -------------------------------------------------------------------------------- /memory/mem-dereference/check: -------------------------------------------------------------------------------- 1 | ../../check -------------------------------------------------------------------------------- /memory/mem-double-deref/.py/chal.py: -------------------------------------------------------------------------------- 1 | ../../../secret-value-checker.py -------------------------------------------------------------------------------- /memory/mem-double-deref/.py/chalconf.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | addr_chain = [ random.randrange(0x1337000, 0x1338000), random.randrange(0x123400, 0x123500) ] 4 | secret_addr_reg = 'rax' 5 | must_set_regs = [ "rax", "rdi" ] 6 | final_reg_vals = { "rax": 60 } 7 | num_instructions = 4 8 | -------------------------------------------------------------------------------- /memory/mem-double-deref/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | In the last few levels, you have: 2 | 3 | - Used an address that we told you (in one level, `133700`, and in another, `123400`) to load a secret value from memory. 4 | - Used an address that we put into `rax` for you to load a secret value from memory. 5 | - Used an address that we told you (in the last level, `567800`) to load _the address_ of a secret value from memory into a register, then used that register as a pointer to retrieve the secret value from memory! 6 | 7 | Let's put those last two together. 8 | In this challenge, we stored our `SECRET_VALUE` in memory at the address `SECRET_LOCATION_1`, then stored `SECRET_LOCATION_1` in memory at the address `SECRET_LOCATION_2`. 9 | Then, we put `SECRET_LOCATION_2` into `rax`! 10 | The result looks something like this, using `123400` for `SECRET_LOCATION_1` and `133700` for `SECRET_LOCATION_2` (not, in the real challenge, these values will be different and hidden from you!): 11 | 12 | ```text 13 | Address │ Contents 14 | +────────────────────+ 15 | ┌──▸│ 133700 │ 123400 │─┐ 16 | │ +────────────────────+ │ 17 | │ ┌▸│ 123400 │ 42 │ │ 18 | │ │ +────────────────────+ │ 19 | │ └────────────────────────┘ 20 | └──────────────────────────┐ 21 | │ 22 | Register │ Contents │ 23 | +────────────────────+ │ 24 | │ rax │ 133700 │─┘ 25 | +────────────────────+ 26 | ``` 27 | 28 | Here, you will need to perform two memory reads: one dereferencing `rax` to read `SECRET_LOCATION_1` from the location that `rax` is pointing to (which is `SECRET_LOCATION_2`), and the second one dereferencing whatever register now holds `SECRET_LOCATION_1` to read `SECRET_VALUE` into `rdi`, so you can use it as the exit code! 29 | 30 | That sounds like a lot, but you've done basically all of this already. 31 | Go put it together! 32 | -------------------------------------------------------------------------------- /memory/mem-double-deref/check: -------------------------------------------------------------------------------- 1 | ../../check -------------------------------------------------------------------------------- /memory/mem-load-2/.py/chal.py: -------------------------------------------------------------------------------- 1 | ../../../secret-value-checker.py -------------------------------------------------------------------------------- /memory/mem-load-2/.py/chalconf.py: -------------------------------------------------------------------------------- 1 | addr_chain = [ 123400 ] 2 | must_set_regs = [ "rax", "rdi" ] 3 | final_reg_vals = { "rax": 60 } 4 | -------------------------------------------------------------------------------- /memory/mem-load-2/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | You look like you need just a tiny bit more practice. 2 | In this level, we put the secret value at `123400` instead of `133700`, as so: 3 | 4 | ```text 5 | Address │ Contents 6 | +────────────────────+ 7 | │ 123400 │ ??? │ 8 | +────────────────────+ 9 | ``` 10 | 11 | Go load it into `rdi` and `exit` with that as the exit code! 12 | -------------------------------------------------------------------------------- /memory/mem-load-2/check: -------------------------------------------------------------------------------- 1 | ../../check -------------------------------------------------------------------------------- /memory/mem-load/.py/chal.py: -------------------------------------------------------------------------------- 1 | ../../../secret-value-checker.py -------------------------------------------------------------------------------- /memory/mem-load/.py/chalconf.py: -------------------------------------------------------------------------------- 1 | addr_chain = [ 133700 ] 2 | must_set_regs = [ "rax", "rdi" ] 3 | final_reg_vals = { "rax": 60 } 4 | -------------------------------------------------------------------------------- /memory/mem-load/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | As seen by your program, computer memory is a huge place where data is housed. 2 | Like houses on a street, every part of memory has a numeric _address_, and like houses on a street, these numbers are (mostly) sequential. 3 | Modern computers have enormous amounts of memory, and the view of memory of a typical modern program actually has large gaps (think: a portion of the street that hasn't had houses built on it, and so those addresses are skipped). 4 | But these are all details: the point is, computers store data, mostly sequentially, in memory. 5 | 6 | In this level, we will practice accessing data stored in memory. 7 | How might we do this? 8 | Recall that to move a value into a register, we did something like: 9 | 10 | ```assembly 11 | mov rdi, 31337 12 | ``` 13 | 14 | After this, the value of `rdi` is `31337`. 15 | Cool. 16 | Well, we can use the same instruction to access memory! 17 | There is another format of the command that, instead, uses the second parameter as an address to access memory! 18 | Consider that our memory looks like this: 19 | 20 | ```text 21 | Address │ Contents 22 | +────────────────────+ 23 | │ 31337 │ 42 │ 24 | +────────────────────+ 25 | ``` 26 | 27 | To access the memory contents at memory address 31337, you can do: 28 | 29 | ```assembly 30 | mov rdi, [31337] 31 | ``` 32 | 33 | When the CPU executes this instruction, it of course understands that `31337` is an _address_, not a raw value. 34 | If you think of the instruction as a person telling the CPU what to do, and we stick with our "houses on a street" analogy, then instead of just handing the CPU data, the instruction/person _points at a house on the street_. 35 | The CPU will then go to that address, ring its doorbell, open its front door, drag the data that's in there out, and put it into `rdi`. 36 | Thus, the `31337` in this context is the _memory address_ and serves to _point to_ the data stored at that memory address. 37 | After this instruction executes, the value stored in `rdi` will be `42`! 38 | 39 | Let's put this into practice! 40 | I've stored a secret number at memory address `133700`, as so: 41 | 42 | ```text 43 | Address │ Contents 44 | +────────────────────+ 45 | │ 133700 │ ??? │ 46 | +────────────────────+ 47 | ``` 48 | 49 | You must retrieve this secret number and use it as the exit code for your program. 50 | To do this, you must read it into `rdi`, whose value, if you recall, is the first parameter to `exit` and is used as the exit code. 51 | Good luck! 52 | -------------------------------------------------------------------------------- /memory/mem-load/check: -------------------------------------------------------------------------------- 1 | ../../check -------------------------------------------------------------------------------- /memory/mem-offsets/.py/chal.py: -------------------------------------------------------------------------------- 1 | ../../../secret-value-checker.py -------------------------------------------------------------------------------- /memory/mem-offsets/.py/chalconf.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | addr_chain = [ random.randrange(0x1337000, 0x1338000), ] 4 | secret_addr_reg = 'rdi' 5 | value_offset = 8 6 | num_instructions = 3 7 | must_set_regs = [ "rax", "rdi" ] 8 | final_reg_vals = { "rax": 60 } 9 | -------------------------------------------------------------------------------- /memory/mem-offsets/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | So now you can dereference pointers in memory like a pro! 2 | But pointers don't always point directly at the data you need. 3 | Sometimes, for example, a pointer might point to a collection of data (say, an entire book), and you'll need to reference partway into this collection for the specific data you need. 4 | 5 | For example, if your pointer (say, `rdi`) points to a sequence of numbers in memory, as so: 6 | 7 | ```text 8 | Address │ Contents 9 | +────────────────────+ 10 | ┌▸│ 133700 │ 50 │ 11 | │ │ 133701 │ 42 │ 12 | │ │ 133702 │ 99 │ 13 | │ │ 133703 │ 14 │ 14 | │ +────────────────────+ 15 | │ 16 | └────────────────────────┐ 17 | │ 18 | Register │ Contents │ 19 | +────────────────────+ │ 20 | │ rdi │ 133700 │─┘ 21 | +────────────────────+ 22 | ``` 23 | 24 | 25 | If you want the second number of that sequence, you could do: 26 | 27 | ```assembly 28 | mov rax, [rdi+1] 29 | ``` 30 | 31 | Wow, super simple! 32 | In memory terms, we call these number slots _bytes_: each memory address represents a specific byte of memory. 33 | The above example is accessing memory 1 byte after the memory address pointed to by `rdi`. 34 | In memory terms, we call this 1 byte difference an _offset_, so in this example, there is an offset of 1 from the address pointed to by `rdi`. 35 | 36 | Let's practice this concept. 37 | As before, we will initialize `rdi` to point at the secret value, but not _directly_ at it. 38 | This time, the secret value will have an offset of 8 bytes from where `rdi` points, something analogous to this: 39 | 40 | ```text 41 | Address │ Contents 42 | +────────────────────+ 43 | ┌▸│ 31337 │ 0 │ 44 | │ │ 31337+1 │ 0 │ 45 | │ │ 31337+2 │ 0 │ 46 | │ │ 31337+3 │ 0 │ 47 | │ │ 31337+4 │ 0 │ 48 | │ │ 31337+5 │ 0 │ 49 | │ │ 31337+6 │ 0 │ 50 | │ │ 31337+7 │ 0 │ 51 | │ │ 31337+8 │ ??? │ 52 | │ +────────────────────+ 53 | │ 54 | └────────────────────────┐ 55 | │ 56 | Register │ Contents │ 57 | +────────────────────+ │ 58 | │ rdi │ 31337 │─┘ 59 | +────────────────────+ 60 | ``` 61 | 62 | Of course, the actual memory address is not `31337`. 63 | We'll choose it randomly, and store it in `rdi`. 64 | Go dereference `rdi` with offset `8` and get the flag! 65 | -------------------------------------------------------------------------------- /memory/mem-offsets/check: -------------------------------------------------------------------------------- 1 | ../../check -------------------------------------------------------------------------------- /memory/mem-stored-addr/.py/chal.py: -------------------------------------------------------------------------------- 1 | ../../../secret-value-checker.py -------------------------------------------------------------------------------- /memory/mem-stored-addr/.py/chalconf.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | addr_chain = [ 567800, random.randrange(0x1337000, 0x1338000) ] 4 | num_instructions = 4 5 | must_set_regs = [ "rax", "rdi" ] 6 | final_reg_vals = { "rax": 60 } 7 | -------------------------------------------------------------------------------- /memory/mem-stored-addr/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Pointers can get even more interesting! 2 | Imagine that your friend lives in a different house on your street. 3 | Rather than remembering their address, you might write it down, and store the paper with their house address in _your_ house. 4 | Then, to get data from your friend, you'd need to point the CPU at your house, have it go in there and find the friend's address, and use that address as a pointer to their house. 5 | 6 | Similarly, since memory addresses are really just values, they can be stored in memory, and retrieved later! 7 | Let's explore a scenario where we store the value `133700` at the address `123400`, and store the value `42` at the address `133700`. 8 | Consider the following instructions: 9 | 10 | ```assembly 11 | mov rdi, 123400 # after this, rdi becomes 123400 12 | mov rdi, [rdi] # after this, rdi becomes the value stored at 123400 (which is 133700) 13 | mov rax, [rdi] # here we dereference rdi, reading 42 into rax! 14 | ``` 15 | 16 | Wow! 17 | This storing of addresses is _extremely_ common in programs. 18 | Addresses and data are stored, loaded, moved around, and, sometimes, mixed up with each other! 19 | When that happens, security issues can arise, and you'll romp through many such issues during your pwn.college journey. 20 | 21 | For now, let's practice dereferencing an address stored in memory. 22 | I'll store a secret value at a secret address, then store that secret address at the address `567800`. 23 | You must read the address, dereference it, get the secret value, and then `exit` with it as the exit code. 24 | You got this! 25 | -------------------------------------------------------------------------------- /memory/mem-stored-addr/check: -------------------------------------------------------------------------------- 1 | ../../check -------------------------------------------------------------------------------- /memory/mem-triple-deref/.py/chal.py: -------------------------------------------------------------------------------- 1 | ../../../secret-value-checker.py -------------------------------------------------------------------------------- /memory/mem-triple-deref/.py/chalconf.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | addr_chain = [ 4 | random.randrange(0x1337000, 0x1338000), 5 | random.randrange(0x123400, 0x123500), 6 | random.randrange(0x100000, 0x101000) 7 | ] 8 | secret_addr_reg = 'rdi' 9 | num_instructions = 5 10 | must_set_regs = [ "rax", "rdi" ] 11 | final_reg_vals = { "rax": 60 } 12 | -------------------------------------------------------------------------------- /memory/mem-triple-deref/DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | Okay, let's stretch that to one more depth! 2 | We've added an additional level of indirection in this challenge, so now you'll need *three* dereferences to find the secret value. 3 | Something like this: 4 | 5 | ```text 6 | Address │ Contents 7 | +────────────────────+ 8 | ┌────▸│ 133700 │ 123400 │───┐ 9 | │ +────────────────────+ │ 10 | │ ┌──▸│ 123400 │ 100000 │─┐ │ 11 | │ │ +────────────────────+ │ │ 12 | │ │ ┌▸│ 100000 │ 42 │ │ │ 13 | │ │ │ +────────────────────+ │ │ 14 | │ │ └────────────────────────┘ │ 15 | │ └────────────────────────────┘ 16 | └──────────────────────────────┐ 17 | │ 18 | Register │ Contents │ 19 | +────────────────────+ │ 20 | │ rdi │ 133700 │───┘ 21 | +────────────────────+ 22 | ``` 23 | 24 | As you can see, we'll place the first address that you must dereference into rdi. 25 | Go get the value! 26 | -------------------------------------------------------------------------------- /memory/mem-triple-deref/check: -------------------------------------------------------------------------------- 1 | ../../check -------------------------------------------------------------------------------- /memory/module.yml: -------------------------------------------------------------------------------- 1 | name: Computer Memory 2 | resources: 3 | - name: "(review) Computer Architecture" 4 | type: lecture 5 | video: o_kSgUPJk4c 6 | playlist: PL-ymxv0nOtqox6nF4HXtXHnTQFGkRQU_2 7 | slides: 1sVyPL92gbzg_it9aIeC-CjXtF2tpvAmZTKjWc-SlU0c 8 | - name: "Memory" 9 | type: lecture 10 | video: HahXfnOsSUU 11 | playlist: PL-ymxv0nOtqox6nF4HXtXHnTQFGkRQU_2 12 | slides: 1lbPbd-jLj7VS7M-ntGhpv4_8WqSfqzLBNWG68_BIRL0 13 | challenges: 14 | - id: mem-load 15 | name: "Loading From Memory" 16 | - id: mem-load-2 17 | name: "More Loading Practice" 18 | - id: mem-dereference 19 | name: "Dereferencing Pointers" 20 | - id: mem-dereference-self 21 | name: "Dereferencing Yourself" 22 | - id: mem-offsets 23 | name: "Dereferencing with Offsets" 24 | - id: mem-stored-addr 25 | name: "Stored Addresses" 26 | - id: mem-double-deref 27 | name: "Double Dereference" 28 | - id: mem-triple-deref 29 | name: "Triple Dereference" 30 | -------------------------------------------------------------------------------- /secret-value-checker.py: -------------------------------------------------------------------------------- 1 | import __main__ as checker 2 | import random 3 | import struct 4 | import time 5 | import re 6 | 7 | import chalconf #pylint:disable=import-error 8 | addr_chain = getattr(chalconf, 'addr_chain', None) 9 | secret_addr_reg = getattr(chalconf, 'secret_addr_reg', None) 10 | secret_value_reg = getattr(chalconf, 'secret_value_reg', None) 11 | value_offset = getattr(chalconf, 'value_offset', 0) 12 | num_instructions = getattr(chalconf, 'num_instructions', 3) 13 | final_reg_vals = getattr(chalconf, 'final_reg_vals', {}) 14 | must_set_regs = getattr(chalconf, 'must_set_regs', []) 15 | must_get_regs = getattr(chalconf, 'must_get_regs', []) 16 | secret_checks = getattr(chalconf, 'secret_checks', ['exit']) 17 | secret_value = getattr(chalconf, 'secret_value', random.randint(15, 255)) 18 | secret_value_desc = getattr(chalconf, 'secret_value_desc', f"value {secret_value}") 19 | clean_exit = getattr(chalconf, 'clean_exit', True) 20 | skip_deref_checks = getattr(chalconf, 'skip_deref_checks', False) 21 | exit_code = getattr(chalconf, 'exit_code', secret_value) 22 | stdin = getattr(chalconf, 'stdin', None) 23 | 24 | check_runtime_success = getattr(chalconf, "success_message", "Neat! Your program passed the tests! Great job!") 25 | 26 | #pylint:disable=global-statement 27 | 28 | allow_asm = True 29 | give_flag = True 30 | returncode = None 31 | 32 | assembly_prefix = "" 33 | mapped_pages = set() 34 | for n,_addr in enumerate(addr_chain): 35 | _page = _addr - _addr%0x1000 36 | assembly_prefix += "mov r9, 0x0; mov r8, 0xffffffff; mov r10, 0x32; mov rdx, 0x3; mov rsi, 0x1000;" 37 | assembly_prefix += f"mov rdi, {_page}; mov rax, 9; syscall;\n" 38 | try: 39 | assembly_prefix += f"mov qword ptr [{_addr}], {addr_chain[n+1]}\n" 40 | except IndexError: 41 | if type(secret_value) is int: 42 | assembly_prefix += f"mov qword ptr [{_addr+value_offset}], {secret_value}\n" 43 | elif type(secret_value) is bytes: 44 | for i,sb in enumerate(secret_value): 45 | assembly_prefix += f"mov qword ptr [{_addr+value_offset+i}], {sb}\n" 46 | else: 47 | raise AssertionError("unexpected type for secret_value. Contact professors.") #pylint:disable=raise-missing-from 48 | 49 | if secret_addr_reg: 50 | assembly_prefix += f"mov {secret_addr_reg}, {addr_chain[0]}\n" 51 | 52 | if secret_value_reg: 53 | assembly_prefix += f"mov {secret_value_reg}, {secret_value}\n" 54 | 55 | if stdin is not None: 56 | check_runtime_prologue = f""" 57 | Let's check what your output is! It should be our secret value, {stdin}, 58 | as passed into the stdin of your program! 59 | """.strip() 60 | elif secret_value_reg is not None: 61 | check_runtime_prologue = f""" 62 | Let's check what your exit code is! It should be our secret 63 | value stored in register {secret_value_reg} ({secret_value_desc}) to succeed! 64 | """.strip() 65 | elif secret_addr_reg and len(addr_chain) == 1: 66 | check_runtime_prologue = f""" 67 | Let's check what your exit code is! It should be our secret 68 | value pointed to by {secret_addr_reg} ({secret_value_desc}) to succeed! 69 | """.strip() 70 | elif secret_addr_reg: 71 | check_runtime_prologue = f""" 72 | Let's check what your exit code is! It should be our secret 73 | value pointed to by a chain of pointers starting at {secret_addr_reg}! 74 | """.strip() 75 | elif len(addr_chain) == 1: 76 | check_runtime_prologue = f""" 77 | Let's check what your exit code is! It should be our secret value 78 | stored at memory address {addr_chain[-1]} ({secret_value_desc}) to succeed! 79 | """.strip() 80 | else: 81 | check_runtime_prologue = f""" 82 | Let's check what your exit code is! It should be our secret 83 | value pointed to by a chain of pointers starting at address {addr_chain[-1]}! 84 | """.strip() 85 | 86 | def check_disassembly(disas): 87 | mov_operands = [ d.op_str.split(", ") for d in disas if d.mnemonic == 'mov' ] 88 | 89 | set_regs, get_args = zip(*mov_operands) 90 | assert set(set_regs) >= set(must_set_regs), ( 91 | "You must set each of the following registers (using the mov instruction):\n "+", ".join(must_set_regs) 92 | ) 93 | 94 | assert set(get_args) >= set(must_get_regs), ( 95 | "You must get values from each of the following registers (using the\nmov instruction): "+", ".join(must_get_regs) 96 | ) 97 | 98 | for r,vs in final_reg_vals.items(): 99 | v = vs 100 | s = None 101 | if type(vs) in (tuple,list): 102 | v,s = vs 103 | vv = hex(v) if v > 1 else str(v) 104 | assert [r,vv] in mov_operands, ( 105 | f"You must properly set register {r} to the value {v}" + 106 | (f" ({s})!" if s else "!") 107 | ) 108 | last_mov_rax = max(i for i,m in enumerate(mov_operands) if m[0] == r) 109 | last_val_set = len(mov_operands) - mov_operands[::-1].index([r, vv]) - 1 110 | assert last_mov_rax <= last_val_set, ( 111 | f"You are overwriting the required value ({v}) that you need to put\n" 112 | "into 'rax'. You can use 'rax' for other stuff, but make sure to move\n" 113 | f"{v} into it afterwards!" 114 | ) 115 | 116 | assert (not clean_exit) or mov_operands.index(['rax',"0x3c"]) == max( 117 | i for i,m in enumerate(mov_operands) if m[0] == 'rax' 118 | ), ( 119 | "Uh oh! It looks like you're overwriting exit's syscall index (in rax) after\n" 120 | "setting it. If you overwrite it, then your eventual syscall instruction will\n" 121 | "trigger the wrong system call!" 122 | ) 123 | 124 | if secret_addr_reg: 125 | try: 126 | idx_deref = max(i for i,m in enumerate(mov_operands) if secret_addr_reg in m[1] and "[" in m[1]) 127 | except ValueError as e: 128 | raise AssertionError( 129 | "It looks like you never dereference the register with the secret\n" 130 | f"address ({secret_addr_reg})! You need to dereference it to read the\n" 131 | "required exit code!" 132 | ) from e 133 | 134 | try: 135 | earliest_nonderef_overwrite = min(i for i,m in enumerate(mov_operands) if m[0] == secret_addr_reg and "[" not in m[1]) 136 | assert earliest_nonderef_overwrite >= idx_deref, ( 137 | f"Uh oh! It looks like you're overwriting the address in {secret_addr_reg} before\n" 138 | "dereferncing it. Once you overwrite this value, you will lose the secret\n" 139 | "address that we initialized it with! Dereference it first before overwriting\n" 140 | "it.\n" 141 | ) 142 | except ValueError: 143 | pass 144 | 145 | if not skip_deref_checks: 146 | all_derefs = [ m for m in mov_operands if "[" in m[1] ] 147 | for r,s in mov_operands: 148 | if r == 'rax' and s == hex(60): 149 | continue 150 | 151 | if len(all_derefs) == len(addr_chain): 152 | continue 153 | 154 | if '[' not in s and s.startswith("0x"): 155 | raise AssertionError( 156 | f"In the line 'mov {r}, {int(s,16)}', you are moving the _value_ {int(s,16)} into\n" 157 | f"{r}, rather than reading memory at the address {int(s,16)}. To read memory,\n" 158 | f"you must enclose the value in [], such as: [{int(s,16)}]." 159 | ) 160 | if '[' not in s and re.match(r"[a-zA-Z]*", s): 161 | raise AssertionError( 162 | f"In the line 'mov {r}, {s}', you are moving the _value_ in register\n" 163 | f"{s} into {r}, rather than reading memory at the address pointed to by\n" 164 | "{s}. To read memory, you must enclose the register in [], such as: [{s}]." 165 | ) 166 | 167 | #first_str = f"dereference {secret_addr_reg}" if secret_addr_reg else f"load memory from {addr_chain[0]}" 168 | #assert len(all_derefs) == len(addr_chain), ( 169 | # f"To retrieve the secret value in this level, you must do {len(all_derefs)}\n" 170 | # "memory reads! First, " + first_str + "and then dereference the\n" 171 | # f"loaded value {len(addr_chain)-1} times!\n" 172 | #) if len(addr_chain) > 1 else ( 173 | # f"To retrieve the secret value in this level, you must\n"+first_str+"!" 174 | #) 175 | 176 | operation = disas[-1].mnemonic 177 | assert operation == "syscall", ( 178 | "Your last instruction should be the 'syscall' instruction to invoke\n" 179 | f"the exit system call, but you used the '{operation}' instruction!" 180 | ) 181 | 182 | return True 183 | 184 | def check_runtime(filename): 185 | global returncode 186 | #pylint:disable=c-extension-no-member 187 | 188 | try: 189 | print("") 190 | returncode = checker.dramatic_command(filename, stdin=stdin, actual_command = f"bash -c 'exec {filename} 2> >(tee /tmp/stderr 2>&1) > >(tee /tmp/stdout)'") 191 | time.sleep(0.1) 192 | for c in secret_checks: 193 | if c == "cat": 194 | actual_bytes = open(f"/tmp/stdout", "rb").read() #pylint:disable=consider-using-with,unspecified-encoding 195 | assert actual_bytes == stdin, ( 196 | f"The value you wrote to stdout ({actual_bytes}) does not match the inputted value ({stdin})!" 197 | ) 198 | if c in ["stdout", "stderr"]: 199 | actual_bytes = open(f"/tmp/{c}", "rb").read() #pylint:disable=consider-using-with,unspecified-encoding 200 | if type(secret_value) is int: 201 | expected_bytes = struct.pack("