Komplexpraktikum Systemnahe Programmierung
Dozent | Prof. Dr.-Ing. Horst Schirmeier, Dr. Michael Roitzsch |
Modul | INF-B-510, INF-B-520, INF-B-530, INF-B-540, INF-04-KP, INF-DSE-20-E-ADSE, INF-DSE-20-M-INT, INF-E-4, INF-MA-PR, INF-VERT4, IST-05-FG-AVS, MINF-04-KP-FG2 (Einbringen in INF-BAS4 u.U. möglich) |
Umfang und Art |
4 SWS Praktikum (Englisch) |
Turnus | Blockveranstaltung vor Beginn des Wintersemesters |
Zeit und Ort | 23.09.–01.10.2024, 09:30–16:00, APB/E040 |
Anmeldung | Anmeldung durch Einschreiben in die Mailingliste notwendig (begrenzte Teilnehmerzahl) |
Mailingliste | Bitte mit einer TU-Dresden-Adresse einschreiben |
Feedback | per E-Mail oder anonymen Briefkasten |
Das Praktikum besteht aus zwei Teilen: Der Workshop findet vor Beginn des Wintersemesters statt (23.09.–01.10.2024). Es gibt eine einstündige Mittagspause und weitere Pausen nach Bedarf und Zeit. Die Anmeldung für das Praktikum läuft per Einschreibung in die Mailingliste. Die praktischen Aufgaben werden im Anschluss individuell bearbeitet und die Lösungen per Mail beim jeweiligen Ansprechpartner eingereicht.
Workshop
Der Workshop vermittelt fortgeschrittene Fertigkeiten in der systemnahen Programmierung. Er dient deshalb auch der Vorbereitung auf Programmier-Aufgaben in den praktischen Veranstaltungen der Professur für Betriebssysteme wie Betriebssystembau, Mikrokernbasierte Betriebssysteme, Mikrokernkonstruktion oder dem Komplexpraktikum mikrokernbasierte Betriebssysteme.
Es handelt sich hier nicht um einen Programmierkurs für Anfänger. Wir wollen euch fortgeschrittene Fertigkeiten vermitteln und setzen daher voraus:
- Ihr habt Programmier-Erfahrungen, zum Beispiel in Java oder Python.
- Ihr wisst, wie man in einer Linux- bzw. Unix-Kommandozeile arbeitet.
- Ihr habt schon mal eine Man-Page gesehen.
- Ihr könnt mit einem Texteditor arbeiten.
- Ihr könnt Google bedienen.
Wenn ihr alle diese Punkte bejahen könnt, ist dieser Kurs für euch. Der Workshop vertieft eure Fähigkeiten der C++ Programmierung Gebieten wie Compiler und Werkzeuge, Bauen von Bibliotheken, Debugging, Assembler, Multithreading, POSIX und Systemcalls.
Folien (nur auf Englisch)
Werden im Laufe des Workshops veröffentlicht.
- Introduction
- Tools
- Rust
- Debugging & Assembler
- Threads
- No Libc
- C++ (source files)
Praktischer Teil
Die folgenden vier Aufgaben sind eigenständig zu bearbeiten. Jeder Aufgabe ist ein verantwortlicher Betreuer zugeordnet. Die Betreuer stehen zur Beantwortung von Fragen zur Aufgabenstellung bereit.
Für die Implementierungen können die Sprachen C, C++ sowie Rust, inklusive der zugehörigen Standardbibliotheken, genutzt werden. Die Einbeziehung zusätzlicher Programme oder Bibliotheken bzw. Crates ist nur nach Rücksprache mit dem Betreuer zulässig. Ausnahmen sind in der jeweiligen Aufgabenstellung angegeben.
Eine vollständige Lösung muss alle in der Aufgabenstellung aufgeführten Funktionalitäten implementieren. Dabei ist die vorgegebene Syntax/Grammatik (für Kommandozeilenoptionen und Shell-Sprache) einzuhalten.
Achten Sie auf eine gute Code-Qualität:
- klare Struktur (Funktionen bzw. Klassen)
- defensives und offensives Programmieren
- sinnvolle, Englisch-sprachige Kommentare
- geeignete Benennung von Variablen und Funktionen
- möglichst begrenzter Sichtbarkeitsbereich („scope“) für Variablen
- Konstanten statt „magischer Zahlen“
- „Don't repeat yourself“ (DRY)
- Vermeidung von Compiler-Warnungen
Abgabe
Die Lösung ist beim jeweiligen Aufgabenbetreuer per E-Mail einzureichen. Der E-Mail sollte dabei ein komprimiertes tar-Archiv mit den Quelltext-Dateien sowie dem zugehörigen Makefile bzw. Cargo-Manifest anhängen. Bitte fügen Sie keine Binärdateien bei. Nutzen Sie für Erklärungen oder Antworten zu etwaigen Fragen direkt den Nachrichtentext der E-Mail. Dabei steht es Ihnen frei Deutsch oder Englisch zu verwenden.
Es gibt keine feste Abgabefrist; solange das Komplexpraktikum angeboten wird, können Lösungen für die praktischen Aufgaben eingereicht werden. Wir empfehlen jedoch die Bearbeitung bis zum Ende des Wintersemesters, das auf die Blockveranstaltung folgt. Beachten Sie, dass es aufgrund der zeitlich flexiblen Möglichkeit zur Einreichung zu Verzögerungen bei der Rückmeldung kommen kann, wenn der Betreuer gerade anderweitig gebunden ist. Sollte eine zügige Bearbeitung unbedingt nötig sein, weisen Sie bitte direkt bei der Abgabe deutlich darauf hin.
Bei Mängeln an der eingesandten Lösung sind (auch wiederholte) Nachbesserungen möglich, können aber Abwertungen zur Folge haben. Testen Sie ihre Lösung daher bitte vor dem Einsenden möglichst ausgiebig.
Tutor: Robin Thunig
This exercise is about using GNU/Make to automate compiling and linking C files into an application.
-
Write a C application sincos that prints a table of sin/cos values for angles between 0 and 360 degrees. The application should receive exactly one argument which is the number of steps to use for printing.
A sample session with your program might look like this:
$> ./sincos 10
0.000 0.000 1.000
36.000 0.588 0.809
72.000 0.951 0.309
108.000 0.951 -0.309
144.000 0.588 -0.809
180.000 -0.000 -1.000
216.000 -0.588 -0.809
252.000 -0.951 -0.309
288.000 -0.951 0.309
324.000 -0.588 0.809
360.000 0.000 1.000
Your application shall consist of two C files:-
main.c: This file contains the main method of your application, checks the correctness of the command line parameter and then calls a function provided by the second file in order to perform real actions.
-
sincos.c: This file provides a function that iterates over the range between 0 and 360 degrees using the steps provided by the user. For each iteration it prints the current degree value as well as its sine and cosine.
-
The interface provided by sincos.c shall be defined in a header file that is included by the main application.
-
Write a Makefile to automate your application's build. The Makefile shall consist of two rules:
-
A rule to create the program from your two object files and
-
A clean rule to remove all generated files.
-
Dependency files should be created and included automatically by the Makefile.
Furthermore, the program shall be compiled with the compiler flags -Wall and -Wextra and compilation should not emit any warnings.
- Compile and link the application using GNU/Make.
- Test the correctness of your dependencies and file generation. A reference environment is given with the following VirtualBox image:
Tutor: Carsten Weinhold
The goal of this exercise is to familiarize yourself with the basic operations and data structures of the UNIX file system.
The UNIX utility find searches the file system for files that meet certain requirements. Your task is to implement a small find utility that accepts the following command-line syntax:
find <directory name> [-name <pattern>] [-type <f | d>] [-follow] [-xdev]
Your program should print a filename if the corresponding file or directory matches all the constraints specified on the command line (if any). Using the -type switch, the user can specify that either regular files or directories will match, but not both. The option -follow shall tell your program to follow symbolic links. The option -xdev specifies that 'find' shall not search directories whose contents are located in another file system (e.g., another disk partition). The option -name shall accept wildcards as explained in the following manpage excerpt:
A string is a wildcard pattern if it contains one of the characters `?', `*' or
`['. Globbing is the operation that expands a wildcard pattern into the list of
pathnames matching the pattern. Matching is defined by:
A `?' (not between brackets) matches any single character.
A `*' (not between brackets) matches any string, including the empty string.
...
(see also glob(7))
If you are in doubt about what the exact behavior of your tool shall be for the various options, see what GNU find outputs in your test environment (comes with your Linux distribution). Reading the GNU find manpage is also a good idea. You might find the functions readdir(), stat(), and fnmatch() or glob() to be useful when you develop the functionality to traverse directory trees. Note that it is not allowed use existing directory tree walkers such nftw() or ftw(), instead you are expected to build this functionality yourself.
Please make sure that you test your program thoroughly and any debugging output is disabled before you submit it. We look at the source code, but we primarily use automated tests to verify the correctness of your find tool. Therefore, your program should print all matching pathnames like GNU find does. Don't use output formats such as "found file: /path/to/file" or "found match: type=d, /path/to/dir"!
Important: To get good test coverage, you should download this tarball, which contains a shell script for creating a directory hierarchy for testing your find program. It is particularly useful for making sure that your implementation of the '-follow' option behaves in sensible way, like for example GNU find does! Also, see the included README for details on how to test the '-xdev' option.
Resources
Tutor: Till Smejkal
In this exercise you are going to write a simple shell that is able to execute command lines, interpret pipes and use input/and output redirection. You may use Flex and Bison to specify a parser for your command lines.
First, implement a shell with the following required features:
- Execute command lines with the following syntax:
command <args> [< input_redirect] (| command <args>)* [> output_redirect] [&]
As a starting point you may consider the files provided in the resources section below. - Support an arbitrary amount of arguments to programs as well as an arbitrary length for the pipe chain.
- Detect erroneous input and give useful feedback to the user.
Second, add convenience features to your shell:
- The readline library provides line editing features, such as deleting a word. libreadline comes along with libhistory which provides means to store strings in a list and navigate through this list. Use both libraries in order to implement easy-to-use line editing as well as access to previously entered commands using the arrow keys.
- Add support for the following shell builtins to your shell
- cd to change to another working directory
- pwd to print the current working directory
- kill <signo> <pid> to send a signal to a process
- alias and unalias to manage aliases (which are replaced by by their meaning during execution of commands).
Resources
- Flex and Bison are successors to Lex and Yacc, so all relevant information for Lex and Yacc is also useful for these tools.
- Manual for the Flex scanner generator
- Manual for the Bison parser generator
- Template Flex input file
- Template Bison input file
- The GNU readline library
- When implementing builtins you might have a look at the BASH reference manual.
- For executing processes, have a look at the man pages for fork(), execve(), and wait().
- For input and output redirection, take a look at open(), close(), and dup2().
- Pipes are created by pipe(), signal handling can be set up using sigaction().
Tutor: Jan Bierbaum
In this exercise you will apply widely used debugging tools, namely, gdb and objdump to reverse engineer and exploit a very simple and vulnerable login programme.
In Unix systems, a login programme is normally invoked by some login shell (like getty) together with a user-name. The login executable is often owned by the superuser and its SUID bit is set. Therefore, from an attacker's point of view, a vulnerable login programme means potential root access to the system.
Your task is to exploit a simplified version of a login programme that asks the user for a password and, depending on the input, will return a success or failure message. Note, that the given executable is an x86 ELF binary. You may need to set up support for running 32-bit binaries in your system. In case your machine cannot natively run x86 code, you can work remotely on the university's login server. If you need help with the setup, ask the tutor.
- First, extract the correct password by analysing the binary with objdump or gdb. Describe how to extract the password from the binary, e.g., by sending the output of a terminal session where you use objdump and/or gdb. In addition, please send the plain password.
- Next, exploit the buffer overflow vulnerability of this binary. By choosing a suitable input, you are able to skip the password check and force the program to jump directly to printing the success message. Note, that your task is to make the programme run the existing code, not to inject code that prints a similar message. Start your solution by answering the following questions:
- How does gcc translate function calls to assembly code? How are arguments and return values passed? How can the programme continue executing where ii “left off” when the function call happened?
- Which standard C-Library functions are vulnerable to buffer overflows and why? You don't need to name the individual functions but rather give examples and name the properties they share.
- How to use this knowledge to overwrite the return address of a function?
- Write a short programme in C, C++, or Rust that takes an address in hex format as input and produces a string as output. When using that string as input for the login program, it should directly jump to the given hex address. Additionally, send the hex address you would use to circumvent the authentication check and explain how you you found it. An example session using your resulting program should look similar to this one (replacing 1234ABCD with the correct value of course):
$> ./cracker 1234ABCD | ./simple_login
Enter the correct password:
Successful login! Now, we would execute a shell ...
- When simply overwriting the return address of the password authentication function, you will experience problems, namely a segmentation fault. Explain why this happens. How could you avoid this in theory? (Bonus: Extend your implementation so that the generated string does not result in a segmentation fault when fed into simple_login.)
- What practical countermeasures of contemporary UNIX systems and compilers complicate buffer overflow attacks?
Resources
Weiterführende Links
- C++/Standard Template Library – Referenz
- Tutorial über Zeiger in C
- PThreads-Tutorial
- Coding Styles
- C++-Standard
Notenvergabe
Für das Bestehen bzw. die Note sind die Ergebnisse des praktischen Teils maßgeblich. Dabei gehen Korrektheit und Vollständigkeit der Lösung, die Code-Qualität sowie ggf. die erforderlichen Nachbesserungen mit ein. Je nach gewähltem Modul werden die abgegebenen Lösungen im Rahmen eines mündlichen Kolloquiums abschließend evaluiert.