Src |
Date (GMT) |
Titre |
Description |
Tags |
Stories |
Notes |
|
2021-03-18 17:07:36 |
BSidesSF CTF 2021 Author writeup: glitter-printer, a buffer underflow where you modify the actual code (lien direct) |
Hi Everybody!
This is going to be a challenge-author writeup for the Glitter Printer challenge from BSides San Francisco 2021.
First, a bit of history: the original idea I had behind Glitter Printer was to make a video game challenge involving cartridge-swap, where I'd write a handful of simple video games in 100% x86 code with no imports or anything (like an old fashioned cartridge game), and the player could swap between them without memory being re-initialized. Folks used to do this sorta thing on NES, and maybe I'll use it in a future challenge, but I decided to make this a bit simpler.
While experimenting with writing libraries without libc, I realized just how much work it was going to be to write a bunch of games, and decided to simplify. My next ide was to write a “driver” type thing, where a blob of code is loaded into +RWX memory and the player could go wild on it. The the name Glitter Printer came across my radar, I don't even remember why, and that gave me the idea to do an LPR server.
That's quite the background!
The code
So I don't know if anybody actually noticed, but I implemented a good chunk of the Line Printer (LPR) RFC correctly. Well, I tried to anyways, I didn't actually test it with a real client. The hard part was introducing a realistic-looking vulnerability - in many of my CTF challenges, the vuln comes naturally. So naturally, in fact, that I often don't even need to plan it in advance! I ended up settling on an integer underflow.
If you look at the source (specifically, core.c), you can see that the main loop reads a single byte, then performs an action accordingly:
void _start(queue_t *queues) {
while(1) {
char command = read_byte(STDIN);
if(command == 1) {
print_waiting_jobs();
} else if(command == 2) {
receive_job(queues);
} else if(command == 3) {
queue_state_list(queues, 0);
} else if(command == 4) {
queue_state_list(queues, 1);
} else if(command == 5) {
// 05 Queue SP Agent SP List LF - Remove jobs
} else {
exit(6);
}
}
}
As part of several commands (such as receive_job()), an ASCII number is sent to choose a queue to operate on. The queue number isn't a byte (like “\x01”), it's a number like “123” that needs to be parsed.
And by the way, this is still how LPR actually works!
Here's the code I used for parsing numbers.. I'm pretty sure I just grabbed this from Stack Overflow:
int read_number(char *terminator) {
int result = 0;
while(1) {
// Read a single byte
char buffer = read_byte();
// If it's not a valid byte, we're done (and we consume the terminator)
if(buffer < '0' || buffer > '9') {
|
Vulnerability
Guideline
|
|
★★★★
|