Chapter 25 Exercises: System Calls
Section A: Raw Syscall Mechanics (No libc)
1. Hello, Syscall World
Write a complete NASM program (hello_raw.asm) that:
- Writes "Hello from ring 3!\n" to stdout using only sys_write
- Exits with code 0 using sys_exit
- Uses NO external libraries (link with ld, not gcc)
Build and run:
nasm -f elf64 hello_raw.asm -o hello_raw.o
ld hello_raw.o -o hello_raw
./hello_raw
echo "Exit code: $?"
2. File Copy Program
Write a NASM program (rawcopy.asm) that copies one file to another using only system calls. Use:
- sys_open to open source (read) and create destination (write, create, truncate)
- sys_read / sys_write in a loop to copy chunks of 4096 bytes
- sys_close on both file descriptors
- Proper error checking: if any syscall returns negative, print an error message and exit with code 1
Test: ./rawcopy /etc/hostname /tmp/hostname_copy && diff /etc/hostname /tmp/hostname_copy
3. The errno Dance
Write a program that intentionally triggers four different error codes:
- ENOENT (-2): try to open a non-existent file
- EACCES (-13): try to open /etc/shadow for writing
- EBADF (-9): try to write to file descriptor 99 (which is not open)
- EINVAL (-22): call sys_mmap with length 0
For each error, print the syscall name and the numeric errno value to stderr (fd=2). Check your answers against errno.h.
4. Argument Passing Verification
Use strace to verify the system call arguments your programs produce:
strace -e trace=write ./hello_raw 2>&1
Confirm that the arguments shown by strace match the registers you set before syscall. Try this with your file copy program and verify sys_open flags are correct.
Section B: Using strace
5. strace Investigation: What Does bash Do on Startup? Run:
strace -c /bin/bash -c "exit"
Answer these questions from the strace -c output:
- Which syscall is called most frequently?
- How many read calls are made?
- Which file-related syscalls appear?
- What is the total number of system calls?
6. strace a Network Program
Trace curl http://example.com with strace:
strace -e trace=network curl http://example.com 2>&1 | head -30
Identify:
- The socket call and its arguments (AF_INET or AF_INET6? SOCK_STREAM?)
- The connect call — what IP address and port?
- The first sendto or write — what is the HTTP request?
7. strace for Bug Finding Given this program that fails with "No such file or directory" but the programmer is confused why:
FILE *f = fopen("config.txt", "r");
Explain how you would use strace to find the exact path the program is looking for, even though the source code says "config.txt". What strace flag shows the current working directory at the time of the call?
8. Counting Syscalls in Your Own Program
Write a C program that does something non-trivial (reads a file, allocates memory, prints results). Compile it with gcc -O0. Run it under strace -c. Count:
- How many syscalls are in the "setup" phase (before your code runs)?
- How many are from your actual code?
- What percentage of total syscalls are "infrastructure" (dynamic linker, etc.)?
Now compile the same program statically (gcc -static) and repeat. What changes?
Section C: Syscall Wrappers
9. Implement memcpy and memset Without using any library functions, implement:
; memcpy(void *dst, const void *src, size_t n)
; Returns dst
; memset(void *dst, int c, size_t n)
; Returns dst
Use these to build a program that reads stdin into a dynamically allocated buffer (sys_mmap) and writes it back to stdout in reverse order.
10. Implement printf (minimal)
Implement a function print_int(int64_t n) that prints a 64-bit signed integer to stdout using only sys_write. Requirements:
- Handle negative numbers (print leading -)
- Handle zero
- Handle INT64_MIN (tricky: negating it overflows)
No itoa, no sprintf — convert digits manually using division and modulo.
11. Build a Minimal libc
Extend the minimal_libc.asm from the chapter to include:
- puts(const char *s) — write null-terminated string + newline to stdout
- strlen(const char *s) — return length of null-terminated string (no syscall needed)
- malloc(size_t n) — allocate via sys_mmap, return pointer
- free(void *p) — for simplicity, implement as sys_munmap (hint: you'll need to store the size)
Write a test program that uses all four functions.
Section D: Process Management
12. fork/exec/wait: Process Creation
Write a NASM program that:
1. Forks a child process
2. The child executes /bin/ls with arguments ["/bin/ls", "/tmp", NULL] using sys_execve
3. The parent waits for the child with sys_wait4 and extracts the exit status from the status value:
- If child exited normally: (status >> 8) & 0xFF = exit code
- If child was killed by signal: status & 0x7F = signal number
Print the child's exit code.
13. Pipe Between Two Processes
Using sys_pipe (22), sys_fork, and sys_execve, implement a minimal ls | grep pipeline in NASM:
1. Create a pipe: two fds (read end, write end)
2. Fork
3. Child 1: close read end, dup2 write end to stdout (fd 1), exec /bin/ls
4. Child 2 (fork again from parent): close write end, dup2 read end to stdin (fd 0), exec /bin/grep txt
5. Parent: close both pipe ends, wait for both children
Use sys_dup2 (33) to redirect file descriptors.
14. The Minimal Shell — Enhancements
Extend the minish.asm from the chapter:
- Add a built-in exit command that exits the shell
- Add a built-in cd command using sys_chdir (80)
- After each command completes, print the exit status if non-zero: [exited: N]
- Handle the case where execve fails (print "command not found")
Section E: Memory System Calls
15. mmap Tricks
Write a program that demonstrates mmap for file I/O:
1. Open a file with sys_open (use /etc/hostname)
2. Use sys_fstat (5) to get the file size from the stat structure (offset 48 = st_size)
3. Map the entire file with sys_mmap (prot=PROT_READ, flags=MAP_PRIVATE, fd=your_fd, offset=0)
4. Walk through the mapped bytes and count newlines without reading through sys_read
5. Print the line count
6. Unmap with sys_munmap
16. Anonymous Shared Memory Between Processes
Create two processes that communicate through shared memory:
1. Parent creates shared mapping: mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0)
2. Parent forks
3. Child writes "Hello from child!\n" to the shared page
4. Child exits
5. Parent waits for child, then reads and prints the string from the shared page
This demonstrates that MAP_SHARED|MAP_ANONYMOUS is inherited across fork and shares physical pages.
17. vDSO vs. Raw Syscall Timing
Write a program that:
1. Calls clock_gettime(CLOCK_MONOTONIC, &ts) via the C library (which uses vDSO) 1,000,000 times and measures total time
2. Makes the same call using a raw syscall instruction 1,000,000 times and measures total time
3. Reports the average nanoseconds per call for each
Use RDTSC for measurement (or clock_gettime itself for wall time — just not the one being benchmarked). What is the speedup from vDSO?
18. Socket Programming: Minimal HTTP Client
Write a NASM program that:
1. Creates a TCP socket (sys_socket)
2. Resolves 93.184.216.34 (example.com) — use the hardcoded IP, no DNS
3. Connects to port 80 (sys_connect)
4. Sends "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n" (sys_write)
5. Reads and prints the response (sys_read in a loop until 0 bytes)
6. Closes the socket
This is a complete network client in pure assembly, no libc.
20. strace Security Audit
Download a suspicious binary (use any CTF binary or compile a simple program that pretends to do something legitimate). Use strace to determine:
- Does it read any files outside its expected directory?
- Does it make any network connections?
- Does it try to execute any other programs?
- Does it call sys_ptrace (101) — which could indicate anti-debugging?
Write a one-page security analysis of the binary's behavior based solely on its system call trace.